blob: 80f5112498a5a44c7d7c996348b0db26c14f7790 [file] [log] [blame]
/*
* Copyright (C) 2019 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 "incfs.h"
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/properties.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#include <android-base/unique_fd.h>
#include <errno.h>
#include <libgen.h>
#include <sys/mount.h>
#include <sys/poll.h>
#include <sys/stat.h>
#include <sys/vfs.h>
#include <unistd.h>
#include <chrono>
#include <fstream>
#include <optional>
#include <string_view>
#include "MountRegistry.h"
#include "path.h"
namespace {
namespace impl {
using namespace std::literals;
using namespace android::incfs;
namespace base = android::base;
struct Control {
Control(int error) : cmd(error), log(error) {}
Control(base::unique_fd&& cmd, base::unique_fd&& log)
: cmd(std::move(cmd)), log(std::move(log)) {}
base::unique_fd cmd;
base::unique_fd log;
};
MountRegistry& registry() {
static MountRegistry instance;
return instance;
}
base::unique_fd openRaw(std::string_view dir, std::string_view name) {
auto file = base::StringPrintf("%.*s/%.*s", (int)dir.size(), dir.data(), (int)name.size(),
name.data());
auto fd = base::unique_fd(TEMP_FAILURE_RETRY(::open(file.c_str(), O_RDWR)));
if (fd < 0) {
const auto error = errno;
PLOG(ERROR) << "[incfs] failed to open IncFS file " << file;
return base::unique_fd{-error};
}
return fd;
}
base::unique_fd openCmd(std::string_view dir) {
return openRaw(dir, ".cmd");
}
base::unique_fd openLog(std::string_view dir) {
return openRaw(dir, ".log");
}
static std::optional<Version> readIncFsVersion() {
const std::string sSysfsVersionFile = "/sys/fs/" + std::string(INCFS_NAME) + "/version";
std::ifstream verFile(sSysfsVersionFile);
if (!verFile) {
return {};
}
Version version = -1;
if (!(verFile >> version)) {
return {};
}
return version > 0 ? std::optional(version) : std::nullopt;
}
Version version() {
return readIncFsVersion().value_or(kVersionNone);
}
// TODO: make this function public in android::fs_mgr.
static bool isFsAvailable(const std::string& filesystem) {
static constexpr auto kProcFilesystems = "/proc/filesystems"sv;
std::string filesystems;
if (!android::base::ReadFileToString(kProcFilesystems.data(), &filesystems)) {
return false;
}
return filesystems.find("\t" + filesystem + "\n") != std::string::npos;
}
bool enabled() {
// First check if incfs is installed on the device
if (!isFsAvailable(INCFS_NAME)) {
return false;
}
// Check if property is enabled (default is true)
// TODO: change default to false
static constexpr auto kIncrementalEnabledProperty = "persist.incremental.enabled"sv;
return base::GetBoolProperty(std::string(kIncrementalEnabledProperty), true /*false*/);
}
bool isIncFsPath(const char* path) {
if (!enabled()) {
return false;
}
struct statfs fs;
memset(&fs, 0, sizeof(fs));
if (statfs(path, &fs) != 0) {
PLOG(ERROR) << "Could not statfs " << path;
return false;
}
return fs.f_type == INCFS_MAGIC_NUMBER;
}
Control mount(std::string_view imagePath, std::string_view targetDir, int32_t flags,
std::chrono::milliseconds timeout, int mode) {
if (!enabled()) {
LOG(WARNING) << "[incfs] Feature is not enabled";
return {-ENOTSUP};
}
if (isIncFsPath(details::c_str(targetDir))) {
LOG(ERROR) << "[incfs] mounting over existing incfs mount is not allowed";
return {-EINVAL};
}
int openFlags = O_CREAT | O_RDWR | O_CLOEXEC;
if (flags & android::incfs::createOnly) {
openFlags |= O_EXCL;
} else if (flags & android::incfs::truncate) {
openFlags |= O_TRUNC;
}
auto backingFd =
base::unique_fd(TEMP_FAILURE_RETRY(::open(details::c_str(imagePath), openFlags, mode)));
if (backingFd < 0) {
const auto error = errno;
PLOG(ERROR) << "[incfs] failed to open or create an image file: " << imagePath;
return {-error};
}
using namespace std::chrono;
auto opts = base::StringPrintf("backing_fd=%d,read_timeout_ms=%u,readahead=0,rlog_pages=%u",
backingFd.get(),
(unsigned)duration_cast<milliseconds>(timeout).count(),
unsigned(INCFS_DEFAULT_PAGE_READ_BUFFER_PAGES));
if (TEMP_FAILURE_RETRY(::mount(INCFS_NAME, details::c_str(targetDir), INCFS_NAME,
MS_NOSUID | MS_NODEV | MS_NOATIME, opts.c_str()))) {
const auto error = errno;
PLOG(ERROR) << "[incfs] Failed to mount IncFS filesystem: " << targetDir;
return {-error};
}
LOG(DEBUG) << "[incfs] mounted IncFS at " << targetDir << ", backing file " << imagePath
<< ", opts " << opts;
auto fd = openCmd(targetDir);
if (fd < 0) {
unmount(targetDir);
return {fd};
}
registry().addRoot(targetDir);
return {std::move(fd), openLog(targetDir)};
}
int bindMount(std::string_view source, std::string_view target) {
if (!enabled()) {
return -ENOTSUP;
}
if (TEMP_FAILURE_RETRY(::mount(details::c_str(source), details::c_str(target), nullptr, MS_BIND,
nullptr))) {
PLOG(ERROR) << "[incfs] Failed to bind mount " << source << " to " << target;
return -errno;
}
registry().addBind(source, target);
return 0;
}
int unmount(std::string_view dir) {
if (!enabled()) {
return -ENOTSUP;
}
registry().removeBind(dir);
errno = 0;
if (TEMP_FAILURE_RETRY(::umount2(details::c_str(dir), MNT_FORCE)) == 0 || errno == EINVAL ||
errno == ENOENT) {
// EINVAL - not a mount point, ENOENT - doesn't exist at all
return -errno;
}
PLOG(WARNING) << __func__ << ": umount(force) failed, detaching '" << dir << '\'';
errno = 0;
if (!TEMP_FAILURE_RETRY(::umount2(details::c_str(dir), MNT_DETACH))) {
return 0;
}
PLOG(INFO) << __func__ << ": umount(detach) returned non-zero for '" << dir << '\'';
return 0;
}
static int sendInstruction(int fd, incfs_instruction& inst) {
inst.version = INCFS_HEADER_VER;
if (::ioctl(fd, INCFS_IOC_PROCESS_INSTRUCTION, (void*)&inst) == 0) {
return 0;
}
return -errno;
}
static int64_t addInodeToDir(int fd, Inode inode, std::string_view name, uint64_t dirInode) {
LOG(INFO) << __func__ << " @ " << fd << " / " << dirInode << " / " << name << " adding "
<< inode;
auto inst = incfs_instruction{.type = INCFS_INSTRUCTION_ADD_DIR_ENTRY,
.dir_entry = {.dir_ino = dirInode,
.child_ino = uint64_t(inode),
.name = uint64_t(name.data()),
.name_len = uint16_t(name.size())}};
const auto res = sendInstruction(fd, inst);
if (res < 0) {
LOG(ERROR) << "addInodeToDir failed: " << res;
return res;
}
if (res != 0) {
return -EIO;
}
return int64_t(inode);
}
static Inode makeInode(int fd, std::string_view name, Inode parentInode, Size size, mode_t mode,
std::string_view metadata) {
// TODO(zyy): add signature information
LOG(INFO) << __func__ << "(" << ((mode & S_IFDIR) ? "dir" : "file") << ") @ " << fd << " / "
<< parentInode << " / " << name << " of " << size << " bytes, '" << metadata << '\'';
if (metadata.size() > INCFS_MAX_FILE_ATTR_SIZE) {
LOG(ERROR) << "Input metadata size " << metadata.size() << " is bigger than max "
<< INCFS_MAX_FILE_ATTR_SIZE;
return -E2BIG;
}
auto inst = incfs_instruction{.type = INCFS_INSTRUCTION_NEW_FILE,
.file = {.size = uint64_t(size),
.mode = uint16_t(mode),
.file_attr_len = uint32_t(metadata.size()),
.file_attr = uint64_t(metadata.data())}};
auto res = sendInstruction(fd, inst);
if (res < 0) {
LOG(ERROR) << "makeInode failed: " << res;
return res;
}
if (res != 0) {
return -EIO;
}
return addInodeToDir(fd, inst.file.ino_out, name, parentInode);
}
Control open(std::string_view dir) {
auto root = registry().rootFor(dir);
return {openCmd(root), openLog(root)};
}
Inode makeDir(int fd, std::string_view name, Inode parent, std::string_view metadata, int mode) {
return makeInode(fd, name, parent, 0, S_IFDIR | (mode & 0777), metadata);
}
Inode makeFile(int fd, std::string_view name, Inode parent, Size size, std::string_view metadata,
int mode) {
return makeInode(fd, name, parent, size, S_IFREG | (mode & 0777), metadata);
}
int link(int fd, Inode item, Inode targetParent, std::string_view name) {
const auto res = addInodeToDir(fd, item, name, targetParent);
return res < 0 ? int(res) : 0;
}
int unlink(int fd, Inode parent, std::string_view name) {
incfs_instruction inst = {.type = INCFS_INSTRUCTION_REMOVE_DIR_ENTRY,
.dir_entry = {.dir_ino = uint64_t(parent),
.name = uint64_t(name.data()),
.name_len = uint16_t(name.size())}};
auto res = sendInstruction(fd, inst);
if (res < 0) {
return res;
}
if (res != 0) {
return -EIO;
}
return 0;
}
int waitForReads(int fd, std::chrono::milliseconds timeout,
incfs_pending_read_info pendingReadsBuffer[], size_t* pendingReadsBufferSize) {
using namespace std::chrono;
auto hrTimeout = high_resolution_clock::duration(timeout);
while (hrTimeout > hrTimeout.zero() || (!pendingReadsBuffer && hrTimeout == hrTimeout.zero())) {
const auto startTs = steady_clock::now();
pollfd pfd = {fd, POLLIN, 0};
const auto res = ::poll(&pfd, 1, duration_cast<milliseconds>(hrTimeout).count());
if (res > 0) {
break;
}
if (res == 0) {
if (pendingReadsBufferSize) {
*pendingReadsBufferSize = 0;
}
return -ETIMEDOUT;
}
const auto error = errno;
if (error != EINTR) {
PLOG(ERROR) << "poll() failed";
return -error;
}
hrTimeout -= steady_clock::now() - startTs;
}
if (!pendingReadsBuffer) {
return hrTimeout < hrTimeout.zero() ? -ETIMEDOUT : 0;
}
auto res = ::read(fd, pendingReadsBuffer, *pendingReadsBufferSize);
if (res < 0) {
const auto error = errno;
PLOG(ERROR) << "read() failed";
return -error;
}
if (res == 0) {
*pendingReadsBufferSize = 0;
return -ETIMEDOUT;
}
if ((res % sizeof(*pendingReadsBuffer)) != 0) {
PLOG(ERROR) << "read() returned half of a struct??";
return -EFAULT;
}
*pendingReadsBufferSize = res / sizeof(*pendingReadsBuffer);
return 0;
}
int writeBlocks(int fd, const incfs_new_data_block blocks[], int blocksCount) {
if (blocksCount < 0) {
return -EINVAL;
}
if (blocksCount == 0) {
return 0;
}
auto ptr = blocks;
const auto end = blocks + blocksCount;
do {
const auto written = ::write(fd, ptr, (end - ptr) * sizeof(*ptr));
if (written < 0) {
if (errno == EINTR) {
continue;
}
const auto error = errno;
PLOG(WARNING) << "writing IncFS blocks failed";
if (ptr == blocks) {
return -error;
}
// something has been written, return a success here and let the
// next call handle the error.
break;
}
if ((written % sizeof(*ptr)) != 0) {
PLOG(ERROR) << "write() handled half of an instruction?? " << written;
return -EFAULT;
}
ptr += written / sizeof(*ptr);
} while (ptr < end);
return ptr - blocks;
}
std::string root(int fd) {
auto cmdFile = path::fromFd(fd);
if (cmdFile.empty()) {
return {};
}
auto res = ::dirname(cmdFile.c_str());
if (!res || !*res) {
return {};
}
return res;
}
IncFsErrorCode getMetadata(int fd, Inode inode, char buffer[], size_t* bufferSize) {
incfs_get_file_attr_request request = {
.version = INCFS_HEADER_VER,
.ino = uint64_t(inode),
.file_attr = uint64_t(buffer),
.file_attr_buf_size = uint32_t(*bufferSize),
};
auto res = ::ioctl(fd, INCFS_IOC_READ_FILE_ATTR, &request);
if (res < 0) {
PLOG(ERROR) << "Driver call failed for inode " << inode;
return res;
}
if (request.file_attr_len_out > *bufferSize) {
LOG(ERROR) << "Not enough space in the buffer, " << request.file_attr_len_out << " vs "
<< INCFS_MAX_FILE_ATTR_SIZE << " max allowed in the driver API";
return -EOVERFLOW;
}
*bufferSize = request.file_attr_len_out;
return 0;
}
} // namespace impl
} // namespace
bool IncFs_Enabled() {
return impl::enabled();
}
IncFsVersion IncFs_Version() {
return impl::version();
}
IncFsErrorCode IncFs_IsIncFsPath(const char* path) {
return impl::isIncFsPath(path);
}
IncFsControl IncFs_Mount(const char* imagePath, const char* targetDir, int32_t flags,
int32_t timeoutMs, int32_t mode) {
auto control =
impl::mount(imagePath, targetDir, flags, std::chrono::milliseconds(timeoutMs), mode);
return {control.cmd.release(), control.log.release()};
}
IncFsErrorCode IncFs_Unmount(const char* dir) {
return impl::unmount(dir);
}
IncFsErrorCode IncFs_BindMount(const char* sourceDir, const char* targetDir) {
return impl::bindMount(sourceDir, targetDir);
}
IncFsErrorCode IncFs_Root(IncFsControl control, char buffer[], size_t* bufferSize) {
std::string result = impl::root(control.cmdFd);
if (*bufferSize <= result.size()) {
return -EOVERFLOW;
}
*bufferSize = result.size();
result.copy(buffer, *bufferSize, 0);
buffer[*bufferSize] = '\0';
return 0;
}
IncFsControl IncFs_Open(const char* dir) {
auto control = impl::open(dir);
return {control.cmd.release(), control.log.release()};
}
IncFsInode IncFs_MakeFile(IncFsControl control, const char* name, IncFsInode parent, IncFsSize size,
const char metadata[], size_t metadataSize, int32_t mode) {
return impl::makeFile(control.cmdFd, name, parent, size,
std::string_view(metadata, metadataSize), mode);
}
IncFsInode IncFs_MakeDir(IncFsControl control, const char* name, IncFsInode parent,
const char metadata[], size_t metadataSize, int32_t mode) {
return impl::makeDir(control.cmdFd, name, parent, std::string_view(metadata, metadataSize),
mode);
}
IncFsErrorCode IncFs_GetMetadata(IncFsControl control, IncFsInode inode, char buffer[],
size_t* bufferSize) {
return impl::getMetadata(control.cmdFd, inode, buffer, bufferSize);
}
IncFsErrorCode IncFs_Link(IncFsControl control, IncFsInode item, IncFsInode targetParent,
const char* name) {
return impl::link(control.cmdFd, item, targetParent, name);
}
IncFsErrorCode IncFs_Unlink(IncFsControl control, IncFsInode parent, const char* name) {
return impl::unlink(control.cmdFd, parent, name);
}
IncFsErrorCode IncFs_WaitForPendingReads(IncFsControl control, int32_t timeoutMs,
IncFsPendingReadInfo buffer[], size_t* bufferSize) {
return impl::waitForReads(control.cmdFd, std::chrono::milliseconds(timeoutMs), buffer,
bufferSize);
}
IncFsErrorCode IncFs_WaitForPageReads(IncFsControl control, int32_t timeoutMs,
IncFsPageReadInfo buffer[], size_t* bufferSize) {
if (control.logFd < 0) {
return -EINVAL;
}
return impl::waitForReads(control.logFd, std::chrono::milliseconds(timeoutMs), buffer,
bufferSize);
}
IncFsErrorCode IncFs_WriteBlocks(IncFsControl control, const incfs_new_data_block blocks[],
size_t blocksCount) {
return impl::writeBlocks(control.cmdFd, blocks, blocksCount);
}