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 <fcntl.h>
7 #include <limits.h>
8 #include <stdint.h>
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include <sys/stat.h>
13 #include <unistd.h>
14
15 #include <zircon/compiler.h>
16 #include <zircon/syscalls.h>
17 #include <fbl/algorithm.h>
18 #include <fbl/alloc_checker.h>
19 #include <fbl/array.h>
20 #include <fbl/string_piece.h>
21 #include <fbl/unique_ptr.h>
22
23 #include "filesystems.h"
24 #include "misc.h"
25
is_directory(const char * const path)26 constexpr bool is_directory(const char* const path) {
27 return path[fbl::constexpr_strlen(path) - 1] == '/';
28 }
29
TestPersistSimple(void)30 bool TestPersistSimple(void) {
31 BEGIN_TEST;
32
33 if (!test_info->can_be_mounted) {
34 fprintf(stderr, "Filesystem cannot be mounted; cannot test persistence\n");
35 return true;
36 }
37
38 const char* const paths[] = {
39 "::abc",
40 "::def/",
41 "::def/def_subdir/",
42 "::def/def_subdir/def_subfile",
43 "::ghi",
44 "::jkl",
45 "::mnopqrstuvxyz"
46 };
47 for (size_t i = 0; i < fbl::count_of(paths); i++) {
48 if (is_directory(paths[i])) {
49 ASSERT_EQ(mkdir(paths[i], 0644), 0);
50 } else {
51 int fd = open(paths[i], O_RDWR | O_CREAT | O_EXCL, 0644);
52 ASSERT_GT(fd, 0);
53 ASSERT_EQ(close(fd), 0);
54 }
55 }
56
57 ASSERT_TRUE(check_remount(), "Could not remount filesystem");
58
59 // The files should still exist when we remount
60 for (ssize_t i = fbl::count_of(paths) - 1; i >= 0; i--) {
61 if (is_directory(paths[i])) {
62 ASSERT_EQ(rmdir(paths[i]), 0);
63 } else {
64 ASSERT_EQ(unlink(paths[i]), 0);
65 }
66 }
67
68 ASSERT_TRUE(check_remount(), "Could not remount filesystem");
69
70 // But they should stay deleted!
71 for (ssize_t i = fbl::count_of(paths) - 1; i >= 0; i--) {
72 if (is_directory(paths[i])) {
73 ASSERT_EQ(rmdir(paths[i]), -1);
74 } else {
75 ASSERT_EQ(unlink(paths[i]), -1);
76 }
77 }
78
79 END_TEST;
80 }
81
TestPersistRapidRemount(void)82 bool TestPersistRapidRemount(void) {
83 BEGIN_TEST;
84
85 if (!test_info->can_be_mounted) {
86 fprintf(stderr, "Filesystem cannot be mounted; cannot test persistence\n");
87 return true;
88 }
89
90 for (size_t i = 0; i < 100; i++) {
91 ASSERT_TRUE(check_remount(), "Could not remount filesystem");
92 }
93
94 END_TEST;
95 }
96
97
98 template <size_t BufferSize>
TestPersistWithData(void)99 bool TestPersistWithData(void) {
100 BEGIN_TEST;
101
102 if (!test_info->can_be_mounted) {
103 fprintf(stderr, "Filesystem cannot be mounted; cannot test persistence\n");
104 return true;
105 }
106
107 const char* const files[] = {
108 "::abc",
109 "::def",
110 "::and-another-file-filled-with-data",
111 };
112 fbl::unique_ptr<uint8_t[]> buffers[fbl::count_of(files)];
113 unsigned int seed = static_cast<unsigned int>(zx_ticks_get());
114 unittest_printf("Persistent data test using seed: %u\n", seed);
115 fbl::AllocChecker ac;
116 for (size_t i = 0; i < fbl::count_of(files); i++) {
117 buffers[i].reset(new (&ac) uint8_t[BufferSize]);
118 ASSERT_TRUE(ac.check());
119
120 for (size_t j = 0; j < BufferSize; j++) {
121 buffers[i][j] = (uint8_t) rand_r(&seed);
122 }
123 int fd = open(files[i], O_RDWR | O_CREAT, 0644);
124 ASSERT_GT(fd, 0);
125 ASSERT_EQ(write(fd, &buffers[i][0], BufferSize), BufferSize);
126 ASSERT_EQ(fsync(fd), 0);
127 ASSERT_EQ(close(fd), 0);
128 }
129
130 ASSERT_TRUE(check_remount(), "Could not remount filesystem");
131
132 // Read files
133 for (size_t i = 0; i < fbl::count_of(files); i++) {
134 fbl::unique_ptr<uint8_t[]> rbuf(new (&ac) uint8_t[BufferSize]);
135 ASSERT_TRUE(ac.check());
136 int fd = open(files[i], O_RDONLY, 0644);
137 ASSERT_GT(fd, 0);
138
139 struct stat buf;
140 ASSERT_EQ(fstat(fd, &buf), 0);
141 ASSERT_EQ(buf.st_nlink, 1);
142 ASSERT_EQ(buf.st_size, BufferSize);
143
144 ASSERT_EQ(read(fd, &rbuf[0], BufferSize), BufferSize);
145 for (size_t j = 0; j < BufferSize; j++) {
146 ASSERT_EQ(rbuf[j], buffers[i][j]);
147 }
148
149 ASSERT_EQ(close(fd), 0);
150 }
151
152 ASSERT_TRUE(check_remount(), "Could not remount filesystem");
153
154 // Delete all files
155 for (size_t i = 0; i < fbl::count_of(files); i++) {
156 ASSERT_EQ(unlink(files[i]), 0);
157 }
158
159 ASSERT_TRUE(check_remount(), "Could not remount filesystem");
160
161 // Files should stay deleted
162
163 DIR* dirp = opendir("::.");
164 ASSERT_NONNULL(dirp);
165 struct dirent* de;
166 de = readdir(dirp);
167 ASSERT_NONNULL(de);
168 ASSERT_EQ(strncmp(de->d_name, ".", 1), 0);
169 ASSERT_NULL(readdir(dirp));
170 ASSERT_EQ(closedir(dirp), 0);
171
172 END_TEST;
173 }
174
175 constexpr size_t kMaxLoopLength = 26;
176
177 template <bool MoveDirectory, size_t LoopLength, size_t Moves>
TestRenameLoop(void)178 bool TestRenameLoop(void) {
179 BEGIN_TEST;
180
181 if (!test_info->can_be_mounted) {
182 fprintf(stderr, "Filesystem cannot be mounted; cannot test persistence\n");
183 return true;
184 }
185
186 static_assert(LoopLength <= kMaxLoopLength, "Loop length too long");
187
188 char src[128];
189 // Create "LoopLength" directories
190 for (size_t i = 0; i < LoopLength; i++) {
191 ASSERT_GT(sprintf(src, "::%c", static_cast<char>('a' + i)), 0);
192 ASSERT_EQ(mkdir(src, 0644), 0);
193 }
194
195 // Create a 'target'
196 if (MoveDirectory) {
197 ASSERT_EQ(mkdir("::a/target", 0644), 0);
198 } else {
199 int fd = open("::a/target", O_RDWR | O_CREAT);
200 ASSERT_GT(fd, 0);
201 ASSERT_EQ(close(fd), 0);
202 }
203
204 // Move the target through the loop a bunch of times
205 size_t moves = Moves;
206 strcpy(src, "::a/target");
207 size_t char_index = 0;
208 while (moves--) {
209 char dst[128];
210 strcpy(dst, src);
211 char_index = (char_index + 1) % LoopLength;
212 dst[2] = static_cast<char>('a' + char_index);
213 ASSERT_EQ(rename(src, dst), 0);
214 strcpy(src, dst);
215 }
216
217 ASSERT_TRUE(check_remount(), "Could not remount filesystem");
218
219 // Check that the target only exists in ONE directory
220 bool target_found = false;
221 for (size_t i = 0; i < LoopLength; i++) {
222 ASSERT_GT(sprintf(src, "::%c", static_cast<char>('a' + i)), 0);
223 DIR* dirp = opendir(src);
224 ASSERT_NONNULL(dirp);
225 struct dirent* de;
226 de = readdir(dirp);
227 ASSERT_NONNULL(de);
228 ASSERT_EQ(strcmp(de->d_name, "."), 0);
229 de = readdir(dirp);
230 if (de != nullptr) {
231 ASSERT_FALSE(target_found, "Target found twice!");
232 ASSERT_EQ(strcmp(de->d_name, "target"), 0, "Non-target found");
233 target_found = true;
234 }
235
236 ASSERT_EQ(closedir(dirp), 0);
237 }
238 ASSERT_TRUE(target_found);
239
240 ASSERT_TRUE(check_remount(), "Could not remount filesystem");
241
242 // Clean up
243
244 target_found = false;
245 for (size_t i = 0; i < LoopLength; i++) {
246 ASSERT_GT(sprintf(src, "::%c", static_cast<char>('a' + i)), 0);
247 int ret = unlink(src);
248 if (ret != 0) {
249 ASSERT_FALSE(target_found);
250 ASSERT_GT(sprintf(src, "::%c/target", static_cast<char>('a' + i)), 0);
251 ASSERT_EQ(unlink(src), 0);
252 ASSERT_GT(sprintf(src, "::%c", static_cast<char>('a' + i)), 0);
253 ASSERT_EQ(unlink(src), 0);
254 target_found = true;
255 }
256 }
257 ASSERT_TRUE(target_found, "Target was never unlinked");
258
259 END_TEST;
260 }
261
262 RUN_FOR_ALL_FILESYSTEMS(persistence_tests,
263 RUN_TEST_MEDIUM(TestPersistSimple)
264 RUN_TEST_LARGE(TestPersistRapidRemount)
265 RUN_TEST_MEDIUM((TestPersistWithData<1>))
266 RUN_TEST_MEDIUM((TestPersistWithData<100>))
267 RUN_TEST_LARGE((TestPersistWithData<8192 - 1>))
268 RUN_TEST_LARGE((TestPersistWithData<8192>))
269 RUN_TEST_LARGE((TestPersistWithData<8192 + 1>))
270 RUN_TEST_LARGE((TestPersistWithData<8192 * 128>))
271 RUN_TEST_MEDIUM((TestRenameLoop<false, 2, 2>));
272 RUN_TEST_LARGE((TestRenameLoop<false, 2, 100>));
273 RUN_TEST_LARGE((TestRenameLoop<false, 15, 100>));
274 RUN_TEST_LARGE((TestRenameLoop<false, 25, 500>));
275 RUN_TEST_MEDIUM((TestRenameLoop<true, 2, 2>));
276 RUN_TEST_LARGE((TestRenameLoop<true, 2, 100>));
277 RUN_TEST_LARGE((TestRenameLoop<true, 15, 100>));
278 RUN_TEST_LARGE((TestRenameLoop<true, 25, 500>));
279 )
280