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