blob: 77bda233810610b4141fb50fafbc33ab12eff57a [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 <inttypes.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <zircon/assert.h>
#include <limits>
#ifdef __Fuchsia__
#include <lib/zx/vmo.h>
#include <zircon/syscalls.h>
#endif
#include <zircon/compiler.h>
#include <zircon/types.h>
#include <fbl/algorithm.h>
#include <fbl/unique_fd.h>
#include "src/storage/fvm/format.h"
#include "src/storage/fvm/fvm.h"
namespace fvm {
namespace {
// Return true if g1 is greater than or equal to g2.
// Safe against integer overflow.
bool GenerationGE(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 CheckHash(const void* metadata, size_t metadata_size) {
ZX_ASSERT(metadata_size >= sizeof(Header));
const Header* header = static_cast<const 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(Header, hash));
digest.Update(empty_hash, sizeof(empty_hash));
digest.Update(metadata_after_hash,
metadata_size - (offsetof(Header, hash) + sizeof(header->hash)));
digest.Final();
return digest == header->hash;
}
} // namespace
void UpdateHash(void* metadata, size_t metadata_size) {
Header* header = static_cast<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));
}
std::optional<SuperblockType> PickValidHeader(const void* primary_metadata,
const void* secondary_metadata,
size_t metadata_size) {
return PickValidHeader(std::numeric_limits<uint64_t>::max(), kBlockSize, primary_metadata,
secondary_metadata, metadata_size);
}
std::optional<SuperblockType> PickValidHeader(uint64_t disk_size, uint64_t disk_block_size,
const void* primary_metadata,
const void* secondary_metadata,
size_t metadata_size) {
std::string header_error;
// Validate primary header.
const Header* primary_header = static_cast<const Header*>(primary_metadata);
bool primary_valid = false;
if (primary_header->IsValid(disk_size, disk_block_size, header_error)) {
if (CheckHash(primary_metadata, primary_header->GetMetadataUsedBytes())) {
primary_valid = true;
} else {
fprintf(stderr, "fvm: Primary metadata has invalid content hash\n");
}
} else {
fprintf(stderr, "fvm: Primary metadata invalid: %s\n", header_error.c_str());
}
// Validate secondary header.
const Header* secondary_header = static_cast<const Header*>(secondary_metadata);
header_error.clear();
bool secondary_valid = false;
if (secondary_header->IsValid(disk_size, disk_block_size, header_error)) {
if (CheckHash(secondary_metadata, secondary_header->GetMetadataUsedBytes())) {
secondary_valid = true;
} else {
fprintf(stderr, "fvm: Secondary metadata has invalid content hash\n");
}
} else {
fprintf(stderr, "fvm: Secondary metadata invalid: %s\n", header_error.c_str());
}
// Decide if we should use the primary or the secondary copy of metadata for reading.
if (!primary_valid && !secondary_valid) {
return std::nullopt;
}
if (primary_valid && !secondary_valid) {
return SuperblockType::kPrimary;
}
if (!primary_valid && secondary_valid) {
return SuperblockType::kSecondary;
}
// Both valid, pick the newest.
return GenerationGE(primary_header->generation, secondary_header->generation)
? SuperblockType::kPrimary
: SuperblockType::kSecondary;
}
} // namespace fvm