1 // Copyright 2017 The Fuchsia Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include <assert.h>
6 #include <errno.h>
7 #include <dirent.h>
8 #include <fcntl.h>
9 #include <limits.h>
10 #include <stdbool.h>
11 #include <stdint.h>
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <string.h>
15 #include <sys/stat.h>
16 #include <unistd.h>
17
18 #include <zircon/compiler.h>
19
20 #include "filesystems.h"
21 #include "misc.h"
22
check_link_count(const char * path,unsigned count)23 bool check_link_count(const char* path, unsigned count) {
24 struct stat s;
25 ASSERT_EQ(stat(path, &s), 0, "");
26 ASSERT_EQ(s.st_nlink, count, "");
27 return true;
28 }
29
test_link_basic(void)30 bool test_link_basic(void) {
31 BEGIN_TEST;
32
33 if (!test_info->supports_hardlinks) {
34 return true;
35 }
36
37 const char* oldpath = "::a";
38 const char* newpath = "::b";
39
40 // Make a file, fill it with content
41 int fd = open(oldpath, O_RDWR | O_CREAT | O_EXCL, 0644);
42 ASSERT_GT(fd, 0, "");
43 uint8_t buf[100];
44 for (size_t i = 0; i < sizeof(buf); i++) {
45 buf[i] = (uint8_t) rand();
46 }
47 ASSERT_STREAM_ALL(write, fd, buf, sizeof(buf));
48 ASSERT_TRUE(check_file_contents(fd, buf, sizeof(buf)), "");
49 ASSERT_TRUE(check_link_count(oldpath, 1), "");
50
51 ASSERT_EQ(link(oldpath, newpath), 0, "");
52 ASSERT_TRUE(check_link_count(oldpath, 2), "");
53 ASSERT_TRUE(check_link_count(newpath, 2), "");
54
55 // Confirm that both the old link and the new links exist
56 int fd2 = open(newpath, O_RDONLY, 0644);
57 ASSERT_GT(fd2, 0, "");
58 ASSERT_TRUE(check_file_contents(fd2, buf, sizeof(buf)), "");
59 ASSERT_TRUE(check_file_contents(fd, buf, sizeof(buf)), "");
60
61 // Remove the old link
62 ASSERT_EQ(close(fd), 0, "");
63 ASSERT_EQ(close(fd2), 0, "");
64 ASSERT_EQ(unlink(oldpath), 0, "");
65 ASSERT_TRUE(check_link_count(newpath, 1), "");
66
67 // Open the link by its new name, and verify that the contents have
68 // not been altered by the removal of the old link.
69 fd = open(newpath, O_RDONLY, 0644);
70 ASSERT_GT(fd, 0, "");
71 ASSERT_TRUE(check_file_contents(fd, buf, sizeof(buf)), "");
72
73 ASSERT_EQ(close(fd), 0, "");
74 ASSERT_EQ(unlink(newpath), 0, "");
75
76 END_TEST;
77 }
78
test_link_count_dirs(void)79 bool test_link_count_dirs(void) {
80 BEGIN_TEST;
81
82 if (!test_info->supports_hardlinks) {
83 return true;
84 }
85
86 ASSERT_EQ(mkdir("::dira", 0755), 0, "");
87 // New directories should have two links:
88 // Parent --> newdir
89 // newdir ('.') --> newdir
90 ASSERT_TRUE(check_link_count("::dira", 2), "");
91
92 // Adding a file won't change the parent link count...
93 int fd = open("::dira/file", O_RDWR | O_CREAT | O_EXCL, 0644);
94 ASSERT_GT(fd, 0, "");
95 ASSERT_EQ(close(fd), 0, "");
96 ASSERT_TRUE(check_link_count("::dira", 2), "");
97 ASSERT_TRUE(check_link_count("::dira/file", 1), "");
98
99 // But adding a directory WILL change the parent link count.
100 ASSERT_EQ(mkdir("::dira/dirb", 0755), 0, "");
101 ASSERT_TRUE(check_link_count("::dira", 3), "");
102 ASSERT_TRUE(check_link_count("::dira/dirb", 2), "");
103
104 // Test that adding "depth" increases the dir count as we expect.
105 ASSERT_EQ(mkdir("::dira/dirb/dirc", 0755), 0, "");
106 ASSERT_TRUE(check_link_count("::dira", 3), "");
107 ASSERT_TRUE(check_link_count("::dira/dirb", 3), "");
108 ASSERT_TRUE(check_link_count("::dira/dirb/dirc", 2), "");
109
110 // Demonstrate that unwinding also reduces the link count.
111 ASSERT_EQ(unlink("::dira/dirb/dirc"), 0, "");
112 ASSERT_TRUE(check_link_count("::dira", 3), "");
113 ASSERT_TRUE(check_link_count("::dira/dirb", 2), "");
114
115 ASSERT_EQ(unlink("::dira/dirb"), 0, "");
116 ASSERT_TRUE(check_link_count("::dira", 2), "");
117
118 // Test that adding "width" increases the dir count too.
119 ASSERT_EQ(mkdir("::dira/dirb", 0755), 0, "");
120 ASSERT_TRUE(check_link_count("::dira", 3), "");
121 ASSERT_TRUE(check_link_count("::dira/dirb", 2), "");
122
123 ASSERT_EQ(mkdir("::dira/dirc", 0755), 0, "");
124 ASSERT_TRUE(check_link_count("::dira", 4), "");
125 ASSERT_TRUE(check_link_count("::dira/dirb", 2), "");
126 ASSERT_TRUE(check_link_count("::dira/dirc", 2), "");
127
128 // Demonstrate that unwinding also reduces the link count.
129 ASSERT_EQ(unlink("::dira/dirc"), 0, "");
130 ASSERT_TRUE(check_link_count("::dira", 3), "");
131 ASSERT_TRUE(check_link_count("::dira/dirb", 2), "");
132
133 ASSERT_EQ(unlink("::dira/dirb"), 0, "");
134 ASSERT_TRUE(check_link_count("::dira", 2), "");
135
136 ASSERT_EQ(unlink("::dira/file"), 0, "");
137 ASSERT_EQ(unlink("::dira"), 0, "");
138
139 END_TEST;
140 }
141
test_link_count_rename(void)142 bool test_link_count_rename(void) {
143 BEGIN_TEST;
144
145 if (!test_info->supports_hardlinks) {
146 return true;
147 }
148
149 // Check that link count does not change with simple rename
150 ASSERT_EQ(mkdir("::dir", 0755), 0, "");
151 ASSERT_TRUE(check_link_count("::dir", 2), "");
152 ASSERT_EQ(rename("::dir", "::dir_parent"), 0, "");
153 ASSERT_TRUE(check_link_count("::dir_parent", 2), "");
154
155 // Set up parent directory with child directories
156 ASSERT_EQ(mkdir("::dir_parent/dir_child_a", 0755), 0, "");
157 ASSERT_EQ(mkdir("::dir_parent/dir_child_b", 0755), 0, "");
158 ASSERT_TRUE(check_link_count("::dir_parent", 4), "");
159 ASSERT_TRUE(check_link_count("::dir_parent/dir_child_a", 2), "");
160 ASSERT_TRUE(check_link_count("::dir_parent/dir_child_b", 2), "");
161
162 // Rename a child directory out of its parent directory
163 ASSERT_EQ(rename("::dir_parent/dir_child_b", "::dir_parent_alt"), 0, "");
164 ASSERT_TRUE(check_link_count("::dir_parent", 3), "");
165 ASSERT_TRUE(check_link_count("::dir_parent/dir_child_a", 2), "");
166 ASSERT_TRUE(check_link_count("::dir_parent_alt", 2), "");
167
168 // Rename a parent directory into another directory
169 ASSERT_EQ(rename("::dir_parent", "::dir_parent_alt/dir_semi_parent"), 0, "");
170 ASSERT_TRUE(check_link_count("::dir_parent_alt", 3), "");
171 ASSERT_TRUE(check_link_count("::dir_parent_alt/dir_semi_parent", 3), "");
172 ASSERT_TRUE(check_link_count("::dir_parent_alt/dir_semi_parent/dir_child_a", 2), "");
173
174 // Rename a directory on top of an empty directory
175 ASSERT_EQ(mkdir("::dir_child", 0755), 0, "");
176 ASSERT_EQ(rename("::dir_child", "::dir_parent_alt/dir_semi_parent/dir_child_a"), 0, "");
177 ASSERT_TRUE(check_link_count("::dir_parent_alt", 3), "");
178 ASSERT_TRUE(check_link_count("::dir_parent_alt/dir_semi_parent", 3), "");
179 ASSERT_TRUE(check_link_count("::dir_parent_alt/dir_semi_parent/dir_child_a", 2), "");
180
181 // Rename a directory on top of an empty directory from a non-root directory
182 ASSERT_EQ(mkdir("::dir", 0755), 0, "");
183 ASSERT_EQ(mkdir("::dir/dir_child", 0755), 0, "");
184 ASSERT_TRUE(check_link_count("::dir", 3), "");
185 ASSERT_TRUE(check_link_count("::dir/dir_child", 2), "");
186 ASSERT_EQ(rename("::dir/dir_child", "::dir_parent_alt/dir_semi_parent/dir_child_a"), 0, "");
187 ASSERT_TRUE(check_link_count("::dir", 2), "");
188 ASSERT_TRUE(check_link_count("::dir_parent_alt", 3), "");
189 ASSERT_TRUE(check_link_count("::dir_parent_alt/dir_semi_parent", 3), "");
190 ASSERT_TRUE(check_link_count("::dir_parent_alt/dir_semi_parent/dir_child_a", 2), "");
191
192 // Rename a file on top of a file from a non-root directory
193 ASSERT_EQ(unlink("::dir_parent_alt/dir_semi_parent/dir_child_a"), 0, "");
194 int fd = open("::dir/dir_child", O_RDWR | O_CREAT | O_EXCL, 0644);
195 ASSERT_GT(fd, 0, "");
196 ASSERT_TRUE(check_link_count("::dir", 2), "");
197 ASSERT_TRUE(check_link_count("::dir/dir_child", 1), "");
198 int fd2 = open("::dir_parent_alt/dir_semi_parent/dir_child_a", O_RDWR | O_CREAT | O_EXCL, 0644);
199 ASSERT_GT(fd2, 0, "");
200 ASSERT_EQ(rename("::dir/dir_child", "::dir_parent_alt/dir_semi_parent/dir_child_a"), 0, "");
201 ASSERT_TRUE(check_link_count("::dir", 2), "");
202 ASSERT_TRUE(check_link_count("::dir_parent_alt", 3), "");
203 ASSERT_TRUE(check_link_count("::dir_parent_alt/dir_semi_parent", 2), "");
204 ASSERT_TRUE(check_link_count("::dir_parent_alt/dir_semi_parent/dir_child_a", 1), "");
205 ASSERT_EQ(close(fd), 0, "");
206 ASSERT_EQ(close(fd2), 0, "");
207
208 // Clean up
209 ASSERT_EQ(unlink("::dir_parent_alt/dir_semi_parent/dir_child_a"), 0, "");
210 ASSERT_TRUE(check_link_count("::dir_parent_alt", 3), "");
211 ASSERT_TRUE(check_link_count("::dir_parent_alt/dir_semi_parent", 2), "");
212 ASSERT_EQ(unlink("::dir_parent_alt/dir_semi_parent"), 0, "");
213 ASSERT_TRUE(check_link_count("::dir_parent_alt", 2), "");
214 ASSERT_EQ(unlink("::dir_parent_alt"), 0, "");
215 ASSERT_EQ(unlink("::dir"), 0, "");
216
217 END_TEST;
218 }
219
test_link_between_dirs(void)220 bool test_link_between_dirs(void) {
221 BEGIN_TEST;
222
223 if (!test_info->supports_hardlinks) {
224 return true;
225 }
226
227 ASSERT_EQ(mkdir("::dira", 0755), 0, "");
228 // New directories should have two links:
229 // Parent --> newdir
230 // newdir ('.') --> newdir
231 ASSERT_TRUE(check_link_count("::dira", 2), "");
232
233 ASSERT_EQ(mkdir("::dirb", 0755), 0, "");
234 ASSERT_TRUE(check_link_count("::dirb", 2), "");
235
236 const char* oldpath = "::dira/a";
237 const char* newpath = "::dirb/b";
238
239 // Make a file, fill it with content
240 int fd = open(oldpath, O_RDWR | O_CREAT | O_EXCL, 0644);
241 ASSERT_GT(fd, 0, "");
242 uint8_t buf[100];
243 for (size_t i = 0; i < sizeof(buf); i++) {
244 buf[i] = (uint8_t) rand();
245 }
246 ASSERT_STREAM_ALL(write, fd, buf, sizeof(buf));
247 ASSERT_TRUE(check_file_contents(fd, buf, sizeof(buf)), "");
248
249 ASSERT_EQ(link(oldpath, newpath), 0, "");
250
251 // Confirm that both the old link and the new links exist
252 int fd2 = open(newpath, O_RDWR, 0644);
253 ASSERT_GT(fd2, 0, "");
254 ASSERT_TRUE(check_file_contents(fd2, buf, sizeof(buf)), "");
255 ASSERT_TRUE(check_file_contents(fd, buf, sizeof(buf)), "");
256
257 // Remove the old link
258 ASSERT_EQ(close(fd), 0, "");
259 ASSERT_EQ(close(fd2), 0, "");
260 ASSERT_EQ(unlink(oldpath), 0, "");
261
262 // Open the link by its new name
263 fd = open(newpath, O_RDWR, 0644);
264 ASSERT_GT(fd, 0, "");
265 ASSERT_TRUE(check_file_contents(fd, buf, sizeof(buf)), "");
266
267 ASSERT_EQ(close(fd), 0, "");
268 ASSERT_EQ(unlink(newpath), 0, "");
269 ASSERT_EQ(unlink("::dira"), 0, "");
270 ASSERT_EQ(unlink("::dirb"), 0, "");
271
272 END_TEST;
273 }
274
test_link_errors(void)275 bool test_link_errors(void) {
276 BEGIN_TEST;
277
278 if (!test_info->supports_hardlinks) {
279 return true;
280 }
281
282 const char* dirpath = "::dir";
283 const char* oldpath = "::a";
284 const char* newpath = "::b";
285 const char* newpathdir = "::b/";
286
287 // We should not be able to create hard links to directories
288 ASSERT_EQ(mkdir(dirpath, 0755), 0, "");
289 ASSERT_EQ(link(dirpath, newpath), -1, "");
290 ASSERT_EQ(unlink(dirpath), 0, "");
291
292 // We should not be able to create hard links to non-existent files
293 ASSERT_EQ(link(oldpath, newpath), -1, "");
294 ASSERT_EQ(errno, ENOENT, "");
295
296 int fd = open(oldpath, O_RDWR | O_CREAT | O_EXCL, 0644);
297 ASSERT_GT(fd, 0, "");
298 ASSERT_EQ(close(fd), 0, "");
299
300 // We should not be able to link to or from . or ..
301 ASSERT_EQ(link(oldpath, "::."), -1, "");
302 ASSERT_EQ(link(oldpath, "::.."), -1, "");
303 ASSERT_EQ(link("::.", newpath), -1, "");
304 ASSERT_EQ(link("::..", newpath), -1, "");
305
306 // We should not be able to link a file to itself
307 ASSERT_EQ(link(oldpath, oldpath), -1, "");
308 ASSERT_EQ(errno, EEXIST, "");
309
310 // We should not be able to link a file to a path that implies it must be a directory
311 ASSERT_EQ(link(oldpath, newpathdir), -1, "");
312
313 // After linking, we shouldn't be able to link again
314 ASSERT_EQ(link(oldpath, newpath), 0, "");
315 ASSERT_EQ(link(oldpath, newpath), -1, "");
316 ASSERT_EQ(errno, EEXIST, "");
317 // In either order
318 ASSERT_EQ(link(newpath, oldpath), -1, "");
319 ASSERT_EQ(errno, EEXIST, "");
320
321 ASSERT_EQ(unlink(newpath), 0, "");
322 ASSERT_EQ(unlink(oldpath), 0, "");
323
324 END_TEST;
325 }
326
327 RUN_FOR_ALL_FILESYSTEMS(hard_link_tests,
328 RUN_TEST_MEDIUM(test_link_basic)
329 RUN_TEST_MEDIUM(test_link_count_dirs)
330 RUN_TEST_MEDIUM(test_link_count_rename)
331 RUN_TEST_MEDIUM(test_link_between_dirs)
332 RUN_TEST_MEDIUM(test_link_errors)
333 )
334