774 lines
27 KiB
C++
774 lines
27 KiB
C++
|
/*
|
||
|
* Copyright (C) 2018 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 <libfiemap/fiemap_writer.h>
|
||
|
|
||
|
#include <dirent.h>
|
||
|
#include <fcntl.h>
|
||
|
#include <linux/fs.h>
|
||
|
#include <stdio.h>
|
||
|
#include <sys/ioctl.h>
|
||
|
#include <sys/stat.h>
|
||
|
#include <sys/sysmacros.h>
|
||
|
#include <sys/types.h>
|
||
|
#include <sys/vfs.h>
|
||
|
#include <unistd.h>
|
||
|
|
||
|
#include <limits>
|
||
|
#include <string>
|
||
|
#include <utility>
|
||
|
#include <vector>
|
||
|
|
||
|
#include <android-base/file.h>
|
||
|
#include <android-base/logging.h>
|
||
|
#include <android-base/stringprintf.h>
|
||
|
#include <android-base/strings.h>
|
||
|
#include <android-base/unique_fd.h>
|
||
|
#include <libdm/dm.h>
|
||
|
#include "utility.h"
|
||
|
|
||
|
namespace android {
|
||
|
namespace fiemap {
|
||
|
|
||
|
using namespace android::dm;
|
||
|
|
||
|
// We cap the maximum number of extents as a robustness measure.
|
||
|
static constexpr uint32_t kMaxExtents = 50000;
|
||
|
|
||
|
// TODO: Fallback to using fibmap if FIEMAP_EXTENT_MERGED is set.
|
||
|
static constexpr const uint32_t kUnsupportedExtentFlags =
|
||
|
FIEMAP_EXTENT_UNKNOWN | FIEMAP_EXTENT_UNWRITTEN | FIEMAP_EXTENT_DELALLOC |
|
||
|
FIEMAP_EXTENT_NOT_ALIGNED | FIEMAP_EXTENT_DATA_INLINE | FIEMAP_EXTENT_DATA_TAIL |
|
||
|
FIEMAP_EXTENT_UNWRITTEN | FIEMAP_EXTENT_SHARED;
|
||
|
|
||
|
// Large file support must be enabled.
|
||
|
static_assert(sizeof(off_t) == sizeof(uint64_t));
|
||
|
|
||
|
static inline void cleanup(const std::string& file_path, bool created) {
|
||
|
if (created) {
|
||
|
unlink(file_path.c_str());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static bool ValidateDmTarget(const DeviceMapper::TargetInfo& target) {
|
||
|
const auto& entry = target.spec;
|
||
|
if (entry.sector_start != 0) {
|
||
|
LOG(INFO) << "Stopping at target with non-zero starting sector";
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
auto target_type = DeviceMapper::GetTargetType(entry);
|
||
|
if (target_type == "bow" || target_type == "default-key" || target_type == "crypt") {
|
||
|
return true;
|
||
|
}
|
||
|
if (target_type == "linear") {
|
||
|
auto pieces = android::base::Split(target.data, " ");
|
||
|
if (pieces[1] != "0") {
|
||
|
LOG(INFO) << "Stopping at complex linear target with non-zero starting sector: "
|
||
|
<< pieces[1];
|
||
|
return false;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
LOG(INFO) << "Stopping at complex target type " << target_type;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
static bool DeviceMapperStackPop(const std::string& bdev, std::string* bdev_raw) {
|
||
|
*bdev_raw = bdev;
|
||
|
|
||
|
if (!::android::base::StartsWith(bdev, "dm-")) {
|
||
|
// We are at the bottom of the device mapper stack.
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// Get the device name.
|
||
|
auto dm_name_file = "/sys/block/" + bdev + "/dm/name";
|
||
|
std::string dm_name;
|
||
|
if (!android::base::ReadFileToString(dm_name_file, &dm_name)) {
|
||
|
PLOG(ERROR) << "Could not read file: " << dm_name_file;
|
||
|
return false;
|
||
|
}
|
||
|
dm_name = android::base::Trim(dm_name);
|
||
|
|
||
|
auto& dm = DeviceMapper::Instance();
|
||
|
std::vector<DeviceMapper::TargetInfo> table;
|
||
|
if (!dm.GetTableInfo(dm_name, &table)) {
|
||
|
LOG(ERROR) << "Could not read device-mapper table for " << dm_name << " at " << bdev;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// The purpose of libfiemap is to provide an extent-based view into
|
||
|
// a file. This is difficult if devices are not layered in a 1:1 manner;
|
||
|
// we would have to translate and break up extents based on the actual
|
||
|
// block mapping. Since this is too complex, we simply stop processing
|
||
|
// the device-mapper stack if we encounter a complex case.
|
||
|
//
|
||
|
// It is up to the caller to decide whether stopping at a virtual block
|
||
|
// device is allowable. In most cases it is not, because we want either
|
||
|
// "userdata" or an external volume. It is useful for tests however.
|
||
|
// Callers can check by comparing the device number to that of userdata,
|
||
|
// or by checking whether is a device-mapper node.
|
||
|
if (table.size() > 1) {
|
||
|
LOG(INFO) << "Stopping at complex table for " << dm_name << " at " << bdev;
|
||
|
return true;
|
||
|
}
|
||
|
if (!ValidateDmTarget(table[0])) {
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
auto dm_leaf_dir = "/sys/block/" + bdev + "/slaves";
|
||
|
auto d = std::unique_ptr<DIR, decltype(&closedir)>(opendir(dm_leaf_dir.c_str()), closedir);
|
||
|
if (d == nullptr) {
|
||
|
PLOG(ERROR) << "Failed to open: " << dm_leaf_dir;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
struct dirent* de;
|
||
|
uint32_t num_leaves = 0;
|
||
|
std::string bdev_next = "";
|
||
|
while ((de = readdir(d.get())) != nullptr) {
|
||
|
if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// We set the first name we find here
|
||
|
if (bdev_next.empty()) {
|
||
|
bdev_next = de->d_name;
|
||
|
}
|
||
|
num_leaves++;
|
||
|
}
|
||
|
|
||
|
// if we have more than one leaves, we return immediately. We can't continue to create the
|
||
|
// file since we don't know how to write it out using fiemap, so it will be readable via the
|
||
|
// underlying block devices later. The reader will also have to construct the same device mapper
|
||
|
// target in order read the file out.
|
||
|
if (num_leaves > 1) {
|
||
|
LOG(ERROR) << "Found " << num_leaves << " leaf block devices under device mapper device "
|
||
|
<< bdev;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// recursively call with the block device we found in order to pop the device mapper stack.
|
||
|
return DeviceMapperStackPop(bdev_next, bdev_raw);
|
||
|
}
|
||
|
|
||
|
bool FiemapWriter::GetBlockDeviceForFile(const std::string& file_path, std::string* bdev_path,
|
||
|
bool* uses_dm) {
|
||
|
struct stat sb;
|
||
|
if (stat(file_path.c_str(), &sb)) {
|
||
|
PLOG(ERROR) << "Failed to get stat for: " << file_path;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
std::string bdev;
|
||
|
if (!BlockDeviceToName(major(sb.st_dev), minor(sb.st_dev), &bdev)) {
|
||
|
LOG(ERROR) << "Failed to get block device name for " << major(sb.st_dev) << ":"
|
||
|
<< minor(sb.st_dev);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
std::string bdev_raw;
|
||
|
if (!DeviceMapperStackPop(bdev, &bdev_raw)) {
|
||
|
LOG(ERROR) << "Failed to get the bottom of the device mapper stack for device: " << bdev;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (uses_dm) {
|
||
|
*uses_dm = (bdev_raw != bdev);
|
||
|
}
|
||
|
|
||
|
LOG(DEBUG) << "Popped device (" << bdev_raw << ") from device mapper stack starting with ("
|
||
|
<< bdev << ")";
|
||
|
|
||
|
*bdev_path = ::android::base::StringPrintf("/dev/block/%s", bdev_raw.c_str());
|
||
|
|
||
|
// Make sure we are talking to a block device before calling it a success.
|
||
|
if (stat(bdev_path->c_str(), &sb)) {
|
||
|
PLOG(ERROR) << "Failed to get stat for block device: " << *bdev_path;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ((sb.st_mode & S_IFMT) != S_IFBLK) {
|
||
|
PLOG(ERROR) << "File: " << *bdev_path << " is not a block device";
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
static bool GetBlockDeviceSize(int bdev_fd, const std::string& bdev_path, uint64_t* bdev_size) {
|
||
|
uint64_t size_in_bytes = 0;
|
||
|
if (ioctl(bdev_fd, BLKGETSIZE64, &size_in_bytes)) {
|
||
|
PLOG(ERROR) << "Failed to get total size for: " << bdev_path;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
*bdev_size = size_in_bytes;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
static uint64_t GetFileSize(const std::string& file_path) {
|
||
|
struct stat sb;
|
||
|
if (stat(file_path.c_str(), &sb)) {
|
||
|
PLOG(ERROR) << "Failed to get size for file: " << file_path;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
return sb.st_size;
|
||
|
}
|
||
|
|
||
|
static bool PerformFileChecks(const std::string& file_path, uint64_t* blocksz, uint32_t* fs_type) {
|
||
|
struct statfs64 sfs;
|
||
|
if (statfs64(file_path.c_str(), &sfs)) {
|
||
|
PLOG(ERROR) << "Failed to read file system status at: " << file_path;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (!sfs.f_bsize) {
|
||
|
LOG(ERROR) << "Unsupported block size: " << sfs.f_bsize;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Check if the filesystem is of supported types.
|
||
|
// Only ext4, f2fs, and vfat are tested and supported.
|
||
|
switch (sfs.f_type) {
|
||
|
case EXT4_SUPER_MAGIC:
|
||
|
case F2FS_SUPER_MAGIC:
|
||
|
case MSDOS_SUPER_MAGIC:
|
||
|
break;
|
||
|
default:
|
||
|
LOG(ERROR) << "Unsupported file system type: 0x" << std::hex << sfs.f_type;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
*blocksz = sfs.f_bsize;
|
||
|
*fs_type = sfs.f_type;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
static FiemapStatus FallocateFallback(int file_fd, uint64_t block_size, uint64_t file_size,
|
||
|
const std::string& file_path,
|
||
|
const std::function<bool(uint64_t, uint64_t)>& on_progress) {
|
||
|
// Even though this is much faster than writing zeroes, it is still slow
|
||
|
// enough that we need to fire the progress callback periodically. To
|
||
|
// easily achieve this, we seek in chunks. We use 1000 chunks since
|
||
|
// normally we only fire the callback on 1/1000th increments.
|
||
|
uint64_t bytes_per_chunk = std::max(file_size / 1000, block_size);
|
||
|
|
||
|
// Seek just to the end of each chunk and write a single byte, causing
|
||
|
// the filesystem to allocate blocks.
|
||
|
off_t cursor = 0;
|
||
|
off_t end = static_cast<off_t>(file_size);
|
||
|
while (cursor < end) {
|
||
|
cursor = std::min(static_cast<off_t>(cursor + bytes_per_chunk), end);
|
||
|
auto rv = TEMP_FAILURE_RETRY(lseek(file_fd, cursor - 1, SEEK_SET));
|
||
|
if (rv < 0) {
|
||
|
PLOG(ERROR) << "Failed to lseek " << file_path;
|
||
|
return FiemapStatus::FromErrno(errno);
|
||
|
}
|
||
|
if (rv != cursor - 1) {
|
||
|
LOG(ERROR) << "Seek returned wrong offset " << rv << " for file " << file_path;
|
||
|
return FiemapStatus::Error();
|
||
|
}
|
||
|
char buffer[] = {0};
|
||
|
if (!android::base::WriteFully(file_fd, buffer, 1)) {
|
||
|
PLOG(ERROR) << "Write failed: " << file_path;
|
||
|
return FiemapStatus::FromErrno(errno);
|
||
|
}
|
||
|
if (on_progress && !on_progress(cursor, file_size)) {
|
||
|
return FiemapStatus::Error();
|
||
|
}
|
||
|
}
|
||
|
return FiemapStatus::Ok();
|
||
|
}
|
||
|
|
||
|
// F2FS-specific ioctl
|
||
|
// It requires the below kernel commit merged in v4.16-rc1.
|
||
|
// 1ad71a27124c ("f2fs: add an ioctl to disable GC for specific file")
|
||
|
// In android-4.4,
|
||
|
// 56ee1e817908 ("f2fs: updates on v4.16-rc1")
|
||
|
// In android-4.9,
|
||
|
// 2f17e34672a8 ("f2fs: updates on v4.16-rc1")
|
||
|
// In android-4.14,
|
||
|
// ce767d9a55bc ("f2fs: updates on v4.16-rc1")
|
||
|
#ifndef F2FS_IOC_SET_PIN_FILE
|
||
|
#ifndef F2FS_IOCTL_MAGIC
|
||
|
#define F2FS_IOCTL_MAGIC 0xf5
|
||
|
#endif
|
||
|
#define F2FS_IOC_GET_PIN_FILE _IOR(F2FS_IOCTL_MAGIC, 14, __u32)
|
||
|
#define F2FS_IOC_SET_PIN_FILE _IOW(F2FS_IOCTL_MAGIC, 13, __u32)
|
||
|
#endif
|
||
|
|
||
|
static bool IsFilePinned(int file_fd, const std::string& file_path, uint32_t fs_type) {
|
||
|
if (fs_type != F2FS_SUPER_MAGIC) {
|
||
|
// No pinning necessary for ext4 or vfat. The blocks, once allocated,
|
||
|
// are expected to be fixed.
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// f2fs: export FS_NOCOW_FL flag to user
|
||
|
uint32_t flags;
|
||
|
int error = ioctl(file_fd, FS_IOC_GETFLAGS, &flags);
|
||
|
if (error < 0) {
|
||
|
if ((errno == ENOTTY) || (errno == ENOTSUP)) {
|
||
|
PLOG(ERROR) << "Failed to get flags, not supported by kernel: " << file_path;
|
||
|
} else {
|
||
|
PLOG(ERROR) << "Failed to get flags: " << file_path;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
if (!(flags & FS_NOCOW_FL)) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// F2FS_IOC_GET_PIN_FILE returns the number of blocks moved.
|
||
|
uint32_t moved_blocks_nr;
|
||
|
error = ioctl(file_fd, F2FS_IOC_GET_PIN_FILE, &moved_blocks_nr);
|
||
|
if (error < 0) {
|
||
|
if ((errno == ENOTTY) || (errno == ENOTSUP)) {
|
||
|
PLOG(ERROR) << "Failed to get file pin status, not supported by kernel: " << file_path;
|
||
|
} else {
|
||
|
PLOG(ERROR) << "Failed to get file pin status: " << file_path;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (moved_blocks_nr) {
|
||
|
LOG(WARNING) << moved_blocks_nr << " blocks moved in file " << file_path;
|
||
|
}
|
||
|
return moved_blocks_nr == 0;
|
||
|
}
|
||
|
|
||
|
static bool PinFile(int file_fd, const std::string& file_path, uint32_t fs_type) {
|
||
|
if (IsFilePinned(file_fd, file_path, fs_type)) {
|
||
|
return true;
|
||
|
}
|
||
|
if (fs_type != F2FS_SUPER_MAGIC) {
|
||
|
// No pinning necessary for ext4/msdos. The blocks, once allocated, are
|
||
|
// expected to be fixed.
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
uint32_t pin_status = 1;
|
||
|
int error = ioctl(file_fd, F2FS_IOC_SET_PIN_FILE, &pin_status);
|
||
|
if (error < 0) {
|
||
|
if ((errno == ENOTTY) || (errno == ENOTSUP)) {
|
||
|
PLOG(ERROR) << "Failed to pin file, not supported by kernel: " << file_path;
|
||
|
} else {
|
||
|
PLOG(ERROR) << "Failed to pin file: " << file_path;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// write zeroes in 'blocksz' byte increments until we reach file_size to make sure the data
|
||
|
// blocks are actually written to by the file system and thus getting rid of the holes in the
|
||
|
// file.
|
||
|
static FiemapStatus WriteZeroes(int file_fd, const std::string& file_path, size_t blocksz,
|
||
|
uint64_t file_size,
|
||
|
const std::function<bool(uint64_t, uint64_t)>& on_progress) {
|
||
|
auto buffer = std::unique_ptr<void, decltype(&free)>(calloc(1, blocksz), free);
|
||
|
if (buffer == nullptr) {
|
||
|
LOG(ERROR) << "failed to allocate memory for writing file";
|
||
|
return FiemapStatus::Error();
|
||
|
}
|
||
|
|
||
|
off64_t offset = lseek64(file_fd, 0, SEEK_SET);
|
||
|
if (offset < 0) {
|
||
|
PLOG(ERROR) << "Failed to seek at the beginning of : " << file_path;
|
||
|
return FiemapStatus::FromErrno(errno);
|
||
|
}
|
||
|
|
||
|
int permille = -1;
|
||
|
while (offset < file_size) {
|
||
|
if (!::android::base::WriteFully(file_fd, buffer.get(), blocksz)) {
|
||
|
PLOG(ERROR) << "Failed to write" << blocksz << " bytes at offset" << offset
|
||
|
<< " in file " << file_path;
|
||
|
return FiemapStatus::FromErrno(errno);
|
||
|
}
|
||
|
|
||
|
offset += blocksz;
|
||
|
|
||
|
// Don't invoke the callback every iteration - wait until a significant
|
||
|
// chunk (here, 1/1000th) of the data has been processed.
|
||
|
int new_permille = (static_cast<uint64_t>(offset) * 1000) / file_size;
|
||
|
if (new_permille != permille && static_cast<uint64_t>(offset) != file_size) {
|
||
|
if (on_progress && !on_progress(offset, file_size)) {
|
||
|
return FiemapStatus::Error();
|
||
|
}
|
||
|
permille = new_permille;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (lseek64(file_fd, 0, SEEK_SET) < 0) {
|
||
|
PLOG(ERROR) << "Failed to reset offset at the beginning of : " << file_path;
|
||
|
return FiemapStatus::FromErrno(errno);
|
||
|
}
|
||
|
return FiemapStatus::Ok();
|
||
|
}
|
||
|
|
||
|
// Reserve space for the file on the file system and write it out to make sure the extents
|
||
|
// don't come back unwritten. Return from this function with the kernel file offset set to 0.
|
||
|
// If the filesystem is f2fs, then we also PIN the file on disk to make sure the blocks
|
||
|
// aren't moved around.
|
||
|
static FiemapStatus AllocateFile(int file_fd, const std::string& file_path, uint64_t blocksz,
|
||
|
uint64_t file_size, unsigned int fs_type,
|
||
|
std::function<bool(uint64_t, uint64_t)> on_progress) {
|
||
|
bool need_explicit_writes = true;
|
||
|
switch (fs_type) {
|
||
|
case EXT4_SUPER_MAGIC:
|
||
|
break;
|
||
|
case F2FS_SUPER_MAGIC: {
|
||
|
bool supported;
|
||
|
if (!F2fsPinBeforeAllocate(file_fd, &supported)) {
|
||
|
return FiemapStatus::Error();
|
||
|
}
|
||
|
if (supported) {
|
||
|
if (!PinFile(file_fd, file_path, fs_type)) {
|
||
|
return FiemapStatus::Error();
|
||
|
}
|
||
|
need_explicit_writes = false;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
case MSDOS_SUPER_MAGIC:
|
||
|
// fallocate() is not supported, and not needed, since VFAT does not support holes.
|
||
|
// Instead we can perform a much faster allocation.
|
||
|
return FallocateFallback(file_fd, blocksz, file_size, file_path, on_progress);
|
||
|
default:
|
||
|
LOG(ERROR) << "Missing fallocate() support for file system " << fs_type;
|
||
|
return FiemapStatus::Error();
|
||
|
}
|
||
|
|
||
|
if (fallocate(file_fd, 0, 0, file_size)) {
|
||
|
PLOG(ERROR) << "Failed to allocate space for file: " << file_path << " size: " << file_size;
|
||
|
return FiemapStatus::FromErrno(errno);
|
||
|
}
|
||
|
|
||
|
if (need_explicit_writes) {
|
||
|
auto status = WriteZeroes(file_fd, file_path, blocksz, file_size, on_progress);
|
||
|
if (!status.is_ok()) {
|
||
|
return status;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// flush all writes here ..
|
||
|
if (fsync(file_fd)) {
|
||
|
PLOG(ERROR) << "Failed to synchronize written file:" << file_path;
|
||
|
return FiemapStatus::FromErrno(errno);
|
||
|
}
|
||
|
|
||
|
// Send one last progress notification.
|
||
|
if (on_progress && !on_progress(file_size, file_size)) {
|
||
|
return FiemapStatus::Error();
|
||
|
}
|
||
|
return FiemapStatus::Ok();
|
||
|
}
|
||
|
|
||
|
bool FiemapWriter::HasPinnedExtents(const std::string& file_path) {
|
||
|
android::base::unique_fd fd(open(file_path.c_str(), O_NOFOLLOW | O_CLOEXEC | O_RDONLY));
|
||
|
if (fd < 0) {
|
||
|
PLOG(ERROR) << "open: " << file_path;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
struct statfs64 sfs;
|
||
|
if (fstatfs64(fd, &sfs)) {
|
||
|
PLOG(ERROR) << "fstatfs64: " << file_path;
|
||
|
return false;
|
||
|
}
|
||
|
return IsFilePinned(fd, file_path, sfs.f_type);
|
||
|
}
|
||
|
|
||
|
static bool IsValidExtent(const fiemap_extent* extent, std::string_view file_path) {
|
||
|
if (extent->fe_flags & kUnsupportedExtentFlags) {
|
||
|
LOG(ERROR) << "Extent at location " << extent->fe_logical << " of file " << file_path
|
||
|
<< " has unsupported flags";
|
||
|
return false;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
static bool IsLastExtent(const fiemap_extent* extent) {
|
||
|
return !!(extent->fe_flags & FIEMAP_EXTENT_LAST);
|
||
|
}
|
||
|
|
||
|
static bool FiemapToExtents(struct fiemap* fiemap, std::vector<struct fiemap_extent>* extents,
|
||
|
std::string_view file_path) {
|
||
|
uint32_t num_extents = fiemap->fm_mapped_extents;
|
||
|
if (num_extents == 0) {
|
||
|
LOG(ERROR) << "File " << file_path << " has zero extent";
|
||
|
return false;
|
||
|
}
|
||
|
const struct fiemap_extent* last_extent = &fiemap->fm_extents[num_extents - 1];
|
||
|
if (!IsLastExtent(last_extent)) {
|
||
|
LOG(ERROR) << "FIEMAP did not return a final extent for file: " << file_path
|
||
|
<< " num_extents=" << num_extents << " max_extents=" << kMaxExtents;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Iterate through each extent, read and make sure its valid before adding it to the vector
|
||
|
// merging contiguous extents.
|
||
|
fiemap_extent* prev = &fiemap->fm_extents[0];
|
||
|
if (!IsValidExtent(prev, file_path)) return false;
|
||
|
|
||
|
for (uint32_t i = 1; i < num_extents; i++) {
|
||
|
fiemap_extent* next = &fiemap->fm_extents[i];
|
||
|
|
||
|
// Make sure extents are returned in order
|
||
|
if (next != last_extent && IsLastExtent(next)) {
|
||
|
LOG(ERROR) << "Extents are being received out-of-order";
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Check if extent's flags are valid
|
||
|
if (!IsValidExtent(next, file_path)) return false;
|
||
|
|
||
|
// Check if the current extent is contiguous with the previous one.
|
||
|
// An extent can be combined with its predecessor only if:
|
||
|
// 1. There is no physical space between the previous and the current
|
||
|
// extent, and
|
||
|
// 2. The physical distance between the previous and current extent
|
||
|
// corresponds to their logical distance (contiguous mapping).
|
||
|
if (prev->fe_physical + prev->fe_length == next->fe_physical &&
|
||
|
next->fe_physical - prev->fe_physical == next->fe_logical - prev->fe_logical) {
|
||
|
prev->fe_length += next->fe_length;
|
||
|
} else {
|
||
|
extents->emplace_back(*prev);
|
||
|
prev = next;
|
||
|
}
|
||
|
}
|
||
|
extents->emplace_back(*prev);
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
static bool ReadFiemap(int file_fd, const std::string& file_path,
|
||
|
std::vector<struct fiemap_extent>* extents) {
|
||
|
uint64_t fiemap_size = sizeof(struct fiemap) + kMaxExtents * sizeof(struct fiemap_extent);
|
||
|
auto buffer = std::unique_ptr<void, decltype(&free)>(calloc(1, fiemap_size), free);
|
||
|
if (buffer == nullptr) {
|
||
|
LOG(ERROR) << "Failed to allocate memory for fiemap";
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
struct fiemap* fiemap = reinterpret_cast<struct fiemap*>(buffer.get());
|
||
|
fiemap->fm_start = 0;
|
||
|
fiemap->fm_length = UINT64_MAX;
|
||
|
// make sure file is synced to disk before we read the fiemap
|
||
|
fiemap->fm_flags = FIEMAP_FLAG_SYNC;
|
||
|
fiemap->fm_extent_count = kMaxExtents;
|
||
|
|
||
|
if (ioctl(file_fd, FS_IOC_FIEMAP, fiemap)) {
|
||
|
PLOG(ERROR) << "Failed to get FIEMAP from the kernel for file: " << file_path;
|
||
|
return false;
|
||
|
}
|
||
|
return FiemapToExtents(fiemap, extents, file_path);
|
||
|
}
|
||
|
|
||
|
static bool ReadFibmap(int file_fd, const std::string& file_path,
|
||
|
std::vector<struct fiemap_extent>* extents) {
|
||
|
struct stat s;
|
||
|
if (fstat(file_fd, &s)) {
|
||
|
PLOG(ERROR) << "Failed to stat " << file_path;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
unsigned int blksize;
|
||
|
if (ioctl(file_fd, FIGETBSZ, &blksize) < 0) {
|
||
|
PLOG(ERROR) << "Failed to get FIGETBSZ for " << file_path;
|
||
|
return false;
|
||
|
}
|
||
|
if (!blksize) {
|
||
|
LOG(ERROR) << "Invalid filesystem block size: " << blksize;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
uint64_t num_blocks = (s.st_size + blksize - 1) / blksize;
|
||
|
if (num_blocks > std::numeric_limits<uint32_t>::max()) {
|
||
|
LOG(ERROR) << "Too many blocks for FIBMAP (" << num_blocks << ")";
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
for (uint32_t last_block, block_number = 0; block_number < num_blocks; block_number++) {
|
||
|
uint32_t block = block_number;
|
||
|
if (ioctl(file_fd, FIBMAP, &block)) {
|
||
|
PLOG(ERROR) << "Failed to get FIBMAP for file " << file_path;
|
||
|
return false;
|
||
|
}
|
||
|
if (!block) {
|
||
|
LOG(ERROR) << "Logical block " << block_number << " is a hole, which is not supported";
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (!extents->empty() && block == last_block + 1) {
|
||
|
extents->back().fe_length += blksize;
|
||
|
} else {
|
||
|
extents->push_back(fiemap_extent{.fe_logical = block_number,
|
||
|
.fe_physical = static_cast<uint64_t>(block) * blksize,
|
||
|
.fe_length = static_cast<uint64_t>(blksize),
|
||
|
.fe_flags = 0});
|
||
|
if (extents->size() > kMaxExtents) {
|
||
|
LOG(ERROR) << "File has more than " << kMaxExtents << "extents: " << file_path;
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
last_block = block;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
FiemapUniquePtr FiemapWriter::Open(const std::string& file_path, uint64_t file_size, bool create,
|
||
|
std::function<bool(uint64_t, uint64_t)> progress) {
|
||
|
FiemapUniquePtr ret;
|
||
|
if (!Open(file_path, file_size, &ret, create, progress).is_ok()) {
|
||
|
return nullptr;
|
||
|
}
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
FiemapStatus FiemapWriter::Open(const std::string& file_path, uint64_t file_size,
|
||
|
FiemapUniquePtr* out, bool create,
|
||
|
std::function<bool(uint64_t, uint64_t)> progress) {
|
||
|
out->reset();
|
||
|
|
||
|
// if 'create' is false, open an existing file and do not truncate.
|
||
|
int open_flags = O_RDWR | O_CLOEXEC;
|
||
|
if (create) {
|
||
|
if (access(file_path.c_str(), F_OK) == 0) {
|
||
|
LOG(WARNING) << "File " << file_path << " already exists, truncating";
|
||
|
}
|
||
|
open_flags |= O_CREAT | O_TRUNC;
|
||
|
}
|
||
|
::android::base::unique_fd file_fd(
|
||
|
TEMP_FAILURE_RETRY(open(file_path.c_str(), open_flags, S_IRUSR | S_IWUSR)));
|
||
|
if (file_fd < 0) {
|
||
|
PLOG(ERROR) << "Failed to create file at: " << file_path;
|
||
|
return FiemapStatus::FromErrno(errno);
|
||
|
}
|
||
|
|
||
|
std::string abs_path;
|
||
|
if (!::android::base::Realpath(file_path, &abs_path)) {
|
||
|
int saved_errno = errno;
|
||
|
PLOG(ERROR) << "Invalid file path: " << file_path;
|
||
|
cleanup(file_path, create);
|
||
|
return FiemapStatus::FromErrno(saved_errno);
|
||
|
}
|
||
|
|
||
|
std::string bdev_path;
|
||
|
if (!GetBlockDeviceForFile(abs_path, &bdev_path)) {
|
||
|
LOG(ERROR) << "Failed to get block dev path for file: " << file_path;
|
||
|
cleanup(abs_path, create);
|
||
|
return FiemapStatus::Error();
|
||
|
}
|
||
|
|
||
|
::android::base::unique_fd bdev_fd(
|
||
|
TEMP_FAILURE_RETRY(open(bdev_path.c_str(), O_RDONLY | O_CLOEXEC)));
|
||
|
if (bdev_fd < 0) {
|
||
|
int saved_errno = errno;
|
||
|
PLOG(ERROR) << "Failed to open block device: " << bdev_path;
|
||
|
cleanup(file_path, create);
|
||
|
return FiemapStatus::FromErrno(saved_errno);
|
||
|
}
|
||
|
|
||
|
uint64_t bdevsz;
|
||
|
if (!GetBlockDeviceSize(bdev_fd, bdev_path, &bdevsz)) {
|
||
|
int saved_errno = errno;
|
||
|
LOG(ERROR) << "Failed to get block device size for : " << bdev_path;
|
||
|
cleanup(file_path, create);
|
||
|
return FiemapStatus::FromErrno(saved_errno);
|
||
|
}
|
||
|
|
||
|
if (!create) {
|
||
|
file_size = GetFileSize(abs_path);
|
||
|
if (file_size == 0) {
|
||
|
LOG(ERROR) << "Invalid file size of zero bytes for file: " << abs_path;
|
||
|
return FiemapStatus::FromErrno(errno);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
uint64_t blocksz;
|
||
|
uint32_t fs_type;
|
||
|
if (!PerformFileChecks(abs_path, &blocksz, &fs_type)) {
|
||
|
LOG(ERROR) << "Failed to validate file or file system for file:" << abs_path;
|
||
|
cleanup(abs_path, create);
|
||
|
return FiemapStatus::Error();
|
||
|
}
|
||
|
|
||
|
// Align up to the nearest block size.
|
||
|
if (file_size % blocksz) {
|
||
|
file_size += blocksz - (file_size % blocksz);
|
||
|
}
|
||
|
|
||
|
if (create) {
|
||
|
auto status =
|
||
|
AllocateFile(file_fd, abs_path, blocksz, file_size, fs_type, std::move(progress));
|
||
|
if (!status.is_ok()) {
|
||
|
LOG(ERROR) << "Failed to allocate file: " << abs_path << " of size: " << file_size
|
||
|
<< " bytes";
|
||
|
cleanup(abs_path, create);
|
||
|
return status;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// f2fs may move the file blocks around.
|
||
|
if (!PinFile(file_fd, abs_path, fs_type)) {
|
||
|
cleanup(abs_path, create);
|
||
|
LOG(ERROR) << "Failed to pin the file in storage";
|
||
|
return FiemapStatus::Error();
|
||
|
}
|
||
|
|
||
|
// now allocate the FiemapWriter and start setting it up
|
||
|
FiemapUniquePtr fmap(new FiemapWriter());
|
||
|
switch (fs_type) {
|
||
|
case EXT4_SUPER_MAGIC:
|
||
|
case F2FS_SUPER_MAGIC:
|
||
|
if (!ReadFiemap(file_fd, abs_path, &fmap->extents_)) {
|
||
|
LOG(ERROR) << "Failed to read fiemap of file: " << abs_path;
|
||
|
cleanup(abs_path, create);
|
||
|
return FiemapStatus::Error();
|
||
|
}
|
||
|
break;
|
||
|
case MSDOS_SUPER_MAGIC:
|
||
|
if (!ReadFibmap(file_fd, abs_path, &fmap->extents_)) {
|
||
|
LOG(ERROR) << "Failed to read fibmap of file: " << abs_path;
|
||
|
cleanup(abs_path, create);
|
||
|
return FiemapStatus::Error();
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
fmap->file_path_ = abs_path;
|
||
|
fmap->bdev_path_ = bdev_path;
|
||
|
fmap->file_size_ = file_size;
|
||
|
fmap->bdev_size_ = bdevsz;
|
||
|
fmap->fs_type_ = fs_type;
|
||
|
fmap->block_size_ = blocksz;
|
||
|
|
||
|
LOG(VERBOSE) << "Successfully created FiemapWriter for file " << abs_path << " on block device "
|
||
|
<< bdev_path;
|
||
|
*out = std::move(fmap);
|
||
|
return FiemapStatus::Ok();
|
||
|
}
|
||
|
|
||
|
} // namespace fiemap
|
||
|
} // namespace android
|