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 <dirent.h>
6#include <errno.h>
7#include <fcntl.h>
8#include <stdint.h>
9#include <stdio.h>
10#include <stdlib.h>
11#include <sys/stat.h>
12#include <threads.h>
13#include <unistd.h>
14
15#include <zircon/syscalls.h>
16#include <fbl/atomic.h>
17#include <unittest/unittest.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.
25template <bool reuse_subdirectory>
26bool test_inode_reuse(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
46constexpr int kSuccess = 1;
47constexpr int kFailure = -1;
48constexpr int kUnexpectedFailure = -2;
49
50using 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.
62template <size_t kNumThreads, size_t kSuccessCount>
63bool 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
89constexpr size_t kIterCount = 10;
90
91bool test_create_unlink_exclusive(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
116bool test_mkdir_rmdir_exclusive(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
140bool test_rename_exclusive(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        fbl::atomic<uint32_t> ctr{0};
159        ASSERT_TRUE((thread_action_test<10, 1>([](void* arg) {
160            auto ctr = reinterpret_cast<fbl::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
195bool test_rename_overwrite(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        fbl::atomic<uint32_t> ctr{0};
201        ASSERT_TRUE((thread_action_test<10, 10>([](void* arg) {
202            auto ctr = reinterpret_cast<fbl::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
218bool test_link_exclusive(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
246RUN_FOR_ALL_FILESYSTEMS(threading_tests,
247    RUN_TEST_LARGE((test_inode_reuse<false>))
248    RUN_TEST_LARGE((test_inode_reuse<true>))
249    RUN_TEST_MEDIUM(test_create_unlink_exclusive)
250    RUN_TEST_MEDIUM(test_mkdir_rmdir_exclusive)
251    RUN_TEST_LARGE(test_rename_exclusive)
252    RUN_TEST_LARGE(test_rename_overwrite)
253    RUN_TEST_LARGE(test_link_exclusive)
254)
255