xref: /netbsd-src/external/bsd/libarchive/dist/libarchive/test/test_write_disk_hardlink.c (revision 65e637ab3a9cc7c3e7749c941a1011ecd65517e6)
1 /*-
2  * Copyright (c) 2003-2007 Tim Kientzle
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 #if defined(_WIN32) && !defined(__CYGWIN__)
28 /* Execution bits, Group members bits and others bits do not work. */
29 #define UMASK 0177
30 #define E_MASK (~0177)
31 #else
32 #define UMASK 022
33 #define E_MASK (~0)
34 #endif
35 
36 /*
37  * Exercise hardlink recreation.
38  *
39  * File permissions are chosen so that the authoritative entry
40  * has the correct permission and the non-authoritative versions
41  * are just writeable files.
42  */
DEFINE_TEST(test_write_disk_hardlink)43 DEFINE_TEST(test_write_disk_hardlink)
44 {
45 #if defined(__HAIKU__)
46 	skipping("archive_write_disk_hardlink; hardlinks are not supported on bfs");
47 #else
48 	static const char data[]="abcdefghijklmnopqrstuvwxyz";
49 	struct archive *ad;
50 	struct archive_entry *ae;
51 #ifdef HAVE_LINKAT
52 	int can_symlink;
53 #endif
54 	int r;
55 
56 	/* Force the umask to something predictable. */
57 	assertUmask(UMASK);
58 
59 	/* Write entries to disk. */
60 	assert((ad = archive_write_disk_new()) != NULL);
61 
62 	/*
63 	 * First, use a tar-like approach; a regular file, then
64 	 * a separate "hardlink" entry.
65 	 */
66 
67 	/* Regular file. */
68 	assert((ae = archive_entry_new()) != NULL);
69 	archive_entry_copy_pathname(ae, "link1a");
70 	archive_entry_set_mode(ae, S_IFREG | 0755);
71 	archive_entry_set_size(ae, sizeof(data));
72 	assertEqualIntA(ad, 0, archive_write_header(ad, ae));
73 	assertEqualInt(sizeof(data),
74 	    archive_write_data(ad, data, sizeof(data)));
75 	assertEqualIntA(ad, 0, archive_write_finish_entry(ad));
76 	archive_entry_free(ae);
77 
78 	/* Link.  Size of zero means this doesn't carry data. */
79 	assert((ae = archive_entry_new()) != NULL);
80 	archive_entry_copy_pathname(ae, "link1b");
81 	archive_entry_set_mode(ae, S_IFREG | 0642);
82 	archive_entry_set_size(ae, 0);
83 	archive_entry_copy_hardlink(ae, "link1a");
84 	assertEqualIntA(ad, 0, r = archive_write_header(ad, ae));
85 	if (r >= ARCHIVE_WARN) {
86 		assertEqualInt(ARCHIVE_WARN,
87 		    archive_write_data(ad, data, sizeof(data)));
88 		assertEqualIntA(ad, 0, archive_write_finish_entry(ad));
89 	}
90 	archive_entry_free(ae);
91 
92 	/*
93 	 * Repeat tar approach test, but use unset to mark the
94 	 * hardlink as having no data.
95 	 */
96 
97 	/* Regular file. */
98 	assert((ae = archive_entry_new()) != NULL);
99 	archive_entry_copy_pathname(ae, "link2a");
100 	archive_entry_set_mode(ae, S_IFREG | 0755);
101 	archive_entry_set_size(ae, sizeof(data));
102 	assertEqualIntA(ad, 0, archive_write_header(ad, ae));
103 	assertEqualInt(sizeof(data),
104 	    archive_write_data(ad, data, sizeof(data)));
105 	assertEqualIntA(ad, 0, archive_write_finish_entry(ad));
106 	archive_entry_free(ae);
107 
108 	/* Link.  Unset size means this doesn't carry data. */
109 	assert((ae = archive_entry_new()) != NULL);
110 	archive_entry_copy_pathname(ae, "link2b");
111 	archive_entry_set_mode(ae, S_IFREG | 0642);
112 	archive_entry_unset_size(ae);
113 	archive_entry_copy_hardlink(ae, "link2a");
114 	assertEqualIntA(ad, 0, r = archive_write_header(ad, ae));
115 	if (r >= ARCHIVE_WARN) {
116 		assertEqualInt(ARCHIVE_WARN,
117 		    archive_write_data(ad, data, sizeof(data)));
118 		assertEqualIntA(ad, 0, archive_write_finish_entry(ad));
119 	}
120 	archive_entry_free(ae);
121 
122 	/*
123 	 * Second, try an old-cpio-like approach; a regular file, then
124 	 * another identical one (which has been marked hardlink).
125 	 */
126 
127 	/* Regular file. */
128 	assert((ae = archive_entry_new()) != NULL);
129 	archive_entry_copy_pathname(ae, "link3a");
130 	archive_entry_set_mode(ae, S_IFREG | 0600);
131 	archive_entry_set_size(ae, sizeof(data));
132 	assertEqualIntA(ad, 0, archive_write_header(ad, ae));
133 	assertEqualInt(sizeof(data), archive_write_data(ad, data, sizeof(data)));
134 	assertEqualIntA(ad, 0, archive_write_finish_entry(ad));
135 	archive_entry_free(ae);
136 
137 	/* Link. */
138 	assert((ae = archive_entry_new()) != NULL);
139 	archive_entry_copy_pathname(ae, "link3b");
140 	archive_entry_set_mode(ae, S_IFREG | 0755);
141 	archive_entry_set_size(ae, sizeof(data));
142 	archive_entry_copy_hardlink(ae, "link3a");
143 	assertEqualIntA(ad, 0, r = archive_write_header(ad, ae));
144 	if (r > ARCHIVE_WARN) {
145 		assertEqualInt(sizeof(data),
146 		    archive_write_data(ad, data, sizeof(data)));
147 		assertEqualIntA(ad, 0, archive_write_finish_entry(ad));
148 	}
149 	archive_entry_free(ae);
150 
151 	/*
152 	 * Third, try a new-cpio-like approach, where the initial
153 	 * regular file is empty and the hardlink has the data.
154 	 */
155 
156 	/* Regular file. */
157 	assert((ae = archive_entry_new()) != NULL);
158 	archive_entry_copy_pathname(ae, "link4a");
159 	archive_entry_set_mode(ae, S_IFREG | 0600);
160 	archive_entry_set_size(ae, 0);
161 	assertEqualIntA(ad, 0, archive_write_header(ad, ae));
162 	assertEqualInt(ARCHIVE_WARN, archive_write_data(ad, data, 1));
163 	assertEqualIntA(ad, 0, archive_write_finish_entry(ad));
164 	archive_entry_free(ae);
165 
166 	/* Link. */
167 	assert((ae = archive_entry_new()) != NULL);
168 	archive_entry_copy_pathname(ae, "link4b");
169 	archive_entry_set_mode(ae, S_IFREG | 0755);
170 	archive_entry_set_size(ae, sizeof(data));
171 	archive_entry_copy_hardlink(ae, "link4a");
172 	assertEqualIntA(ad, 0, r = archive_write_header(ad, ae));
173 	if (r > ARCHIVE_FAILED) {
174 		assertEqualInt(sizeof(data),
175 		    archive_write_data(ad, data, sizeof(data)));
176 		assertEqualIntA(ad, 0, archive_write_finish_entry(ad));
177 	}
178 	archive_entry_free(ae);
179 
180 #ifdef HAVE_LINKAT
181 	/* Finally, try creating a hard link to a dangling symlink */
182 	can_symlink = canSymlink();
183 	if (can_symlink) {
184 		/* Symbolic link: link5a -> foo */
185 		assert((ae = archive_entry_new()) != NULL);
186 		archive_entry_copy_pathname(ae, "link5a");
187 		archive_entry_set_mode(ae, AE_IFLNK | 0642);
188 		archive_entry_unset_size(ae);
189 		archive_entry_copy_symlink(ae, "foo");
190 		assertEqualIntA(ad, 0, r = archive_write_header(ad, ae));
191 		if (r >= ARCHIVE_WARN) {
192 			assertEqualInt(ARCHIVE_WARN,
193 			    archive_write_data(ad, data, sizeof(data)));
194 			assertEqualIntA(ad, 0, archive_write_finish_entry(ad));
195 		}
196 		archive_entry_free(ae);
197 
198 
199 		/* Link.  Size of zero means this doesn't carry data. */
200 		assert((ae = archive_entry_new()) != NULL);
201 		archive_entry_copy_pathname(ae, "link5b");
202 		archive_entry_set_mode(ae, S_IFREG | 0642);
203 		archive_entry_set_size(ae, 0);
204 		archive_entry_copy_hardlink(ae, "link5a");
205 		assertEqualIntA(ad, 0, r = archive_write_header(ad, ae));
206 		if (r >= ARCHIVE_WARN) {
207 			assertEqualInt(ARCHIVE_WARN,
208 			    archive_write_data(ad, data, sizeof(data)));
209 			assertEqualIntA(ad, 0, archive_write_finish_entry(ad));
210 		}
211 		archive_entry_free(ae);
212 	}
213 #endif
214 	assertEqualInt(0, archive_write_free(ad));
215 
216 	/* Test the entries on disk. */
217 
218 	/* Test #1 */
219 	/* If the hardlink was successfully created and the archive
220 	 * doesn't carry data for it, we consider it to be
221 	 * non-authoritative for meta data as well.  This is consistent
222 	 * with GNU tar and BSD pax.  */
223 	assertIsReg("link1a", 0755 & ~UMASK);
224 	assertFileSize("link1a", sizeof(data));
225 	assertFileNLinks("link1a", 2);
226 	assertIsHardlink("link1a", "link1b");
227 
228 	/* Test #2: Should produce identical results to test #1 */
229 	/* Note that marking a hardlink with size = 0 is treated the
230 	 * same as having an unset size.  This is partly for backwards
231 	 * compatibility (we used to not have unset tracking, so
232 	 * relied on size == 0) and partly to match the model used by
233 	 * common file formats that store a size of zero for
234 	 * hardlinks. */
235 	assertIsReg("link2a", 0755 & ~UMASK);
236 	assertFileSize("link2a", sizeof(data));
237 	assertFileNLinks("link2a", 2);
238 	assertIsHardlink("link2a", "link2b");
239 
240 	/* Test #3 */
241 	assertIsReg("link3a", 0755 & ~UMASK);
242 	assertFileSize("link3a", sizeof(data));
243 	assertFileNLinks("link3a", 2);
244 	assertIsHardlink("link3a", "link3b");
245 
246 	/* Test #4 */
247 	assertIsReg("link4a", 0755 & ~UMASK);
248 	assertFileNLinks("link4a", 2);
249 	assertFileSize("link4a", sizeof(data));
250 	assertIsHardlink("link4a", "link4b");
251 
252 #ifdef HAVE_LINKAT
253 	if (can_symlink) {
254 		/* Test #5 */
255 		assertIsSymlink("link5a", "foo", 0);
256 		assertFileNLinks("link5a", 2);
257 		assertIsHardlink("link5a", "link5b");
258 	}
259 #endif
260 #endif
261 }
262