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