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