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