| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <dirent.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <fuchsia/hardware/block/c/fidl.h> |
| #include <fuchsia/hardware/block/partition/c/fidl.h> |
| #include <fuchsia/hardware/block/volume/c/fidl.h> |
| #include <lib/fdio/cpp/caller.h> |
| #include <lib/fdio/directory.h> |
| #include <lib/fdio/fd.h> |
| #include <lib/fdio/fdio.h> |
| #include <lib/fdio/limits.h> |
| #include <lib/fdio/vfs.h> |
| #include <lib/fdio/watcher.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <zircon/compiler.h> |
| #include <zircon/device/block.h> |
| #include <zircon/device/vfs.h> |
| #include <zircon/processargs.h> |
| #include <zircon/syscalls.h> |
| |
| #include <memory> |
| #include <utility> |
| |
| #include <fbl/auto_call.h> |
| #include <fbl/string_printf.h> |
| #include <fbl/unique_fd.h> |
| #include <fs-management/fvm.h> |
| #include <fvm/format.h> |
| |
| namespace { |
| |
| constexpr char kBlockDevPath[] = "/dev/class/block/"; |
| constexpr char kBlockDevRelativePath[] = "class/block/"; |
| |
| // Checks that |fd| is a partition which matches |uniqueGUID| and |typeGUID|. |
| // If either is null, it doesn't compare |fd| with that guid. |
| // At least one of the GUIDs must be non-null. |
| bool IsPartition(const fbl::unique_fd& fd, const uint8_t* uniqueGUID, const uint8_t* typeGUID) { |
| ZX_ASSERT(uniqueGUID || typeGUID); |
| |
| fuchsia_hardware_block_partition_GUID guid; |
| fdio_cpp::UnownedFdioCaller partition_connection(fd.get()); |
| zx::unowned_channel channel(partition_connection.borrow_channel()); |
| zx_status_t io_status, status; |
| if (typeGUID) { |
| io_status = |
| fuchsia_hardware_block_partition_PartitionGetTypeGuid(channel->get(), &status, &guid); |
| if (io_status != ZX_OK || status != ZX_OK || |
| memcmp(guid.value, typeGUID, BLOCK_GUID_LEN) != 0) { |
| return false; |
| } |
| } |
| if (uniqueGUID) { |
| io_status = |
| fuchsia_hardware_block_partition_PartitionGetInstanceGuid(channel->get(), &status, &guid); |
| if (io_status != ZX_OK || status != ZX_OK || |
| memcmp(guid.value, uniqueGUID, BLOCK_GUID_LEN) != 0) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| // Overwrites the FVM and waits for it to disappear from devfs. |
| // |
| // devfs_root_fd: (OPTIONAL) A connection to devfs. If supplied, |path| is relative to this root. |
| // parent_fd: An fd to the parent of the FVM device. |
| // path: The path to the FVM device. Relative to |devfs_root_fd| if supplied. |
| zx_status_t DestroyFVMAndWait(int devfs_root_fd, fbl::unique_fd parent_fd, fbl::unique_fd driver_fd, |
| const char* path) { |
| fuchsia_hardware_block_volume_VolumeInfo volume_info; |
| zx_status_t status = fvm_query(driver_fd.get(), &volume_info); |
| if (status != ZX_OK) { |
| return ZX_ERR_WRONG_TYPE; |
| } |
| |
| struct fvm_destroyer { |
| int devfs_root_fd; |
| uint64_t slice_size; |
| const char* path; |
| bool destroyed; |
| } destroyer; |
| destroyer.devfs_root_fd = devfs_root_fd; |
| destroyer.slice_size = volume_info.slice_size; |
| destroyer.path = path; |
| destroyer.destroyed = false; |
| |
| auto cb = [](int dirfd, int event, const char* fn, void* cookie) { |
| auto destroyer = static_cast<fvm_destroyer*>(cookie); |
| if (event == WATCH_EVENT_WAITING) { |
| zx_status_t status = ZX_ERR_INTERNAL; |
| if (destroyer->devfs_root_fd != -1) { |
| status = fvm_overwrite_with_devfs(destroyer->devfs_root_fd, destroyer->path, |
| destroyer->slice_size); |
| } else { |
| status = fvm_overwrite(destroyer->path, destroyer->slice_size); |
| } |
| destroyer->destroyed = true; |
| return status; |
| } |
| if ((event == WATCH_EVENT_REMOVE_FILE) && !strcmp(fn, "fvm")) { |
| return ZX_ERR_STOP; |
| } |
| return ZX_OK; |
| }; |
| status = fdio_watch_directory(parent_fd.get(), cb, zx::time::infinite().get(), &destroyer); |
| if (status != ZX_ERR_STOP) { |
| return status; |
| } |
| return ZX_OK; |
| } |
| |
| } // namespace |
| |
| __EXPORT |
| zx_status_t fvm_init_preallocated(int fd, uint64_t initial_volume_size, uint64_t max_volume_size, |
| size_t slice_size) { |
| if (slice_size % fvm::kBlockSize != 0) { |
| // Alignment |
| return ZX_ERR_INVALID_ARGS; |
| } else if ((slice_size * fvm::kMaxVSlices) / fvm::kMaxVSlices != slice_size) { |
| // Overflow |
| return ZX_ERR_INVALID_ARGS; |
| } else if (initial_volume_size > max_volume_size || initial_volume_size == 0 || |
| max_volume_size == 0) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| fvm::FormatInfo format_info = |
| fvm::FormatInfo::FromPreallocatedSize(initial_volume_size, max_volume_size, slice_size); |
| |
| std::unique_ptr<uint8_t[]> mvmo(new uint8_t[format_info.metadata_allocated_size() * 2]); |
| // Clear entire primary copy of metadata |
| memset(mvmo.get(), 0, format_info.metadata_allocated_size()); |
| |
| // Superblock |
| fvm::fvm_t* sb = reinterpret_cast<fvm::fvm_t*>(mvmo.get()); |
| sb->magic = fvm::kMagic; |
| sb->version = fvm::kVersion; |
| sb->pslice_count = format_info.slice_count(); |
| sb->slice_size = slice_size; |
| sb->fvm_partition_size = initial_volume_size; |
| sb->vpartition_table_size = fvm::kVPartTableLength; |
| sb->allocation_table_size = fvm::AllocTableLength(max_volume_size, slice_size); |
| sb->generation = 0; |
| |
| if (sb->pslice_count == 0) { |
| return ZX_ERR_NO_SPACE; |
| } |
| |
| fvm_update_hash(mvmo.get(), format_info.metadata_size()); |
| |
| void* backup = mvmo.get() + format_info.GetSuperblockOffset(fvm::SuperblockType::kSecondary); |
| memcpy(backup, mvmo.get(), format_info.metadata_size()); |
| |
| zx_status_t status = |
| fvm_validate_header(mvmo.get(), backup, format_info.metadata_size(), nullptr); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| if (lseek(fd, 0, SEEK_SET) < 0) { |
| return ZX_ERR_BAD_STATE; |
| } |
| // Write to primary copy. |
| if (write(fd, mvmo.get(), format_info.metadata_allocated_size()) != |
| static_cast<ssize_t>(format_info.metadata_allocated_size())) { |
| return ZX_ERR_BAD_STATE; |
| } |
| // Write to secondary copy, to overwrite any previous FVM metadata copy that |
| // could be here. |
| if (write(fd, mvmo.get(), format_info.metadata_allocated_size()) != |
| static_cast<ssize_t>(format_info.metadata_allocated_size())) { |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| return ZX_OK; |
| } |
| |
| __EXPORT |
| zx_status_t fvm_init_with_size(int fd, uint64_t volume_size, size_t slice_size) { |
| return fvm_init_preallocated(fd, volume_size, volume_size, slice_size); |
| } |
| |
| __EXPORT |
| zx_status_t fvm_init(int fd, size_t slice_size) { |
| // The metadata layout of the FVM is dependent on the |
| // size of the FVM's underlying partition. |
| fuchsia_hardware_block_BlockInfo block_info; |
| fdio_cpp::UnownedFdioCaller disk_connection(fd); |
| zx_status_t status; |
| zx_status_t io_status = |
| fuchsia_hardware_block_BlockGetInfo(disk_connection.borrow_channel(), &status, &block_info); |
| if (io_status != ZX_OK) { |
| return io_status; |
| } else if (status != ZX_OK) { |
| return status; |
| } else if (slice_size == 0 || slice_size % block_info.block_size) { |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| return fvm_init_with_size(fd, block_info.block_count * block_info.block_size, slice_size); |
| } |
| |
| // Helper function to overwrite FVM given the slice_size |
| zx_status_t fvm_overwrite_impl(const fbl::unique_fd& fd, size_t slice_size) { |
| fuchsia_hardware_block_BlockInfo block_info; |
| fdio_cpp::UnownedFdioCaller disk_connection(fd.get()); |
| zx::unowned_channel channel(disk_connection.borrow_channel()); |
| zx_status_t status; |
| zx_status_t io_status = fuchsia_hardware_block_BlockGetInfo(channel->get(), &status, &block_info); |
| if (io_status != ZX_OK) { |
| return io_status; |
| } else if (status != ZX_OK) { |
| return status; |
| } |
| |
| size_t disk_size = block_info.block_count * block_info.block_size; |
| size_t metadata_size = fvm::MetadataSize(disk_size, slice_size); |
| |
| std::unique_ptr<uint8_t[]> buf(new uint8_t[metadata_size]); |
| |
| memset(buf.get(), 0, metadata_size); |
| |
| if (lseek(fd.get(), 0, SEEK_SET) < 0) { |
| return ZX_ERR_IO; |
| } |
| |
| // Write to primary copy. |
| if (write(fd.get(), buf.get(), metadata_size) != static_cast<ssize_t>(metadata_size)) { |
| fprintf(stderr, "fvm_overwrite_impl: Failed to write metadata\n"); |
| return ZX_ERR_IO; |
| } |
| |
| // Write to backup copy |
| if (write(fd.get(), buf.get(), metadata_size) != static_cast<ssize_t>(metadata_size)) { |
| fprintf(stderr, "fvm_overwrite_impl: Failed to write metadata (secondary)\n"); |
| return ZX_ERR_IO; |
| } |
| |
| io_status = fuchsia_hardware_block_BlockRebindDevice(channel->get(), &status); |
| if (io_status != ZX_OK) { |
| return io_status; |
| } |
| return status; |
| } |
| |
| __EXPORT |
| zx_status_t fvm_overwrite(const char* path, size_t slice_size) { |
| fbl::unique_fd fd(open(path, O_RDWR)); |
| if (!fd) { |
| fprintf(stderr, "fvm_overwrite: Failed to open block device\n"); |
| return ZX_ERR_BAD_STATE; |
| } |
| return fvm_overwrite_impl(fd, slice_size); |
| } |
| |
| __EXPORT |
| zx_status_t fvm_overwrite_with_devfs(int devfs_root_fd, const char* relative_path, |
| size_t slice_size) { |
| fbl::unique_fd fd(openat(devfs_root_fd, relative_path, O_RDWR)); |
| if (!fd) { |
| fprintf(stderr, "fvm_overwrite_with_devfs: Failed to open block device\n"); |
| return ZX_ERR_BAD_STATE; |
| } |
| return fvm_overwrite_impl(fd, slice_size); |
| } |
| |
| // Helper function to destroy FVM |
| __EXPORT |
| zx_status_t fvm_destroy(const char* path) { |
| fbl::String driver_path = fbl::StringPrintf("%s/fvm", path); |
| |
| fbl::unique_fd parent_fd(open(path, O_RDONLY | O_DIRECTORY)); |
| if (!parent_fd) { |
| return ZX_ERR_NOT_FOUND; |
| } |
| fbl::unique_fd fvm_fd(open(driver_path.c_str(), O_RDWR)); |
| if (!fvm_fd) { |
| return ZX_ERR_NOT_FOUND; |
| } |
| return DestroyFVMAndWait(-1, std::move(parent_fd), std::move(fvm_fd), path); |
| } |
| |
| __EXPORT |
| zx_status_t fvm_destroy_with_devfs(int devfs_root_fd, const char* relative_path) { |
| fbl::String driver_path = fbl::StringPrintf("%s/fvm", relative_path); |
| |
| fbl::unique_fd parent_fd(openat(devfs_root_fd, relative_path, O_RDONLY | O_DIRECTORY)); |
| if (!parent_fd) { |
| return ZX_ERR_NOT_FOUND; |
| } |
| fbl::unique_fd fvm_fd(openat(devfs_root_fd, driver_path.c_str(), O_RDWR)); |
| if (!fvm_fd) { |
| return ZX_ERR_NOT_FOUND; |
| } |
| return DestroyFVMAndWait(devfs_root_fd, std::move(parent_fd), std::move(fvm_fd), relative_path); |
| } |
| |
| int fvm_allocate_partition_impl(int fvm_fd, const alloc_req_t* request) { |
| fdio_cpp::UnownedFdioCaller caller(fvm_fd); |
| |
| fuchsia_hardware_block_partition_GUID type_guid; |
| memcpy(type_guid.value, request->type, BLOCK_GUID_LEN); |
| fuchsia_hardware_block_partition_GUID instance_guid; |
| memcpy(instance_guid.value, request->guid, BLOCK_GUID_LEN); |
| |
| zx_status_t status; |
| zx_status_t io_status = fuchsia_hardware_block_volume_VolumeManagerAllocatePartition( |
| caller.borrow_channel(), request->slice_count, &type_guid, &instance_guid, request->name, |
| BLOCK_NAME_LEN, request->flags, &status); |
| if (io_status != ZX_OK || status != ZX_OK) { |
| return -1; |
| } |
| return 0; |
| } |
| |
| // Helper function to allocate, find, and open VPartition. |
| __EXPORT |
| int fvm_allocate_partition(int fvm_fd, const alloc_req_t* request) { |
| int alloc_status = fvm_allocate_partition_impl(fvm_fd, request); |
| if (alloc_status != 0) { |
| return alloc_status; |
| } |
| return open_partition(request->guid, request->type, ZX_SEC(10), nullptr); |
| } |
| |
| __EXPORT |
| int fvm_allocate_partition_with_devfs(int devfs_root_fd, int fvm_fd, const alloc_req_t* request) { |
| int alloc_status = fvm_allocate_partition_impl(fvm_fd, request); |
| if (alloc_status != 0) { |
| return alloc_status; |
| } |
| return open_partition_with_devfs(devfs_root_fd, request->guid, request->type, ZX_SEC(10), |
| nullptr); |
| } |
| |
| __EXPORT |
| zx_status_t fvm_query(int fvm_fd, fuchsia_hardware_block_volume_VolumeInfo* out) { |
| fdio_cpp::UnownedFdioCaller caller(fvm_fd); |
| |
| zx_status_t status; |
| zx_status_t io_status = |
| fuchsia_hardware_block_volume_VolumeManagerQuery(caller.borrow_channel(), &status, out); |
| if (io_status != ZX_OK) { |
| return io_status; |
| } |
| return status; |
| } |
| |
| // Takes ownership of |dir|. |
| int open_partition_impl(DIR* dir, const char* out_path_base, const uint8_t* uniqueGUID, |
| const uint8_t* typeGUID, zx_duration_t timeout, char* out_path) { |
| typedef struct { |
| const uint8_t* guid; |
| const uint8_t* type; |
| const char* out_path_base; |
| char* out_path; |
| fbl::unique_fd out_partition; |
| } alloc_helper_info_t; |
| |
| alloc_helper_info_t info; |
| info.guid = uniqueGUID; |
| info.type = typeGUID; |
| info.out_path_base = out_path_base; |
| info.out_path = out_path; |
| info.out_partition.reset(); |
| |
| auto cb = [](int dirfd, int event, const char* fn, void* cookie) { |
| if (event != WATCH_EVENT_ADD_FILE) { |
| return ZX_OK; |
| } else if ((strcmp(fn, ".") == 0) || strcmp(fn, "..") == 0) { |
| return ZX_OK; |
| } |
| auto info = static_cast<alloc_helper_info_t*>(cookie); |
| fbl::unique_fd devfd(openat(dirfd, fn, O_RDWR)); |
| if (!devfd) { |
| return ZX_OK; |
| } |
| if (IsPartition(devfd, info->guid, info->type)) { |
| info->out_partition = std::move(devfd); |
| if (info->out_path) { |
| strcpy(info->out_path, info->out_path_base); |
| strcat(info->out_path, fn); |
| } |
| return ZX_ERR_STOP; |
| } |
| return ZX_OK; |
| }; |
| |
| zx_time_t deadline = zx_deadline_after(timeout); |
| if (fdio_watch_directory(dirfd(dir), cb, deadline, &info) != ZX_ERR_STOP) { |
| return -1; |
| } |
| closedir(dir); |
| return info.out_partition.release(); |
| } |
| |
| __EXPORT |
| int open_partition(const uint8_t* uniqueGUID, const uint8_t* typeGUID, zx_duration_t timeout, |
| char* out_path) { |
| ZX_ASSERT(uniqueGUID || typeGUID); |
| |
| DIR* dir = opendir(kBlockDevPath); |
| if (dir == nullptr) { |
| return -1; |
| } |
| |
| return open_partition_impl(dir, kBlockDevPath, uniqueGUID, typeGUID, timeout, out_path); |
| } |
| |
| __EXPORT |
| int open_partition_with_devfs(int devfs_root_fd, const uint8_t* uniqueGUID, const uint8_t* typeGUID, |
| zx_duration_t timeout, char* out_path_relative) { |
| ZX_ASSERT(uniqueGUID || typeGUID); |
| fbl::unique_fd block_dev_fd(openat(devfs_root_fd, kBlockDevRelativePath, O_RDONLY)); |
| if (!block_dev_fd) { |
| return -1; |
| } |
| DIR* dir = fdopendir(block_dev_fd.get()); |
| if (dir == nullptr) { |
| return -1; |
| } |
| |
| return open_partition_impl(dir, kBlockDevRelativePath, uniqueGUID, typeGUID, timeout, |
| out_path_relative); |
| } |
| |
| zx_status_t destroy_partition_impl(fbl::unique_fd&& fd) { |
| fdio_cpp::FdioCaller partition_caller(std::move(fd)); |
| |
| zx_status_t status; |
| zx_status_t io_status = |
| fuchsia_hardware_block_volume_VolumeDestroy(partition_caller.borrow_channel(), &status); |
| if (io_status) { |
| return io_status; |
| } |
| return status; |
| } |
| |
| __EXPORT |
| zx_status_t destroy_partition(const uint8_t* uniqueGUID, const uint8_t* typeGUID) { |
| char path[PATH_MAX]; |
| fbl::unique_fd fd(open_partition(uniqueGUID, typeGUID, 0, path)); |
| if (!fd) { |
| return ZX_ERR_IO; |
| } |
| return destroy_partition_impl(std::move(fd)); |
| } |
| |
| __EXPORT |
| zx_status_t destroy_partition_with_devfs(int devfs_root_fd, const uint8_t* uniqueGUID, |
| const uint8_t* typeGUID) { |
| char relative_path[PATH_MAX]; |
| fbl::unique_fd fd( |
| open_partition_with_devfs(devfs_root_fd, uniqueGUID, typeGUID, 0, relative_path)); |
| if (!fd) { |
| return ZX_ERR_IO; |
| } |
| return destroy_partition_impl(std::move(fd)); |
| } |