504 lines
15 KiB
C++
504 lines
15 KiB
C++
/*
|
|
* Copyright (C) 2020 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
#include "dump.h"
|
|
|
|
#include <android-base/file.h>
|
|
#include <android-base/logging.h>
|
|
#include <android-base/parsebool.h>
|
|
#include <android-base/properties.h>
|
|
#include <android-base/stringprintf.h>
|
|
#include <android-base/strings.h>
|
|
#include <android-base/unique_fd.h>
|
|
#include <dirent.h>
|
|
#include <errno.h>
|
|
#include <libgen.h>
|
|
#include <openssl/sha.h>
|
|
#include <selinux/android.h>
|
|
#include <selinux/selinux.h>
|
|
#include <sys/mount.h>
|
|
#include <sys/poll.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/syscall.h>
|
|
#include <sys/types.h>
|
|
#include <sys/vfs.h>
|
|
#include <sys/xattr.h>
|
|
#include <unistd.h>
|
|
|
|
#include "linux/incrementalfs.h"
|
|
|
|
#include <chrono>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <iterator>
|
|
#include <optional>
|
|
#include <string_view>
|
|
|
|
using namespace std::literals;
|
|
|
|
namespace {
|
|
|
|
// stuff from the internal incfs implementation
|
|
|
|
#ifndef __packed
|
|
#define __packed __attribute__((packed))
|
|
#endif
|
|
|
|
struct mem_range {
|
|
char* data;
|
|
size_t len;
|
|
};
|
|
|
|
#define INCFS_MAX_NAME_LEN 255
|
|
#define INCFS_FORMAT_V1 1
|
|
#define INCFS_FORMAT_CURRENT_VER INCFS_FORMAT_V1
|
|
|
|
enum incfs_metadata_type {
|
|
INCFS_MD_NONE = 0,
|
|
INCFS_MD_BLOCK_MAP = 1,
|
|
INCFS_MD_FILE_ATTR = 2,
|
|
INCFS_MD_SIGNATURE = 3,
|
|
INCFS_MD_STATUS = 4,
|
|
INCFS_MD_VERITY_SIGNATURE = 5,
|
|
};
|
|
|
|
enum incfs_file_header_flags {
|
|
INCFS_FILE_MAPPED = 1 << 1,
|
|
};
|
|
|
|
/* Header included at the beginning of all metadata records on the disk. */
|
|
struct incfs_md_header {
|
|
uint8_t h_md_entry_type;
|
|
|
|
/*
|
|
* Size of the metadata record.
|
|
* (e.g. inode, dir entry etc) not just this struct.
|
|
*/
|
|
int16_t h_record_size;
|
|
|
|
/*
|
|
* CRC32 of the metadata record.
|
|
* (e.g. inode, dir entry etc) not just this struct.
|
|
*/
|
|
int32_t h_unused1;
|
|
|
|
/* Offset of the next metadata entry if any */
|
|
int64_t h_next_md_offset;
|
|
|
|
/* Offset of the previous metadata entry if any */
|
|
int64_t h_unused2;
|
|
|
|
} __packed;
|
|
|
|
/* Backing file header */
|
|
struct incfs_file_header {
|
|
/* Magic number: INCFS_MAGIC_NUMBER */
|
|
__le64 fh_magic;
|
|
|
|
/* Format version: INCFS_FORMAT_CURRENT_VER */
|
|
__le64 fh_version;
|
|
|
|
/* sizeof(incfs_file_header) */
|
|
__le16 fh_header_size;
|
|
|
|
/* INCFS_DATA_FILE_BLOCK_SIZE */
|
|
__le16 fh_data_block_size;
|
|
|
|
/* File flags, from incfs_file_header_flags */
|
|
__le32 fh_flags;
|
|
|
|
union {
|
|
/* Standard incfs file */
|
|
struct {
|
|
/* Offset of the first metadata record */
|
|
__le64 fh_first_md_offset;
|
|
|
|
/* Full size of the file's content */
|
|
__le64 fh_file_size;
|
|
|
|
/* File uuid */
|
|
incfs_uuid_t fh_uuid;
|
|
};
|
|
|
|
/* Mapped file - INCFS_FILE_MAPPED set in fh_flags */
|
|
struct {
|
|
/* Offset in original file */
|
|
__le64 fh_original_offset;
|
|
|
|
/* Full size of the file's content */
|
|
__le64 fh_mapped_file_size;
|
|
|
|
/* Original file's uuid */
|
|
incfs_uuid_t fh_original_uuid;
|
|
};
|
|
};
|
|
} __packed;
|
|
|
|
enum incfs_block_map_entry_flags {
|
|
INCFS_BLOCK_COMPRESSED_LZ4 = 1,
|
|
INCFS_BLOCK_COMPRESSED_ZSTD = 2,
|
|
|
|
/* Reserve 3 bits for compression alg */
|
|
INCFS_BLOCK_COMPRESSED_MASK = 7,
|
|
};
|
|
|
|
/* Block map entry pointing to an actual location of the data block. */
|
|
struct incfs_blockmap_entry {
|
|
/* Offset of the actual data block. Lower 32 bits */
|
|
int32_t me_data_offset_lo;
|
|
|
|
/* Offset of the actual data block. Higher 16 bits */
|
|
int16_t me_data_offset_hi;
|
|
|
|
/* How many bytes the data actually occupies in the backing file */
|
|
int16_t me_data_size;
|
|
|
|
/* Block flags from incfs_block_map_entry_flags */
|
|
int16_t me_flags;
|
|
} __packed;
|
|
|
|
/* Metadata record for locations of file blocks. Type = INCFS_MD_BLOCK_MAP */
|
|
struct incfs_blockmap {
|
|
struct incfs_md_header m_header;
|
|
|
|
/* Base offset of the array of incfs_blockmap_entry */
|
|
int64_t m_base_offset;
|
|
|
|
/* Size of the map entry array in blocks */
|
|
int32_t m_block_count;
|
|
} __packed;
|
|
|
|
/* Metadata record for file signature. Type = INCFS_MD_SIGNATURE */
|
|
struct incfs_file_signature {
|
|
struct incfs_md_header sg_header;
|
|
|
|
int32_t sg_sig_size; /* The size of the signature. */
|
|
|
|
int64_t sg_sig_offset; /* Signature's offset in the backing file */
|
|
|
|
int32_t sg_hash_tree_size; /* The size of the hash tree. */
|
|
|
|
int64_t sg_hash_tree_offset; /* Hash tree offset in the backing file */
|
|
} __packed;
|
|
|
|
struct incfs_status {
|
|
struct incfs_md_header is_header;
|
|
|
|
__le32 is_data_blocks_written; /* Number of data blocks written */
|
|
|
|
__le32 is_hash_blocks_written; /* Number of hash blocks written */
|
|
|
|
__le32 is_dummy[6]; /* Spare fields */
|
|
} __packed;
|
|
|
|
/*
|
|
* Metadata record for verity signature. Type = INCFS_MD_VERITY_SIGNATURE
|
|
*
|
|
* This record will only exist for verity-enabled files with signatures. Verity
|
|
* enabled files without signatures do not have this record. This signature is
|
|
* checked by fs-verity identically to any other fs-verity signature.
|
|
*/
|
|
struct incfs_file_verity_signature {
|
|
struct incfs_md_header vs_header;
|
|
|
|
/* The size of the signature */
|
|
__le32 vs_size;
|
|
|
|
/* Signature's offset in the backing file */
|
|
__le64 vs_offset;
|
|
} __packed;
|
|
|
|
typedef union {
|
|
struct incfs_md_header md_header;
|
|
struct incfs_blockmap blockmap;
|
|
struct incfs_file_signature signature;
|
|
struct incfs_status status;
|
|
struct incfs_file_verity_signature verity_signature;
|
|
} md_buffer;
|
|
|
|
#define INCFS_MAX_METADATA_RECORD_SIZE sizeof(md_buffer)
|
|
|
|
class Dump {
|
|
public:
|
|
Dump(std::string_view backingFile)
|
|
: mBackingFile(android::base::Basename(std::string(backingFile))), mIn(backingFile) {}
|
|
|
|
void run() {
|
|
if (!mIn) {
|
|
err() << "bad input file name " << mBackingFile;
|
|
return;
|
|
}
|
|
|
|
auto header = read<incfs_file_header>();
|
|
out() << "header: " << hex(header.fh_magic) << ", " << header.fh_version << ", "
|
|
<< hex(header.fh_data_block_size) << ", " << header.fh_header_size << ", "
|
|
<< header.fh_file_size;
|
|
if (header.fh_magic != INCFS_MAGIC_NUMBER) {
|
|
err() << "bad magic, expected: " << hex(INCFS_MAGIC_NUMBER);
|
|
}
|
|
if (header.fh_version != INCFS_FORMAT_CURRENT_VER) {
|
|
err() << "bad version, expected: " << INCFS_FORMAT_CURRENT_VER;
|
|
}
|
|
if (header.fh_data_block_size != INCFS_DATA_FILE_BLOCK_SIZE) {
|
|
err() << "bad data block size, expected: " << hex(INCFS_DATA_FILE_BLOCK_SIZE);
|
|
}
|
|
if (header.fh_header_size != sizeof(header)) {
|
|
err() << "bad header size, expected: " << sizeof(header);
|
|
}
|
|
{
|
|
auto ostream = out() << "flags: " << hex(header.fh_flags);
|
|
if (header.fh_flags & INCFS_FILE_MAPPED) {
|
|
ostream << " (mapped file)";
|
|
}
|
|
}
|
|
|
|
if (header.fh_flags & INCFS_FILE_MAPPED) {
|
|
out() << "source " << toString(header.fh_original_uuid);
|
|
out() << "size " << header.fh_mapped_file_size << " @ "
|
|
<< hex(header.fh_original_offset);
|
|
} else {
|
|
out() << "uuid " << toString(header.fh_uuid);
|
|
out() << "size " << header.fh_file_size;
|
|
out() << "first md offset " << hex(header.fh_first_md_offset);
|
|
|
|
int64_t metadataOffset = header.fh_first_md_offset;
|
|
if (metadataOffset >= mIn.tellg()) {
|
|
if (metadataOffset > mIn.tellg()) {
|
|
out() << "gap of " << metadataOffset - mIn.tellg()
|
|
<< " bytes to the first metadata record";
|
|
}
|
|
incfs_md_header prevMd = {};
|
|
do {
|
|
dumpMd(metadataOffset, prevMd);
|
|
} while (metadataOffset != 0);
|
|
}
|
|
}
|
|
out() << "finished" << (mIn ? "" : " with read errors");
|
|
}
|
|
|
|
private:
|
|
auto scopedNesting() {
|
|
++mNesting;
|
|
auto undoNesting = [this](auto) { --mNesting; };
|
|
return std::unique_ptr<Dump, decltype(undoNesting)>(this, std::move(undoNesting));
|
|
}
|
|
|
|
const char* mdType(int type) {
|
|
switch (type) {
|
|
case INCFS_MD_NONE:
|
|
return "none";
|
|
case INCFS_MD_BLOCK_MAP:
|
|
return "block map";
|
|
case INCFS_MD_STATUS:
|
|
return "status";
|
|
case INCFS_MD_SIGNATURE:
|
|
return "signature";
|
|
case INCFS_MD_VERITY_SIGNATURE:
|
|
return "verity signature";
|
|
default:
|
|
return "unknown";
|
|
}
|
|
}
|
|
|
|
std::string blockFlags(int flags) {
|
|
if (!flags) {
|
|
return {};
|
|
}
|
|
std::string res = "(";
|
|
auto compression = flags & INCFS_BLOCK_COMPRESSED_MASK;
|
|
if (compression == INCFS_BLOCK_COMPRESSED_LZ4) {
|
|
res += "|lz4-compressed|";
|
|
} else if (flags == INCFS_BLOCK_COMPRESSED_ZSTD) {
|
|
res += "|zstd-compressed|";
|
|
}
|
|
res += ")";
|
|
return res;
|
|
}
|
|
|
|
void dumpBlockmap(int64_t offset, int64_t count) {
|
|
auto nesting = scopedNesting();
|
|
mIn.seekg(offset);
|
|
for (int64_t i = 0; i != count; ++i) {
|
|
auto ostream = out() << i << " @ " << hex(mIn.tellg()) << ": [ ";
|
|
|
|
auto block = read<incfs_blockmap_entry>();
|
|
auto blockOffset =
|
|
uint64_t(block.me_data_offset_lo) | (uint64_t(block.me_data_offset_hi) << 32);
|
|
if (blockOffset) {
|
|
ostream << block.me_data_size << " @ " << hex(blockOffset);
|
|
} else {
|
|
ostream << "missing";
|
|
}
|
|
ostream << " ], flags = " << block.me_flags << blockFlags(block.me_flags);
|
|
}
|
|
}
|
|
|
|
void dumpTree(int64_t offset, int64_t size) {
|
|
auto nesting = scopedNesting();
|
|
out() << "tree " << offset << " " << size;
|
|
}
|
|
|
|
void dumpMd(int64_t& offset, incfs_md_header& prevMd) {
|
|
md_buffer mdBuf = {};
|
|
auto& md = mdBuf.md_header;
|
|
md = readAt<incfs_md_header>(offset);
|
|
out() << "metadata: " << mdType(md.h_md_entry_type) << "(" << int(md.h_md_entry_type)
|
|
<< ")";
|
|
|
|
auto nesting = scopedNesting();
|
|
out() << "record size: " << md.h_record_size;
|
|
out() << "next md offset: " << hex(md.h_next_md_offset);
|
|
|
|
{
|
|
switch (md.h_md_entry_type) {
|
|
case INCFS_MD_NONE:
|
|
out() << "nothing here";
|
|
break;
|
|
case INCFS_MD_BLOCK_MAP: {
|
|
auto& bm = mdBuf.blockmap;
|
|
bm = readAt<decltype(bm)>(offset);
|
|
out() << "offset: " << hex(bm.m_base_offset);
|
|
out() << "block count: " << bm.m_block_count;
|
|
dumpBlockmap(bm.m_base_offset, bm.m_block_count);
|
|
break;
|
|
}
|
|
case INCFS_MD_SIGNATURE: {
|
|
auto& sig = mdBuf.signature;
|
|
sig = readAt<decltype(sig)>(offset);
|
|
out() << "signature size: " << sig.sg_sig_size;
|
|
out() << "signature offset: " << hex(sig.sg_sig_offset);
|
|
out() << "hash tree size: " << sig.sg_hash_tree_size;
|
|
out() << "hash tree offset: " << hex(sig.sg_hash_tree_offset);
|
|
dumpTree(sig.sg_hash_tree_offset, sig.sg_hash_tree_size);
|
|
break;
|
|
}
|
|
case INCFS_MD_STATUS: {
|
|
auto& st = mdBuf.status;
|
|
st = readAt<decltype(st)>(offset);
|
|
out() << "data blocks written: " << st.is_data_blocks_written;
|
|
out() << "hash blocks written: " << st.is_hash_blocks_written;
|
|
break;
|
|
}
|
|
case INCFS_MD_VERITY_SIGNATURE: {
|
|
auto& vs = mdBuf.verity_signature;
|
|
vs = readAt<decltype(vs)>(offset);
|
|
out() << "verity signature size: " << vs.vs_size;
|
|
out() << "verity signature offset: " << hex(vs.vs_offset);
|
|
break;
|
|
}
|
|
default:
|
|
out() << "don't know how to handle it";
|
|
break;
|
|
}
|
|
}
|
|
|
|
updateMaxPos();
|
|
prevMd = md;
|
|
offset = md.h_next_md_offset;
|
|
}
|
|
|
|
struct OstreamWrapper {
|
|
explicit OstreamWrapper(std::ostream& wrapped) : mWrapped(&wrapped) {}
|
|
OstreamWrapper(OstreamWrapper&& other) noexcept
|
|
: mWrapped(std::exchange(other.mWrapped, nullptr)) {}
|
|
|
|
~OstreamWrapper() {
|
|
if (mWrapped) {
|
|
*mWrapped << '\n';
|
|
}
|
|
}
|
|
|
|
template <class T>
|
|
OstreamWrapper& operator<<(const T& t) & {
|
|
*mWrapped << t;
|
|
return *this;
|
|
}
|
|
template <class T>
|
|
OstreamWrapper&& operator<<(const T& t) && {
|
|
*this << t;
|
|
return std::move(*this);
|
|
}
|
|
|
|
private:
|
|
std::ostream* mWrapped;
|
|
};
|
|
|
|
static std::string hex(uint64_t t) {
|
|
char buf[32] = {};
|
|
snprintf(buf, std::size(buf) - 1, "0x%llx", (unsigned long long)t);
|
|
return buf;
|
|
}
|
|
|
|
static std::string toString(incfs_uuid_t uuid) {
|
|
std::string res;
|
|
for (unsigned char b : uuid.bytes) {
|
|
char buf[3] = {};
|
|
snprintf(buf, std::size(buf) - 1, "%02x", (unsigned int)b);
|
|
res += buf;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
OstreamWrapper out() const {
|
|
nesting(std::cout);
|
|
std::cout << "[" << mBackingFile << "] ";
|
|
return OstreamWrapper(std::cout);
|
|
}
|
|
|
|
OstreamWrapper err() const {
|
|
nesting(std::cerr);
|
|
std::cerr << "[" << mBackingFile << "] ";
|
|
return OstreamWrapper(std::cerr);
|
|
}
|
|
|
|
void nesting(std::ostream& out) const {
|
|
for (int i = 0; i < mNesting; ++i) {
|
|
out << " ";
|
|
}
|
|
}
|
|
|
|
template <class T>
|
|
std::remove_reference_t<T> read() {
|
|
std::remove_reference_t<T> res;
|
|
mIn.read((char*)&res, sizeof(res));
|
|
return res;
|
|
}
|
|
|
|
template <class T>
|
|
std::remove_reference_t<T> readAt(int64_t pos) {
|
|
mIn.seekg(pos);
|
|
return read<T>();
|
|
}
|
|
|
|
void skip(int64_t count) { mIn.seekg(count, std::ios_base::cur); }
|
|
|
|
void updateMaxPos() { mMaxDumpedPos = std::max<int64_t>(mMaxDumpedPos, mIn.tellg()); }
|
|
|
|
std::string mBackingFile;
|
|
std::ifstream mIn;
|
|
int mNesting = 0;
|
|
int64_t mMaxDumpedPos = 0;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
namespace android::incfs {
|
|
|
|
void dump(std::string_view backingFile) {
|
|
Dump(backingFile).run();
|
|
}
|
|
|
|
} // namespace android::incfs
|