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 <stdbool.h>
9 #include <stdint.h>
10 #include <stdio.h>
11 #include <string.h>
12 #include <sys/stat.h>
13 #include <unistd.h>
14 
15 #include <fvm/fvm.h>
16 #include <fs-management/fvm.h>
17 #include <fs-management/mount.h>
18 #include <fs-management/ramdisk.h>
19 #include <zircon/device/block.h>
20 #include <zircon/device/device.h>
21 #include <zircon/device/ramdisk.h>
22 
23 #include "filesystems.h"
24 
25 const char* kTmpfsPath = "/fs-test-tmp";
26 const char* kMountPath = "/fs-test-tmp/mount";
27 
28 bool use_real_disk = false;
29 block_info_t test_disk_info;
30 char test_disk_path[PATH_MAX];
31 char* ramdisk_path;
32 fs_info_t* test_info;
33 
34 static char fvm_disk_path[PATH_MAX];
35 
36 constexpr const char minfs_name[] = "minfs";
37 constexpr const char memfs_name[] = "memfs";
38 constexpr const char thinfs_name[] = "FAT";
39 
40 const fsck_options_t test_fsck_options = {
41     .verbose = false,
42     .never_modify = true,
43     .always_modify = false,
44     .force = true,
45 };
46 
47 #define FVM_DRIVER_LIB "/boot/driver/fvm.so"
48 #define STRLEN(s) sizeof(s) / sizeof((s)[0])
49 
50 const test_disk_t default_test_disk = {
51     .block_count = TEST_BLOCK_COUNT_DEFAULT,
52     .block_size = TEST_BLOCK_SIZE_DEFAULT,
53     .slice_size = TEST_FVM_SLICE_SIZE_DEFAULT,
54 };
55 
56 constexpr uint8_t kTestUniqueGUID[] = {
57     0xFF, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
58     0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
59 };
60 
61 constexpr uint8_t kTestPartGUID[] = {
62     0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
63     0xFF, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07
64 };
65 
setup_fs_test(test_disk_t disk,fs_test_type_t test_class)66 void setup_fs_test(test_disk_t disk, fs_test_type_t test_class) {
67     int r = mkdir(kMountPath, 0755);
68     if ((r < 0) && errno != EEXIST) {
69         fprintf(stderr, "Could not create mount point for test filesystem\n");
70         exit(-1);
71     }
72 
73     ramdisk_path = nullptr;
74 
75     if (!use_real_disk) {
76         if (create_ramdisk(disk.block_size, disk.block_count, test_disk_path) != ZX_OK) {
77             fprintf(stderr, "[FAILED]: Could not create ramdisk for test\n");
78             exit(-1);
79         }
80 
81         test_disk_info.block_size = static_cast<uint32_t>(disk.block_size);
82         test_disk_info.block_count = disk.block_count;
83         ramdisk_path = test_disk_path;
84     }
85 
86     if (test_class == FS_TEST_FVM) {
87         int fd = open(test_disk_path, O_RDWR);
88         if (fd < 0) {
89             fprintf(stderr, "[FAILED]: Could not open test disk\n");
90             exit(-1);
91         }
92         if (fvm_init(fd, disk.slice_size) != ZX_OK) {
93             fprintf(stderr, "[FAILED]: Could not format disk with FVM\n");
94             exit(-1);
95         }
96         if (ioctl_device_bind(fd, FVM_DRIVER_LIB, STRLEN(FVM_DRIVER_LIB)) < 0) {
97             fprintf(stderr, "[FAILED]: Could not bind disk to FVM driver\n");
98             exit(-1);
99         }
100         snprintf(fvm_disk_path, sizeof(fvm_disk_path), "%s/fvm", test_disk_path);
101         if (wait_for_device(fvm_disk_path, ZX_SEC(3)) != ZX_OK) {
102             fprintf(stderr, "[FAILED]: FVM driver never appeared at %s\n", test_disk_path);
103             exit(-1);
104         }
105 
106         // Open "fvm" driver
107         close(fd);
108         int fvm_fd;
109         if ((fvm_fd = open(fvm_disk_path, O_RDWR)) < 0) {
110             fprintf(stderr, "[FAILED]: Could not open FVM driver\n");
111             exit(-1);
112         }
113 
114         alloc_req_t request;
115         memset(&request, 0, sizeof(request));
116         request.slice_count = 1;
117         strcpy(request.name, "fs-test-partition");
118         memcpy(request.type, kTestPartGUID, sizeof(request.type));
119         memcpy(request.guid, kTestUniqueGUID, sizeof(request.guid));
120 
121         if ((fd = fvm_allocate_partition(fvm_fd, &request)) < 0) {
122             fprintf(stderr, "[FAILED]: Could not allocate FVM partition\n");
123             exit(-1);
124         }
125         close(fvm_fd);
126         close(fd);
127 
128         if ((fd = open_partition(kTestUniqueGUID, kTestPartGUID, 0, test_disk_path)) < 0) {
129             fprintf(stderr, "[FAILED]: Could not locate FVM partition\n");
130             exit(-1);
131         }
132         close(fd);
133 
134         // Restore the "fvm_disk_path" to the containing disk, so it can
135         // be destroyed when the test completes
136         fvm_disk_path[strlen(fvm_disk_path) - strlen("/fvm")] = 0;
137         ramdisk_path = fvm_disk_path;
138     }
139 
140     if (test_info->mkfs(test_disk_path)) {
141         fprintf(stderr, "[FAILED]: Could not format disk (%s) for test\n", test_disk_path);
142         exit(-1);
143     }
144 
145     if (test_info->mount(test_disk_path, kMountPath)) {
146         fprintf(stderr, "[FAILED]: Error mounting filesystem\n");
147         exit(-1);
148     }
149 }
150 
teardown_fs_test(fs_test_type_t test_class)151 void teardown_fs_test(fs_test_type_t test_class) {
152     if (test_info->unmount(kMountPath)) {
153         fprintf(stderr, "[FAILED]: Error unmounting filesystem\n");
154         exit(-1);
155     }
156 
157     if (test_info->fsck(test_disk_path)) {
158         fprintf(stderr, "[FAILED]: Filesystem fsck failed\n");
159         exit(-1);
160     }
161 
162     if (test_class == FS_TEST_FVM) {
163         if (use_real_disk) {
164             if (fvm_destroy(fvm_disk_path) != ZX_OK) {
165                 fprintf(stderr, "[FAILED]: Couldn't destroy FVM on test disk\n");
166                 exit(-1);
167             }
168         }
169 
170         // Move the test_disk_path back to the 'real' disk, rather than
171         // a partition within the FVM.
172         strcpy(test_disk_path, fvm_disk_path);
173     }
174 
175     if (!use_real_disk) {
176         if (destroy_ramdisk(test_disk_path)) {
177             fprintf(stderr, "[FAILED]: Error destroying ramdisk\n");
178             exit(-1);
179         }
180     }
181 }
182 
183 // FS-specific functionality:
184 
185 template <const char* fs_name>
should_test_filesystem(void)186 bool should_test_filesystem(void) {
187     return !strcmp(filesystem_name_filter, "") || !strcmp(fs_name, filesystem_name_filter);
188 }
189 
mkfs_memfs(const char * disk_path)190 int mkfs_memfs(const char* disk_path) {
191     return 0;
192 }
193 
fsck_memfs(const char * disk_path)194 int fsck_memfs(const char* disk_path) {
195     return 0;
196 }
197 
198 // TODO(smklein): Even this hacky solution has a hacky implementation, and
199 // should be replaced with a variation of "rm -r" when ready.
unlink_recursive(const char * path)200 static int unlink_recursive(const char* path) {
201     DIR* dir;
202     if ((dir = opendir(path)) == NULL) {
203         return errno;
204     }
205 
206     struct dirent* de;
207     int r = 0;
208     while ((de = readdir(dir)) != NULL) {
209         if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, ".."))
210             continue;
211 
212         char tmp[PATH_MAX];
213         tmp[0] = 0;
214         size_t bytes_left = PATH_MAX - 1;
215         strncat(tmp, path, bytes_left);
216         bytes_left -= strlen(path);
217         strncat(tmp, "/", bytes_left);
218         bytes_left--;
219         strncat(tmp, de->d_name, bytes_left);
220         // At the moment, we don't have a great way of identifying what is /
221         // isn't a directory. Just try to open it as a directory, and return
222         // without an error if we're wrong.
223         if ((r = unlink_recursive(tmp)) < 0) {
224             break;
225         }
226         if ((r = unlink(tmp)) < 0) {
227             break;
228         }
229     }
230 
231     closedir(dir);
232     return r;
233 }
234 
235 // TODO(smklein): It would be cleaner to unmount the filesystem completely,
236 // and remount a fresh copy. However, a hackier (but currently working)
237 // solution involves recursively deleting all files in the mounted
238 // filesystem.
mount_memfs(const char * disk_path,const char * mount_path)239 int mount_memfs(const char* disk_path, const char* mount_path) {
240     struct stat st;
241     if (stat(kMountPath, &st)) {
242         if (mkdir(kMountPath, 0644) < 0) {
243             return -1;
244         }
245     } else if (!S_ISDIR(st.st_mode)) {
246         return -1;
247     }
248     int r = unlink_recursive(kMountPath);
249     return r;
250 }
251 
unmount_memfs(const char * mount_path)252 int unmount_memfs(const char* mount_path) {
253     return unlink_recursive(kMountPath);
254 }
255 
mkfs_common(const char * disk_path,disk_format_t fs_type)256 static int mkfs_common(const char* disk_path, disk_format_t fs_type) {
257     zx_status_t status;
258     if ((status = mkfs(disk_path, fs_type, launch_stdio_sync,
259                        &default_mkfs_options)) != ZX_OK) {
260         fprintf(stderr, "Could not mkfs filesystem(%s)",
261                 disk_format_string(fs_type));
262         return -1;
263     }
264     return 0;
265 }
266 
fsck_common(const char * disk_path,disk_format_t fs_type)267 static int fsck_common(const char* disk_path, disk_format_t fs_type) {
268     zx_status_t status;
269     if ((status = fsck(disk_path, fs_type, &test_fsck_options,
270                        launch_stdio_sync)) != ZX_OK) {
271         fprintf(stderr, "fsck on %s failed", disk_format_string(fs_type));
272         return -1;
273     }
274     return 0;
275 }
276 
mount_common(const char * disk_path,const char * mount_path,disk_format_t fs_type)277 static int mount_common(const char* disk_path, const char* mount_path,
278                         disk_format_t fs_type) {
279     int fd = open(disk_path, O_RDWR);
280 
281     if (fd < 0) {
282         fprintf(stderr, "Could not open disk: %s\n", disk_path);
283         return -1;
284     }
285 
286     // fd consumed by mount. By default, mount waits until the filesystem is
287     // ready to accept commands.
288     zx_status_t status;
289     if ((status = mount(fd, mount_path, fs_type, &default_mount_options,
290                         launch_stdio_async)) != ZX_OK) {
291         fprintf(stderr, "Could not mount %s filesystem\n",
292                 disk_format_string(fs_type));
293         return status;
294     }
295 
296     return 0;
297 }
298 
unmount_common(const char * mount_path)299 static int unmount_common(const char* mount_path) {
300     zx_status_t status = umount(mount_path);
301     if (status != ZX_OK) {
302         fprintf(stderr, "Failed to unmount filesystem\n");
303         return status;
304     }
305     return 0;
306 }
307 
mkfs_minfs(const char * disk_path)308 int mkfs_minfs(const char* disk_path) {
309     return mkfs_common(disk_path, DISK_FORMAT_MINFS);
310 }
311 
fsck_minfs(const char * disk_path)312 int fsck_minfs(const char* disk_path) {
313     return fsck_common(disk_path, DISK_FORMAT_MINFS);
314 }
315 
mount_minfs(const char * disk_path,const char * mount_path)316 int mount_minfs(const char* disk_path, const char* mount_path) {
317     return mount_common(disk_path, mount_path, DISK_FORMAT_MINFS);
318 }
319 
unmount_minfs(const char * mount_path)320 int unmount_minfs(const char* mount_path) {
321     return unmount_common(mount_path);
322 }
323 
should_test_thinfs(void)324 bool should_test_thinfs(void) {
325     struct stat buf;
326     return (stat("/system/bin/thinfs", &buf) == 0) && should_test_filesystem<thinfs_name>();
327 }
328 
mkfs_thinfs(const char * disk_path)329 int mkfs_thinfs(const char* disk_path) {
330     return mkfs_common(disk_path, DISK_FORMAT_FAT);
331 }
332 
fsck_thinfs(const char * disk_path)333 int fsck_thinfs(const char* disk_path) {
334     return fsck_common(disk_path, DISK_FORMAT_FAT);
335 }
336 
mount_thinfs(const char * disk_path,const char * mount_path)337 int mount_thinfs(const char* disk_path, const char* mount_path) {
338     return mount_common(disk_path, mount_path, DISK_FORMAT_FAT);
339 }
340 
unmount_thinfs(const char * mount_path)341 int unmount_thinfs(const char* mount_path) {
342     return unmount_common(mount_path);
343 }
344 
345 fs_info_t FILESYSTEMS[NUM_FILESYSTEMS] = {
346     {memfs_name,
347         should_test_filesystem<memfs_name>, mkfs_memfs, mount_memfs, unmount_memfs, fsck_memfs,
348         .can_be_mounted = false,
349         .can_mount_sub_filesystems = true,
350         .supports_hardlinks = true,
351         .supports_watchers = true,
352         .supports_create_by_vmo = true,
353         .supports_mmap = true,
354         .supports_resize = false,
355         .nsec_granularity = 1,
356     },
357     {minfs_name,
358         should_test_filesystem<minfs_name>, mkfs_minfs, mount_minfs, unmount_minfs, fsck_minfs,
359         .can_be_mounted = true,
360         .can_mount_sub_filesystems = true,
361         .supports_hardlinks = true,
362         .supports_watchers = true,
363         .supports_create_by_vmo = false,
364         .supports_mmap = false,
365         .supports_resize = true,
366         .nsec_granularity = 1,
367     },
368     {thinfs_name,
369         should_test_thinfs, mkfs_thinfs, mount_thinfs, unmount_thinfs, fsck_thinfs,
370         .can_be_mounted = true,
371         .can_mount_sub_filesystems = false,
372         .supports_hardlinks = false,
373         .supports_watchers = false,
374         .supports_create_by_vmo = false,
375         .supports_mmap = false,
376         .supports_resize = false,
377         .nsec_granularity = ZX_SEC(2),
378     },
379 };
380