xref: /freebsd/contrib/libarchive/libarchive/test/test_write_format_pax_align.c (revision 185becb1e1bd2657c156f78aeb52edac05ba5fb5)
1 /*-
2  * Copyright (c) 2024 libarchive contributors
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
15  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17  * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
18  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
19  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
20  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
21  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
23  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25 #include "test.h"
26 
27 /*
28  * The pax "align" option must start each big-enough regular file's data on
29  * an alignment boundary in the archive stream, so it can be reflinked out.
30  */
31 
32 #define ALIGN 4096
33 
34 struct file {
35 	const char *name;
36 	size_t size;
37 	char fill;
38 	int add_xattr;		/* force a "natural" pax extended header */
39 };
40 
41 /*
42  * A mix of entries: small/large regular files, a directory, long-name and
43  * xattr entries (which force a natural pax header), and an exactly-align file.
44  */
45 static const struct file files[] = {
46 	{ "small",		100,		'a', 0 },
47 	{ "big1",		5000,		'b', 0 },
48 	{ "adir/",		0,		0,   0 },
49 	{ "big2",		ALIGN,		'c', 0 },
50 	{ "tiny",		1,		'd', 0 },
51 	{ "big3",		100000,		'e', 0 },
52 	{ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
53 	  "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/longname", 8192, 'f', 0 },
54 	{ "withxattr",		9000,		'g', 1 },
55 	{ NULL, 0, 0, 0 }
56 };
57 
58 static void
verify_align_limit(void)59 verify_align_limit(void)
60 {
61 	struct archive *a;
62 
63 	assert((a = archive_write_new()) != NULL);
64 	assertEqualIntA(a, ARCHIVE_OK, archive_write_set_format_pax_restricted(a));
65 	assertEqualIntA(a, ARCHIVE_OK,
66 	    archive_write_set_options(a, "align=1048576"));
67 	assertEqualIntA(a, ARCHIVE_FAILED,
68 	    archive_write_set_options(a, "align=2097152"));
69 	assertEqualInt(ARCHIVE_OK, archive_write_free(a));
70 }
71 
72 static int
memory_contains(const char * memory,size_t memory_size,const char * needle)73 memory_contains(const char *memory, size_t memory_size, const char *needle)
74 {
75 	size_t needle_size = strlen(needle);
76 	size_t i;
77 
78 	for (i = 0; i + needle_size <= memory_size; i++) {
79 		if (memcmp(memory + i, needle, needle_size) == 0)
80 			return (1);
81 	}
82 	return (0);
83 }
84 
85 static void
verify_sparse_not_padded(void)86 verify_sparse_not_padded(void)
87 {
88 	struct archive *a;
89 	struct archive_entry *ae;
90 	char *buff, *data;
91 	size_t buffsize = 65536, used;
92 
93 	buff = malloc(buffsize);
94 	data = calloc(1, 8192);
95 	assert(buff != NULL);
96 	assert(data != NULL);
97 	assert((a = archive_write_new()) != NULL);
98 	assertEqualIntA(a, ARCHIVE_OK, archive_write_set_format_pax_restricted(a));
99 	assertEqualIntA(a, ARCHIVE_OK, archive_write_add_filter_none(a));
100 	assertEqualIntA(a, ARCHIVE_OK,
101 	    archive_write_set_options(a, "align=4096"));
102 	assertEqualIntA(a, ARCHIVE_OK,
103 	    archive_write_open_memory(a, buff, buffsize, &used));
104 
105 	assert((ae = archive_entry_new()) != NULL);
106 	archive_entry_set_pathname(ae, "sparse");
107 	archive_entry_set_mode(ae, S_IFREG | 0644);
108 	archive_entry_set_size(ae, 8192);
109 	archive_entry_sparse_add_entry(ae, 4096, 4096);
110 	assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
111 	assertEqualIntA(a, 8192, archive_write_data(a, data, 8192));
112 	archive_entry_free(ae);
113 	assertEqualIntA(a, ARCHIVE_OK, archive_write_close(a));
114 	assertEqualInt(ARCHIVE_OK, archive_write_free(a));
115 
116 	failure("Sparse entries must not get misleading alignment padding");
117 	assert(!memory_contains(buff, used, "LIBARCHIVE.pad"));
118 	free(data);
119 	free(buff);
120 }
121 
122 static void
write_archive(char * buff,size_t buffsize,size_t * used,int gzip)123 write_archive(char *buff, size_t buffsize, size_t *used, int gzip)
124 {
125 	struct archive *a;
126 	struct archive_entry *ae;
127 	const struct file *f;
128 	char *data;
129 
130 	assert((a = archive_write_new()) != NULL);
131 	assertEqualIntA(a, ARCHIVE_OK, archive_write_set_format_pax_restricted(a));
132 	if (gzip)
133 		assertEqualIntA(a, ARCHIVE_OK, archive_write_add_filter_gzip(a));
134 	else
135 		assertEqualIntA(a, ARCHIVE_OK, archive_write_add_filter_none(a));
136 	/* Small blocks so the writer doesn't tail-pad past our offsets. */
137 	assertEqualIntA(a, ARCHIVE_OK, archive_write_set_bytes_per_block(a, 512));
138 	assertEqualIntA(a, ARCHIVE_OK,
139 	    archive_write_set_options(a, "align=4096"));
140 	assertEqualIntA(a, ARCHIVE_OK,
141 	    archive_write_open_memory(a, buff, buffsize, used));
142 
143 	assert((ae = archive_entry_new()) != NULL);
144 	for (f = files; f->name != NULL; f++) {
145 		archive_entry_clear(ae);
146 		archive_entry_set_pathname(ae, f->name);
147 		archive_entry_set_mtime(ae, 5, 0);
148 		if (f->name[strlen(f->name) - 1] == '/') {
149 			archive_entry_set_mode(ae, S_IFDIR | 0755);
150 		} else {
151 			archive_entry_set_mode(ae, S_IFREG | 0644);
152 			archive_entry_set_size(ae, f->size);
153 		}
154 		if (f->add_xattr)
155 			archive_entry_xattr_add_entry(ae, "user.test",
156 			    "value", 5);
157 		assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
158 		if (f->size > 0) {
159 			data = malloc(f->size);
160 			assert(data != NULL);
161 			memset(data, f->fill, f->size);
162 			assertEqualIntA(a, f->size,
163 			    archive_write_data(a, data, f->size));
164 			free(data);
165 		}
166 	}
167 	archive_entry_free(ae);
168 	assertEqualIntA(a, ARCHIVE_OK, archive_write_close(a));
169 	assertEqualInt(ARCHIVE_OK, archive_write_free(a));
170 }
171 
172 static void
verify_archive(const char * buff,size_t used,int gzip)173 verify_archive(const char *buff, size_t used, int gzip)
174 {
175 	struct archive *a;
176 	struct archive_entry *ae;
177 	const struct file *f;
178 
179 	assert((a = archive_read_new()) != NULL);
180 	assertEqualIntA(a, ARCHIVE_OK, archive_read_support_format_tar(a));
181 	if (gzip)
182 		assertEqualIntA(a, ARCHIVE_OK, archive_read_support_filter_gzip(a));
183 	else
184 		assertEqualIntA(a, ARCHIVE_OK, archive_read_support_filter_none(a));
185 	assertEqualIntA(a, ARCHIVE_OK,
186 	    archive_read_open_memory(a, buff, used));
187 
188 	for (f = files; f->name != NULL; f++) {
189 		int64_t off, size;
190 		assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae));
191 		assertEqualStringA(a, f->name, archive_entry_pathname(ae));
192 		size = archive_entry_size(ae);
193 		assertEqualIntA(a, f->size, size);
194 		/*
195 		 * archive_filter_bytes(a, 0) is the offset of the data in
196 		 * the *decompressed* archive stream, i.e. exactly where a
197 		 * reflink would read from an uncompressed archive.
198 		 */
199 		off = archive_filter_bytes(a, 0);
200 		if (archive_entry_filetype(ae) == AE_IFREG && size >= ALIGN) {
201 			failure("data for '%s' (size %jd) must start on a "
202 			    "%d-byte boundary but starts at %jd",
203 			    f->name, (intmax_t)size, ALIGN, (intmax_t)off);
204 			assertEqualInt(0, (int)(off % ALIGN));
205 		}
206 		/* Data must still round-trip intact through the padding. */
207 		if (size > 0) {
208 			char *data = malloc((size_t)size);
209 			assert(data != NULL);
210 			assertEqualIntA(a, size,
211 			    archive_read_data(a, data, (size_t)size));
212 			assertMemoryFilledWith(data, (size_t)size, f->fill);
213 			free(data);
214 		}
215 	}
216 	assertEqualIntA(a, ARCHIVE_EOF, archive_read_next_header(a, &ae));
217 	assertEqualInt(ARCHIVE_OK, archive_read_free(a));
218 }
219 
DEFINE_TEST(test_write_format_pax_align)220 DEFINE_TEST(test_write_format_pax_align)
221 {
222 	size_t buffsize = 2000000;
223 	char *buff;
224 	size_t used;
225 
226 	verify_align_limit();
227 	verify_sparse_not_padded();
228 
229 	buff = malloc(buffsize);
230 	assert(buff != NULL);
231 
232 	/* Uncompressed: the archive itself is aligned on disk. */
233 	write_archive(buff, buffsize, &used, 0);
234 	verify_archive(buff, used, 0);
235 
236 	/* Compressed: the *decompressed* stream is still aligned, which is
237 	 * what a reflink-from-decompressed-copy relies on. */
238 	if (canGzip()) {
239 		write_archive(buff, buffsize, &used, 1);
240 		verify_archive(buff, used, 1);
241 	} else {
242 		skipping("gzip unavailable; skipped compressed alignment check");
243 	}
244 
245 	free(buff);
246 }
247