blob: b69624414cc6b9763a9dda90d22530e9138723a1 [file] [log] [blame]
// 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 <fs-management/mount.h>
#include <errno.h>
#include <dirent.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <fbl/alloc_checker.h>
#include <fbl/auto_call.h>
#include <fbl/unique_fd.h>
#include <fbl/unique_ptr.h>
#include <fs-management/fvm.h>
#include <fvm/fvm.h>
#include <lib/fdio/limits.h>
#include <lib/fdio/util.h>
#include <lib/fdio/vfs.h>
#include <lib/fdio/watcher.h>
#include <fs/client.h>
#include <zircon/compiler.h>
#include <zircon/device/block.h>
#include <zircon/device/vfs.h>
#include <zircon/processargs.h>
#include <zircon/syscalls.h>
#include <utility>
namespace {
// 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.
static bool is_partition(int fd, const uint8_t* uniqueGUID, const uint8_t* typeGUID) {
ZX_ASSERT(uniqueGUID || typeGUID);
uint8_t buf[GUID_LEN];
if (fd < 0) {
return false;
}
if (typeGUID) {
if (ioctl_block_get_type_guid(fd, buf, sizeof(buf)) < 0) {
return false;
} else if (memcmp(buf, typeGUID, GUID_LEN) != 0) {
return false;
}
}
if (uniqueGUID) {
if (ioctl_block_get_partition_guid(fd, buf, sizeof(buf)) < 0) {
return false;
} else if (memcmp(buf, uniqueGUID, GUID_LEN) != 0) {
return false;
}
}
return true;
}
constexpr char kBlockDevPath[] = "/dev/class/block/";
} // namespace anonymous
zx_status_t fvm_init(int fd, size_t slice_size) {
if (slice_size % FVM_BLOCK_SIZE != 0) {
// Alignment
return ZX_ERR_INVALID_ARGS;
} else if ((slice_size * VSLICE_MAX) / VSLICE_MAX != slice_size) {
// Overflow
return ZX_ERR_INVALID_ARGS;
}
// The metadata layout of the FVM is dependent on the
// size of the FVM's underlying partition.
block_info_t block_info;
ssize_t rc = ioctl_block_get_info(fd, &block_info);
if (rc < 0) {
return static_cast<zx_status_t>(rc);
} else if (rc != sizeof(block_info)) {
return ZX_ERR_BAD_STATE;
} else if (slice_size == 0 || slice_size % block_info.block_size) {
return ZX_ERR_BAD_STATE;
}
size_t disk_size = block_info.block_count * block_info.block_size;
size_t metadata_size = fvm::MetadataSize(disk_size, slice_size);
fbl::unique_ptr<uint8_t[]> mvmo(new uint8_t[metadata_size * 2]);
// Clear entire primary copy of metadata
memset(mvmo.get(), 0, metadata_size);
// Superblock
fvm::fvm_t* sb = reinterpret_cast<fvm::fvm_t*>(mvmo.get());
sb->magic = FVM_MAGIC;
sb->version = FVM_VERSION;
sb->pslice_count = (disk_size - metadata_size * 2) / slice_size;
sb->slice_size = slice_size;
sb->fvm_partition_size = disk_size;
sb->vpartition_table_size = fvm::kVPartTableLength;
sb->allocation_table_size = fvm::AllocTableLength(disk_size, slice_size);
sb->generation = 0;
if (sb->pslice_count == 0) {
return ZX_ERR_NO_SPACE;
}
fvm_update_hash(mvmo.get(), metadata_size);
const void* backup = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(mvmo.get()) +
metadata_size);
zx_status_t status = fvm_validate_header(mvmo.get(), backup, 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(), metadata_size) != static_cast<ssize_t>(metadata_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(), metadata_size) != static_cast<ssize_t>(metadata_size)) {
return ZX_ERR_BAD_STATE;
}
return ZX_OK;
}
// Helper function to overwrite FVM given the slice_size
zx_status_t fvm_overwrite(const char* path, size_t slice_size) {
int fd = open(path, O_RDWR);
if (fd <= 0) {
fprintf(stderr, "fvm_destroy: Failed to open block device\n");
return -1;
}
block_info_t block_info;
ssize_t rc = ioctl_block_get_info(fd, &block_info);
if (rc < 0 || rc != sizeof(block_info)) {
printf("fvm_destroy: Failed to query block device\n");
return -1;
}
size_t disk_size = block_info.block_count * block_info.block_size;
size_t metadata_size = fvm::MetadataSize(disk_size, slice_size);
fbl::AllocChecker ac;
fbl::unique_ptr<uint8_t[]> buf(new (&ac) uint8_t[metadata_size]);
if (!ac.check()) {
printf("fvm_destroy: Failed to allocate buffer\n");
return -1;
}
memset(buf.get(), 0, metadata_size);
if (lseek(fd, 0, SEEK_SET) < 0) {
return -1;
}
// Write to primary copy.
if (write(fd, buf.get(), metadata_size) != static_cast<ssize_t>(metadata_size)) {
fprintf(stderr, "fvm_overwrite: Failed to write metadata\n");
return -1;
}
// Write to backup copy
if (write(fd, buf.get(), metadata_size) != static_cast<ssize_t>(metadata_size)) {
fprintf(stderr, "fvm_overwrite: Failed to write metadata (secondary)\n");
return -1;
}
if (ioctl_block_rr_part(fd) != 0) {
return -1;
}
close(fd);
return ZX_OK;
}
// Helper function to destroy FVM
zx_status_t fvm_destroy(const char* path) {
char driver_path[PATH_MAX];
if (strlcpy(driver_path, path, sizeof(driver_path)) >= sizeof(driver_path)) {
return ZX_ERR_BAD_PATH;
}
if (strlcat(driver_path, "/fvm", sizeof(driver_path)) >= sizeof(driver_path)) {
return ZX_ERR_BAD_PATH;
}
fbl::unique_fd driver_fd(open(driver_path, O_RDWR));
if (!driver_fd) {
fprintf(stderr, "fvm_destroy: Failed to open fvm driver: %s\n", driver_path);
return -1;
}
fvm_info_t fvm_info;
ssize_t r;
if ((r = ioctl_block_fvm_query(driver_fd.get(), &fvm_info)) <= 0) {
fprintf(stderr, "fvm_destroy: Failed to query fvm: %ld\n", r);
return -1;
}
return fvm_overwrite(path, fvm_info.slice_size);
}
// Helper function to allocate, find, and open VPartition.
int fvm_allocate_partition(int fvm_fd, const alloc_req_t* request) {
ssize_t r;
if ((r = ioctl_block_fvm_alloc_partition(fvm_fd, request)) != ZX_OK) {
return -1;
}
return open_partition(request->guid, request->type, ZX_SEC(10), nullptr);
}
int open_partition(const uint8_t* uniqueGUID, const uint8_t* typeGUID,
zx_duration_t timeout, char* out_path) {
ZX_ASSERT(uniqueGUID || typeGUID);
typedef struct {
const uint8_t* guid;
const uint8_t* type;
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 = 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 (is_partition(devfd.get(), info->guid, info->type)) {
info->out_partition = std::move(devfd);
if (info->out_path) {
strcpy(info->out_path, kBlockDevPath);
strcat(info->out_path, fn);
}
return ZX_ERR_STOP;
}
return ZX_OK;
};
DIR* dir = opendir(kBlockDevPath);
if (dir == nullptr) {
return -1;
}
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();
}
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 static_cast<zx_status_t>(ioctl_block_fvm_destroy_partition(fd.get()));
}