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