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