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 <atomic>
6 #include <dirent.h>
7 #include <errno.h>
8 #include <fcntl.h>
9 #include <stdint.h>
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <sys/stat.h>
13 #include <threads.h>
14 #include <unistd.h>
15
16 #include <unittest/unittest.h>
17 #include <zircon/syscalls.h>
18
19 #include "filesystems.h"
20 #include "misc.h"
21
22 // Try repeatedly creating and removing a file within a directory,
23 // as fast as possible, in an attempt to trigger filesystem-internal
24 // threading races between creation and deletion of a file.
25 template <bool reuse_subdirectory>
TestInodeReuse(void)26 bool TestInodeReuse(void) {
27 BEGIN_TEST;
28
29 ASSERT_EQ(mkdir("::reuse", 0755), 0);
30 DIR* d = opendir("::reuse");
31 ASSERT_NONNULL(d);
32 for (size_t i = 0; i < 1000; i++) {
33 ASSERT_EQ(mkdirat(dirfd(d), "foo", 0666), 0);
34 if (reuse_subdirectory) {
35 ASSERT_EQ(mkdirat(dirfd(d), "foo/bar", 0666), 0);
36 ASSERT_EQ(unlinkat(dirfd(d), "foo/bar", 0), 0);
37 }
38 ASSERT_EQ(unlinkat(dirfd(d), "foo", 0), 0);
39 }
40 ASSERT_EQ(closedir(d), 0);
41 ASSERT_EQ(rmdir("::reuse"), 0);
42 END_TEST;
43 }
44
45 // Return codes from helper threads
46 constexpr int kSuccess = 1;
47 constexpr int kFailure = -1;
48 constexpr int kUnexpectedFailure = -2;
49
50 using thrd_cb_t = int(void*);
51
52 // Launch some threads, and have them all execute callback
53 // 'cb'.
54 //
55 // It is expected that:
56 // - kSuccessCount threads will return "kSuccess"
57 // - ALL OTHER threads will return "kFailure"
58 //
59 // In any other condition, this helper fails.
60 // For example, returning "kUnexpectedFailure" from cb
61 // is an easy way to fail the entire test from a background thread.
62 template <size_t kNumThreads, size_t kSuccessCount>
thread_action_test(thrd_cb_t cb,void * arg=nullptr)63 bool thread_action_test(thrd_cb_t cb, void* arg = nullptr) {
64 BEGIN_HELPER;
65
66 static_assert(kNumThreads >= kSuccessCount, "Need more threads or less successes");
67
68 thrd_t threads[kNumThreads];
69 for (size_t i = 0; i < kNumThreads; i++) {
70 ASSERT_EQ(thrd_create(&threads[i], cb, arg), thrd_success);
71 }
72
73 size_t success_count = 0;
74 for (size_t i = 0; i < kNumThreads; i++) {
75 int rc;
76 ASSERT_EQ(thrd_join(threads[i], &rc), thrd_success);
77 if (rc == kSuccess) {
78 success_count++;
79 ASSERT_LE(success_count, kSuccessCount, "Too many succeeding threads");
80 } else {
81 ASSERT_EQ(rc, kFailure, "Unexpected return code from worker thread");
82 }
83 }
84 ASSERT_EQ(success_count, kSuccessCount, "Not enough succeeding threads");
85
86 END_HELPER;
87 }
88
89 constexpr size_t kIterCount = 10;
90
TestCreateUnlinkExclusive(void)91 bool TestCreateUnlinkExclusive(void) {
92 BEGIN_TEST;
93 for (size_t i = 0; i < kIterCount; i++) {
94 ASSERT_TRUE((thread_action_test<10, 1>([](void* arg) {
95 int fd = open("::exclusive", O_RDWR | O_CREAT | O_EXCL);
96 if (fd > 0) {
97 return close(fd) == 0 ? kSuccess : kUnexpectedFailure;
98 } else if (errno == EEXIST) {
99 return kFailure;
100 }
101 return kUnexpectedFailure;
102 })));
103
104 ASSERT_TRUE((thread_action_test<10, 1>([](void* arg) {
105 if (unlink("::exclusive") == 0) {
106 return kSuccess;
107 } else if (errno == ENOENT) {
108 return kFailure;
109 }
110 return kUnexpectedFailure;
111 })));
112 }
113 END_TEST;
114 }
115
TestMkdirRmdirExclusive(void)116 bool TestMkdirRmdirExclusive(void) {
117 BEGIN_TEST;
118 for (size_t i = 0; i < kIterCount; i++) {
119 ASSERT_TRUE((thread_action_test<10, 1>([](void* arg) {
120 if (mkdir("::exclusive", 0666) == 0) {
121 return kSuccess;
122 } else if (errno == EEXIST) {
123 return kFailure;
124 }
125 return kUnexpectedFailure;
126 })));
127
128 ASSERT_TRUE((thread_action_test<10, 1>([](void* arg) {
129 if (rmdir("::exclusive") == 0) {
130 return kSuccess;
131 } else if (errno == ENOENT) {
132 return kFailure;
133 }
134 return kUnexpectedFailure;
135 })));
136 }
137 END_TEST;
138 }
139
TestRenameExclusive(void)140 bool TestRenameExclusive(void) {
141 BEGIN_TEST;
142 for (size_t i = 0; i < kIterCount; i++) {
143
144 // Test case of renaming from a single source.
145 ASSERT_EQ(mkdir("::rename_start", 0666), 0);
146 ASSERT_TRUE((thread_action_test<10, 1>([](void* arg) {
147 if (rename("::rename_start", "::rename_end") == 0) {
148 return kSuccess;
149 } else if (errno == ENOENT) {
150 return kFailure;
151 }
152 return kUnexpectedFailure;
153 })));
154 ASSERT_EQ(rmdir("::rename_end"), 0);
155
156 // Test case of renaming from multiple sources at once,
157 // to a single destination
158 std::atomic<uint32_t> ctr{0};
159 ASSERT_TRUE((thread_action_test<10, 1>([](void* arg) {
160 auto ctr = reinterpret_cast<std::atomic<uint32_t>*>(arg);
161 char start[128];
162 snprintf(start, sizeof(start) - 1, "::rename_start_%u", ctr->fetch_add(1));
163 if (mkdir(start, 0666)) {
164 return kUnexpectedFailure;
165 }
166
167 // Give the target a child, so it cannot be overwritten as a target
168 char child[256];
169 snprintf(child, sizeof(child) - 1, "%s/child", start);
170 if (mkdir(child, 0666)) {
171 return kUnexpectedFailure;
172 }
173
174 if (rename(start, "::rename_end") == 0) {
175 return kSuccess;
176 } else if (errno == ENOTEMPTY || errno == EEXIST) {
177 return rmdir(child) == 0 && rmdir(start) == 0 ? kFailure :
178 kUnexpectedFailure;
179 }
180 return kUnexpectedFailure;
181 }, &ctr)));
182
183 DIR* dir = opendir("::rename_end");
184 ASSERT_NONNULL(dir);
185 struct dirent* de;
186 while ((de = readdir(dir)) && de != nullptr) {
187 unlinkat(dirfd(dir), de->d_name, AT_REMOVEDIR);
188 }
189 ASSERT_EQ(closedir(dir), 0);
190 ASSERT_EQ(rmdir("::rename_end"), 0);
191 }
192 END_TEST;
193 }
194
TestRenameOverwrite(void)195 bool TestRenameOverwrite(void) {
196 BEGIN_TEST;
197 for (size_t i = 0; i < kIterCount; i++) {
198 // Test case of renaming from multiple sources at once,
199 // to a single destination
200 std::atomic<uint32_t> ctr{0};
201 ASSERT_TRUE((thread_action_test<10, 10>([](void* arg) {
202 auto ctr = reinterpret_cast<std::atomic<uint32_t>*>(arg);
203 char start[128];
204 snprintf(start, sizeof(start) - 1, "::rename_start_%u", ctr->fetch_add(1));
205 if (mkdir(start, 0666)) {
206 return kUnexpectedFailure;
207 }
208 if (rename(start, "::rename_end") == 0) {
209 return kSuccess;
210 }
211 return kUnexpectedFailure;
212 }, &ctr)));
213 ASSERT_EQ(rmdir("::rename_end"), 0);
214 }
215 END_TEST;
216 }
217
TestLinkExclusive(void)218 bool TestLinkExclusive(void) {
219 BEGIN_TEST;
220
221 if (!test_info->supports_hardlinks) {
222 return true;
223 }
224
225 for (size_t i = 0; i < kIterCount; i++) {
226 int fd = open("::link_start", O_RDWR | O_CREAT | O_EXCL);
227 ASSERT_GT(fd, 0);
228 ASSERT_EQ(close(fd), 0);
229
230 ASSERT_TRUE((thread_action_test<10, 1>([](void* arg) {
231 if (link("::link_start", "::link_end") == 0) {
232 return kSuccess;
233 } else if (errno == EEXIST) {
234 return kFailure;
235 }
236 return kUnexpectedFailure;
237 })));
238
239 ASSERT_EQ(unlink("::link_start"), 0);
240 ASSERT_EQ(unlink("::link_end"), 0);
241 ASSERT_TRUE(check_remount());
242 }
243 END_TEST;
244 }
245
246 RUN_FOR_ALL_FILESYSTEMS(threading_tests,
247 RUN_TEST_LARGE((TestInodeReuse<false>))
248 RUN_TEST_LARGE((TestInodeReuse<true>))
249 RUN_TEST_MEDIUM(TestCreateUnlinkExclusive)
250 RUN_TEST_MEDIUM(TestMkdirRmdirExclusive)
251 RUN_TEST_LARGE(TestRenameExclusive)
252 RUN_TEST_LARGE(TestRenameOverwrite)
253 RUN_TEST_LARGE(TestLinkExclusive)
254 )
255