blob: a99c972385a93dc9335b75600a737ea5ceb11885 [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 <fcntl.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#ifdef __Fuchsia__
#include <lib/zx/vmo.h>
#include <zircon/syscalls.h>
#endif
#include <fbl/unique_fd.h>
#include <fbl/unique_ptr.h>
#include <zircon/types.h>
#include "fvm/format.h"
#define ZXDEBUG 0
namespace {
constexpr size_t MetadataSizeOrZero(size_t disk_size, size_t slice_size) {
if (disk_size == 0 || slice_size == 0) {
return 0;
}
return fvm::MetadataSize(disk_size, slice_size);
}
constexpr size_t UsableSlicesCountOrZero(size_t fvm_partition_size, size_t metadata_allocated_size,
size_t slice_size) {
if (slice_size == 0) {
return 0;
}
return (fvm_partition_size - 2 * metadata_allocated_size) / slice_size;
}
// 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::Header));
const fvm::Header* header = static_cast<const fvm::Header*>(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::Header, hash));
digest.Update(empty_hash, sizeof(empty_hash));
digest.Update(metadata_after_hash,
metadata_size - (offsetof(fvm::Header, hash) + sizeof(header->hash)));
digest.Final();
return digest == header->hash;
}
} // namespace
#ifdef __cplusplus
fvm::FormatInfo fvm::FormatInfo::FromSuperBlock(const Header& superblock) {
fvm::FormatInfo summary;
summary.metadata_allocated_size_ = superblock.allocation_table_size + kAllocTableOffset;
summary.metadata_size_ =
MetadataSizeOrZero(superblock.fvm_partition_size, superblock.slice_size);
summary.slice_size_ = superblock.slice_size;
summary.slice_count_ = UsableSlicesCountOrZero(
superblock.fvm_partition_size, summary.metadata_allocated_size(), summary.slice_size());
return summary;
}
fvm::FormatInfo fvm::FormatInfo::FromPreallocatedSize(size_t initial_size, size_t max_size,
size_t slice_size) {
fvm::FormatInfo summary;
summary.metadata_allocated_size_ = MetadataSizeOrZero(max_size, slice_size);
summary.metadata_size_ = MetadataSizeOrZero(initial_size, slice_size);
summary.slice_size_ = slice_size;
summary.slice_count_ = UsableSlicesCountOrZero(initial_size, summary.metadata_allocated_size(),
summary.slice_size());
return summary;
}
fvm::FormatInfo fvm::FormatInfo::FromDiskSize(size_t disk_size, size_t slice_size) {
return FromPreallocatedSize(disk_size, disk_size, slice_size);
}
#endif // __cplusplus
void fvm_update_hash(void* metadata, size_t metadata_size) {
fvm::Header* header = static_cast<fvm::Header*>(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::Header* primary_header = static_cast<const fvm::Header*>(metadata);
const fvm::Header* backup_header = static_cast<const fvm::Header*>(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) {
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::Header* header = use_primary ? primary_header : backup_header;
if (header->magic != fvm::kMagic) {
fprintf(stderr, "fvm: Bad magic\n");
return ZX_ERR_BAD_STATE;
}
if (header->version > fvm::kVersion) {
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;
}