blob: 9bba3e792adbbeba1ba4f3d2454b0176df651938 [file] [log] [blame]
// Copyright 2017 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 <fcntl.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fs/mapped-vmo.h>
#include <zircon/device/block.h>
#include <zircon/syscalls.h>
#include <zircon/types.h>
#include <fdio/debug.h>
#include <fdio/watcher.h>
#include <fbl/unique_ptr.h>
#include "fvm/fvm.h"
#define MXDEBUG 0
namespace {
// Return true if g1 is greater than or equal to g2.
// Safe against integer overflow.
bool generation_ge(uint64_t g1, uint64_t g2) {
if (g1 == UINT64_MAX && g2 == 0) {
return false;
} else if (g1 == 0 && g2 == UINT64_MAX) {
return true;
}
return g1 >= g2;
}
// Validate the metadata's hash value.
// Returns 'true' if it matches, 'false' otherwise.
bool fvm_check_hash(const void* metadata, size_t metadata_size) {
ZX_DEBUG_ASSERT(metadata_size >= sizeof(fvm::fvm_t));
const fvm::fvm_t* header = static_cast<const fvm::fvm_t*>(metadata);
const void* metadata_after_hash =
reinterpret_cast<const void*>(header->hash + sizeof(header->hash));
uint8_t empty_hash[sizeof(header->hash)];
memset(empty_hash, 0, sizeof(empty_hash));
digest::Digest digest;
digest.Init();
digest.Update(metadata, offsetof(fvm::fvm_t, hash));
digest.Update(empty_hash, sizeof(empty_hash));
digest.Update(metadata_after_hash,
metadata_size - (offsetof(fvm::fvm_t, hash) + sizeof(header->hash)));
digest.Final();
return digest == header->hash;
}
static bool is_partition(int fd, const uint8_t* uniqueGUID, const uint8_t* typeGUID) {
uint8_t buf[GUID_LEN];
if (fd < 0) {
return false;
} else if (ioctl_block_get_type_guid(fd, buf, sizeof(buf)) < 0) {
return false;
} else if (memcmp(buf, typeGUID, GUID_LEN) != 0) {
return false;
} else 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
void fvm_update_hash(void* metadata, size_t metadata_size) {
fvm::fvm_t* header = static_cast<fvm::fvm_t*>(metadata);
memset(header->hash, 0, sizeof(header->hash));
digest::Digest digest;
const uint8_t* hash = digest.Hash(metadata, metadata_size);
memcpy(header->hash, hash, sizeof(header->hash));
}
zx_status_t fvm_validate_header(const void* metadata, const void* backup,
size_t metadata_size, const void** out) {
const fvm::fvm_t* primary_header = static_cast<const fvm::fvm_t*>(metadata);
const fvm::fvm_t* backup_header = static_cast<const fvm::fvm_t*>(backup);
bool primary_valid = fvm_check_hash(metadata, metadata_size);
bool backup_valid = fvm_check_hash(backup, metadata_size);
// Decide if we should use the primary or the backup copy of metadata
// for reading.
bool use_primary;
if (!primary_valid && !backup_valid) {
fprintf(stderr, "fvm: Neither copy of metadata is valid\n");
return ZX_ERR_BAD_STATE;
} else if (primary_valid && !backup_valid) {
use_primary = true;
} else if (!primary_valid && backup_valid) {
use_primary = false;
} else {
use_primary = generation_ge(primary_header->generation, backup_header->generation);
}
const fvm::fvm_t* header = use_primary ? primary_header : backup_header;
if (header->magic != FVM_MAGIC) {
fprintf(stderr, "fvm: Bad magic\n");
return ZX_ERR_BAD_STATE;
}
if (header->version > FVM_VERSION) {
fprintf(stderr, "fvm: Header Version does not match fvm driver\n");
return ZX_ERR_BAD_STATE;
}
// TODO(smklein): Additional validation....
if (out) {
*out = use_primary ? metadata : backup;
}
return ZX_OK;
}
zx_status_t fvm_init(int fd, size_t slice_size) {
if (slice_size % FVM_BLOCK_SIZE != 0) {
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<MappedVmo> mvmo;
zx_status_t status = MappedVmo::Create(metadata_size * 2, "fvm-meta", &mvmo);
if (status != ZX_OK) {
return status;
}
// Clear entire primary copy of metadata
memset(mvmo->GetData(), 0, metadata_size);
// Superblock
fvm::fvm_t* sb = static_cast<fvm::fvm_t*>(mvmo->GetData());
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->GetData(), metadata_size);
const void* backup = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(mvmo->GetData()) +
metadata_size);
status = fvm_validate_header(mvmo->GetData(), 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->GetData(), 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->GetData(), metadata_size) != static_cast<ssize_t>(metadata_size)) {
return ZX_ERR_BAD_STATE;
}
xprintf("fvm_init: Success\n");
xprintf("fvm_init: Slice Count: %zu, size: %zu\n", sb->pslice_count, sb->slice_size);
xprintf("fvm_init: Vpart offset: %zu, length: %zu\n",
fvm::kVPartTableOffset, fvm::kVPartTableLength);
xprintf("fvm_init: Atable offset: %zu, length: %zu\n",
fvm::kAllocTableOffset, fvm::AllocTableLength(disk_size, slice_size));
xprintf("fvm_init: Backup meta starts at: %zu\n",
fvm::BackupStart(disk_size, slice_size));
xprintf("fvm_init: Slices start at %zu, there are %zu of them\n",
fvm::SlicesStart(disk_size, slice_size),
fvm::UsableSlicesCount(disk_size, slice_size));
return ZX_OK;
}
// 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(fvm_fd, request)) != ZX_OK) {
return -1;
}
typedef struct {
const alloc_req_t* request;
int out_partition;
} alloc_helper_info_t;
alloc_helper_info_t info;
info.request = request;
auto cb = [](int dirfd, int event, const char* fn, void* cookie) {
if (event != WATCH_EVENT_ADD_FILE) {
return ZX_OK;
}
auto info = static_cast<alloc_helper_info_t*>(cookie);
int devfd = openat(dirfd, fn, O_RDWR);
if (devfd < 0) {
return ZX_OK;
}
if (is_partition(devfd, info->request->guid, info->request->type)) {
info->out_partition = devfd;
return ZX_ERR_STOP;
}
close(devfd);
return ZX_OK;
};
DIR* dir = opendir(kBlockDevPath);
if (dir == nullptr) {
return -1;
}
zx_time_t deadline = zx_deadline_after(ZX_SEC(2));
if (fdio_watch_directory(dirfd(dir), cb, deadline, &info) != ZX_ERR_STOP) {
return -1;
}
closedir(dir);
return info.out_partition;
}
int fvm_open_partition(const uint8_t* uniqueGUID, const uint8_t* typeGUID, char* out) {
DIR* dir = opendir(kBlockDevPath);
if (dir == nullptr) {
return -1;
}
struct dirent* de;
int result_fd = -1;
while ((de = readdir(dir)) != NULL) {
if ((strcmp(de->d_name, ".") == 0) || strcmp(de->d_name, "..") == 0) {
continue;
}
int devfd = openat(dirfd(dir), de->d_name, O_RDWR);
if (devfd < 0) {
continue;
} else if (!is_partition(devfd, uniqueGUID, typeGUID)) {
close(devfd);
continue;
}
result_fd = devfd;
if (out != nullptr) {
strcpy(out, kBlockDevPath);
strcat(out, de->d_name);
}
break;
}
closedir(dir);
return result_fd;
}