Merge Android 12
Bug: 202323961
Merged-In: Ibd657c8fc8a3169a4e83e192a157f2e9e5e1240a
Change-Id: I261359334b74ed35eed116716a33ea55d5083fa3
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..722d5e7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+.vscode
diff --git a/incfs/Android.bp b/incfs/Android.bp
index cb4cb1e..88b3abb 100644
--- a/incfs/Android.bp
+++ b/incfs/Android.bp
@@ -163,6 +163,21 @@
require_root: true,
}
+cc_benchmark {
+ name: "hardening-benchmark",
+ defaults: ["libincfs_defaults"],
+
+ srcs: [
+ "tests/hardening_benchmark.cpp",
+ ],
+ static_libs: [
+ "libziparchive_for_incfs",
+ "libutils",
+ "libincfs",
+ "libincfs-utils",
+ ],
+}
+
cc_binary {
name: "incfsdump",
defaults: ["libincfs_defaults"],
diff --git a/incfs/MountRegistry.cpp b/incfs/MountRegistry.cpp
index c4752f3..b7b53c8 100644
--- a/incfs/MountRegistry.cpp
+++ b/incfs/MountRegistry.cpp
@@ -18,18 +18,18 @@
#include "MountRegistry.h"
+#include <android-base/logging.h>
+#include <poll.h>
+#include <stdlib.h>
+
+#include <charconv>
+#include <set>
+#include <unordered_map>
+
#include "incfs.h"
#include "path.h"
#include "split.h"
-#include <android-base/logging.h>
-
-#include <charconv>
-#include <unordered_map>
-
-#include <poll.h>
-#include <stdlib.h>
-
using namespace std::literals;
namespace android::incfs {
@@ -69,7 +69,7 @@
std::vector<std::pair<std::string_view, std::string_view>> result;
result.reserve(mBase->binds.size());
for (auto it : mBase->binds) {
- result.emplace_back(it->second.first, it->first);
+ result.emplace_back(it->second.subdir, it->first);
}
return result;
}
@@ -88,12 +88,12 @@
std::string_view path) const {
auto it = rootByBindPoint.lower_bound(path);
if (it != rootByBindPoint.end() && it->first == path) {
- return {it->second.second, it};
+ return {it->second.rootIndex, it};
}
if (it != rootByBindPoint.begin()) {
--it;
if (path::startsWith(path, it->first) && path.size() > it->first.size()) {
- const auto index = it->second.second;
+ const auto index = it->second.rootIndex;
if (index >= int(roots.size()) || roots[index].empty()) {
LOG(ERROR) << "[incfs] Root for path '" << path << "' #" << index
<< " is not valid";
@@ -113,24 +113,24 @@
return roots[index].path;
}
-std::pair<std::string_view, std::string> MountRegistry::Mounts::rootAndSubpathFor(
- std::string_view path) const {
+auto MountRegistry::Mounts::rootAndSubpathFor(std::string_view path) const
+ -> std::pair<const Root*, std::string> {
auto normalPath = path::normalize(path);
auto [index, bindIt] = rootIndex(normalPath);
if (index < 0) {
return {};
}
- const auto& bindSubdir = bindIt->second.first;
+ const auto& bindSubdir = bindIt->second.subdir;
const auto pastBindSubdir = path::relativize(bindIt->first, normalPath);
const auto& root = roots[index];
- return {root.path, path::join(bindSubdir, pastBindSubdir)};
+ return {&root, path::join(bindSubdir, pastBindSubdir)};
}
void MountRegistry::Mounts::addRoot(std::string_view root, std::string_view backingDir) {
const auto index = roots.size();
auto absolute = path::normalize(root);
- auto it = rootByBindPoint.insert_or_assign(absolute, std::pair{std::string(), index}).first;
+ auto it = rootByBindPoint.insert_or_assign(absolute, Bind{std::string(), int(index)}).first;
roots.push_back({std::move(absolute), path::normalize(backingDir), {it}});
}
@@ -141,13 +141,17 @@
LOG(WARNING) << "[incfs] Trying to remove non-existent root '" << root << '\'';
return;
}
- const auto index = it->second.second;
+ const auto index = it->second.rootIndex;
if (index >= int(roots.size())) {
LOG(ERROR) << "[incfs] Root '" << root << "' has index " << index
<< " out of bounds (total roots count is " << roots.size();
return;
}
+ for (auto bindIt : roots[index].binds) {
+ rootByBindPoint.erase(bindIt);
+ }
+
if (index + 1 == int(roots.size())) {
roots.pop_back();
// Run a small GC job here as we may be able to remove some obsolete
@@ -158,37 +162,6 @@
} else {
roots[index].clear();
}
- rootByBindPoint.erase(it);
-}
-
-void MountRegistry::Mounts::moveBind(std::string_view src, std::string_view dest) {
- auto srcAbsolute = path::normalize(src);
- auto destAbsolute = path::normalize(dest);
- if (srcAbsolute == destAbsolute) {
- return;
- }
-
- auto [root, rootIt] = rootIndex(srcAbsolute);
- if (root < 0) {
- LOG(ERROR) << "[incfs] No root found for bind move from " << src << " to " << dest;
- return;
- }
-
- if (roots[root].path == srcAbsolute) {
- // moving the whole root
- roots[root].path = destAbsolute;
- }
-
- // const_cast<> here is safe as we're erasing that element on the next line.
- const auto newRootIt = rootByBindPoint
- .insert_or_assign(std::move(destAbsolute),
- std::pair{std::move(const_cast<std::string&>(
- rootIt->second.first)),
- root})
- .first;
- rootByBindPoint.erase(rootIt);
- const auto bindIt = std::find(roots[root].binds.begin(), roots[root].binds.end(), rootIt);
- *bindIt = newRootIt;
}
void MountRegistry::Mounts::addBind(std::string_view what, std::string_view where) {
@@ -201,11 +174,10 @@
const auto& currentBind = rootIt->first;
auto whatSubpath = path::relativize(currentBind, whatAbsolute);
- const auto& subdir = rootIt->second.first;
+ const auto& subdir = rootIt->second.subdir;
auto realSubdir = path::join(subdir, whatSubpath);
auto it = rootByBindPoint
- .insert_or_assign(path::normalize(where),
- std::pair{std::move(realSubdir), root})
+ .insert_or_assign(path::normalize(where), Bind{std::move(realSubdir), root})
.first;
roots[root].binds.push_back(it);
}
@@ -244,10 +216,23 @@
auto lock = ensureUpToDate();
return std::string(mMounts.rootFor(path));
}
+
+auto MountRegistry::detailsFor(std::string_view path) -> Details {
+ auto lock = ensureUpToDate();
+ auto [root, subpath] = mMounts.rootAndSubpathFor(path);
+ if (!root) {
+ return {};
+ }
+ return {root->path, root->backing, subpath};
+}
+
std::pair<std::string, std::string> MountRegistry::rootAndSubpathFor(std::string_view path) {
auto lock = ensureUpToDate();
auto [root, subpath] = mMounts.rootAndSubpathFor(path);
- return {std::string(root), std::move(subpath)};
+ if (!root) {
+ return {};
+ }
+ return {std::string(root->path), std::move(subpath)};
}
MountRegistry::Mounts MountRegistry::copyMounts() {
@@ -319,8 +304,8 @@
bool MountRegistry::Mounts::loadFrom(base::borrowed_fd fd, std::string_view filesystem) {
struct MountInfo {
- std::string root;
std::string backing;
+ std::set<std::string, std::less<>> roots;
std::vector<std::pair<std::string, std::string>> bindPoints;
};
std::unordered_map<std::string, MountInfo> mountsByGroup(16);
@@ -342,20 +327,22 @@
}
const auto groupId = items[2];
auto subdir = items[3];
+ auto backingDir = items.rbegin()[1];
auto mountPoint = std::string(items[4]);
fixProcPath(mountPoint);
mountPoint = path::normalize(mountPoint);
auto& mount = mountsByGroup[std::string(groupId)];
+ if (mount.backing.empty()) {
+ mount.backing.assign(backingDir);
+ } else if (mount.backing != backingDir) {
+ LOG(WARNING) << "[incfs] root '" << *mount.roots.begin()
+ << "' mounted in multiple places with different backing dirs, '"
+ << mount.backing << "' vs new '" << backingDir
+ << "'; updating to the new one";
+ mount.backing.assign(backingDir);
+ }
if (subdir == "/"sv) {
- if (mount.root.empty()) {
- mount.root.assign(mountPoint);
- mount.backing.assign(items.rbegin()[1]);
- fixProcPath(mount.backing);
- } else {
- LOG(WARNING) << "[incfs] incfs root '" << mount.root
- << "' mounted in multiple places, ignoring later mount '" << mountPoint
- << '\'';
- }
+ mount.roots.emplace(mountPoint);
subdir = ""sv;
}
mount.bindPoints.emplace_back(std::string(subdir), std::move(mountPoint));
@@ -366,7 +353,7 @@
}
rootByBindPoint.clear();
- // preserve the allocated capacity, but clean existing data
+ // preserve the allocated capacity, but clear existing data
roots.resize(mountsByGroup.size());
for (auto& root : roots) {
root.binds.clear();
@@ -374,20 +361,32 @@
int index = 0;
for (auto& [_, mount] : mountsByGroup) {
+ if (mount.roots.empty()) {
+ // the mount has no root, and without root we have no good way of accessing the
+ // control files - so the only valid reaction here is to ignore it
+ LOG(WARNING) << "[incfs] mount '" << mount.backing << "' has no root, but "
+ << mount.bindPoints.size() << " bind(s), ignoring";
+ continue;
+ }
+
Root& root = roots[index];
auto& binds = root.binds;
binds.reserve(mount.bindPoints.size());
for (auto& [subdir, bind] : mount.bindPoints) {
- auto it =
- rootByBindPoint
- .insert_or_assign(std::move(bind), std::pair(std::move(subdir), index))
- .first;
+ auto it = rootByBindPoint
+ .insert_or_assign(std::move(bind), Bind{std::move(subdir), index})
+ .first;
binds.push_back(it);
}
- root.path = std::move(mount.root);
root.backing = std::move(mount.backing);
+ fixProcPath(root.backing);
+
+ // a trick here: given that as of now we either have exactly one root, or the preferred one
+ // is always at the front, let's pick that one here.
+ root.path = std::move(mount.roots.extract(mount.roots.begin()).value());
++index;
}
+ roots.resize(index);
LOG(INFO) << "[incfs] Loaded " << filesystem << " mount info: " << roots.size()
<< " instances, " << rootByBindPoint.size() << " mount points";
@@ -396,7 +395,7 @@
LOG(INFO) << "[incfs] '" << root << '\'';
LOG(INFO) << "[incfs] backing: '" << backing << '\'';
for (auto&& bind : binds) {
- LOG(INFO) << "[incfs] bind : '" << bind->second.first << "'->'" << bind->first
+ LOG(INFO) << "[incfs] bind : '" << bind->second.subdir << "'->'" << bind->first
<< '\'';
}
}
diff --git a/incfs/incfs.cpp b/incfs/incfs.cpp
index 5fe675b..490b907 100644
--- a/incfs/incfs.cpp
+++ b/incfs/incfs.cpp
@@ -23,6 +23,7 @@
#include <android-base/logging.h>
#include <android-base/no_destructor.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>
@@ -33,6 +34,7 @@
#include <openssl/sha.h>
#include <selinux/android.h>
#include <selinux/selinux.h>
+#include <sys/inotify.h>
#include <sys/mount.h>
#include <sys/poll.h>
#include <sys/stat.h>
@@ -42,8 +44,8 @@
#include <sys/xattr.h>
#include <unistd.h>
+#include <charconv>
#include <chrono>
-#include <fstream>
#include <iterator>
#include <mutex>
#include <optional>
@@ -53,47 +55,52 @@
#include "path.h"
using namespace std::literals;
-
-using android::base::StringPrintf;
-using android::base::unique_fd;
+using namespace android::incfs;
+using namespace android::sysprop;
+namespace ab = android::base;
struct IncFsControl final {
IncFsFd cmd;
IncFsFd pendingReads;
IncFsFd logs;
- constexpr IncFsControl(IncFsFd cmd, IncFsFd pendingReads, IncFsFd logs)
- : cmd(cmd), pendingReads(pendingReads), logs(logs) {}
+ IncFsFd blocksWritten;
+ constexpr IncFsControl(IncFsFd cmd, IncFsFd pendingReads, IncFsFd logs, IncFsFd blocksWritten)
+ : cmd(cmd), pendingReads(pendingReads), logs(logs), blocksWritten(blocksWritten) {}
};
-static android::incfs::MountRegistry& registry() {
- static android::base::NoDestructor<android::incfs::MountRegistry> instance{};
+static MountRegistry& registry() {
+ static ab::NoDestructor<MountRegistry> instance{};
return *instance;
}
-static unique_fd openRaw(std::string_view file) {
- auto fd = unique_fd(::open(android::incfs::details::c_str(file), O_RDONLY | O_CLOEXEC));
+static ab::unique_fd openRaw(std::string_view file) {
+ auto fd = ab::unique_fd(::open(details::c_str(file), O_RDONLY | O_CLOEXEC));
if (fd < 0) {
- return unique_fd{-errno};
+ return ab::unique_fd{-errno};
}
return fd;
}
-static unique_fd openRaw(std::string_view dir, std::string_view name) {
- return openRaw(android::incfs::path::join(dir, name));
+static ab::unique_fd openRaw(std::string_view dir, std::string_view name) {
+ return openRaw(path::join(dir, name));
+}
+
+static std::string indexPath(std::string_view root, IncFsFileId fileId) {
+ return path::join(root, INCFS_INDEX_NAME, toString(fileId));
}
static std::string rootForCmd(int fd) {
- auto cmdFile = android::incfs::path::fromFd(fd);
+ auto cmdFile = path::fromFd(fd);
if (cmdFile.empty()) {
LOG(INFO) << __func__ << "(): name empty for " << fd;
return {};
}
- auto res = android::incfs::path::dirName(cmdFile);
+ auto res = path::dirName(cmdFile);
if (res.empty()) {
LOG(INFO) << __func__ << "(): dirname empty for " << cmdFile;
return {};
}
- if (!android::incfs::path::endsWith(cmdFile, INCFS_PENDING_READS_FILENAME)) {
+ if (!path::endsWith(cmdFile, INCFS_PENDING_READS_FILENAME)) {
LOG(INFO) << __func__ << "(): invalid file name " << cmdFile;
return {};
}
@@ -104,51 +111,38 @@
return std::string(res);
}
-static android::incfs::Features readIncFsFeatures() {
- static const char kSysfsFeaturesDir[] = "/sys/fs/" INCFS_NAME "/features";
- const auto dir = android::incfs::path::openDir(kSysfsFeaturesDir);
- if (!dir) {
- return android::incfs::Features::none;
- }
-
- int res = android::incfs::Features::none;
- while (auto entry = ::readdir(dir.get())) {
- if (entry->d_type != DT_REG) {
- continue;
- }
- if (entry->d_name == "corefs"sv) {
- res |= android::incfs::Features::core;
- }
- }
-
- return android::incfs::Features(res);
-}
-
-IncFsFeatures IncFs_Features() {
- return IncFsFeatures(readIncFsFeatures());
-}
-
static bool isFsAvailable() {
static const char kProcFilesystems[] = "/proc/filesystems";
std::string filesystems;
- if (!android::base::ReadFileToString(kProcFilesystems, &filesystems)) {
+ if (!ab::ReadFileToString(kProcFilesystems, &filesystems)) {
return false;
}
- return filesystems.find("\t" INCFS_NAME "\n") != std::string::npos;
+ const auto result = filesystems.find("\t" INCFS_NAME "\n") != std::string::npos;
+ LOG(INFO) << "isFsAvailable: " << (result ? "true" : "false");
+ return result;
+}
+
+static int getFirstApiLevel() {
+ uint64_t api_level = android::base::GetUintProperty<uint64_t>("ro.product.first_api_level", 0);
+ LOG(INFO) << "Initial API level of the device: " << api_level;
+ return api_level;
}
static std::string_view incFsPropertyValue() {
- static const android::base::NoDestructor<std::string> kValue{
- android::sysprop::IncrementalProperties::enable().value_or("")};
+ constexpr const int R_API = 30;
+ static const auto kDefaultValue{getFirstApiLevel() > R_API ? "on" : ""};
+ static const ab::NoDestructor<std::string> kValue{
+ IncrementalProperties::enable().value_or(kDefaultValue)};
+ LOG(INFO) << "ro.incremental.enable: " << *kValue;
return *kValue;
}
static std::pair<bool, std::string_view> parseProperty(std::string_view property) {
- auto boolVal = android::base::ParseBool(property);
- if (boolVal == android::base::ParseBoolResult::kTrue) {
+ auto boolVal = ab::ParseBool(property);
+ if (boolVal == ab::ParseBoolResult::kTrue) {
return {isFsAvailable(), {}};
}
- if (boolVal == android::base::ParseBoolResult::kFalse) {
+ if (boolVal == ab::ParseBoolResult::kFalse) {
return {false, {}};
}
@@ -156,11 +150,34 @@
static const auto kModulePrefix = "module:"sv;
if (property.starts_with(kModulePrefix)) {
const auto modulePath = property.substr(kModulePrefix.size());
- return {::access(android::incfs::details::c_str(modulePath), R_OK | X_OK), modulePath};
+ return {::access(details::c_str(modulePath), R_OK | X_OK), modulePath};
}
return {false, {}};
}
+template <class Callback>
+static IncFsErrorCode forEachFileIn(std::string_view dirPath, Callback cb) {
+ auto dir = path::openDir(details::c_str(dirPath));
+ if (!dir) {
+ return -EINVAL;
+ }
+
+ int res = 0;
+ while (auto entry = (errno = 0, ::readdir(dir.get()))) {
+ if (entry->d_type != DT_REG) {
+ continue;
+ }
+ ++res;
+ if (!cb(entry->d_name)) {
+ break;
+ }
+ }
+ if (errno) {
+ return -errno;
+ }
+ return res;
+}
+
namespace {
class IncFsInit {
@@ -186,9 +203,14 @@
return true;
}
std::call_once(loadedFlag_, [this] {
- const unique_fd fd(
- TEMP_FAILURE_RETRY(::open(android::incfs::details::c_str(moduleName_),
- O_RDONLY | O_NOFOLLOW | O_CLOEXEC)));
+ if (isFsAvailable()) {
+ // Loaded from a different process, I suppose.
+ loaded_ = true;
+ LOG(INFO) << "IncFS is already available, skipped loading";
+ return;
+ }
+ const ab::unique_fd fd(TEMP_FAILURE_RETRY(
+ ::open(details::c_str(moduleName_), O_RDONLY | O_NOFOLLOW | O_CLOEXEC)));
if (fd < 0) {
PLOG(ERROR) << "could not open IncFs kernel module \"" << moduleName_ << '"';
return;
@@ -227,7 +249,39 @@
return init().enabled();
}
-bool isIncFsFd(int fd) {
+static Features readIncFsFeatures() {
+ init().enabledAndReady();
+
+ static const char kSysfsFeaturesDir[] = "/sys/fs/" INCFS_NAME "/features";
+ const auto dir = path::openDir(kSysfsFeaturesDir);
+ if (!dir) {
+ PLOG(ERROR) << "IncFs_Features: failed to open features dir, assuming v1/none.";
+ return Features::none;
+ }
+
+ int res = Features::none;
+ while (auto entry = ::readdir(dir.get())) {
+ if (entry->d_type != DT_REG) {
+ continue;
+ }
+ if (entry->d_name == "corefs"sv) {
+ res |= Features::core;
+ } else if (entry->d_name == "v2"sv || entry->d_name == "report_uid"sv) {
+ res |= Features::v2;
+ }
+ }
+
+ LOG(INFO) << "IncFs_Features: " << ((res & Features::v2) ? "v2" : "v1");
+
+ return Features(res);
+}
+
+IncFsFeatures IncFs_Features() {
+ static const auto features = IncFsFeatures(readIncFsFeatures());
+ return features;
+}
+
+bool isIncFsFdImpl(int fd) {
struct statfs fs = {};
if (::fstatfs(fd, &fs) != 0) {
PLOG(WARNING) << __func__ << "(): could not fstatfs fd " << fd;
@@ -237,7 +291,7 @@
return fs.f_type == (decltype(fs.f_type))INCFS_MAGIC_NUMBER;
}
-bool isIncFsPath(const char* path) {
+bool isIncFsPathImpl(const char* path) {
struct statfs fs = {};
if (::statfs(path, &fs) != 0) {
PLOG(WARNING) << __func__ << "(): could not statfs " << path;
@@ -273,14 +327,14 @@
if (const auto err = isDir(path); err != 0) {
return err;
}
- if (const auto err = android::incfs::path::isEmptyDir(path); err != 0) {
+ if (const auto err = path::isEmptyDir(path); err != 0) {
return err;
}
return 0;
}
static int rmDirContent(const char* path) {
- auto dir = android::incfs::path::openDir(path);
+ auto dir = path::openDir(path);
if (!dir) {
return -EINVAL;
}
@@ -288,7 +342,7 @@
if (entry->d_name == "."sv || entry->d_name == ".."sv) {
continue;
}
- auto fullPath = StringPrintf("%s/%s", path, entry->d_name);
+ auto fullPath = ab::StringPrintf("%s/%s", path, entry->d_name);
if (entry->d_type == DT_DIR) {
if (const auto err = rmDirContent(fullPath.c_str()); err != 0) {
return err;
@@ -306,11 +360,18 @@
}
static std::string makeMountOptionsString(IncFsMountOptions options) {
- return StringPrintf("read_timeout_ms=%u,readahead=0,rlog_pages=%u,rlog_wakeup_cnt=1",
- unsigned(options.defaultReadTimeoutMs),
- unsigned(options.readLogBufferPages < 0
- ? INCFS_DEFAULT_PAGE_READ_BUFFER_PAGES
- : options.readLogBufferPages));
+ auto opts = ab::StringPrintf("read_timeout_ms=%u,readahead=0,rlog_pages=%u,rlog_wakeup_cnt=1,",
+ unsigned(options.defaultReadTimeoutMs),
+ unsigned(options.readLogBufferPages < 0
+ ? INCFS_DEFAULT_PAGE_READ_BUFFER_PAGES
+ : options.readLogBufferPages));
+ if (features() & Features::v2) {
+ ab::StringAppendF(&opts, "report_uid,");
+ if (options.sysfsName && *options.sysfsName) {
+ ab::StringAppendF(&opts, "sysfs_name=%s,", options.sysfsName);
+ }
+ }
+ return opts;
}
static IncFsControl* makeControl(const char* root) {
@@ -318,17 +379,30 @@
if (!cmd.ok()) {
return nullptr;
}
- unique_fd pendingReads(fcntl(cmd.get(), F_DUPFD_CLOEXEC, cmd.get()));
+ ab::unique_fd pendingReads(fcntl(cmd.get(), F_DUPFD_CLOEXEC, cmd.get()));
if (!pendingReads.ok()) {
return nullptr;
}
auto logs = openRaw(root, INCFS_LOG_FILENAME);
- // logs may be absent, that's fine
- auto control = IncFs_CreateControl(cmd.get(), pendingReads.get(), logs.get());
+ if (!logs.ok()) {
+ return nullptr;
+ }
+ ab::unique_fd blocksWritten;
+ if (features() & Features::v2) {
+ blocksWritten = openRaw(root, INCFS_BLOCKS_WRITTEN_FILENAME);
+ if (!blocksWritten.ok()) {
+ return nullptr;
+ }
+ }
+ auto control =
+ IncFs_CreateControl(cmd.get(), pendingReads.get(), logs.get(), blocksWritten.get());
if (control) {
(void)cmd.release();
(void)pendingReads.release();
(void)logs.release();
+ (void)blocksWritten.release();
+ } else {
+ errno = ENOMEM;
}
return control;
}
@@ -339,7 +413,7 @@
return {};
}
// TODO: add "/.cmd/" if we decide to use a separate control tree.
- return android::incfs::path::join(itemRoot, subpath);
+ return path::join(itemRoot, subpath);
}
static void toString(IncFsFileId id, char* out) {
@@ -416,18 +490,24 @@
}
static bool restoreconControlFiles(std::string_view targetDir) {
- const std::string controlFilePaths[] =
- {android::incfs::path::join(targetDir, INCFS_PENDING_READS_FILENAME),
- android::incfs::path::join(targetDir, INCFS_LOG_FILENAME)};
- for (size_t i = 0; i < std::size(controlFilePaths); i++) {
- if (const auto err = selinux_android_restorecon(controlFilePaths[i].c_str(),
- SELINUX_ANDROID_RESTORECON_FORCE);
+ static constexpr auto restorecon = [](const char* name) {
+ if (const auto err = selinux_android_restorecon(name, SELINUX_ANDROID_RESTORECON_FORCE);
err != 0) {
- PLOG(ERROR) << "[incfs] Failed to restorecon: " << controlFilePaths[i]
- << " error code: " << err;
errno = -err;
+ PLOG(ERROR) << "[incfs] Failed to restorecon: " << name;
return false;
}
+ return true;
+ };
+ if (!restorecon(path::join(targetDir, INCFS_PENDING_READS_FILENAME).c_str())) {
+ return false;
+ }
+ if (!restorecon(path::join(targetDir, INCFS_LOG_FILENAME).c_str())) {
+ return false;
+ }
+ if ((features() & Features::v2) &&
+ !restorecon(path::join(targetDir, INCFS_BLOCKS_WRITTEN_FILENAME).c_str())) {
+ return false;
}
return true;
}
@@ -449,8 +529,8 @@
return nullptr;
}
- if (options.flags & android::incfs::createOnly) {
- if (const auto err = android::incfs::path::isEmptyDir(backingPath); err != 0) {
+ if (options.flags & createOnly) {
+ if (const auto err = path::isEmptyDir(backingPath); err != 0) {
errno = -err;
return nullptr;
}
@@ -464,17 +544,18 @@
const auto opts = makeMountOptionsString(options);
if (::mount(backingPath, targetDir, INCFS_NAME, MS_NOSUID | MS_NODEV | MS_NOATIME,
opts.c_str())) {
- PLOG(ERROR) << "[incfs] Failed to mount IncFS filesystem: " << targetDir
- << " errno: " << errno;
+ PLOG(ERROR) << "[incfs] Failed to mount IncFS filesystem: " << targetDir;
return nullptr;
}
if (!restoreconControlFiles(targetDir)) {
+ (void)IncFs_Unmount(targetDir);
return nullptr;
}
auto control = makeControl(targetDir);
if (control == nullptr) {
+ (void)IncFs_Unmount(targetDir);
return nullptr;
}
return control;
@@ -486,7 +567,7 @@
errno = EINVAL;
return nullptr;
}
- return makeControl(android::incfs::details::c_str(root));
+ return makeControl(details::c_str(root));
}
IncFsFd IncFs_GetControlFd(const IncFsControl* control, IncFsFdType type) {
@@ -500,6 +581,8 @@
return control->pendingReads;
case LOGS:
return control->logs;
+ case BLOCKS_WRITTEN:
+ return control->blocksWritten;
default:
return -EINVAL;
}
@@ -515,11 +598,13 @@
out[CMD] = std::exchange(control->cmd, -1);
out[PENDING_READS] = std::exchange(control->pendingReads, -1);
out[LOGS] = std::exchange(control->logs, -1);
+ out[BLOCKS_WRITTEN] = std::exchange(control->blocksWritten, -1);
return IncFsFdType::FDS_COUNT;
}
-IncFsControl* IncFs_CreateControl(IncFsFd cmd, IncFsFd pendingReads, IncFsFd logs) {
- return new IncFsControl(cmd, pendingReads, logs);
+IncFsControl* IncFs_CreateControl(IncFsFd cmd, IncFsFd pendingReads, IncFsFd logs,
+ IncFsFd blocksWritten) {
+ return new IncFsControl(cmd, pendingReads, logs, blocksWritten);
}
void IncFs_DeleteControl(IncFsControl* control) {
@@ -533,6 +618,9 @@
if (control->logs >= 0) {
close(control->logs);
}
+ if (control->blocksWritten >= 0) {
+ close(control->blocksWritten);
+ }
delete control;
}
}
@@ -677,7 +765,7 @@
return -ERANGE;
}
- const auto [subdir, name] = android::incfs::path::splitDirBase(subpath);
+ const auto [subdir, name] = path::splitDirBase(subpath);
incfs_new_file_args args = {
.size = (uint64_t)params.size,
.mode = (uint16_t)mode,
@@ -700,13 +788,55 @@
<< " of " << params.size << " bytes";
return -errno;
}
- if (::chmod(android::incfs::path::join(root, subpath).c_str(), mode)) {
+ if (::chmod(path::join(root, subpath).c_str(), mode)) {
PLOG(WARNING) << "[incfs] couldn't change file mode to 0" << std::oct << mode;
}
return 0;
}
+IncFsErrorCode IncFs_MakeMappedFile(const IncFsControl* control, const char* path, int32_t mode,
+ IncFsNewMappedFileParams params) {
+ if (!control) {
+ return -EINVAL;
+ }
+
+ auto [root, subpath] = registry().rootAndSubpathFor(path);
+ if (root.empty()) {
+ PLOG(WARNING) << "[incfs] makeMappedFile failed for path " << path << ", root is empty.";
+ return -EINVAL;
+ }
+ if (params.size < 0) {
+ LOG(WARNING) << "[incfs] makeMappedFile failed for path " << path
+ << ", size is invalid: " << params.size;
+ return -ERANGE;
+ }
+
+ const auto [subdir, name] = path::splitDirBase(subpath);
+ incfs_create_mapped_file_args args = {
+ .size = (uint64_t)params.size,
+ .mode = (uint16_t)mode,
+ .directory_path = (uint64_t)subdir.data(),
+ .file_name = (uint64_t)name.data(),
+ .source_offset = (uint64_t)params.sourceOffset,
+ };
+ static_assert(sizeof(args.source_file_id.bytes) == sizeof(params.sourceId.data));
+ memcpy(args.source_file_id.bytes, params.sourceId.data, sizeof(args.source_file_id.bytes));
+
+ if (::ioctl(control->cmd, INCFS_IOC_CREATE_MAPPED_FILE, &args)) {
+ PLOG(WARNING) << "[incfs] makeMappedFile failed for " << root << " / " << subdir << " / "
+ << name << " of " << params.size << " bytes starting at "
+ << params.sourceOffset;
+ return -errno;
+ }
+ if (::chmod(path::join(root, subpath).c_str(), mode)) {
+ PLOG(WARNING) << "[incfs] makeMappedFile error: couldn't change file mode to 0" << std::oct
+ << mode;
+ }
+
+ return 0;
+}
+
static IncFsErrorCode makeDir(const char* commandPath, int32_t mode, bool allowExisting) {
if (!::mkdir(commandPath, mode)) {
if (::chmod(commandPath, mode)) {
@@ -720,7 +850,7 @@
static IncFsErrorCode makeDirs(std::string_view commandPath, std::string_view path,
std::string_view root, int32_t mode) {
- auto commandCPath = android::incfs::details::c_str(commandPath);
+ auto commandCPath = details::c_str(commandPath);
const auto mkdirRes = makeDir(commandCPath, mode, true);
if (!mkdirRes) {
return 0;
@@ -730,13 +860,13 @@
return mkdirRes;
}
- const auto parent = android::incfs::path::dirName(commandPath);
- if (!android::incfs::path::startsWith(parent, root)) {
+ const auto parent = path::dirName(commandPath);
+ if (!path::startsWith(parent, root)) {
// went too far, already out of the root mount
return -EINVAL;
}
- if (auto parentMkdirRes = makeDirs(parent, android::incfs::path::dirName(path), root, mode)) {
+ if (auto parentMkdirRes = makeDirs(parent, path::dirName(path), root, mode)) {
return parentMkdirRes;
}
return makeDir(commandCPath, mode, true);
@@ -781,10 +911,10 @@
}
static IncFsErrorCode getMetadata(const char* path, char buffer[], size_t* bufferSize) {
- const auto res = ::getxattr(path, android::incfs::kMetadataAttrName, buffer, *bufferSize);
+ const auto res = ::getxattr(path, kMetadataAttrName, buffer, *bufferSize);
if (res < 0) {
if (errno == ERANGE) {
- auto neededSize = ::getxattr(path, android::incfs::kMetadataAttrName, buffer, 0);
+ auto neededSize = ::getxattr(path, kMetadataAttrName, buffer, 0);
if (neededSize >= 0) {
*bufferSize = neededSize;
return 0;
@@ -806,8 +936,8 @@
if (root.empty()) {
return -EINVAL;
}
- auto name = android::incfs::path::join(root, android::incfs::kIndexDir, toStringImpl(fileId));
- return getMetadata(android::incfs::details::c_str(name), buffer, bufferSize);
+ auto name = indexPath(root, fileId);
+ return getMetadata(details::c_str(name), buffer, bufferSize);
}
IncFsErrorCode IncFs_GetMetadataByPath(const IncFsControl* control, const char* path, char buffer[],
@@ -824,6 +954,16 @@
return getMetadata(path, buffer, bufferSize);
}
+template <class GetterFunc, class Param>
+static IncFsFileId getId(GetterFunc getter, Param param) {
+ char buffer[kIncFsFileIdStringLength];
+ const auto res = getter(param, kIdAttrName, buffer, sizeof(buffer));
+ if (res != sizeof(buffer)) {
+ return kIncFsInvalidFileId;
+ }
+ return toFileIdImpl({buffer, std::size(buffer)});
+}
+
IncFsFileId IncFs_GetId(const IncFsControl* control, const char* path) {
if (!control) {
return kIncFsInvalidFileId;
@@ -834,12 +974,7 @@
errno = EINVAL;
return kIncFsInvalidFileId;
}
- char buffer[kIncFsFileIdStringLength];
- const auto res = ::getxattr(path, android::incfs::kIdAttrName, buffer, sizeof(buffer));
- if (res != sizeof(buffer)) {
- return kIncFsInvalidFileId;
- }
- return toFileIdImpl({buffer, std::size(buffer)});
+ return getId(::getxattr, path);
}
static IncFsErrorCode getSignature(int fd, char buffer[], size_t* bufferSize) {
@@ -869,7 +1004,7 @@
if (root.empty()) {
return -EINVAL;
}
- auto file = android::incfs::path::join(root, android::incfs::kIndexDir, toStringImpl(fileId));
+ auto file = indexPath(root, fileId);
auto fd = openRaw(file);
if (fd < 0) {
return fd.get();
@@ -950,16 +1085,17 @@
return 0;
}
-static int waitForReads(int fd, int32_t timeoutMs, incfs_pending_read_info pendingReadsBuffer[],
- size_t* pendingReadsBufferSize) {
- auto hrTimeout = std::chrono::steady_clock::duration(std::chrono::milliseconds(timeoutMs));
+template <class RawPendingRead>
+static int waitForReadsImpl(int fd, int32_t timeoutMs, RawPendingRead pendingReadsBuffer[],
+ size_t* pendingReadsBufferSize) {
+ using namespace std::chrono;
+ auto hrTimeout = steady_clock::duration(milliseconds(timeoutMs));
while (hrTimeout > hrTimeout.zero() || (!pendingReadsBuffer && hrTimeout == hrTimeout.zero())) {
- const auto startTs = std::chrono::steady_clock::now();
+ const auto startTs = steady_clock::now();
pollfd pfd = {fd, POLLIN, 0};
- const auto res =
- ::poll(&pfd, 1, duration_cast<std::chrono::milliseconds>(hrTimeout).count());
+ const auto res = ::poll(&pfd, 1, duration_cast<milliseconds>(hrTimeout).count());
if (res > 0) {
break;
}
@@ -974,7 +1110,7 @@
PLOG(ERROR) << "poll() failed";
return -error;
}
- hrTimeout -= std::chrono::steady_clock::now() - startTs;
+ hrTimeout -= steady_clock::now() - startTs;
}
if (!pendingReadsBuffer) {
return hrTimeout < hrTimeout.zero() ? -ETIMEDOUT : 0;
@@ -999,57 +1135,85 @@
return 0;
}
+template <class PublicPendingRead, class RawPendingRead>
+PublicPendingRead convertRead(RawPendingRead rawRead) {
+ PublicPendingRead res = {
+ .bootClockTsUs = rawRead.timestamp_us,
+ .block = (IncFsBlockIndex)rawRead.block_index,
+ .serialNo = rawRead.serial_number,
+ };
+ memcpy(&res.id.data, rawRead.file_id.bytes, sizeof(res.id.data));
+
+ if constexpr (std::is_same_v<PublicPendingRead, IncFsReadInfoWithUid>) {
+ if constexpr (std::is_same_v<RawPendingRead, incfs_pending_read_info2>) {
+ res.uid = rawRead.uid;
+ } else {
+ res.uid = kIncFsNoUid;
+ }
+ }
+ return res;
+}
+
+template <class RawPendingRead, class PublicPendingRead>
+static int waitForReads(IncFsFd readFd, int32_t timeoutMs, PublicPendingRead buffer[],
+ size_t* bufferSize) {
+ std::vector<RawPendingRead> pendingReads(*bufferSize);
+ if (const auto res = waitForReadsImpl(readFd, timeoutMs, pendingReads.data(), bufferSize)) {
+ return res;
+ }
+ for (size_t i = 0; i != *bufferSize; ++i) {
+ buffer[i] = convertRead<PublicPendingRead>(pendingReads[i]);
+ }
+ return 0;
+}
+
+template <class PublicPendingRead>
+static int waitForReads(IncFsFd readFd, int32_t timeoutMs, PublicPendingRead buffer[],
+ size_t* bufferSize) {
+ if (features() & Features::v2) {
+ return waitForReads<incfs_pending_read_info2>(readFd, timeoutMs, buffer, bufferSize);
+ }
+ return waitForReads<incfs_pending_read_info>(readFd, timeoutMs, buffer, bufferSize);
+}
+
IncFsErrorCode IncFs_WaitForPendingReads(const IncFsControl* control, int32_t timeoutMs,
IncFsReadInfo buffer[], size_t* bufferSize) {
if (!control || control->pendingReads < 0) {
return -EINVAL;
}
- std::vector<incfs_pending_read_info> pendingReads;
- pendingReads.resize(*bufferSize);
- if (const auto res =
- waitForReads(control->pendingReads, timeoutMs, pendingReads.data(), bufferSize)) {
- return res;
+ return waitForReads(control->pendingReads, timeoutMs, buffer, bufferSize);
+}
+
+IncFsErrorCode IncFs_WaitForPendingReadsWithUid(const IncFsControl* control, int32_t timeoutMs,
+ IncFsReadInfoWithUid buffer[], size_t* bufferSize) {
+ if (!control || control->pendingReads < 0) {
+ return -EINVAL;
}
- for (size_t i = 0; i != *bufferSize; ++i) {
- buffer[i] = IncFsReadInfo{
- .bootClockTsUs = pendingReads[i].timestamp_us,
- .block = (IncFsBlockIndex)pendingReads[i].block_index,
- .serialNo = pendingReads[i].serial_number,
- };
- memcpy(&buffer[i].id.data, pendingReads[i].file_id.bytes, sizeof(buffer[i].id.data));
- }
- return 0;
+
+ return waitForReads(control->pendingReads, timeoutMs, buffer, bufferSize);
}
IncFsErrorCode IncFs_WaitForPageReads(const IncFsControl* control, int32_t timeoutMs,
IncFsReadInfo buffer[], size_t* bufferSize) {
- if (!control) {
+ if (!control || control->logs < 0) {
return -EINVAL;
}
- auto logsFd = control->logs;
- if (logsFd < 0) {
+ return waitForReads(control->logs, timeoutMs, buffer, bufferSize);
+}
+
+IncFsErrorCode IncFs_WaitForPageReadsWithUid(const IncFsControl* control, int32_t timeoutMs,
+ IncFsReadInfoWithUid buffer[], size_t* bufferSize) {
+ if (!control || control->logs < 0) {
return -EINVAL;
}
- std::vector<incfs_pending_read_info> pendingReads;
- pendingReads.resize(*bufferSize);
- if (const auto res = waitForReads(logsFd, timeoutMs, pendingReads.data(), bufferSize)) {
- return res;
- }
- for (size_t i = 0; i != *bufferSize; ++i) {
- buffer[i] = IncFsReadInfo{
- .bootClockTsUs = pendingReads[i].timestamp_us,
- .block = (IncFsBlockIndex)pendingReads[i].block_index,
- .serialNo = pendingReads[i].serial_number,
- };
- memcpy(&buffer[i].id.data, pendingReads[i].file_id.bytes, sizeof(buffer[i].id.data));
- }
- return 0;
+
+ return waitForReads(control->logs, timeoutMs, buffer, bufferSize);
}
static IncFsFd openForSpecialOps(int cmd, const char* path) {
- unique_fd fd(::open(path, O_RDONLY | O_CLOEXEC));
+ ab::unique_fd fd(::open(path, O_RDONLY | O_CLOEXEC));
if (fd < 0) {
return -errno;
}
@@ -1085,7 +1249,7 @@
if (root.empty()) {
return -EINVAL;
}
- auto name = android::incfs::path::join(root, android::incfs::kIndexDir, toStringImpl(id));
+ auto name = indexPath(root, id);
return openForSpecialOps(cmd, makeCommandPath(root, name).c_str());
}
@@ -1156,7 +1320,7 @@
}
IncFsErrorCode IncFs_BindMount(const char* sourceDir, const char* targetDir) {
- if (!android::incfs::enabled()) {
+ if (!enabled()) {
return -ENOTSUP;
}
@@ -1182,7 +1346,7 @@
}
IncFsErrorCode IncFs_Unmount(const char* dir) {
- if (!android::incfs::enabled()) {
+ if (!enabled()) {
return -ENOTSUP;
}
@@ -1201,11 +1365,11 @@
}
bool IncFs_IsIncFsFd(int fd) {
- return isIncFsFd(fd);
+ return isIncFsFdImpl(fd);
}
bool IncFs_IsIncFsPath(const char* path) {
- return isIncFsPath(path);
+ return isIncFsPathImpl(path);
}
IncFsErrorCode IncFs_GetFilledRanges(int fd, IncFsSpan outBuffer, IncFsFilledRanges* filledRanges) {
@@ -1314,7 +1478,17 @@
return -error;
}
-IncFsErrorCode IncFs_IsFullyLoaded(int fd) {
+static IncFsErrorCode isFullyLoadedV2(std::string_view root, IncFsFileId id) {
+ if (::access(path::join(root, INCFS_INCOMPLETE_NAME, toStringImpl(id)).c_str(), F_OK)) {
+ if (errno == ENOENT) {
+ return 0; // no such incomplete file -> it's fully loaded.
+ }
+ return -errno;
+ }
+ return -ENODATA;
+}
+
+static IncFsErrorCode isFullyLoadedSlow(int fd) {
char buffer[2 * sizeof(IncFsBlockRange)];
IncFsFilledRanges ranges;
auto res = IncFs_GetFilledRanges(fd, IncFsSpan{.data = buffer, .size = std::size(buffer)},
@@ -1352,6 +1526,554 @@
return -ENODATA;
}
-android::incfs::MountRegistry& android::incfs::defaultMountRegistry() {
+IncFsErrorCode IncFs_IsFullyLoaded(int fd) {
+ if (features() & Features::v2) {
+ const auto fdPath = path::fromFd(fd);
+ if (fdPath.empty()) {
+ return errno ? -errno : -EINVAL;
+ }
+ const auto id = getId(::fgetxattr, fd);
+ if (id == kIncFsInvalidFileId) {
+ return -errno;
+ }
+ return isFullyLoadedV2(registry().rootFor(fdPath), id);
+ }
+ return isFullyLoadedSlow(fd);
+}
+IncFsErrorCode IncFs_IsFullyLoadedByPath(const IncFsControl* control, const char* path) {
+ if (!control || !path) {
+ return -EINVAL;
+ }
+ const auto root = rootForCmd(control->cmd);
+ if (root.empty()) {
+ return -EINVAL;
+ }
+ const auto pathRoot = registry().rootFor(path);
+ if (pathRoot != root) {
+ return -EINVAL;
+ }
+ if (features() & Features::v2) {
+ const auto id = getId(::getxattr, path);
+ if (id == kIncFsInvalidFileId) {
+ return -errno;
+ }
+ return isFullyLoadedV2(root, id);
+ }
+ return isFullyLoadedSlow(openForSpecialOps(control->cmd, makeCommandPath(root, path).c_str()));
+}
+IncFsErrorCode IncFs_IsFullyLoadedById(const IncFsControl* control, IncFsFileId fileId) {
+ if (!control) {
+ return -EINVAL;
+ }
+ const auto root = rootForCmd(control->cmd);
+ if (root.empty()) {
+ return -EINVAL;
+ }
+ if (features() & Features::v2) {
+ return isFullyLoadedV2(root, fileId);
+ }
+ return isFullyLoadedSlow(
+ openForSpecialOps(control->cmd,
+ makeCommandPath(root, indexPath(root, fileId)).c_str()));
+}
+
+static IncFsErrorCode isEverythingLoadedV2(const IncFsControl* control) {
+ const auto root = rootForCmd(control->cmd);
+ if (root.empty()) {
+ return -EINVAL;
+ }
+ auto res = forEachFileIn(path::join(root, INCFS_INCOMPLETE_NAME), [](auto) { return false; });
+ return res < 0 ? res : res > 0 ? -ENODATA : 0;
+}
+
+static IncFsErrorCode isEverythingLoadedSlow(const IncFsControl* control) {
+ const auto root = rootForCmd(control->cmd);
+ if (root.empty()) {
+ return -EINVAL;
+ }
+ // No special API for this version of the driver, need to recurse and check each file
+ // separately. Can at least speed it up by iterating over the .index/ dir and not dealing with
+ // the directory tree.
+ const auto indexPath = path::join(root, INCFS_INDEX_NAME);
+ const auto dir = path::openDir(indexPath.c_str());
+ if (!dir) {
+ return -EINVAL;
+ }
+ while (const auto entry = ::readdir(dir.get())) {
+ if (entry->d_type != DT_REG) {
+ continue;
+ }
+ const auto name = path::join(indexPath, entry->d_name);
+ auto fd =
+ ab::unique_fd(openForSpecialOps(control->cmd, makeCommandPath(root, name).c_str()));
+ if (fd.get() < 0) {
+ PLOG(WARNING) << __func__ << "(): can't open " << entry->d_name << " for special ops";
+ return fd.release();
+ }
+ const auto checkFullyLoaded = IncFs_IsFullyLoaded(fd.get());
+ if (checkFullyLoaded == 0 || checkFullyLoaded == -EOPNOTSUPP ||
+ checkFullyLoaded == -ENOTSUP || checkFullyLoaded == -ENOENT) {
+ // special kinds of files may return an error here, but it still means
+ // _this_ file is OK - you simply need to check the rest. E.g. can't query
+ // a mapped file, instead need to check its parent.
+ continue;
+ }
+ return checkFullyLoaded;
+ }
+ return 0;
+}
+
+IncFsErrorCode IncFs_IsEverythingFullyLoaded(const IncFsControl* control) {
+ if (!control) {
+ return -EINVAL;
+ }
+ if (features() & Features::v2) {
+ return isEverythingLoadedV2(control);
+ }
+ return isEverythingLoadedSlow(control);
+}
+
+IncFsErrorCode IncFs_SetUidReadTimeouts(const IncFsControl* control,
+ const IncFsUidReadTimeouts timeouts[], size_t count) {
+ if (!control) {
+ return -EINVAL;
+ }
+ if (!(features() & Features::v2)) {
+ return -ENOTSUP;
+ }
+
+ std::vector<incfs_per_uid_read_timeouts> argTimeouts(count);
+ for (size_t i = 0; i != count; ++i) {
+ argTimeouts[i] = incfs_per_uid_read_timeouts{
+ .uid = (uint32_t)timeouts[i].uid,
+ .min_time_us = timeouts[i].minTimeUs,
+ .min_pending_time_us = timeouts[i].minPendingTimeUs,
+ .max_pending_time_us = timeouts[i].maxPendingTimeUs,
+ };
+ }
+ incfs_set_read_timeouts_args args = {.timeouts_array = (uint64_t)(uintptr_t)argTimeouts.data(),
+ .timeouts_array_size = uint32_t(
+ argTimeouts.size() * sizeof(*argTimeouts.data()))};
+ if (::ioctl(control->cmd, INCFS_IOC_SET_READ_TIMEOUTS, &args)) {
+ PLOG(WARNING) << "[incfs] setUidReadTimeouts failed";
+ return -errno;
+ }
+ return 0;
+}
+
+IncFsErrorCode IncFs_GetUidReadTimeouts(const IncFsControl* control,
+ IncFsUidReadTimeouts timeouts[], size_t* bufferSize) {
+ if (!control || !bufferSize) {
+ return -EINVAL;
+ }
+ if (!(features() & Features::v2)) {
+ return -ENOTSUP;
+ }
+
+ std::vector<incfs_per_uid_read_timeouts> argTimeouts(*bufferSize);
+ incfs_get_read_timeouts_args args = {.timeouts_array = (uint64_t)(uintptr_t)argTimeouts.data(),
+ .timeouts_array_size = uint32_t(
+ argTimeouts.size() * sizeof(*argTimeouts.data())),
+ .timeouts_array_size_out = args.timeouts_array_size};
+ if (::ioctl(control->cmd, INCFS_IOC_GET_READ_TIMEOUTS, &args)) {
+ if (errno == E2BIG) {
+ *bufferSize = args.timeouts_array_size_out / sizeof(*argTimeouts.data());
+ }
+ return -errno;
+ }
+
+ *bufferSize = args.timeouts_array_size_out / sizeof(*argTimeouts.data());
+ for (size_t i = 0; i != *bufferSize; ++i) {
+ timeouts[i].uid = argTimeouts[i].uid;
+ timeouts[i].minTimeUs = argTimeouts[i].min_time_us;
+ timeouts[i].minPendingTimeUs = argTimeouts[i].min_pending_time_us;
+ timeouts[i].maxPendingTimeUs = argTimeouts[i].max_pending_time_us;
+ }
+ return 0;
+}
+
+static IncFsErrorCode getFileBlockCount(int fd, IncFsBlockCounts* blockCount) {
+ incfs_get_block_count_args args = {};
+ auto res = ::ioctl(fd, INCFS_IOC_GET_BLOCK_COUNT, &args);
+ if (res < 0) {
+ return -errno;
+ }
+ *blockCount = IncFsBlockCounts{
+ .totalDataBlocks = args.total_data_blocks_out,
+ .filledDataBlocks = args.filled_data_blocks_out,
+ .totalHashBlocks = args.total_hash_blocks_out,
+ .filledHashBlocks = args.filled_hash_blocks_out,
+ };
+ return 0;
+}
+
+IncFsErrorCode IncFs_GetFileBlockCountById(const IncFsControl* control, IncFsFileId id,
+ IncFsBlockCounts* blockCount) {
+ if (!control) {
+ return -EINVAL;
+ }
+ if (!(features() & Features::v2)) {
+ return -ENOTSUP;
+ }
+ const auto root = rootForCmd(control->cmd);
+ if (root.empty()) {
+ return -EINVAL;
+ }
+ auto name = indexPath(root, id);
+ auto fd = openRaw(name);
+ if (fd < 0) {
+ return fd.get();
+ }
+ return getFileBlockCount(fd, blockCount);
+}
+
+IncFsErrorCode IncFs_GetFileBlockCountByPath(const IncFsControl* control, const char* path,
+ IncFsBlockCounts* blockCount) {
+ if (!control) {
+ return -EINVAL;
+ }
+ if (!(features() & Features::v2)) {
+ return -ENOTSUP;
+ }
+ const auto pathRoot = registry().rootFor(path);
+ const auto root = rootForCmd(control->cmd);
+ if (root.empty() || root != pathRoot) {
+ return -EINVAL;
+ }
+ auto fd = openRaw(path);
+ if (fd < 0) {
+ return fd.get();
+ }
+ return getFileBlockCount(fd, blockCount);
+}
+
+IncFsErrorCode IncFs_ListIncompleteFiles(const IncFsControl* control, IncFsFileId ids[],
+ size_t* bufferSize) {
+ if (!control || !bufferSize) {
+ return -EINVAL;
+ }
+ if (!(features() & Features::v2)) {
+ return -ENOTSUP;
+ }
+ const auto root = rootForCmd(control->cmd);
+ if (root.empty()) {
+ return -EINVAL;
+ }
+ size_t index = 0;
+ int error = 0;
+ const auto res = forEachFileIn(path::join(root, INCFS_INCOMPLETE_NAME), [&](const char* name) {
+ if (index >= *bufferSize) {
+ error = -E2BIG;
+ } else {
+ ids[index] = IncFs_FileIdFromString(name);
+ }
+ ++index;
+ return true;
+ });
+ if (res < 0) {
+ return res;
+ }
+ *bufferSize = index;
+ return error ? error : 0;
+}
+
+IncFsErrorCode IncFs_ForEachFile(const IncFsControl* control, void* context, FileCallback cb) {
+ if (!control || !cb) {
+ return -EINVAL;
+ }
+ const auto root = rootForCmd(control->cmd);
+ if (root.empty()) {
+ return -EINVAL;
+ }
+ return forEachFileIn(path::join(root, INCFS_INDEX_NAME), [&](const char* name) {
+ return cb(context, control, IncFs_FileIdFromString(name));
+ });
+}
+
+IncFsErrorCode IncFs_ForEachIncompleteFile(const IncFsControl* control, void* context,
+ FileCallback cb) {
+ if (!control || !cb) {
+ return -EINVAL;
+ }
+ if (!(features() & Features::v2)) {
+ return -ENOTSUP;
+ }
+ const auto root = rootForCmd(control->cmd);
+ if (root.empty()) {
+ return -EINVAL;
+ }
+ return forEachFileIn(path::join(root, INCFS_INCOMPLETE_NAME), [&](const char* name) {
+ return cb(context, control, IncFs_FileIdFromString(name));
+ });
+}
+
+IncFsErrorCode IncFs_WaitForLoadingComplete(const IncFsControl* control, int32_t timeoutMs) {
+ if (!control) {
+ return -EINVAL;
+ }
+ if (!(features() & Features::v2)) {
+ return -ENOTSUP;
+ }
+
+ using namespace std::chrono;
+ auto hrTimeout = steady_clock::duration(milliseconds(timeoutMs));
+
+ const auto root = rootForCmd(control->cmd);
+ if (root.empty()) {
+ return -EINVAL;
+ }
+
+ ab::unique_fd fd(inotify_init1(IN_NONBLOCK | IN_CLOEXEC));
+ if (!fd.ok()) {
+ return -EFAULT;
+ }
+
+ // first create all the watches, and only then list existing files to prevent races
+ auto dirPath = path::join(root, INCFS_INCOMPLETE_NAME);
+ int watchFd = inotify_add_watch(fd.get(), dirPath.c_str(), IN_DELETE);
+ if (watchFd < 0) {
+ return -errno;
+ }
+
+ size_t count = 0;
+ auto res = IncFs_ListIncompleteFiles(control, nullptr, &count);
+ if (!res) {
+ return 0;
+ }
+ if (res != -E2BIG) {
+ return res;
+ }
+
+ while (hrTimeout > hrTimeout.zero()) {
+ const auto startTs = steady_clock::now();
+
+ pollfd pfd = {fd.get(), POLLIN, 0};
+ const auto res = ::poll(&pfd, 1, duration_cast<milliseconds>(hrTimeout).count());
+ if (res == 0) {
+ return -ETIMEDOUT;
+ }
+ if (res < 0) {
+ const auto error = errno;
+ if (error != EINTR) {
+ PLOG(ERROR) << "poll() failed";
+ return -error;
+ }
+ } else {
+ // empty the inotify fd first to not miss any new deletions,
+ // then check if the directory is empty.
+ char buffer[sizeof(inotify_event) + NAME_MAX + 1];
+ for (;;) {
+ auto err = TEMP_FAILURE_RETRY(::read(fd.get(), buffer, sizeof(buffer)));
+ if (err < 0) {
+ if (errno == EAGAIN) { // no new events
+ break;
+ }
+ return -errno;
+ }
+ }
+
+ size_t count = 0;
+ auto res = IncFs_ListIncompleteFiles(control, nullptr, &count);
+ if (!res) {
+ return 0;
+ }
+ if (res != -E2BIG) {
+ return res;
+ }
+ }
+ hrTimeout -= steady_clock::now() - startTs;
+ }
+
+ return -ETIMEDOUT;
+}
+
+IncFsErrorCode IncFs_WaitForFsWrittenBlocksChange(const IncFsControl* control, int32_t timeoutMs,
+ IncFsSize* count) {
+ if (!control || !count) {
+ return -EINVAL;
+ }
+ if (!(features() & Features::v2)) {
+ return -ENOTSUP;
+ }
+
+ using namespace std::chrono;
+ auto hrTimeout = steady_clock::duration(milliseconds(timeoutMs));
+
+ while (hrTimeout > hrTimeout.zero()) {
+ const auto startTs = steady_clock::now();
+
+ pollfd pfd = {control->blocksWritten, POLLIN, 0};
+ const auto res = ::poll(&pfd, 1, duration_cast<milliseconds>(hrTimeout).count());
+ if (res > 0) {
+ break;
+ }
+ if (res == 0) {
+ return -ETIMEDOUT;
+ }
+ const auto error = errno;
+ if (error != EINTR) {
+ PLOG(ERROR) << "poll() failed";
+ return -error;
+ }
+ hrTimeout -= steady_clock::now() - startTs;
+ }
+
+ char str[32];
+ auto size = ::read(control->blocksWritten, str, sizeof(str));
+ if (size < 0) {
+ const auto error = errno;
+ PLOG(ERROR) << "read() failed";
+ return -error;
+ }
+ const auto res = std::from_chars(str, str + size, *count);
+ if (res.ec != std::errc{}) {
+ return res.ec == std::errc::invalid_argument ? -EINVAL : -ERANGE;
+ }
+
+ return 0;
+}
+
+static IncFsErrorCode reserveSpace(const char* backingPath, IncFsSize size) {
+ auto fd = ab::unique_fd(::open(backingPath, O_WRONLY | O_CLOEXEC));
+ if (fd < 0) {
+ return -errno;
+ }
+ struct stat st = {};
+ if (::fstat(fd.get(), &st)) {
+ return -errno;
+ }
+ if (size == kIncFsTrimReservedSpace) {
+ if (::ftruncate(fd.get(), st.st_size)) {
+ return -errno;
+ }
+ } else {
+ // Add 1.5% of the size for the hash tree and the blockmap, and some more blocks
+ // for fixed overhead.
+ // hash tree is ~33 bytes / page, and blockmap is 10 bytes / page
+ // no need to round to a page size as filesystems already do that.
+ const auto backingSize = IncFsSize(size * 1.015) + INCFS_DATA_FILE_BLOCK_SIZE * 4;
+ if (backingSize < st.st_size) {
+ return -EPERM;
+ }
+ if (::fallocate(fd.get(), FALLOC_FL_KEEP_SIZE, 0, backingSize)) {
+ return -errno;
+ }
+ }
+ return 0;
+}
+
+IncFsErrorCode IncFs_ReserveSpaceByPath(const IncFsControl* control, const char* path,
+ IncFsSize size) {
+ if (!control || (size != kIncFsTrimReservedSpace && size < 0)) {
+ return -EINVAL;
+ }
+ const auto [pathRoot, backingRoot, subpath] = registry().detailsFor(path);
+ const auto root = rootForCmd(control->cmd);
+ if (root.empty() || root != pathRoot) {
+ return -EINVAL;
+ }
+ return reserveSpace(path::join(backingRoot, subpath).c_str(), size);
+}
+
+IncFsErrorCode IncFs_ReserveSpaceById(const IncFsControl* control, IncFsFileId id, IncFsSize size) {
+ if (!control || (size != kIncFsTrimReservedSpace && size < 0)) {
+ return -EINVAL;
+ }
+ const auto root = rootForCmd(control->cmd);
+ if (root.empty()) {
+ return -EINVAL;
+ }
+ auto path = indexPath(root, id);
+ const auto [pathRoot, backingRoot, subpath] = registry().detailsFor(path);
+ if (root != pathRoot) {
+ return -EINVAL;
+ }
+ return reserveSpace(path::join(backingRoot, subpath).c_str(), size);
+}
+
+template <class IntType>
+static int readIntFromFile(std::string_view rootDir, std::string_view subPath, IntType& result) {
+ std::string content;
+ if (!ab::ReadFileToString(path::join(rootDir, subPath), &content)) {
+ PLOG(ERROR) << "IncFs_GetMetrics: failed to read file: " << rootDir << "/" << subPath;
+ return -errno;
+ }
+ const auto res = std::from_chars(content.data(), content.data() + content.size(), result);
+ if (res.ec != std::errc()) {
+ return -static_cast<int>(res.ec);
+ }
+ return 0;
+}
+
+IncFsErrorCode IncFs_GetMetrics(const char* sysfsName, IncFsMetrics* metrics) {
+ if (!sysfsName || !*sysfsName) {
+ return -EINVAL;
+ }
+
+ const auto kSysfsMetricsDir =
+ ab::StringPrintf("/sys/fs/%s/instances/%s", INCFS_NAME, sysfsName);
+
+ int err;
+ if (err = readIntFromFile(kSysfsMetricsDir, "reads_delayed_min", metrics->readsDelayedMin);
+ err != 0) {
+ return err;
+ }
+ if (err = readIntFromFile(kSysfsMetricsDir, "reads_delayed_min_us", metrics->readsDelayedMinUs);
+ err != 0) {
+ return err;
+ }
+ if (err = readIntFromFile(kSysfsMetricsDir, "reads_delayed_pending",
+ metrics->readsDelayedPending);
+ err != 0) {
+ return err;
+ }
+ if (err = readIntFromFile(kSysfsMetricsDir, "reads_delayed_pending_us",
+ metrics->readsDelayedPendingUs);
+ err != 0) {
+ return err;
+ }
+ if (err = readIntFromFile(kSysfsMetricsDir, "reads_failed_hash_verification",
+ metrics->readsFailedHashVerification);
+ err != 0) {
+ return err;
+ }
+ if (err = readIntFromFile(kSysfsMetricsDir, "reads_failed_other", metrics->readsFailedOther);
+ err != 0) {
+ return err;
+ }
+ if (err = readIntFromFile(kSysfsMetricsDir, "reads_failed_timed_out",
+ metrics->readsFailedTimedOut);
+ err != 0) {
+ return err;
+ }
+ return 0;
+}
+
+IncFsErrorCode IncFs_GetLastReadError(const IncFsControl* control,
+ IncFsLastReadError* lastReadError) {
+ if (!control) {
+ return -EINVAL;
+ }
+ if (!(features() & Features::v2)) {
+ return -ENOTSUP;
+ }
+ incfs_get_last_read_error_args args = {};
+ auto res = ::ioctl(control->cmd, INCFS_IOC_GET_LAST_READ_ERROR, &args);
+ if (res < 0) {
+ PLOG(ERROR) << "[incfs] IncFs_GetLastReadError failed.";
+ return -errno;
+ }
+ *lastReadError = IncFsLastReadError{
+ .timestampUs = args.time_us_out,
+ .block = static_cast<IncFsBlockIndex>(args.page_out),
+ .errorNo = args.errno_out,
+ .uid = static_cast<IncFsUid>(args.uid_out),
+ };
+ static_assert(sizeof(args.file_id_out.bytes) == sizeof(lastReadError->id.data));
+ memcpy(lastReadError->id.data, args.file_id_out.bytes, sizeof(args.file_id_out.bytes));
+ return 0;
+}
+
+MountRegistry& android::incfs::defaultMountRegistry() {
return registry();
}
diff --git a/incfs/include/MountRegistry.h b/incfs/include/MountRegistry.h
index f8cfe4d..1ff320f 100644
--- a/incfs/include/MountRegistry.h
+++ b/incfs/include/MountRegistry.h
@@ -35,8 +35,12 @@
class MountRegistry final {
public:
+ struct Bind {
+ std::string subdir;
+ int rootIndex;
+ };
// std::less<> enables heterogeneous lookups, e.g. by a string_view
- using BindMap = std::map<std::string, std::pair<std::string, int>, std::less<>>;
+ using BindMap = std::map<std::string, Bind, std::less<>>;
class Mounts final {
struct Root {
@@ -80,7 +84,7 @@
bool empty() const { return roots.empty(); }
std::string_view rootFor(std::string_view path) const;
- std::pair<std::string_view, std::string> rootAndSubpathFor(std::string_view path) const;
+ std::pair<const Root*, std::string> rootAndSubpathFor(std::string_view path) const;
void swap(Mounts& other);
void clear();
@@ -88,7 +92,6 @@
void addRoot(std::string_view root, std::string_view backingDir);
void removeRoot(std::string_view root);
void addBind(std::string_view what, std::string_view where);
- void moveBind(std::string_view src, std::string_view dest);
void removeBind(std::string_view what);
private:
@@ -103,6 +106,14 @@
std::string rootFor(std::string_view path);
std::pair<std::string, std::string> rootAndSubpathFor(std::string_view path);
+
+ struct Details {
+ std::string root;
+ std::string backing;
+ std::string subpath;
+ };
+ Details detailsFor(std::string_view path);
+
Mounts copyMounts();
void reload();
diff --git a/incfs/include/incfs.h b/incfs/include/incfs.h
index 1ecefaa..76619c2 100644
--- a/incfs/include/incfs.h
+++ b/incfs/include/incfs.h
@@ -20,6 +20,7 @@
#include <array>
#include <chrono>
#include <functional>
+#include <optional>
#include <string>
#include <string_view>
#include <utility>
@@ -39,6 +40,7 @@
enum Features {
none = INCFS_FEATURE_NONE,
core = INCFS_FEATURE_CORE,
+ v2 = INCFS_FEATURE_V2,
};
enum class HashAlgorithm {
@@ -49,6 +51,7 @@
enum class CompressionKind {
none = INCFS_COMPRESSION_KIND_NONE,
lz4 = INCFS_COMPRESSION_KIND_LZ4,
+ zstd = INCFS_COMPRESSION_KIND_ZSTD,
};
enum class BlockKind {
@@ -97,9 +100,12 @@
IncFsFd cmd() const;
IncFsFd pendingReads() const;
IncFsFd logs() const;
- operator IncFsControl*() const { return mControl; };
+ IncFsFd blocksWritten() const;
+
void close();
+ operator IncFsControl*() const { return mControl; }
+
using Fds = std::array<UniqueFd, IncFsFdType::FDS_COUNT>;
[[nodiscard]] Fds releaseFds();
@@ -175,16 +181,24 @@
using BlockIndex = IncFsBlockIndex;
using ErrorCode = IncFsErrorCode;
using Fd = IncFsFd;
+using Uid = IncFsUid;
using ReadInfo = IncFsReadInfo;
+using ReadInfoWithUid = IncFsReadInfoWithUid;
using RawMetadata = ByteBuffer;
using RawSignature = ByteBuffer;
using MountOptions = IncFsMountOptions;
using DataBlock = IncFsDataBlock;
using NewFileParams = IncFsNewFileParams;
+using NewMappedFileParams = IncFsNewMappedFileParams;
+using BlockCounts = IncFsBlockCounts;
+using UidReadTimeouts = IncFsUidReadTimeouts;
+using Metrics = IncFsMetrics;
+using LastReadError = IncFsLastReadError;
constexpr auto kDefaultReadTimeout = std::chrono::milliseconds(INCFS_DEFAULT_READ_TIMEOUT_MS);
constexpr int kBlockSize = INCFS_DATA_FILE_BLOCK_SIZE;
const auto kInvalidFileId = kIncFsInvalidFileId;
+const auto kNoUid = kIncFsNoUid;
bool enabled();
Features features();
@@ -197,7 +211,7 @@
UniqueControl mount(std::string_view backingPath, std::string_view targetDir,
IncFsMountOptions options);
UniqueControl open(std::string_view dir);
-UniqueControl createControl(IncFsFd cmd, IncFsFd pendingReads, IncFsFd logs);
+UniqueControl createControl(IncFsFd cmd, IncFsFd pendingReads, IncFsFd logs, IncFsFd blocksWritten);
ErrorCode setOptions(const Control& control, MountOptions newOptions);
@@ -208,6 +222,8 @@
ErrorCode makeFile(const Control& control, std::string_view path, int mode, FileId fileId,
NewFileParams params);
+ErrorCode makeMappedFile(const Control& control, std::string_view path, int mode,
+ NewMappedFileParams params);
ErrorCode makeDir(const Control& control, std::string_view path, int mode = 0555);
ErrorCode makeDirs(const Control& control, std::string_view path, int mode = 0555);
@@ -227,6 +243,10 @@
std::vector<ReadInfo>* pendingReadsBuffer);
WaitResult waitForPageReads(const Control& control, std::chrono::milliseconds timeout,
std::vector<ReadInfo>* pageReadsBuffer);
+WaitResult waitForPendingReads(const Control& control, std::chrono::milliseconds timeout,
+ std::vector<ReadInfoWithUid>* pendingReadsBuffer);
+WaitResult waitForPageReads(const Control& control, std::chrono::milliseconds timeout,
+ std::vector<ReadInfoWithUid>* pageReadsBuffer);
UniqueFd openForSpecialOps(const Control& control, FileId fileId);
UniqueFd openForSpecialOps(const Control& control, std::string_view path);
@@ -236,8 +256,33 @@
std::pair<ErrorCode, FilledRanges> getFilledRanges(int fd, FilledRanges::RangeBuffer&& buffer);
std::pair<ErrorCode, FilledRanges> getFilledRanges(int fd, FilledRanges&& resumeFrom);
+ErrorCode setUidReadTimeouts(const Control& control, Span<const UidReadTimeouts> timeouts);
+std::optional<std::vector<UidReadTimeouts>> getUidReadTimeouts(const Control& control);
+
+std::optional<BlockCounts> getBlockCount(const Control& control, FileId fileId);
+std::optional<BlockCounts> getBlockCount(const Control& control, std::string_view path);
+
+std::optional<std::vector<FileId>> listIncompleteFiles(const Control& control);
+
+template <class Callback>
+ErrorCode forEachFile(const Control& control, Callback&& cb);
+template <class Callback>
+ErrorCode forEachIncompleteFile(const Control& control, Callback&& cb);
+
+WaitResult waitForLoadingComplete(const Control& control, std::chrono::milliseconds timeout);
+
enum class LoadingState { Full, MissingBlocks };
LoadingState isFullyLoaded(int fd);
+LoadingState isFullyLoaded(const Control& control, std::string_view path);
+LoadingState isFullyLoaded(const Control& control, FileId fileId);
+LoadingState isEverythingFullyLoaded(const Control& control);
+
+static const auto kTrimReservedSpace = kIncFsTrimReservedSpace;
+ErrorCode reserveSpace(const Control& control, std::string_view path, Size size);
+ErrorCode reserveSpace(const Control& control, FileId id, Size size);
+
+std::optional<Metrics> getMetrics(std::string_view sysfsName);
+std::optional<LastReadError> getLastReadError(const Control& control);
// Some internal secret API as well that's not backed by C API yet.
class MountRegistry;
diff --git a/incfs/include/incfs_inline.h b/incfs/include/incfs_inline.h
index 946255c..f3d1e89 100644
--- a/incfs/include/incfs_inline.h
+++ b/incfs/include/incfs_inline.h
@@ -28,14 +28,14 @@
constexpr char kSizeAttrName[] = INCFS_XATTR_SIZE_NAME;
constexpr char kMetadataAttrName[] = INCFS_XATTR_METADATA_NAME;
-constexpr char kIndexDir[] = ".index";
-
namespace details {
class CStrWrapper {
public:
CStrWrapper(std::string_view sv) {
- if (sv[sv.size()] == '\0') {
+ if (!sv.data()) {
+ mCstr = "";
+ } else if (sv[sv.size()] == '\0') {
mCstr = sv.data();
} else {
mCopy.emplace(sv);
@@ -116,6 +116,10 @@
return IncFs_GetControlFd(mControl, LOGS);
}
+inline IncFsFd UniqueControl::blocksWritten() const {
+ return IncFs_GetControlFd(mControl, BLOCKS_WRITTEN);
+}
+
inline UniqueControl::Fds UniqueControl::releaseFds() {
Fds result;
IncFsFd fds[result.size()];
@@ -137,8 +141,9 @@
return UniqueControl(control);
}
-inline UniqueControl createControl(IncFsFd cmd, IncFsFd pendingReads, IncFsFd logs) {
- return UniqueControl(IncFs_CreateControl(cmd, pendingReads, logs));
+inline UniqueControl createControl(IncFsFd cmd, IncFsFd pendingReads, IncFsFd logs,
+ IncFsFd blocksWritten) {
+ return UniqueControl(IncFs_CreateControl(cmd, pendingReads, logs, blocksWritten));
}
inline ErrorCode setOptions(const Control& control, MountOptions newOptions) {
@@ -169,6 +174,10 @@
NewFileParams params) {
return IncFs_MakeFile(control, details::c_str(path), mode, fileId, params);
}
+inline ErrorCode makeMappedFile(const Control& control, std::string_view path, int mode,
+ NewMappedFileParams params) {
+ return IncFs_MakeMappedFile(control, details::c_str(path), mode, params);
+}
inline ErrorCode makeDir(const Control& control, std::string_view path, int mode) {
return IncFs_MakeDir(control, details::c_str(path), mode);
}
@@ -229,15 +238,15 @@
return IncFs_Unlink(control, details::c_str(path));
}
-inline WaitResult waitForPendingReads(const Control& control, std::chrono::milliseconds timeout,
- std::vector<ReadInfo>* pendingReadsBuffer) {
- static constexpr auto kDefaultBufferSize = INCFS_DEFAULT_PENDING_READ_BUFFER_SIZE;
+template <class ReadInfoStruct, class Impl>
+WaitResult waitForReads(const Control& control, std::chrono::milliseconds timeout,
+ std::vector<ReadInfoStruct>* pendingReadsBuffer, size_t defaultBufferSize,
+ Impl impl) {
if (pendingReadsBuffer->empty()) {
- pendingReadsBuffer->resize(kDefaultBufferSize);
+ pendingReadsBuffer->resize(defaultBufferSize);
}
size_t size = pendingReadsBuffer->size();
- IncFsErrorCode err =
- IncFs_WaitForPendingReads(control, timeout.count(), pendingReadsBuffer->data(), &size);
+ IncFsErrorCode err = impl(control, timeout.count(), pendingReadsBuffer->data(), &size);
pendingReadsBuffer->resize(size);
switch (err) {
case 0:
@@ -248,24 +257,32 @@
return WaitResult(err);
}
+inline WaitResult waitForPendingReads(const Control& control, std::chrono::milliseconds timeout,
+ std::vector<ReadInfo>* pendingReadsBuffer) {
+ return waitForReads(control, timeout, pendingReadsBuffer,
+ INCFS_DEFAULT_PENDING_READ_BUFFER_SIZE, IncFs_WaitForPendingReads);
+}
+
+inline WaitResult waitForPendingReads(const Control& control, std::chrono::milliseconds timeout,
+ std::vector<ReadInfoWithUid>* pendingReadsBuffer) {
+ return waitForReads(control, timeout, pendingReadsBuffer,
+ INCFS_DEFAULT_PENDING_READ_BUFFER_SIZE, IncFs_WaitForPendingReadsWithUid);
+}
+
inline WaitResult waitForPageReads(const Control& control, std::chrono::milliseconds timeout,
std::vector<ReadInfo>* pageReadsBuffer) {
static constexpr auto kDefaultBufferSize =
INCFS_DEFAULT_PAGE_READ_BUFFER_PAGES * PAGE_SIZE / sizeof(ReadInfo);
- if (pageReadsBuffer->empty()) {
- pageReadsBuffer->resize(kDefaultBufferSize);
- }
- size_t size = pageReadsBuffer->size();
- IncFsErrorCode err =
- IncFs_WaitForPageReads(control, timeout.count(), pageReadsBuffer->data(), &size);
- pageReadsBuffer->resize(size);
- switch (err) {
- case 0:
- return WaitResult::HaveData;
- case -ETIMEDOUT:
- return WaitResult::Timeout;
- }
- return WaitResult(err);
+ return waitForReads(control, timeout, pageReadsBuffer, kDefaultBufferSize,
+ IncFs_WaitForPageReads);
+}
+
+inline WaitResult waitForPageReads(const Control& control, std::chrono::milliseconds timeout,
+ std::vector<ReadInfoWithUid>* pageReadsBuffer) {
+ static constexpr auto kDefaultBufferSize =
+ INCFS_DEFAULT_PAGE_READ_BUFFER_PAGES * PAGE_SIZE / sizeof(ReadInfoWithUid);
+ return waitForReads(control, timeout, pageReadsBuffer, kDefaultBufferSize,
+ IncFs_WaitForPageReadsWithUid);
}
inline UniqueFd openForSpecialOps(const Control& control, FileId fileId) {
@@ -320,8 +337,7 @@
return {res, FilledRanges(std::move(buffer), rawRanges)};
}
-inline LoadingState isFullyLoaded(int fd) {
- auto res = IncFs_IsFullyLoaded(fd);
+inline LoadingState toLoadingState(IncFsErrorCode res) {
switch (res) {
case 0:
return LoadingState::Full;
@@ -332,6 +348,138 @@
}
}
+inline LoadingState isFullyLoaded(int fd) {
+ return toLoadingState(IncFs_IsFullyLoaded(fd));
+}
+inline LoadingState isFullyLoaded(const Control& control, std::string_view path) {
+ return toLoadingState(IncFs_IsFullyLoadedByPath(control, details::c_str(path)));
+}
+inline LoadingState isFullyLoaded(const Control& control, FileId fileId) {
+ return toLoadingState(IncFs_IsFullyLoadedById(control, fileId));
+}
+
+inline LoadingState isEverythingFullyLoaded(const Control& control) {
+ return toLoadingState(IncFs_IsEverythingFullyLoaded(control));
+}
+
+inline std::optional<std::vector<FileId>> listIncompleteFiles(const Control& control) {
+ std::vector<FileId> ids(32);
+ size_t count = ids.size();
+ auto err = IncFs_ListIncompleteFiles(control, ids.data(), &count);
+ if (err == -E2BIG) {
+ ids.resize(count);
+ err = IncFs_ListIncompleteFiles(control, ids.data(), &count);
+ }
+ if (err) {
+ errno = -err;
+ return {};
+ }
+ ids.resize(count);
+ return std::move(ids);
+}
+
+template <class Callback>
+inline ErrorCode forEachFile(const Control& control, Callback&& cb) {
+ struct Context {
+ const Control& c;
+ const Callback& cb;
+ } context = {control, cb};
+ return IncFs_ForEachFile(control, &context, [](void* pcontext, const IncFsControl*, FileId id) {
+ const auto context = (Context*)pcontext;
+ return context->cb(context->c, id);
+ });
+}
+template <class Callback>
+inline ErrorCode forEachIncompleteFile(const Control& control, Callback&& cb) {
+ struct Context {
+ const Control& c;
+ const Callback& cb;
+ } context = {control, cb};
+ return IncFs_ForEachIncompleteFile(control, &context,
+ [](void* pcontext, const IncFsControl*, FileId id) {
+ const auto context = (Context*)pcontext;
+ return context->cb(context->c, id);
+ });
+}
+
+inline WaitResult waitForLoadingComplete(const Control& control,
+ std::chrono::milliseconds timeout) {
+ const auto res = IncFs_WaitForLoadingComplete(control, timeout.count());
+ switch (res) {
+ case 0:
+ return WaitResult::HaveData;
+ case -ETIMEDOUT:
+ return WaitResult::Timeout;
+ default:
+ return WaitResult(res);
+ }
+}
+
+inline std::optional<BlockCounts> getBlockCount(const Control& control, FileId fileId) {
+ BlockCounts counts;
+ auto res = IncFs_GetFileBlockCountById(control, fileId, &counts);
+ if (res) {
+ errno = -res;
+ return {};
+ }
+ return counts;
+}
+
+inline std::optional<BlockCounts> getBlockCount(const Control& control, std::string_view path) {
+ BlockCounts counts;
+ auto res = IncFs_GetFileBlockCountByPath(control, details::c_str(path), &counts);
+ if (res) {
+ errno = -res;
+ return {};
+ }
+ return counts;
+}
+
+inline ErrorCode setUidReadTimeouts(const Control& control, Span<const UidReadTimeouts> timeouts) {
+ return IncFs_SetUidReadTimeouts(control, timeouts.data(), timeouts.size());
+}
+
+inline std::optional<std::vector<UidReadTimeouts>> getUidReadTimeouts(const Control& control) {
+ std::vector<UidReadTimeouts> timeouts(32);
+ size_t count = timeouts.size();
+ auto res = IncFs_GetUidReadTimeouts(control, timeouts.data(), &count);
+ if (res == -E2BIG) {
+ timeouts.resize(count);
+ res = IncFs_GetUidReadTimeouts(control, timeouts.data(), &count);
+ }
+ if (res) {
+ errno = -res;
+ return {};
+ }
+ timeouts.resize(count);
+ return std::move(timeouts);
+}
+
+inline ErrorCode reserveSpace(const Control& control, std::string_view path, Size size) {
+ return IncFs_ReserveSpaceByPath(control, details::c_str(path), size);
+}
+inline ErrorCode reserveSpace(const Control& control, FileId id, Size size) {
+ return IncFs_ReserveSpaceById(control, id, size);
+}
+
+inline std::optional<Metrics> getMetrics(std::string_view sysfsName) {
+ Metrics metrics;
+ if (const auto res = IncFs_GetMetrics(details::c_str(sysfsName), &metrics); res < 0) {
+ errno = -res;
+ return {};
+ }
+ return metrics;
+}
+
+inline std::optional<LastReadError> getLastReadError(const Control& control) {
+ LastReadError lastReadError;
+ if (const auto res = IncFs_GetLastReadError(control, &lastReadError); res < 0) {
+ errno = -res;
+ return {};
+ }
+ return lastReadError;
+}
+
} // namespace android::incfs
inline bool operator==(const IncFsFileId& l, const IncFsFileId& r) {
diff --git a/incfs/include/incfs_ndk.h b/incfs/include/incfs_ndk.h
index cfc8f5d..2a538bc 100644
--- a/incfs/include/incfs_ndk.h
+++ b/incfs/include/incfs_ndk.h
@@ -44,6 +44,7 @@
typedef enum {
INCFS_FEATURE_NONE = 0,
INCFS_FEATURE_CORE = 1,
+ INCFS_FEATURE_V2 = 2,
} IncFsFeatures;
typedef int IncFsErrorCode;
@@ -51,6 +52,9 @@
typedef int32_t IncFsBlockIndex;
typedef int IncFsFd;
typedef struct IncFsControl IncFsControl;
+typedef int IncFsUid;
+
+static const IncFsUid kIncFsNoUid = -1;
typedef struct {
const char* data;
@@ -61,6 +65,7 @@
CMD,
PENDING_READS,
LOGS,
+ BLOCKS_WRITTEN,
FDS_COUNT,
} IncFsFdType;
@@ -85,11 +90,13 @@
int32_t defaultReadTimeoutMs;
int32_t readLogBufferPages;
int32_t readLogDisableAfterTimeoutMs;
+ const char* sysfsName;
} IncFsMountOptions;
typedef enum {
INCFS_COMPRESSION_KIND_NONE,
INCFS_COMPRESSION_KIND_LZ4,
+ INCFS_COMPRESSION_KIND_ZSTD,
} IncFsCompressionKind;
typedef enum {
@@ -113,6 +120,12 @@
} IncFsNewFileParams;
typedef struct {
+ IncFsFileId sourceId;
+ IncFsSize sourceOffset;
+ IncFsSize size;
+} IncFsNewMappedFileParams;
+
+typedef struct {
IncFsFileId id;
uint64_t bootClockTsUs;
IncFsBlockIndex block;
@@ -120,6 +133,14 @@
} IncFsReadInfo;
typedef struct {
+ IncFsFileId id;
+ uint64_t bootClockTsUs;
+ IncFsBlockIndex block;
+ uint32_t serialNo;
+ IncFsUid uid;
+} IncFsReadInfoWithUid;
+
+typedef struct {
IncFsBlockIndex begin;
IncFsBlockIndex end;
} IncFsBlockRange;
@@ -132,6 +153,40 @@
IncFsBlockIndex endIndex;
} IncFsFilledRanges;
+typedef struct {
+ IncFsSize totalDataBlocks;
+ IncFsSize filledDataBlocks;
+ IncFsSize totalHashBlocks;
+ IncFsSize filledHashBlocks;
+} IncFsBlockCounts;
+
+typedef struct {
+ IncFsUid uid;
+ uint32_t minTimeUs;
+ uint32_t minPendingTimeUs;
+ uint32_t maxPendingTimeUs;
+} IncFsUidReadTimeouts;
+
+typedef struct {
+ uint32_t readsDelayedMin;
+ uint64_t readsDelayedMinUs;
+ uint32_t readsDelayedPending;
+ uint64_t readsDelayedPendingUs;
+ uint32_t readsFailedHashVerification;
+ uint32_t readsFailedOther;
+ uint32_t readsFailedTimedOut;
+ uint64_t reserved;
+ uint64_t reserved1;
+} IncFsMetrics;
+
+typedef struct {
+ IncFsFileId id;
+ uint64_t timestampUs;
+ IncFsBlockIndex block;
+ uint32_t errorNo;
+ IncFsUid uid;
+} IncFsLastReadError;
+
// All functions return -errno in case of failure.
// All IncFsFd functions return >=0 in case of success.
// All IncFsFileId functions return invalid IncFsFileId on error.
@@ -155,7 +210,8 @@
IncFsControl* IncFs_Mount(const char* backingPath, const char* targetDir,
IncFsMountOptions options);
IncFsControl* IncFs_Open(const char* dir);
-IncFsControl* IncFs_CreateControl(IncFsFd cmd, IncFsFd pendingReads, IncFsFd logs);
+IncFsControl* IncFs_CreateControl(IncFsFd cmd, IncFsFd pendingReads, IncFsFd logs,
+ IncFsFd blocksWritten);
void IncFs_DeleteControl(IncFsControl* control);
IncFsFd IncFs_GetControlFd(const IncFsControl* control, IncFsFdType type);
IncFsSize IncFs_ReleaseControlFds(IncFsControl* control, IncFsFd out[], IncFsSize outSize);
@@ -169,6 +225,9 @@
IncFsErrorCode IncFs_MakeFile(const IncFsControl* control, const char* path, int32_t mode,
IncFsFileId id, IncFsNewFileParams params);
+IncFsErrorCode IncFs_MakeMappedFile(const IncFsControl* control, const char* path, int32_t mode,
+ IncFsNewMappedFileParams params);
+
IncFsErrorCode IncFs_MakeDir(const IncFsControl* control, const char* path, int32_t mode);
IncFsErrorCode IncFs_MakeDirs(const IncFsControl* control, const char* path, int32_t mode);
@@ -194,11 +253,45 @@
IncFsErrorCode IncFs_WaitForPageReads(const IncFsControl* control, int32_t timeoutMs,
IncFsReadInfo buffer[], size_t* bufferSize);
+IncFsErrorCode IncFs_WaitForPendingReadsWithUid(const IncFsControl* control, int32_t timeoutMs,
+ IncFsReadInfoWithUid buffer[], size_t* bufferSize);
+IncFsErrorCode IncFs_WaitForPageReadsWithUid(const IncFsControl* control, int32_t timeoutMs,
+ IncFsReadInfoWithUid buffer[], size_t* bufferSize);
+
+IncFsErrorCode IncFs_WaitForFsWrittenBlocksChange(const IncFsControl* control, int32_t timeoutMs,
+ IncFsSize* count);
+
IncFsFd IncFs_OpenForSpecialOpsByPath(const IncFsControl* control, const char* path);
IncFsFd IncFs_OpenForSpecialOpsById(const IncFsControl* control, IncFsFileId id);
IncFsErrorCode IncFs_WriteBlocks(const IncFsDataBlock blocks[], size_t blocksCount);
+IncFsErrorCode IncFs_SetUidReadTimeouts(const IncFsControl* control,
+ const IncFsUidReadTimeouts timeouts[], size_t count);
+IncFsErrorCode IncFs_GetUidReadTimeouts(const IncFsControl* control,
+ IncFsUidReadTimeouts timeoutsBuffer[], size_t* bufferSize);
+
+IncFsErrorCode IncFs_GetFileBlockCountByPath(const IncFsControl* control, const char* path,
+ IncFsBlockCounts* blockCount);
+IncFsErrorCode IncFs_GetFileBlockCountById(const IncFsControl* control, IncFsFileId id,
+ IncFsBlockCounts* blockCount);
+
+IncFsErrorCode IncFs_ListIncompleteFiles(const IncFsControl* control, IncFsFileId ids[],
+ size_t* bufferSize);
+
+// Calls a passed callback for each file on the mounted filesystem, or, in the second case,
+// for each incomplete file (only for v2 IncFS).
+// Callback can stop the iteration early by returning |false|.
+// Return codes:
+// >=0 - number of files iterated,
+// <0 - -errno
+typedef bool (*FileCallback)(void* context, const IncFsControl* control, IncFsFileId fileId);
+IncFsErrorCode IncFs_ForEachFile(const IncFsControl* control, void* context, FileCallback cb);
+IncFsErrorCode IncFs_ForEachIncompleteFile(const IncFsControl* control, void* context,
+ FileCallback cb);
+
+IncFsErrorCode IncFs_WaitForLoadingComplete(const IncFsControl* control, int32_t timeoutMs);
+
// Gets a collection of filled ranges in the file from IncFS. Uses the |outBuffer| memory, it has
// to be big enough to fit all the ranges the caller is expecting.
// Return codes:
@@ -214,6 +307,38 @@
// -ENODATA - some blocks are missing,
// <0 - error from the syscall.
IncFsErrorCode IncFs_IsFullyLoaded(int fd);
+IncFsErrorCode IncFs_IsFullyLoadedByPath(const IncFsControl* control, const char* path);
+IncFsErrorCode IncFs_IsFullyLoadedById(const IncFsControl* control, IncFsFileId fileId);
+
+// Check if all files on the mount are fully loaded. Return codes:
+// 0 - fully loaded,
+// -ENODATA - some blocks are missing,
+// <0 - error from the syscall.
+IncFsErrorCode IncFs_IsEverythingFullyLoaded(const IncFsControl* control);
+
+// Reserve |size| bytes for the file. Trims reserved space to the current file size when |size = -1|
+static const IncFsSize kIncFsTrimReservedSpace = -1;
+IncFsErrorCode IncFs_ReserveSpaceByPath(const IncFsControl* control, const char* path,
+ IncFsSize size);
+IncFsErrorCode IncFs_ReserveSpaceById(const IncFsControl* control, IncFsFileId id, IncFsSize size);
+
+// Gets the metrics of a mount by specifying the corresponding sysfs subpath.
+// Return codes:
+// =0 - success
+// <0 - -errno
+IncFsErrorCode IncFs_GetMetrics(const char* sysfsName, IncFsMetrics* metrics);
+
+// Gets information about the last read error of a mount.
+// Return codes:
+// =0 - success
+// <0 - -errno
+// When there is no read error, still returns success. Fields in IncFsLastReadError will be all 0.
+// Possible values of IncFsLastReadError.errorNo:
+// -ETIME for read timeout;
+// -EBADMSG for hash verification failure;
+// Other negative values for other types of errors.
+IncFsErrorCode IncFs_GetLastReadError(const IncFsControl* control,
+ IncFsLastReadError* lastReadError);
__END_DECLS
diff --git a/incfs/path.h b/incfs/include/path.h
similarity index 62%
rename from incfs/path.h
rename to incfs/include/path.h
index f312d17..325eb1d 100644
--- a/incfs/path.h
+++ b/incfs/include/path.h
@@ -48,8 +48,43 @@
std::string_view relativize(std::string_view parent, std::string&& nested) = delete;
// Note: some system headers #define 'dirname' and 'basename' as macros
-std::string_view dirName(std::string_view path);
-std::string_view baseName(std::string_view path);
+
+inline std::string_view baseName(std::string_view path) {
+ using namespace std::literals;
+ if (path.empty()) {
+ return {};
+ }
+ if (path == "/"sv) {
+ return "/"sv;
+ }
+ auto pos = path.rfind('/');
+ while (!path.empty() && pos == path.size() - 1) {
+ path.remove_suffix(1);
+ pos = path.rfind('/');
+ }
+ if (pos == path.npos) {
+ return path.empty() ? "/"sv : path;
+ }
+ return path.substr(pos + 1);
+}
+
+inline std::string_view dirName(std::string_view path) {
+ using namespace std::literals;
+ if (path.empty()) {
+ return {};
+ }
+ if (path == "/"sv) {
+ return "/"sv;
+ }
+ const auto pos = path.rfind('/');
+ if (pos == 0) {
+ return "/"sv;
+ }
+ if (pos == path.npos) {
+ return "."sv;
+ }
+ return path.substr(0, pos);
+}
// Split the |full| path into its directory and basename components.
// This modifies the input string to null-terminate the output directory
@@ -60,20 +95,32 @@
bool endsWith(std::string_view path, std::string_view prefix);
inline auto openDir(const char* path) {
- auto dir = std::unique_ptr<DIR, decltype(&closedir)>(::opendir(path), &::closedir);
+ struct closer {
+ void operator()(DIR* d) const { ::closedir(d); }
+ };
+ auto dir = std::unique_ptr<DIR, closer>(::opendir(path));
return dir;
}
template <class... Paths>
-std::string join(std::string_view first, std::string_view second, Paths&&... paths) {
- std::string result;
+std::string join(std::string&& first, std::string_view second, Paths&&... paths) {
+ std::string& result = first;
{
using std::size;
result.reserve(first.size() + second.size() + 1 + (sizeof...(paths) + ... + size(paths)));
}
- result.assign(first);
- (details::appendNextPath(result, second), ..., details::appendNextPath(result, paths));
+ (details::appendNextPath(result, second), ...,
+ details::appendNextPath(result, std::forward<Paths>(paths)));
return result;
}
+template <class... Paths>
+std::string join(std::string_view first, std::string_view second, Paths&&... paths) {
+ return join(std::string(), first, second, std::forward<Paths>(paths)...);
+}
+template <class... Paths>
+std::string join(const char* first, std::string_view second, Paths&&... paths) {
+ return path::join(std::string_view(first), second, std::forward<Paths>(paths)...);
+}
+
} // namespace android::incfs::path
diff --git a/incfs/kernel-headers/linux/incrementalfs.h b/incfs/kernel-headers/linux/incrementalfs.h
index b9f4c7a..d164cd9 100644
--- a/incfs/kernel-headers/linux/incrementalfs.h
+++ b/incfs/kernel-headers/linux/incrementalfs.h
@@ -18,7 +18,7 @@
/* ===== constants ===== */
#define INCFS_NAME "incremental-fs"
-#define INCFS_MAGIC_NUMBER (0x5346434e49ul)
+#define INCFS_MAGIC_NUMBER (unsigned long)(0x5346434e49ul)
#define INCFS_DATA_FILE_BLOCK_SIZE 4096
#define INCFS_HEADER_VER 1
@@ -28,11 +28,15 @@
#define INCFS_MAX_HASH_SIZE 32
#define INCFS_MAX_FILE_ATTR_SIZE 512
+#define INCFS_INDEX_NAME ".index"
+#define INCFS_INCOMPLETE_NAME ".incomplete"
#define INCFS_PENDING_READS_FILENAME ".pending_reads"
#define INCFS_LOG_FILENAME ".log"
+#define INCFS_BLOCKS_WRITTEN_FILENAME ".blocks_written"
#define INCFS_XATTR_ID_NAME (XATTR_USER_PREFIX "incfs.id")
#define INCFS_XATTR_SIZE_NAME (XATTR_USER_PREFIX "incfs.size")
#define INCFS_XATTR_METADATA_NAME (XATTR_USER_PREFIX "incfs.metadata")
+#define INCFS_XATTR_VERITY_NAME (XATTR_USER_PREFIX "incfs.verity")
#define INCFS_MAX_SIGNATURE_SIZE 8096
#define INCFS_SIGNATURE_VERSION 2
@@ -42,7 +46,10 @@
/* ===== ioctl requests on the command dir ===== */
-/* Create a new file */
+/*
+ * Create a new file
+ * May only be called on .pending_reads file
+ */
#define INCFS_IOC_CREATE_FILE \
_IOWR(INCFS_IOCTL_BASE_CODE, 30, struct incfs_new_file_args)
@@ -92,9 +99,73 @@
#define INCFS_IOC_GET_FILLED_BLOCKS \
_IOR(INCFS_IOCTL_BASE_CODE, 34, struct incfs_get_filled_blocks_args)
+/*
+ * Creates a new mapped file
+ * May only be called on .pending_reads file
+ */
+#define INCFS_IOC_CREATE_MAPPED_FILE \
+ _IOWR(INCFS_IOCTL_BASE_CODE, 35, struct incfs_create_mapped_file_args)
+
+/*
+ * Get number of blocks, total and filled
+ * May only be called on .pending_reads file
+ */
+#define INCFS_IOC_GET_BLOCK_COUNT \
+ _IOR(INCFS_IOCTL_BASE_CODE, 36, struct incfs_get_block_count_args)
+
+/*
+ * Get per UID read timeouts
+ * May only be called on .pending_reads file
+ */
+#define INCFS_IOC_GET_READ_TIMEOUTS \
+ _IOR(INCFS_IOCTL_BASE_CODE, 37, struct incfs_get_read_timeouts_args)
+
+/*
+ * Set per UID read timeouts
+ * May only be called on .pending_reads file
+ */
+#define INCFS_IOC_SET_READ_TIMEOUTS \
+ _IOW(INCFS_IOCTL_BASE_CODE, 38, struct incfs_set_read_timeouts_args)
+
+/*
+ * Get last read error
+ * May only be called on .pending_reads file
+ */
+#define INCFS_IOC_GET_LAST_READ_ERROR \
+ _IOW(INCFS_IOCTL_BASE_CODE, 39, struct incfs_get_last_read_error_args)
+
+/* ===== sysfs feature flags ===== */
+/*
+ * Each flag is represented by a file in /sys/fs/incremental-fs/features
+ * If the file exists the feature is supported
+ * Also the file contents will be the line "supported"
+ */
+
+/*
+ * Basic flag stating that the core incfs file system is available
+ */
+#define INCFS_FEATURE_FLAG_COREFS "corefs"
+
+/*
+ * zstd compression support
+ */
+#define INCFS_FEATURE_FLAG_ZSTD "zstd"
+
+/*
+ * v2 feature set support. Covers:
+ * INCFS_IOC_CREATE_MAPPED_FILE
+ * INCFS_IOC_GET_BLOCK_COUNT
+ * INCFS_IOC_GET_READ_TIMEOUTS/INCFS_IOC_SET_READ_TIMEOUTS
+ * .blocks_written status file
+ * .incomplete folder
+ * report_uid mount option
+ */
+#define INCFS_FEATURE_FLAG_V2 "v2"
+
enum incfs_compression_alg {
COMPRESSION_NONE = 0,
- COMPRESSION_LZ4 = 1
+ COMPRESSION_LZ4 = 1,
+ COMPRESSION_ZSTD = 2,
};
enum incfs_block_flags {
@@ -109,6 +180,8 @@
/*
* Description of a pending read. A pending read - a read call by
* a userspace program for which the filesystem currently doesn't have data.
+ *
+ * Reads from .pending_reads and .log return an array of these structure
*/
struct incfs_pending_read_info {
/* Id of a file that is being read from. */
@@ -125,6 +198,32 @@
};
/*
+ * Description of a pending read. A pending read - a read call by
+ * a userspace program for which the filesystem currently doesn't have data.
+ *
+ * This version of incfs_pending_read_info is used whenever the file system is
+ * mounted with the report_uid flag
+ */
+struct incfs_pending_read_info2 {
+ /* Id of a file that is being read from. */
+ incfs_uuid_t file_id;
+
+ /* A number of microseconds since system boot to the read. */
+ __aligned_u64 timestamp_us;
+
+ /* Index of a file block that is being read. */
+ __u32 block_index;
+
+ /* A serial number of this pending read. */
+ __u32 serial_number;
+
+ /* The UID of the reading process */
+ __u32 uid;
+
+ __u32 reserved;
+};
+
+/*
* Description of a data or hash block to add to a data file.
*/
struct incfs_fill_block {
@@ -321,7 +420,7 @@
/* Actual number of blocks in file */
__u32 total_blocks_out;
- /* The number of data blocks in file */
+ /* The number of data blocks in file */
__u32 data_blocks_out;
/* Number of bytes written to range buffer */
@@ -331,4 +430,154 @@
__u32 index_out;
};
-#endif /* _UAPI_LINUX_INCREMENTALFS_H */
+/*
+ * Create a new mapped file
+ * Argument for INCFS_IOC_CREATE_MAPPED_FILE
+ */
+struct incfs_create_mapped_file_args {
+ /*
+ * Total size of the new file.
+ */
+ __aligned_u64 size;
+
+ /*
+ * File mode. Permissions and dir flag.
+ */
+ __u16 mode;
+
+ __u16 reserved1;
+
+ __u32 reserved2;
+
+ /*
+ * A pointer to a null-terminated relative path to the incfs mount
+ * point
+ * Max length: PATH_MAX
+ *
+ * Equivalent to: char *directory_path;
+ */
+ __aligned_u64 directory_path;
+
+ /*
+ * A pointer to a null-terminated file name.
+ * Max length: PATH_MAX
+ *
+ * Equivalent to: char *file_name;
+ */
+ __aligned_u64 file_name;
+
+ /* Id of source file to map. */
+ incfs_uuid_t source_file_id;
+
+ /*
+ * Offset in source file to start mapping. Must be a multiple of
+ * INCFS_DATA_FILE_BLOCK_SIZE
+ */
+ __aligned_u64 source_offset;
+};
+
+/*
+ * Get information about the blocks in this file
+ * Argument for INCFS_IOC_GET_BLOCK_COUNT
+ */
+struct incfs_get_block_count_args {
+ /* Total number of data blocks in the file */
+ __u32 total_data_blocks_out;
+
+ /* Number of filled data blocks in the file */
+ __u32 filled_data_blocks_out;
+
+ /* Total number of hash blocks in the file */
+ __u32 total_hash_blocks_out;
+
+ /* Number of filled hash blocks in the file */
+ __u32 filled_hash_blocks_out;
+};
+
+/* Description of timeouts for one UID */
+struct incfs_per_uid_read_timeouts {
+ /* UID to apply these timeouts to */
+ __u32 uid;
+
+ /*
+ * Min time in microseconds to read any block. Note that this doesn't
+ * apply to reads which are satisfied from the page cache.
+ */
+ __u32 min_time_us;
+
+ /*
+ * Min time in microseconds to satisfy a pending read. Any pending read
+ * which is filled before this time will be delayed so that the total
+ * read time >= this value.
+ */
+ __u32 min_pending_time_us;
+
+ /*
+ * Max time in microseconds to satisfy a pending read before the read
+ * times out. If set to U32_MAX, defaults to mount options
+ * read_timeout_ms * 1000. Must be >= min_pending_time_us
+ */
+ __u32 max_pending_time_us;
+};
+
+/*
+ * Get the read timeouts array
+ * Argument for INCFS_IOC_GET_READ_TIMEOUTS
+ */
+struct incfs_get_read_timeouts_args {
+ /*
+ * A pointer to a buffer to fill with the current timeouts
+ *
+ * Equivalent to struct incfs_per_uid_read_timeouts *
+ */
+ __aligned_u64 timeouts_array;
+
+ /* Size of above buffer in bytes */
+ __u32 timeouts_array_size;
+
+ /* Size used in bytes, or size needed if -ENOMEM returned */
+ __u32 timeouts_array_size_out;
+};
+
+/*
+ * Set the read timeouts array
+ * Arguments for INCFS_IOC_SET_READ_TIMEOUTS
+ */
+struct incfs_set_read_timeouts_args {
+ /*
+ * A pointer to an array containing the new timeouts
+ * This will replace any existing timeouts
+ *
+ * Equivalent to struct incfs_per_uid_read_timeouts *
+ */
+ __aligned_u64 timeouts_array;
+
+ /* Size of above array in bytes. Must be < 256 */
+ __u32 timeouts_array_size;
+};
+
+/*
+ * Get last read error struct
+ * Arguments for INCFS_IOC_GET_LAST_READ_ERROR
+ */
+struct incfs_get_last_read_error_args {
+ /* File id of last file that had a read error */
+ incfs_uuid_t file_id_out;
+
+ /* Time of last read error, in us, from CLOCK_MONOTONIC */
+ __u64 time_us_out;
+
+ /* Index of page that was being read at last read error */
+ __u32 page_out;
+
+ /* errno of last read error */
+ __u32 errno_out;
+
+ /* uid of last read error */
+ __u32 uid_out;
+
+ __u32 reserved1;
+ __u64 reserved2;
+};
+
+#endif /* _UAPI_LINUX_INCREMENTALFS_H */
\ No newline at end of file
diff --git a/incfs/path.cpp b/incfs/path.cpp
index 8781571..f6f87ae 100644
--- a/incfs/path.cpp
+++ b/incfs/path.cpp
@@ -121,23 +121,19 @@
snprintf(fdNameBuffer, std::size(fdNameBuffer), fdNameFormat, fd);
std::string res;
- // lstat() is supposed to return us exactly the needed buffer size, but
- // somehow it may also return a smaller (but still >0) st_size field.
- // That's why let's only use it for the initial estimate.
- struct stat st = {};
- if (::lstat(fdNameBuffer, &st) || st.st_size == 0) {
- st.st_size = PATH_MAX;
- }
- auto bufSize = st.st_size;
+ // We used to call lstat() here to preallocate the buffer to the exact required size; turns out
+ // that call is significantly more expensive than anything else, so doing a couple extra
+ // iterations is worth the savings.
+ auto bufSize = 256;
for (;;) {
- res.resize(bufSize + 1, '\0');
+ res.resize(bufSize - 1, '\0');
auto size = ::readlink(fdNameBuffer, &res[0], res.size());
if (size < 0) {
PLOG(ERROR) << "readlink failed for " << fdNameBuffer;
return {};
}
- if (size > bufSize) {
- // File got renamed in between lstat() and readlink() calls? Retry.
+ if (size >= ssize_t(res.size())) {
+ // can't tell if the name is exactly that long, or got truncated - just repeat the call.
bufSize *= 2;
continue;
}
@@ -149,13 +145,14 @@
}
}
-static void preparePathComponent(std::string_view& path, bool trimFront) {
- if (trimFront) {
- while (!path.empty() && path.front() == '/') {
- path.remove_prefix(1);
- }
+static void preparePathComponent(std::string_view& path, bool trimAll) {
+ // need to check for double front slash as a single one has a separate meaning in front
+ while (!path.empty() && path.front() == '/' &&
+ (trimAll || (path.size() > 1 && path[1] == '/'))) {
+ path.remove_prefix(1);
}
- while (!path.empty() && path.back() == '/') {
+ // for the back we don't care about double-vs-single slash difference
+ while (path.size() > !trimAll && path.back() == '/') {
path.remove_suffix(1);
}
}
@@ -178,7 +175,7 @@
}
void details::appendNextPath(std::string& res, std::string_view path) {
- preparePathComponent(path, true);
+ preparePathComponent(path, !res.empty());
if (path.empty()) {
return;
}
@@ -188,41 +185,6 @@
res += path;
}
-std::string_view baseName(std::string_view path) {
- if (path.empty()) {
- return {};
- }
- if (path == "/"sv) {
- return "/"sv;
- }
- auto pos = path.rfind('/');
- while (!path.empty() && pos == path.size() - 1) {
- path.remove_suffix(1);
- pos = path.rfind('/');
- }
- if (pos == path.npos) {
- return path.empty() ? "/"sv : path;
- }
- return path.substr(pos + 1);
-}
-
-std::string_view dirName(std::string_view path) {
- if (path.empty()) {
- return {};
- }
- if (path == "/"sv) {
- return "/"sv;
- }
- const auto pos = path.rfind('/');
- if (pos == 0) {
- return "/"sv;
- }
- if (pos == path.npos) {
- return "."sv;
- }
- return path.substr(0, pos);
-}
-
std::pair<std::string_view, std::string_view> splitDirBase(std::string& full) {
auto res = std::pair(dirName(full), baseName(full));
if (res.first.data() == full.data()) {
diff --git a/incfs/tests/MountRegistry_test.cpp b/incfs/tests/MountRegistry_test.cpp
index 2b82eb0..bb3ba49 100644
--- a/incfs/tests/MountRegistry_test.cpp
+++ b/incfs/tests/MountRegistry_test.cpp
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+#include "MountRegistry.h"
+
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/unique_fd.h>
@@ -21,10 +23,11 @@
#include <sys/select.h>
#include <unistd.h>
+#include <iterator>
#include <optional>
#include <thread>
-#include "MountRegistry.h"
+#include "incfs.h"
#include "path.h"
using namespace android::incfs;
@@ -72,9 +75,97 @@
ASSERT_STREQ("/root", r().rootFor("/bind").data());
ASSERT_STREQ("/root", r().rootFor("/bind2").data());
ASSERT_STREQ("/root", r().rootFor("/other/bind/dir").data());
- ASSERT_EQ(std::pair("/root"sv, ""s), r().rootAndSubpathFor("/root"));
- ASSERT_EQ(std::pair("/root"sv, "1"s), r().rootAndSubpathFor("/bind"));
- ASSERT_EQ(std::pair("/root"sv, "2/3"s), r().rootAndSubpathFor("/bind2"));
- ASSERT_EQ(std::pair("/root"sv, "2/3/blah"s), r().rootAndSubpathFor("/bind2/blah"));
- ASSERT_EQ(std::pair("/root"sv, "2/3/blah"s), r().rootAndSubpathFor("/other/bind/blah"));
+ ASSERT_EQ("/root"s, r().rootAndSubpathFor("/root").first->path);
+ ASSERT_EQ(""s, r().rootAndSubpathFor("/root").second);
+ ASSERT_EQ("/root"s, r().rootAndSubpathFor("/bind").first->path);
+ ASSERT_EQ("1"s, r().rootAndSubpathFor("/bind").second);
+ ASSERT_EQ("/root"s, r().rootAndSubpathFor("/bind2").first->path);
+ ASSERT_EQ("2/3"s, r().rootAndSubpathFor("/bind2").second);
+ ASSERT_EQ("/root"s, r().rootAndSubpathFor("/bind2/blah").first->path);
+ ASSERT_EQ("2/3/blah"s, r().rootAndSubpathFor("/bind2/blah").second);
+ ASSERT_EQ("/root"s, r().rootAndSubpathFor("/other/bind/blah").first->path);
+ ASSERT_EQ("2/3/blah"s, r().rootAndSubpathFor("/other/bind/blah").second);
+}
+
+TEST_F(MountRegistryTest, MultiRoot) {
+ r().addRoot("/root", "/backing");
+ r().addBind("/root", "/bind");
+ ASSERT_STREQ("/root", r().rootFor("/root").data());
+ ASSERT_STREQ("/root", r().rootFor("/bind").data());
+ ASSERT_STREQ("/root", r().rootFor("/bind/2").data());
+}
+
+static MountRegistry::Mounts makeFrom(std::string_view str) {
+ TemporaryFile f;
+ EXPECT_TRUE(android::base::WriteFully(f.fd, str.data(), str.size()));
+ EXPECT_EQ(0, lseek(f.fd, 0, SEEK_SET)); // rewind
+
+ MountRegistry::Mounts m;
+ EXPECT_TRUE(m.loadFrom(f.fd, INCFS_NAME));
+ return std::move(m);
+}
+
+TEST_F(MountRegistryTest, MultiRootLoad) {
+ constexpr char mountsFile[] =
+ R"(4605 34 0:154 / /mnt/installer/0/0000000000000000000000000000CAFEF00D2019 rw,nosuid,nodev,noexec,noatime shared:45 master:43 - fuse /dev/fuse rw,lazytime,user_id=0,group_id=0,allow_other
+4561 35 0:154 / /mnt/androidwritable/0/0000000000000000000000000000CAFEF00D2019 rw,nosuid,nodev,noexec,noatime shared:44 master:43 - fuse /dev/fuse rw,lazytime,user_id=0,group_id=0,allow_other
+4560 99 0:154 / /storage/0000000000000000000000000000CAFEF00D2019 rw,nosuid,nodev,noexec,noatime master:43 - fuse /dev/fuse rw,lazytime,user_id=0,group_id=0,allow_other
+4650 30 0:44 /MyFiles /mnt/pass_through/0/0000000000000000000000000000CAFEF00D2019 rw,nosuid,nodev,noexec,relatime shared:31 - 9p media rw,sync,dirsync,access=client,trans=virtio
+3181 79 0:146 / /data/incremental/MT_data_app_vmdl703/mount rw,nosuid,nodev,noatime shared:46 - incremental-fs /data/incremental/MT_data_app_vmdl703/backing_store rw,seclabel,read_timeout_ms=10000,readahead=0
+3182 77 0:146 / /var/run/mount/data/mount/data/incremental/MT_data_app_vmdl703/mount rw,nosuid,nodev,noatime shared:46 - incremental-fs /data/incremental/MT_data_app_vmdl703/backing_store rw,seclabel,read_timeout_ms=10000,readahead=0
+)";
+
+ auto m = makeFrom(mountsFile);
+
+ EXPECT_EQ(size_t(1), m.size());
+ EXPECT_STREQ("/data/incremental/MT_data_app_vmdl703/mount",
+ m.rootFor("/data/incremental/MT_data_app_vmdl703/mount/123/2").data());
+ EXPECT_STREQ("/data/incremental/MT_data_app_vmdl703/mount",
+ m.rootFor("/var/run/mount/data/mount/data/incremental/MT_data_app_vmdl703/mount/"
+ "some/thing")
+ .data());
+}
+
+TEST_F(MountRegistryTest, MultiRootLoadReversed) {
+ constexpr char mountsFile[] =
+ R"(4605 34 0:154 / /mnt/installer/0/0000000000000000000000000000CAFEF00D2019 rw,nosuid,nodev,noexec,noatime shared:45 master:43 - fuse /dev/fuse rw,lazytime,user_id=0,group_id=0,allow_other
+4561 35 0:154 / /mnt/androidwritable/0/0000000000000000000000000000CAFEF00D2019 rw,nosuid,nodev,noexec,noatime shared:44 master:43 - fuse /dev/fuse rw,lazytime,user_id=0,group_id=0,allow_other
+4560 99 0:154 / /storage/0000000000000000000000000000CAFEF00D2019 rw,nosuid,nodev,noexec,noatime master:43 - fuse /dev/fuse rw,lazytime,user_id=0,group_id=0,allow_other
+4650 30 0:44 /MyFiles /mnt/pass_through/0/0000000000000000000000000000CAFEF00D2019 rw,nosuid,nodev,noexec,relatime shared:31 - 9p media rw,sync,dirsync,access=client,trans=virtio
+3182 77 0:146 / /var/run/mount/data/mount/data/incremental/MT_data_app_vmdl703/mount rw,nosuid,nodev,noatime shared:46 - incremental-fs /data/incremental/MT_data_app_vmdl703/backing_store rw,seclabel,read_timeout_ms=10000,readahead=0
+3181 79 0:146 / /data/incremental/MT_data_app_vmdl703/mount rw,nosuid,nodev,noatime shared:46 - incremental-fs /data/incremental/MT_data_app_vmdl703/backing_store rw,seclabel,read_timeout_ms=10000,readahead=0
+)";
+
+ auto m = makeFrom(mountsFile);
+
+ EXPECT_EQ(size_t(1), m.size());
+ EXPECT_STREQ("/data/incremental/MT_data_app_vmdl703/mount",
+ m.rootFor("/data/incremental/MT_data_app_vmdl703/mount/123/2").data());
+ EXPECT_STREQ("/data/incremental/MT_data_app_vmdl703/mount",
+ m.rootFor("/var/run/mount/data/mount/data/incremental/MT_data_app_vmdl703/mount/"
+ "some/thing")
+ .data());
+}
+
+TEST_F(MountRegistryTest, LoadInvalid) {
+ constexpr char mountsFile[] =
+ R"(9465 93 0:281 // /data/incremental1 shared:56 - incremental-fs /data/incremental2 rw,seclabel,read_timeout_ms=10000,readahead=0,report_uid
+9529 93 0:281 /st_232_0 /data/app/vmdl1998506227.tmp rw,nosuid,nodev,noatime shared:56 - incremental-fs /data/incremental/MT_data_app_vmdl199/backing_store rw,seclabel,read_timeout_ms=10000,readahead=0,report_uid
+9657 93 0:282 /st_233_0 /data/app/vmdl2034419270.tmp rw,nosuid,nodev,noatime shared:57 - incremental-fs /data/incremental/MT_data_app_vmdl203/backing_store rw,seclabel,read_timeout_ms=10000,readahead=0,report_uid
+9721 93 0:283 / /data/incremental/MT_data_app_vmdl154/mount rw,nosuid,nodev,noatime shared:58 - incremental-fs /data/incremental/MT_data_app_vmdl154/backing_store rw,seclabel,read_timeout_ms=10000,readahead=0,report_uid
+9785 93 0:283 /st_234_0 /data/app/vmdl1545425783.tmp rw,nosuid,nodev,noatime shared:58 - incremental-fs /data/incremental/MT_data_app_vmdl154/backing_store rw,seclabel,read_timeout_ms=10000,readahead=0,report_uid
+9849 93 0:284 / /data/incremental/MT_data_app_vmdl209/mount rw,nosuid,nodev,noatime shared:59 - incremental-fs /data/incremental/MT_data_app_vmdl209/backing_store rw,seclabel,read_timeout_ms=10000,readahead=0,report_uid
+9913 93 0:284 /st_235_0 /data/app/vmdl2099748756.tmp rw,nosuid,nodev,noatime shared:59 - incremental-fs /data/incremental/MT_data_app_vmdl209/backing_store rw,seclabel,read_timeout_ms=10000,readahead=0,report_uid
+4007 93 0:269 /st_240_1 /data/app/~~I499PLubwcOVbJaEFqpHHQ== rw,nosuid,nodev,noatime shared:44 - incremental-fs /data/incremental/MT_data_app_vmdl158/backing_store rw,seclabel,read_timeout_ms=10000,readahead=0,report_uid
+5285 93 0:270 /st_241_1 /data/app/~~CvgMdGm9eNJpvdq-62Jktg== rw,nosuid,nodev,noatime shared:45 - incremental-fs /data/incremental/MT_data_app_vmdl943/backing_store rw,seclabel,read_timeout_ms=10000,readahead=0,report_uid
+8786 93 0:271 /st_242_1 /data/app/~~_oNudLuBvqtSE78VoMVY5Q== rw,nosuid,nodev,noatime shared:46 - incremental-fs /data/incremental/MT_data_app_vmdl144/backing_store rw,seclabel,read_timeout_ms=10000,readahead=0,report_uid
+10358 93 0:272 /st_243_1 /data/app/~~o5-RadxV4DUe-mgPyv9SkQ== rw,nosuid,nodev,noatime shared:47 - incremental-fs /data/incremental/MT_data_app_vmdl642/backing_store rw,seclabel,read_timeout_ms=10000,readahead=0,report_uid
+10422 93 0:131 /st_244_1 /data/app/~~rTnKswx1F427UguGO9nDRA== rw,nosuid,nodev,noatime shared:48 - incremental-fs /data/incremental/MT_data_app_vmdl336/backing_store rw,seclabel,read_timeout_ms=10000,readahead=0,report_uid
+10486 93 0:132 /st_245_1 /data/app/~~ZIoVqPDBjLBeajD4thHsYA== rw,nosuid,nodev,noatime shared:49 - incremental-fs /data/incremental/MT_data_app_vmdl993/backing_store rw,seclabel,read_timeout_ms=10000,readahead=0,report_uid
+10550 93 0:133 /st_246_1 /data/app/~~2qjtCtx5rqPW2Hwlrciu2w== rw,nosuid,nodev,noatime shared:50 - incremental-fs /data/incremental/MT_data_app_vmdl827/backing_store rw,seclabel,read_timeout_ms=10000,readahead=0,report_uid
+)";
+
+ auto m = makeFrom(mountsFile);
+ // only two of the mounts in this file are valid
+ EXPECT_EQ(size_t(2), m.size());
}
diff --git a/incfs/tests/hardening_benchmark.cpp b/incfs/tests/hardening_benchmark.cpp
new file mode 100644
index 0000000..8e4f047
--- /dev/null
+++ b/incfs/tests/hardening_benchmark.cpp
@@ -0,0 +1,165 @@
+/*
+ * 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 <android-base/file.h>
+#include <android-base/mapped_file.h>
+#include <benchmark/benchmark.h>
+#include <unistd.h>
+
+#include "incfs_support/access.h"
+#include "incfs_support/signal_handling.h"
+#include "util/map_ptr.h"
+
+static std::unique_ptr<TemporaryFile> makeFile() {
+ auto tmp = std::unique_ptr<TemporaryFile>(new TemporaryFile());
+ char c = 1;
+ write(tmp->fd, &c, sizeof(c));
+ return tmp;
+}
+
+static std::pair<std::unique_ptr<TemporaryFile>, std::unique_ptr<android::base::MappedFile>>
+makeEmptyFileMapping() {
+ auto tmp = makeFile();
+ // mmap() only works for non-empty files, but it's "ok" to resize it back to empty afterwards
+ auto mapping = android::base::MappedFile::FromFd(tmp->fd, 0, 1, PROT_READ);
+ ftruncate(tmp->fd, 0);
+ return {std::move(tmp), std::move(mapping)};
+}
+
+static void TestEmpty(benchmark::State& state) {
+ int val = 0;
+ for (auto _ : state) {
+ benchmark::DoNotOptimize(val += 1);
+ }
+}
+BENCHMARK(TestEmpty);
+
+static void TestSignal(benchmark::State& state) {
+ auto tmp = makeFile();
+ auto mapping = android::base::MappedFile::FromFd(tmp->fd, 0, 1, PROT_READ);
+
+ int val;
+ for (auto _ : state) {
+ SCOPED_SIGBUS_HANDLER({ break; });
+ val += *mapping->data();
+ }
+}
+BENCHMARK(TestSignal);
+
+static void TestRead(benchmark::State& state) {
+ auto tmp = makeFile();
+ int val = 0;
+ for (auto _ : state) {
+ char c;
+ pread(tmp->fd, &c, sizeof(c), 0);
+ val += c;
+ }
+}
+BENCHMARK(TestRead);
+
+static void TestMapPtrRaw(benchmark::State& state) {
+ auto tmp = makeFile();
+ android::incfs::IncFsFileMap map;
+ map.CreateForceVerification(tmp->fd, 0, 1, tmp->path, true);
+ int val = 0;
+ const uint8_t* prev_block = nullptr;
+ for (auto _ : state) {
+ auto start = static_cast<const uint8_t*>(map.unsafe_data());
+ auto end = start + map.length();
+ val += map.Verify(start, end, &prev_block);
+ }
+}
+BENCHMARK(TestMapPtrRaw);
+
+static void TestMapPtr(benchmark::State& state) {
+ auto tmp = makeFile();
+ android::incfs::IncFsFileMap map;
+ map.CreateForceVerification(tmp->fd, 0, 1, tmp->path, true);
+ int val = 0;
+ for (auto _ : state) {
+ val += map.data<char>().verify();
+ }
+}
+BENCHMARK(TestMapPtr);
+
+static void TestAccess(benchmark::State& state) {
+ auto tmp = makeFile();
+ auto mapping = android::base::MappedFile::FromFd(tmp->fd, 0, 1, PROT_READ);
+ int val = 0;
+ for (auto _ : state) {
+ incfs::access(mapping->data(), [&](auto ptr) { val += *ptr; });
+ }
+}
+BENCHMARK(TestAccess);
+
+static void TestAccessFast(benchmark::State& state) {
+ auto tmp = makeFile();
+ auto mapping = android::base::MappedFile::FromFd(tmp->fd, 0, 1, PROT_READ);
+ int val = 0;
+ incfs::access(mapping->data(), [&](auto ptr) {
+ for (auto _ : state) {
+ val += *ptr;
+ }
+ });
+}
+BENCHMARK(TestAccessFast);
+
+static void TestAccessVal(benchmark::State& state) {
+ auto tmp = makeFile();
+ auto mapping = android::base::MappedFile::FromFd(tmp->fd, 0, 1, PROT_READ);
+ int val = 0;
+ for (auto _ : state) {
+ incfs::access(mapping->data(), [&](auto ptr) { return val += *ptr; });
+ }
+}
+BENCHMARK(TestAccessVal);
+
+static void TestAccessNested(benchmark::State& state) {
+ auto tmp = makeFile();
+ auto mapping = android::base::MappedFile::FromFd(tmp->fd, 0, 1, PROT_READ);
+ int val = 0;
+ incfs::access(nullptr, [&](auto) {
+ for (auto _ : state) {
+ incfs::access(mapping->data(), [&](auto ptr) { val += *ptr; });
+ }
+ });
+}
+BENCHMARK(TestAccessNested);
+
+static void TestAccessDoubleNested(benchmark::State& state) {
+ auto tmp = makeFile();
+ auto mapping = android::base::MappedFile::FromFd(tmp->fd, 0, 1, PROT_READ);
+ int val = 0;
+ incfs::access(nullptr, [&](auto) {
+ incfs::access(nullptr, [&](auto) {
+ for (auto _ : state) {
+ incfs::access(mapping->data(), [&](auto ptr) { val += *ptr; });
+ }
+ });
+ });
+}
+BENCHMARK(TestAccessDoubleNested);
+
+static void TestAccessError(benchmark::State& state) {
+ auto [tmp, mapping] = makeEmptyFileMapping();
+ int val = 0;
+ for (auto _ : state) {
+ incfs::access(mapping->data(), [&](auto ptr) { val += *ptr; });
+ }
+}
+BENCHMARK(TestAccessError);
+
+BENCHMARK_MAIN();
diff --git a/incfs/tests/incfs_test.cpp b/incfs/tests/incfs_test.cpp
index bc02d37..e651f2e 100644
--- a/incfs/tests/incfs_test.cpp
+++ b/incfs/tests/incfs_test.cpp
@@ -15,6 +15,7 @@
*/
#include <android-base/file.h>
+#include <android-base/stringprintf.h>
#include <sys/select.h>
#include <unistd.h>
@@ -26,6 +27,7 @@
using namespace android::incfs;
using namespace std::literals;
+namespace ab = android::base;
struct ScopedUnmount {
std::string path_;
@@ -43,32 +45,6 @@
return {.data = sv.data(), .size = IncFsSize(sv.size())};
}
- int makeFileWithHash(int id) {
- // calculate the required size for two leaf hash blocks
- constexpr auto size =
- (INCFS_DATA_FILE_BLOCK_SIZE / INCFS_MAX_HASH_SIZE + 1) * INCFS_DATA_FILE_BLOCK_SIZE;
-
- // assemble a signature/hashing data for it
- struct __attribute__((packed)) Signature {
- uint32_t version = INCFS_SIGNATURE_VERSION;
- uint32_t hashingSize = sizeof(hashing);
- struct __attribute__((packed)) Hashing {
- uint32_t algo = INCFS_HASH_TREE_SHA256;
- uint8_t log2Blocksize = 12;
- uint32_t saltSize = 0;
- uint32_t rootHashSize = INCFS_MAX_HASH_SIZE;
- char rootHash[INCFS_MAX_HASH_SIZE] = {};
- } hashing;
- uint32_t signingSize = 0;
- } signature;
-
- int res = makeFile(control_, mountPath(test_file_name_), 0555, fileId(id),
- {.size = size,
- .signature = {.data = (char*)&signature, .size = sizeof(signature)}});
- EXPECT_EQ(0, res);
- return res ? -1 : size;
- }
-
static int sizeToPages(int size) {
return (size + INCFS_DATA_FILE_BLOCK_SIZE - 1) / INCFS_DATA_FILE_BLOCK_SIZE;
}
@@ -129,6 +105,94 @@
ASSERT_EQ((int)std::size(blocks), writeBlocks({blocks, std::size(blocks)}));
}
+ template <class ReadStruct>
+ void testWriteBlockAndPageRead() {
+ const auto id = fileId(1);
+ ASSERT_TRUE(control_.logs() >= 0);
+ ASSERT_EQ(0,
+ makeFile(control_, mountPath(test_file_name_), 0555, id,
+ {.size = test_file_size_}));
+ auto fd = openForSpecialOps(control_, fileId(1));
+ ASSERT_GE(fd.get(), 0);
+
+ std::vector<char> data(INCFS_DATA_FILE_BLOCK_SIZE);
+ auto block = DataBlock{
+ .fileFd = fd.get(),
+ .pageIndex = 0,
+ .compression = INCFS_COMPRESSION_KIND_NONE,
+ .dataSize = (uint32_t)data.size(),
+ .data = data.data(),
+ };
+ ASSERT_EQ(1, writeBlocks({&block, 1}));
+
+ std::thread wait_page_read_thread([&]() {
+ std::vector<ReadStruct> reads;
+ auto res = waitForPageReads(control_, std::chrono::seconds(5), &reads);
+ ASSERT_EQ(WaitResult::HaveData, res) << (int)res;
+ ASSERT_FALSE(reads.empty());
+ EXPECT_EQ(0, memcmp(&id, &reads[0].id, sizeof(id)));
+ EXPECT_EQ(0, int(reads[0].block));
+ if constexpr (std::is_same_v<ReadStruct, ReadInfoWithUid>) {
+ if (features() & Features::v2) {
+ EXPECT_NE(kIncFsNoUid, int(reads[0].uid));
+ } else {
+ EXPECT_EQ(kIncFsNoUid, int(reads[0].uid));
+ }
+ }
+ });
+
+ const auto file_path = mountPath(test_file_name_);
+ const android::base::unique_fd readFd(
+ open(file_path.c_str(), O_RDONLY | O_CLOEXEC | O_BINARY));
+ ASSERT_TRUE(readFd >= 0);
+ char buf[INCFS_DATA_FILE_BLOCK_SIZE];
+ ASSERT_TRUE(android::base::ReadFully(readFd, buf, sizeof(buf)));
+ wait_page_read_thread.join();
+ }
+ template <class PendingRead>
+ void testWaitForPendingReads() {
+ const auto id = fileId(1);
+ ASSERT_EQ(0,
+ makeFile(control_, mountPath(test_file_name_), 0555, id,
+ {.size = test_file_size_}));
+
+ std::thread wait_pending_read_thread([&]() {
+ std::vector<PendingRead> pending_reads;
+ ASSERT_EQ(WaitResult::HaveData,
+ waitForPendingReads(control_, std::chrono::seconds(10), &pending_reads));
+ ASSERT_GT(pending_reads.size(), 0u);
+ EXPECT_EQ(0, memcmp(&id, &pending_reads[0].id, sizeof(id)));
+ EXPECT_EQ(0, (int)pending_reads[0].block);
+ if constexpr (std::is_same_v<PendingRead, ReadInfoWithUid>) {
+ if (features() & Features::v2) {
+ EXPECT_NE(kIncFsNoUid, int(pending_reads[0].uid));
+ } else {
+ EXPECT_EQ(kIncFsNoUid, int(pending_reads[0].uid));
+ }
+ }
+
+ auto fd = openForSpecialOps(control_, fileId(1));
+ ASSERT_GE(fd.get(), 0);
+
+ std::vector<char> data(INCFS_DATA_FILE_BLOCK_SIZE);
+ auto block = DataBlock{
+ .fileFd = fd.get(),
+ .pageIndex = 0,
+ .compression = INCFS_COMPRESSION_KIND_NONE,
+ .dataSize = (uint32_t)data.size(),
+ .data = data.data(),
+ };
+ ASSERT_EQ(1, writeBlocks({&block, 1}));
+ });
+
+ const auto file_path = mountPath(test_file_name_);
+ const android::base::unique_fd fd(open(file_path.c_str(), O_RDONLY | O_CLOEXEC | O_BINARY));
+ ASSERT_GE(fd.get(), 0);
+ char buf[INCFS_DATA_FILE_BLOCK_SIZE];
+ ASSERT_TRUE(android::base::ReadFully(fd, buf, sizeof(buf)));
+ wait_pending_read_thread.join();
+ }
+
inline static const int test_file_size_ = INCFS_DATA_FILE_BLOCK_SIZE;
};
@@ -175,9 +239,10 @@
EXPECT_GE(IncFs_GetControlFd(control_, CMD), 0);
EXPECT_GE(IncFs_GetControlFd(control_, PENDING_READS), 0);
EXPECT_GE(IncFs_GetControlFd(control_, LOGS), 0);
+ EXPECT_EQ((features() & Features::v2) != 0, IncFs_GetControlFd(control_, BLOCKS_WRITTEN) >= 0);
auto fds = control_.releaseFds();
- EXPECT_GE(fds.size(), size_t(3));
+ EXPECT_GE(fds.size(), size_t(4));
EXPECT_GE(fds[0].get(), 0);
EXPECT_GE(fds[1].get(), 0);
EXPECT_GE(fds[2].get(), 0);
@@ -185,26 +250,28 @@
EXPECT_LT(IncFs_GetControlFd(control_, CMD), 0);
EXPECT_LT(IncFs_GetControlFd(control_, PENDING_READS), 0);
EXPECT_LT(IncFs_GetControlFd(control_, LOGS), 0);
+ EXPECT_LT(IncFs_GetControlFd(control_, BLOCKS_WRITTEN), 0);
control_.close();
EXPECT_FALSE(control_);
- auto control = IncFs_CreateControl(fds[0].release(), fds[1].release(), fds[2].release());
+ auto control = IncFs_CreateControl(fds[0].release(), fds[1].release(), fds[2].release(), -1);
ASSERT_TRUE(control);
EXPECT_GE(IncFs_GetControlFd(control, CMD), 0);
EXPECT_GE(IncFs_GetControlFd(control, PENDING_READS), 0);
EXPECT_GE(IncFs_GetControlFd(control, LOGS), 0);
- IncFsFd rawFds[3];
+ IncFsFd rawFds[4];
EXPECT_EQ(-EINVAL, IncFs_ReleaseControlFds(nullptr, rawFds, 3));
EXPECT_EQ(-EINVAL, IncFs_ReleaseControlFds(control, nullptr, 3));
EXPECT_EQ(-ERANGE, IncFs_ReleaseControlFds(control, rawFds, 2));
- EXPECT_EQ(3, IncFs_ReleaseControlFds(control, rawFds, 3));
+ EXPECT_EQ(4, IncFs_ReleaseControlFds(control, rawFds, 4));
EXPECT_GE(rawFds[0], 0);
EXPECT_GE(rawFds[1], 0);
EXPECT_GE(rawFds[2], 0);
::close(rawFds[0]);
::close(rawFds[1]);
::close(rawFds[2]);
+ if (rawFds[3] >= 0) ::close(rawFds[3]);
IncFs_DeleteControl(control);
}
@@ -256,7 +323,7 @@
TEST_F(IncFsTest, RootInvalidControl) {
const TemporaryFile tmp_file;
- auto control{createControl(tmp_file.fd, -1, -1)};
+ auto control{createControl(tmp_file.fd, -1, -1, -1)};
ASSERT_EQ("", root(control)) << "Error: " << errno;
}
@@ -342,73 +409,19 @@
}
TEST_F(IncFsTest, WriteBlocksAndPageRead) {
- const auto id = fileId(1);
- ASSERT_TRUE(control_.logs() >= 0);
- ASSERT_EQ(0,
- makeFile(control_, mountPath(test_file_name_), 0555, id, {.size = test_file_size_}));
- auto fd = openForSpecialOps(control_, fileId(1));
- ASSERT_GE(fd.get(), 0);
+ ASSERT_NO_FATAL_FAILURE(testWriteBlockAndPageRead<ReadInfo>());
+}
- std::vector<char> data(INCFS_DATA_FILE_BLOCK_SIZE);
- auto block = DataBlock{
- .fileFd = fd.get(),
- .pageIndex = 0,
- .compression = INCFS_COMPRESSION_KIND_NONE,
- .dataSize = (uint32_t)data.size(),
- .data = data.data(),
- };
- ASSERT_EQ(1, writeBlocks({&block, 1}));
-
- std::thread wait_page_read_thread([&]() {
- std::vector<ReadInfo> reads;
- ASSERT_EQ(WaitResult::HaveData,
- waitForPageReads(control_, std::chrono::seconds(5), &reads));
- ASSERT_FALSE(reads.empty());
- ASSERT_EQ(0, memcmp(&id, &reads[0].id, sizeof(id)));
- ASSERT_EQ(0, int(reads[0].block));
- });
-
- const auto file_path = mountPath(test_file_name_);
- const android::base::unique_fd readFd(open(file_path.c_str(), O_RDONLY | O_CLOEXEC | O_BINARY));
- ASSERT_TRUE(readFd >= 0);
- char buf[INCFS_DATA_FILE_BLOCK_SIZE];
- ASSERT_TRUE(android::base::ReadFully(readFd, buf, sizeof(buf)));
- wait_page_read_thread.join();
+TEST_F(IncFsTest, WriteBlocksAndPageReadWithUid) {
+ ASSERT_NO_FATAL_FAILURE(testWriteBlockAndPageRead<ReadInfoWithUid>());
}
TEST_F(IncFsTest, WaitForPendingReads) {
- const auto id = fileId(1);
- ASSERT_EQ(0,
- makeFile(control_, mountPath(test_file_name_), 0555, id, {.size = test_file_size_}));
+ ASSERT_NO_FATAL_FAILURE(testWaitForPendingReads<ReadInfo>());
+}
- std::thread wait_pending_read_thread([&]() {
- std::vector<ReadInfo> pending_reads;
- ASSERT_EQ(WaitResult::HaveData,
- waitForPendingReads(control_, std::chrono::seconds(10), &pending_reads));
- ASSERT_GT(pending_reads.size(), 0u);
- ASSERT_EQ(0, memcmp(&id, &pending_reads[0].id, sizeof(id)));
- ASSERT_EQ(0, (int)pending_reads[0].block);
-
- auto fd = openForSpecialOps(control_, fileId(1));
- ASSERT_GE(fd.get(), 0);
-
- std::vector<char> data(INCFS_DATA_FILE_BLOCK_SIZE);
- auto block = DataBlock{
- .fileFd = fd.get(),
- .pageIndex = 0,
- .compression = INCFS_COMPRESSION_KIND_NONE,
- .dataSize = (uint32_t)data.size(),
- .data = data.data(),
- };
- ASSERT_EQ(1, writeBlocks({&block, 1}));
- });
-
- const auto file_path = mountPath(test_file_name_);
- const android::base::unique_fd fd(open(file_path.c_str(), O_RDONLY | O_CLOEXEC | O_BINARY));
- ASSERT_GE(fd.get(), 0);
- char buf[INCFS_DATA_FILE_BLOCK_SIZE];
- ASSERT_TRUE(android::base::ReadFully(fd, buf, sizeof(buf)));
- wait_pending_read_thread.join();
+TEST_F(IncFsTest, WaitForPendingReadsWithUid) {
+ ASSERT_NO_FATAL_FAILURE(testWaitForPendingReads<ReadInfoWithUid>());
}
TEST_F(IncFsTest, GetFilledRangesBad) {
@@ -458,6 +471,7 @@
EXPECT_EQ(0, filledRanges.hashRangesCount);
EXPECT_EQ(-ENODATA, IncFs_IsFullyLoaded(fd.get()));
+ EXPECT_EQ(-ENODATA, IncFs_IsEverythingFullyLoaded(control_));
// write one block
std::vector<char> data(INCFS_DATA_FILE_BLOCK_SIZE);
@@ -491,6 +505,7 @@
EXPECT_EQ(0, filledRanges.hashRangesCount);
EXPECT_EQ(-ENODATA, IncFs_IsFullyLoaded(fd.get()));
+ EXPECT_EQ(-ENODATA, IncFs_IsEverythingFullyLoaded(control_));
// append one more block next to the first one
block.pageIndex = 1;
@@ -519,6 +534,7 @@
EXPECT_EQ(0, filledRanges.hashRangesCount);
EXPECT_EQ(-ENODATA, IncFs_IsFullyLoaded(fd.get()));
+ EXPECT_EQ(-ENODATA, IncFs_IsEverythingFullyLoaded(control_));
// now create a gap between filled blocks
block.pageIndex = 3;
@@ -559,6 +575,7 @@
EXPECT_EQ(0, filledRanges.hashRangesCount);
EXPECT_EQ(-ENODATA, IncFs_IsFullyLoaded(fd.get()));
+ EXPECT_EQ(-ENODATA, IncFs_IsEverythingFullyLoaded(control_));
// at last fill the whole file and make sure we report it as having a single range
block.pageIndex = 2;
@@ -586,6 +603,7 @@
EXPECT_EQ(0, filledRanges.hashRangesCount);
EXPECT_EQ(0, IncFs_IsFullyLoaded(fd.get()));
+ EXPECT_EQ(0, IncFs_IsEverythingFullyLoaded(control_));
}
TEST_F(IncFsTest, GetFilledRangesSmallBuffer) {
@@ -717,6 +735,7 @@
EXPECT_EQ(size_t(1), ranges3.hashRanges()[1].size());
EXPECT_EQ(LoadingState::MissingBlocks, isFullyLoaded(fd.get()));
+ EXPECT_EQ(LoadingState::MissingBlocks, isEverythingFullyLoaded(control_));
{
std::vector<char> data(INCFS_DATA_FILE_BLOCK_SIZE);
@@ -736,4 +755,632 @@
}
}
EXPECT_EQ(LoadingState::Full, isFullyLoaded(fd.get()));
+ EXPECT_EQ(LoadingState::Full, isEverythingFullyLoaded(control_));
+}
+
+TEST_F(IncFsTest, BlocksWritten) {
+ if (!(features() & Features::v2)) {
+ GTEST_SKIP() << "test not supported: IncFS is too old";
+ return;
+ }
+ const auto id = fileId(1);
+ ASSERT_EQ(0,
+ makeFile(control_, mountPath(test_file_name_), 0555, id, {.size = test_file_size_}));
+
+ IncFsSize blocksWritten = 0;
+ ASSERT_EQ(0, IncFs_WaitForFsWrittenBlocksChange(control_, 0, &blocksWritten));
+ EXPECT_EQ(0, blocksWritten);
+
+ auto fd = openForSpecialOps(control_, fileId(1));
+ ASSERT_GE(fd.get(), 0);
+
+ std::vector<char> data(INCFS_DATA_FILE_BLOCK_SIZE);
+ auto block = DataBlock{
+ .fileFd = fd.get(),
+ .pageIndex = 0,
+ .compression = INCFS_COMPRESSION_KIND_NONE,
+ .dataSize = (uint32_t)data.size(),
+ .data = data.data(),
+ };
+ ASSERT_EQ(1, writeBlocks({&block, 1}));
+
+ ASSERT_EQ(0, IncFs_WaitForFsWrittenBlocksChange(control_, 0, &blocksWritten));
+ EXPECT_EQ(1, blocksWritten);
+}
+
+TEST_F(IncFsTest, Timeouts) {
+ if (!(features() & Features::v2)) {
+ GTEST_SKIP() << "test not supported: IncFS is too old";
+ return;
+ }
+
+ IncFsUidReadTimeouts timeouts[2] = {{1, 1000, 2000, 3000}, {2, 1000, 3000, 4000}};
+
+ EXPECT_EQ(0, IncFs_SetUidReadTimeouts(control_, timeouts, std::size(timeouts)));
+
+ IncFsUidReadTimeouts outTimeouts[3];
+
+ size_t outSize = 1;
+ EXPECT_EQ(-E2BIG, IncFs_GetUidReadTimeouts(control_, outTimeouts, &outSize));
+ EXPECT_EQ(size_t(2), outSize);
+
+ outSize = 3;
+ EXPECT_EQ(0, IncFs_GetUidReadTimeouts(control_, outTimeouts, &outSize));
+ EXPECT_EQ(size_t(2), outSize);
+
+ EXPECT_EQ(0, memcmp(timeouts, outTimeouts, 2 * sizeof(timeouts[0])));
+}
+
+TEST_F(IncFsTest, CompletionNoFiles) {
+ if (!(features() & Features::v2)) {
+ GTEST_SKIP() << "test not supported: IncFS is too old";
+ return;
+ }
+
+ size_t count = 0;
+ EXPECT_EQ(0, IncFs_ListIncompleteFiles(control_, nullptr, &count));
+ EXPECT_EQ(size_t(0), count);
+ EXPECT_EQ(0, IncFs_WaitForLoadingComplete(control_, 0));
+}
+
+TEST_F(IncFsTest, CompletionOneFile) {
+ if (!(features() & Features::v2)) {
+ GTEST_SKIP() << "test not supported: IncFS is too old";
+ return;
+ }
+
+ const auto id = fileId(1);
+ ASSERT_EQ(0,
+ makeFile(control_, mountPath(test_file_name_), 0555, id, {.size = test_file_size_}));
+
+ size_t count = 0;
+ EXPECT_EQ(-E2BIG, IncFs_ListIncompleteFiles(control_, nullptr, &count));
+ EXPECT_EQ(size_t(1), count);
+ EXPECT_EQ(-ETIMEDOUT, IncFs_WaitForLoadingComplete(control_, 0));
+
+ IncFsFileId ids[2];
+ count = 2;
+ EXPECT_EQ(0, IncFs_ListIncompleteFiles(control_, ids, &count));
+ EXPECT_EQ(size_t(1), count);
+ EXPECT_EQ(id, ids[0]);
+
+ auto fd = openForSpecialOps(control_, id);
+ ASSERT_GE(fd.get(), 0);
+ std::vector<char> data(INCFS_DATA_FILE_BLOCK_SIZE);
+ auto block = DataBlock{
+ .fileFd = fd.get(),
+ .pageIndex = 0,
+ .compression = INCFS_COMPRESSION_KIND_NONE,
+ .dataSize = (uint32_t)data.size(),
+ .data = data.data(),
+ };
+ ASSERT_EQ(1, writeBlocks({&block, 1}));
+
+ count = 2;
+ EXPECT_EQ(0, IncFs_ListIncompleteFiles(control_, ids, &count));
+ EXPECT_EQ(size_t(0), count);
+ EXPECT_EQ(0, IncFs_WaitForLoadingComplete(control_, 0));
+}
+
+TEST_F(IncFsTest, CompletionMultiple) {
+ if (!(features() & Features::v2)) {
+ GTEST_SKIP() << "test not supported: IncFS is too old";
+ return;
+ }
+
+ const auto id = fileId(1);
+ ASSERT_EQ(0,
+ makeFile(control_, mountPath(test_file_name_), 0555, id, {.size = test_file_size_}));
+
+ size_t count = 0;
+ EXPECT_EQ(-E2BIG, IncFs_ListIncompleteFiles(control_, nullptr, &count));
+ EXPECT_EQ(size_t(1), count);
+ EXPECT_EQ(-ETIMEDOUT, IncFs_WaitForLoadingComplete(control_, 0));
+
+ // fill the existing file but add another one
+ const auto id2 = fileId(2);
+ ASSERT_EQ(0, makeFile(control_, mountPath("test2"), 0555, id2, {.size = test_file_size_}));
+
+ IncFsFileId ids[2];
+ count = 2;
+ EXPECT_EQ(0, IncFs_ListIncompleteFiles(control_, ids, &count));
+ EXPECT_EQ(size_t(2), count);
+ EXPECT_EQ(id, ids[0]);
+ EXPECT_EQ(id2, ids[1]);
+
+ auto fd = openForSpecialOps(control_, id);
+ ASSERT_GE(fd.get(), 0);
+ std::vector<char> data(INCFS_DATA_FILE_BLOCK_SIZE);
+ auto block = DataBlock{
+ .fileFd = fd.get(),
+ .pageIndex = 0,
+ .compression = INCFS_COMPRESSION_KIND_NONE,
+ .dataSize = (uint32_t)data.size(),
+ .data = data.data(),
+ };
+ ASSERT_EQ(1, writeBlocks({&block, 1}));
+
+ count = 2;
+ EXPECT_EQ(0, IncFs_ListIncompleteFiles(control_, ids, &count));
+ EXPECT_EQ(size_t(1), count);
+ EXPECT_EQ(id2, ids[0]);
+ EXPECT_EQ(-ETIMEDOUT, IncFs_WaitForLoadingComplete(control_, 0));
+}
+
+TEST_F(IncFsTest, CompletionWait) {
+ if (!(features() & Features::v2)) {
+ GTEST_SKIP() << "test not supported: IncFS is too old";
+ return;
+ }
+
+ ASSERT_EQ(0,
+ makeFile(control_, mountPath("test1"), 0555, fileId(1),
+ {.size = INCFS_DATA_FILE_BLOCK_SIZE}));
+ ASSERT_EQ(0,
+ makeFile(control_, mountPath("test2"), 0555, fileId(2),
+ {.size = INCFS_DATA_FILE_BLOCK_SIZE}));
+ ASSERT_EQ(0,
+ makeFile(control_, mountPath("test3"), 0555, fileId(3),
+ {.size = INCFS_DATA_FILE_BLOCK_SIZE}));
+
+ std::atomic<int> res = -1;
+ auto waiter = std::thread([&] { res = IncFs_WaitForLoadingComplete(control_, 5 * 1000); });
+
+ std::vector<char> data(INCFS_DATA_FILE_BLOCK_SIZE);
+
+ {
+ auto fd = openForSpecialOps(control_, fileId(1));
+ ASSERT_GE(fd.get(), 0);
+ auto block = DataBlock{
+ .fileFd = fd.get(),
+ .pageIndex = 0,
+ .compression = INCFS_COMPRESSION_KIND_NONE,
+ .dataSize = (uint32_t)data.size(),
+ .data = data.data(),
+ };
+ ASSERT_EQ(1, writeBlocks({&block, 1}));
+ }
+ ASSERT_TRUE(res == -1);
+
+ {
+ auto fd = openForSpecialOps(control_, fileId(3));
+ ASSERT_GE(fd.get(), 0);
+ auto block = DataBlock{
+ .fileFd = fd.get(),
+ .pageIndex = 0,
+ .compression = INCFS_COMPRESSION_KIND_NONE,
+ .dataSize = (uint32_t)data.size(),
+ .data = data.data(),
+ };
+ ASSERT_EQ(1, writeBlocks({&block, 1}));
+ }
+ ASSERT_TRUE(res == -1);
+
+ {
+ auto fd = openForSpecialOps(control_, fileId(2));
+ ASSERT_GE(fd.get(), 0);
+ auto block = DataBlock{
+ .fileFd = fd.get(),
+ .pageIndex = 0,
+ .compression = INCFS_COMPRESSION_KIND_NONE,
+ .dataSize = (uint32_t)data.size(),
+ .data = data.data(),
+ };
+ ASSERT_EQ(1, writeBlocks({&block, 1}));
+ }
+
+ waiter.join();
+
+ auto listIncomplete = [&] {
+ IncFsFileId ids[3];
+ size_t count = 3;
+ if (IncFs_ListIncompleteFiles(control_, ids, &count) != 0) {
+ return "error listing incomplete files"s;
+ }
+ auto res = ab::StringPrintf("[%d]", int(count));
+ for (size_t i = 0; i < count; ++i) {
+ ab::StringAppendF(&res, " %s", toString(ids[i]).c_str());
+ }
+ return res;
+ };
+ EXPECT_EQ(0, res) << "Incomplete files: " << listIncomplete();
+}
+
+TEST_F(IncFsTest, GetBlockCounts) {
+ if (!(features() & Features::v2)) {
+ GTEST_SKIP() << "test not supported: IncFS is too old";
+ return;
+ }
+
+ const auto id = fileId(1);
+ ASSERT_EQ(0,
+ makeFile(control_, mountPath(test_file_name_), 0555, id,
+ {.size = 20 * INCFS_DATA_FILE_BLOCK_SIZE + 3}));
+
+ IncFsBlockCounts counts = {};
+ EXPECT_EQ(0,
+ IncFs_GetFileBlockCountByPath(control_, mountPath(test_file_name_).c_str(), &counts));
+ EXPECT_EQ(21, counts.totalDataBlocks);
+ EXPECT_EQ(0, counts.filledDataBlocks);
+ EXPECT_EQ(0, counts.totalHashBlocks);
+ EXPECT_EQ(0, counts.filledHashBlocks);
+
+ EXPECT_EQ(0, IncFs_GetFileBlockCountById(control_, id, &counts));
+ EXPECT_EQ(21, counts.totalDataBlocks);
+ EXPECT_EQ(0, counts.filledDataBlocks);
+ EXPECT_EQ(0, counts.totalHashBlocks);
+ EXPECT_EQ(0, counts.filledHashBlocks);
+
+ auto fd = openForSpecialOps(control_, id);
+ ASSERT_GE(fd.get(), 0);
+ std::vector<char> data(INCFS_DATA_FILE_BLOCK_SIZE);
+ auto block = DataBlock{
+ .fileFd = fd.get(),
+ .pageIndex = 3,
+ .compression = INCFS_COMPRESSION_KIND_NONE,
+ .dataSize = (uint32_t)data.size(),
+ .data = data.data(),
+ };
+ ASSERT_EQ(1, writeBlocks({&block, 1}));
+
+ EXPECT_EQ(0,
+ IncFs_GetFileBlockCountByPath(control_, mountPath(test_file_name_).c_str(), &counts));
+ EXPECT_EQ(21, counts.totalDataBlocks);
+ EXPECT_EQ(1, counts.filledDataBlocks);
+ EXPECT_EQ(0, counts.totalHashBlocks);
+ EXPECT_EQ(0, counts.filledHashBlocks);
+
+ EXPECT_EQ(0, IncFs_GetFileBlockCountById(control_, id, &counts));
+ EXPECT_EQ(21, counts.totalDataBlocks);
+ EXPECT_EQ(1, counts.filledDataBlocks);
+ EXPECT_EQ(0, counts.totalHashBlocks);
+ EXPECT_EQ(0, counts.filledHashBlocks);
+}
+
+TEST_F(IncFsTest, GetBlockCountsHash) {
+ if (!(features() & Features::v2)) {
+ GTEST_SKIP() << "test not supported: IncFS is too old";
+ return;
+ }
+
+ auto size = makeFileWithHash(1);
+ ASSERT_GT(size, 0);
+
+ IncFsBlockCounts counts = {};
+ EXPECT_EQ(0,
+ IncFs_GetFileBlockCountByPath(control_, mountPath(test_file_name_).c_str(), &counts));
+ EXPECT_EQ(sizeToPages(size), counts.totalDataBlocks);
+ EXPECT_EQ(0, counts.filledDataBlocks);
+ EXPECT_EQ(3, counts.totalHashBlocks);
+ EXPECT_EQ(0, counts.filledHashBlocks);
+
+ ASSERT_NO_FATAL_FAILURE(writeTestRanges(1, size));
+
+ EXPECT_EQ(0,
+ IncFs_GetFileBlockCountByPath(control_, mountPath(test_file_name_).c_str(), &counts));
+ EXPECT_EQ(sizeToPages(size), counts.totalDataBlocks);
+ EXPECT_EQ(4, counts.filledDataBlocks);
+ EXPECT_EQ(3, counts.totalHashBlocks);
+ EXPECT_EQ(2, counts.filledHashBlocks);
+}
+
+TEST_F(IncFsTest, ReserveSpace) {
+ auto size = makeFileWithHash(1);
+ ASSERT_GT(size, 0);
+
+ EXPECT_EQ(-ENOENT,
+ IncFs_ReserveSpaceByPath(control_, mountPath("1"s += test_file_name_).c_str(), size));
+ EXPECT_EQ(0, IncFs_ReserveSpaceByPath(control_, mountPath(test_file_name_).c_str(), size));
+ EXPECT_EQ(0, IncFs_ReserveSpaceByPath(control_, mountPath(test_file_name_).c_str(), 2 * size));
+ EXPECT_EQ(0, IncFs_ReserveSpaceByPath(control_, mountPath(test_file_name_).c_str(), 2 * size));
+ EXPECT_EQ(0,
+ IncFs_ReserveSpaceByPath(control_, mountPath(test_file_name_).c_str(),
+ kTrimReservedSpace));
+ EXPECT_EQ(0,
+ IncFs_ReserveSpaceByPath(control_, mountPath(test_file_name_).c_str(),
+ kTrimReservedSpace));
+
+ EXPECT_EQ(-ENOENT, IncFs_ReserveSpaceById(control_, fileId(2), size));
+ EXPECT_EQ(0, IncFs_ReserveSpaceById(control_, fileId(1), size));
+ EXPECT_EQ(0, IncFs_ReserveSpaceById(control_, fileId(1), 2 * size));
+ EXPECT_EQ(0, IncFs_ReserveSpaceById(control_, fileId(1), 2 * size));
+ EXPECT_EQ(0, IncFs_ReserveSpaceById(control_, fileId(1), kTrimReservedSpace));
+ EXPECT_EQ(0, IncFs_ReserveSpaceById(control_, fileId(1), kTrimReservedSpace));
+}
+
+TEST_F(IncFsTest, ForEachFile) {
+ const auto incompleteSupported = (features() & Features::v2) != 0;
+ EXPECT_EQ(-EINVAL, IncFs_ForEachFile(nullptr, nullptr, nullptr));
+ EXPECT_EQ(-EINVAL, IncFs_ForEachIncompleteFile(nullptr, nullptr, nullptr));
+ EXPECT_EQ(-EINVAL, IncFs_ForEachFile(control_, nullptr, nullptr));
+ EXPECT_EQ(-EINVAL, IncFs_ForEachIncompleteFile(control_, nullptr, nullptr));
+ EXPECT_EQ(0, IncFs_ForEachFile(control_, nullptr, [](auto, auto, auto) { return true; }));
+ EXPECT_EQ(incompleteSupported ? 0 : -ENOTSUP,
+ IncFs_ForEachIncompleteFile(control_, nullptr,
+ [](auto, auto, auto) { return true; }));
+ EXPECT_EQ(0, IncFs_ForEachFile(control_, this, [](auto, auto, auto) { return true; }));
+ EXPECT_EQ(incompleteSupported ? 0 : -ENOTSUP,
+ IncFs_ForEachIncompleteFile(control_, this, [](auto, auto, auto) { return true; }));
+
+ int res = makeFile(control_, mountPath("incomplete.txt"), 0555, fileId(1),
+ {.metadata = metadata("md")});
+ ASSERT_EQ(res, 0);
+
+ EXPECT_EQ(1, IncFs_ForEachFile(control_, this, [](auto, auto context, auto id) {
+ auto self = (IncFsTest*)context;
+ EXPECT_EQ(self->fileId(1), id);
+ return true;
+ }));
+ EXPECT_EQ(incompleteSupported ? 0 : -ENOTSUP,
+ IncFs_ForEachIncompleteFile(control_, this, [](auto, auto, auto) { return true; }));
+
+ auto size = makeFileWithHash(2);
+ ASSERT_GT(size, 0);
+
+ EXPECT_EQ(1, IncFs_ForEachFile(control_, this, [](auto, auto context, auto id) {
+ auto self = (IncFsTest*)context;
+ EXPECT_TRUE(id == self->fileId(1) || id == self->fileId(2));
+ return false;
+ }));
+ EXPECT_EQ(2, IncFs_ForEachFile(control_, this, [](auto, auto context, auto id) {
+ auto self = (IncFsTest*)context;
+ EXPECT_TRUE(id == self->fileId(1) || id == self->fileId(2));
+ return true;
+ }));
+ EXPECT_EQ(incompleteSupported ? 1 : -ENOTSUP,
+ IncFs_ForEachIncompleteFile(control_, this, [](auto, auto context, auto id) {
+ auto self = (IncFsTest*)context;
+ EXPECT_EQ(self->fileId(2), id);
+ return true;
+ }));
+}
+
+TEST(CStrWrapperTest, EmptyStringView) {
+ ASSERT_STREQ("", details::c_str({}).get());
+ ASSERT_STREQ("", details::c_str({nullptr, 0}).get());
+}
+
+class IncFsGetMetricsTest : public IncFsTestBase {
+protected:
+ int32_t getReadTimeout() override { return 100 /* 0.1 second */; }
+};
+
+TEST_F(IncFsGetMetricsTest, MetricsWithNoEvents) {
+ if (!(features() & Features::v2)) {
+ GTEST_SKIP() << "test not supported: IncFS is too old";
+ return;
+ }
+ IncFsLastReadError lastReadError = {.id = fileId(-1),
+ .timestampUs = static_cast<uint64_t>(-1),
+ .block = static_cast<IncFsBlockIndex>(-1),
+ .errorNo = static_cast<uint32_t>(-1),
+ .uid = static_cast<IncFsUid>(-1)};
+ EXPECT_EQ(0, IncFs_GetLastReadError(control_, &lastReadError));
+ // All fields should be zero
+ EXPECT_EQ(FileId{}, lastReadError.id);
+ EXPECT_EQ(0, (int)lastReadError.timestampUs);
+ EXPECT_EQ(0, (int)lastReadError.block);
+ EXPECT_EQ(0, (int)lastReadError.errorNo);
+ EXPECT_EQ(0, (int)lastReadError.uid);
+
+ IncFsMetrics incfsMetrics = {10, 10, 10, 10, 10, 10, 10, 10, 10};
+ EXPECT_EQ(0, IncFs_GetMetrics(metrics_key_.c_str(), &incfsMetrics));
+ EXPECT_EQ(0, (int)incfsMetrics.readsDelayedMin);
+ EXPECT_EQ(0, (int)incfsMetrics.readsDelayedMinUs);
+ EXPECT_EQ(0, (int)incfsMetrics.readsDelayedPending);
+ EXPECT_EQ(0, (int)incfsMetrics.readsDelayedPendingUs);
+ EXPECT_EQ(0, (int)incfsMetrics.readsFailedHashVerification);
+ EXPECT_EQ(0, (int)incfsMetrics.readsFailedOther);
+ EXPECT_EQ(0, (int)incfsMetrics.readsFailedTimedOut);
+}
+
+TEST_F(IncFsGetMetricsTest, MetricsWithReadsTimeOut) {
+ if (!(features() & Features::v2)) {
+ GTEST_SKIP() << "test not supported: IncFS is too old";
+ return;
+ }
+ const auto id = fileId(1);
+ ASSERT_EQ(0,
+ makeFile(control_, mountPath(test_file_name_), 0555, id,
+ {.size = INCFS_DATA_FILE_BLOCK_SIZE}));
+
+ const auto file_path = mountPath(test_file_name_);
+ const android::base::unique_fd fd(open(file_path.c_str(), O_RDONLY | O_CLOEXEC | O_BINARY));
+ ASSERT_GE(fd.get(), 0);
+ // Read should timeout immediately
+ char buf[INCFS_DATA_FILE_BLOCK_SIZE];
+ EXPECT_FALSE(android::base::ReadFully(fd, buf, sizeof(buf)));
+ IncFsLastReadError lastReadError = {.id = fileId(-1),
+ .timestampUs = static_cast<uint64_t>(-1),
+ .block = static_cast<IncFsBlockIndex>(-1),
+ .errorNo = static_cast<uint32_t>(-1),
+ .uid = static_cast<IncFsUid>(-1)};
+ EXPECT_EQ(0, IncFs_GetLastReadError(control_, &lastReadError));
+ EXPECT_EQ(id, lastReadError.id);
+ EXPECT_TRUE(lastReadError.timestampUs > 0);
+ EXPECT_EQ(0, (int)lastReadError.block);
+ EXPECT_EQ(-ETIME, (int)lastReadError.errorNo);
+ EXPECT_EQ((int)getuid(), (int)lastReadError.uid);
+
+ IncFsMetrics incfsMetrics = {10, 10, 10, 10, 10, 10, 10, 10, 10};
+ EXPECT_EQ(0, IncFs_GetMetrics(metrics_key_.c_str(), &incfsMetrics));
+ EXPECT_EQ(0, (int)incfsMetrics.readsDelayedMin);
+ EXPECT_EQ(0, (int)incfsMetrics.readsDelayedMinUs);
+ EXPECT_EQ(0, (int)incfsMetrics.readsDelayedPending);
+ EXPECT_EQ(0, (int)incfsMetrics.readsDelayedPendingUs);
+ EXPECT_EQ(0, (int)incfsMetrics.readsFailedHashVerification);
+ EXPECT_EQ(0, (int)incfsMetrics.readsFailedOther);
+ EXPECT_EQ(1, (int)incfsMetrics.readsFailedTimedOut);
+}
+
+TEST_F(IncFsGetMetricsTest, MetricsWithHashFailure) {
+ if (!(features() & Features::v2)) {
+ GTEST_SKIP() << "test not supported: IncFS is too old";
+ return;
+ }
+ auto size = makeFileWithHash(1);
+ ASSERT_GT(size, 0);
+ // Make data and hash mismatch
+ const auto id = fileId(1);
+ char data[INCFS_DATA_FILE_BLOCK_SIZE]{static_cast<char>(-1)};
+ char hashData[INCFS_DATA_FILE_BLOCK_SIZE]{};
+ auto wfd = openForSpecialOps(control_, id);
+ ASSERT_GE(wfd.get(), 0);
+ DataBlock blocks[] = {{
+ .fileFd = wfd.get(),
+ .pageIndex = 0,
+ .compression = INCFS_COMPRESSION_KIND_NONE,
+ .dataSize = INCFS_DATA_FILE_BLOCK_SIZE,
+ .data = data,
+ },
+ {
+ .fileFd = wfd.get(),
+ // first hash page
+ .pageIndex = 0,
+ .compression = INCFS_COMPRESSION_KIND_NONE,
+ .dataSize = INCFS_DATA_FILE_BLOCK_SIZE,
+ .kind = INCFS_BLOCK_KIND_HASH,
+ .data = hashData,
+ },
+ {
+ .fileFd = wfd.get(),
+ .pageIndex = 2,
+ .compression = INCFS_COMPRESSION_KIND_NONE,
+ .dataSize = INCFS_DATA_FILE_BLOCK_SIZE,
+ .kind = INCFS_BLOCK_KIND_HASH,
+ .data = hashData,
+ }};
+ ASSERT_EQ((int)std::size(blocks), writeBlocks({blocks, std::size(blocks)}));
+ const auto file_path = mountPath(test_file_name_);
+ const android::base::unique_fd fd(open(file_path.c_str(), O_RDONLY | O_CLOEXEC | O_BINARY));
+ ASSERT_GE(fd.get(), 0);
+ // Read should fail at reading the first block due to hash failure
+ char buf[INCFS_DATA_FILE_BLOCK_SIZE];
+ EXPECT_FALSE(android::base::ReadFully(fd, buf, sizeof(buf)));
+ IncFsLastReadError lastReadError = {.id = fileId(-1),
+ .timestampUs = static_cast<uint64_t>(-1),
+ .block = static_cast<IncFsBlockIndex>(-1),
+ .errorNo = static_cast<uint32_t>(-1),
+ .uid = static_cast<IncFsUid>(-1)};
+ EXPECT_EQ(0, IncFs_GetLastReadError(control_, &lastReadError));
+ EXPECT_EQ(0, std::strcmp(lastReadError.id.data, id.data));
+ EXPECT_TRUE(lastReadError.timestampUs > 0);
+ EXPECT_EQ(0, (int)lastReadError.block);
+ EXPECT_EQ(-EBADMSG, (int)lastReadError.errorNo);
+ EXPECT_EQ((int)getuid(), (int)lastReadError.uid);
+
+ IncFsMetrics incfsMetrics = {10, 10, 10, 10, 10, 10, 10, 10, 10};
+ EXPECT_EQ(0, IncFs_GetMetrics(metrics_key_.c_str(), &incfsMetrics));
+ EXPECT_EQ(0, (int)incfsMetrics.readsDelayedMin);
+ EXPECT_EQ(0, (int)incfsMetrics.readsDelayedMinUs);
+ EXPECT_EQ(0, (int)incfsMetrics.readsDelayedPending);
+ EXPECT_EQ(0, (int)incfsMetrics.readsDelayedPendingUs);
+ EXPECT_EQ(1, (int)incfsMetrics.readsFailedHashVerification);
+ EXPECT_EQ(0, (int)incfsMetrics.readsFailedOther);
+ EXPECT_EQ(0, (int)incfsMetrics.readsFailedTimedOut);
+}
+
+TEST_F(IncFsGetMetricsTest, MetricsWithReadsDelayed) {
+ if (!(features() & Features::v2)) {
+ GTEST_SKIP() << "test not supported: IncFS is too old";
+ return;
+ }
+ const auto id = fileId(1);
+ int testFileSize = INCFS_DATA_FILE_BLOCK_SIZE;
+ int waitBeforeWriteUs = 10000;
+ ASSERT_EQ(0, makeFile(control_, mountPath(test_file_name_), 0555, id, {.size = testFileSize}));
+ std::thread wait_before_write_thread([&]() {
+ std::vector<ReadInfoWithUid> pending_reads;
+ ASSERT_EQ(WaitResult::HaveData,
+ waitForPendingReads(control_, std::chrono::seconds(1), &pending_reads));
+ // Additional wait is needed for the kernel jiffies counter to increment
+ usleep(waitBeforeWriteUs);
+ auto fd = openForSpecialOps(control_, fileId(1));
+ ASSERT_GE(fd.get(), 0);
+ std::vector<char> data(INCFS_DATA_FILE_BLOCK_SIZE);
+ auto block = DataBlock{
+ .fileFd = fd.get(),
+ .pageIndex = 0,
+ .compression = INCFS_COMPRESSION_KIND_NONE,
+ .dataSize = (uint32_t)data.size(),
+ .data = data.data(),
+ };
+ ASSERT_EQ(1, writeBlocks({&block, 1}));
+ });
+
+ const auto file_path = mountPath(test_file_name_);
+ const android::base::unique_fd fd(open(file_path.c_str(), O_RDONLY | O_CLOEXEC | O_BINARY));
+ ASSERT_GE(fd.get(), 0);
+ char buf[testFileSize];
+ EXPECT_TRUE(android::base::ReadFully(fd, buf, sizeof(buf)));
+ wait_before_write_thread.join();
+
+ IncFsLastReadError lastReadError = {.id = fileId(-1), 1, 1, 1, 1};
+ EXPECT_EQ(0, IncFs_GetLastReadError(control_, &lastReadError));
+ EXPECT_EQ(FileId{}, lastReadError.id);
+ EXPECT_EQ(0, (int)lastReadError.timestampUs);
+ EXPECT_EQ(0, (int)lastReadError.block);
+ EXPECT_EQ(0, (int)lastReadError.errorNo);
+ EXPECT_EQ(0, (int)lastReadError.uid);
+
+ IncFsMetrics incfsMetrics = {10, 10, 10, 10, 10, 10, 10, 10, 10};
+ EXPECT_EQ(0, IncFs_GetMetrics(metrics_key_.c_str(), &incfsMetrics));
+ EXPECT_EQ(0, (int)incfsMetrics.readsDelayedMin);
+ EXPECT_EQ(0, (int)incfsMetrics.readsDelayedMinUs);
+ EXPECT_EQ(1, (int)incfsMetrics.readsDelayedPending);
+ EXPECT_TRUE((int)incfsMetrics.readsDelayedPendingUs > 0);
+ EXPECT_EQ(0, (int)incfsMetrics.readsFailedHashVerification);
+ EXPECT_EQ(0, (int)incfsMetrics.readsFailedOther);
+ EXPECT_EQ(0, (int)incfsMetrics.readsFailedTimedOut);
+}
+
+TEST_F(IncFsGetMetricsTest, MetricsWithReadsDelayedPerUidTimeout) {
+ if (!(features() & Features::v2)) {
+ GTEST_SKIP() << "test not supported: IncFS is too old";
+ return;
+ }
+ const auto id = fileId(1);
+ int testFileSize = INCFS_DATA_FILE_BLOCK_SIZE;
+ ASSERT_EQ(0, makeFile(control_, mountPath(test_file_name_), 0555, id, {.size = testFileSize}));
+
+ auto fdToFill = openForSpecialOps(control_, fileId(1));
+ ASSERT_GE(fdToFill.get(), 0);
+ std::vector<char> data(INCFS_DATA_FILE_BLOCK_SIZE);
+ auto block = DataBlock{
+ .fileFd = fdToFill.get(),
+ .pageIndex = 0,
+ .compression = INCFS_COMPRESSION_KIND_NONE,
+ .dataSize = (uint32_t)data.size(),
+ .data = data.data(),
+ };
+ ASSERT_EQ(1, writeBlocks({&block, 1}));
+
+ // Set per-uid read timeout then read
+ uint32_t readTimeoutUs = 1000000;
+ IncFsUidReadTimeouts timeouts[1] = {
+ {static_cast<IncFsUid>(getuid()), readTimeoutUs, readTimeoutUs, readTimeoutUs}};
+ ASSERT_EQ(0, IncFs_SetUidReadTimeouts(control_, timeouts, std::size(timeouts)));
+ const auto file_path = mountPath(test_file_name_);
+ const android::base::unique_fd fd(open(file_path.c_str(), O_RDONLY | O_CLOEXEC | O_BINARY));
+ char buf[testFileSize];
+ ASSERT_GE(fd.get(), 0);
+ ASSERT_TRUE(android::base::ReadFully(fd, buf, sizeof(buf)));
+
+ IncFsLastReadError lastReadError = {.id = fileId(-1), 1, 1, 1, 1};
+ EXPECT_EQ(0, IncFs_GetLastReadError(control_, &lastReadError));
+ EXPECT_EQ(FileId{}, lastReadError.id);
+ EXPECT_EQ(0, (int)lastReadError.timestampUs);
+ EXPECT_EQ(0, (int)lastReadError.block);
+ EXPECT_EQ(0, (int)lastReadError.errorNo);
+ EXPECT_EQ(0, (int)lastReadError.uid);
+
+ IncFsMetrics incfsMetrics = {10, 10, 10, 10, 10, 10, 10, 10, 10};
+ EXPECT_EQ(0, IncFs_GetMetrics(metrics_key_.c_str(), &incfsMetrics));
+ EXPECT_EQ(1, (int)incfsMetrics.readsDelayedMin);
+ EXPECT_EQ(readTimeoutUs, (uint32_t)incfsMetrics.readsDelayedMinUs);
+ EXPECT_EQ(0, (int)incfsMetrics.readsDelayedPending);
+ EXPECT_EQ(0, (int)incfsMetrics.readsDelayedPendingUs);
+ EXPECT_EQ(0, (int)incfsMetrics.readsFailedHashVerification);
+ EXPECT_EQ(0, (int)incfsMetrics.readsFailedOther);
+ EXPECT_EQ(0, (int)incfsMetrics.readsFailedTimedOut);
}
diff --git a/incfs/tests/include/IncFsTestBase.h b/incfs/tests/include/IncFsTestBase.h
index 520c736..5f8c311 100644
--- a/incfs/tests/include/IncFsTestBase.h
+++ b/incfs/tests/include/IncFsTestBase.h
@@ -42,9 +42,11 @@
if (!enabled()) {
GTEST_SKIP() << "test not supported: IncFS is not enabled";
} else {
+ metrics_key_ = path::baseName(image_dir_path_);
control_ = mount(image_dir_path_, mount_dir_path_,
MountOptions{.readLogBufferPages = 4,
- .defaultReadTimeoutMs = getReadTimeout()});
+ .defaultReadTimeoutMs = getReadTimeout(),
+ .sysfsName = metrics_key_.c_str()});
ASSERT_TRUE(control_.cmd() >= 0) << "Expected >= 0 got " << control_.cmd();
ASSERT_TRUE(control_.pendingReads() >= 0);
ASSERT_TRUE(control_.logs() >= 0);
@@ -84,13 +86,40 @@
return path::join(mount_dir_path_, std::forward<Paths>(paths)...);
}
+ virtual int makeFileWithHash(int id) {
+ // calculate the required size for two leaf hash blocks
+ constexpr auto size =
+ (INCFS_DATA_FILE_BLOCK_SIZE / INCFS_MAX_HASH_SIZE + 1) * INCFS_DATA_FILE_BLOCK_SIZE;
+
+ // assemble a signature/hashing data for it
+ struct __attribute__((packed)) Signature {
+ uint32_t version = INCFS_SIGNATURE_VERSION;
+ uint32_t hashingSize = sizeof(hashing);
+ struct __attribute__((packed)) Hashing {
+ uint32_t algo = INCFS_HASH_TREE_SHA256;
+ uint8_t log2Blocksize = 12;
+ uint32_t saltSize = 0;
+ uint32_t rootHashSize = INCFS_MAX_HASH_SIZE;
+ char rootHash[INCFS_MAX_HASH_SIZE] = {};
+ } hashing;
+ uint32_t signingSize = 0;
+ } signature;
+
+ int res = makeFile(control_, mountPath(test_file_name_), 0555, fileId(id),
+ {.size = size,
+ .signature = {.data = (char*)&signature, .size = sizeof(signature)}});
+ EXPECT_EQ(0, res);
+ return res ? -1 : size;
+ }
+
std::string mount_dir_path_;
std::optional<TemporaryDir> tmp_dir_for_mount_;
std::string image_dir_path_;
std::optional<TemporaryDir> tmp_dir_for_image_;
inline static const std::string_view test_file_name_ = "test.txt";
inline static const std::string_view test_dir_name_ = "test_dir";
+ std::string metrics_key_;
Control control_;
};
-} // namespace android::incfs
\ No newline at end of file
+} // namespace android::incfs
diff --git a/incfs/util/include/util/map_ptr.h b/incfs/util/include/util/map_ptr.h
index cda80f9..304540f 100644
--- a/incfs/util/include/util/map_ptr.h
+++ b/incfs/util/include/util/map_ptr.h
@@ -16,13 +16,16 @@
#pragma once
-#include <memory>
-#include <shared_mutex>
-#include <vector>
-
#include <android-base/logging.h>
#include <android-base/off64_t.h>
+#include <atomic>
+#include <iterator>
+#include <memory>
+#include <shared_mutex>
+#include <type_traits>
+#include <vector>
+
#ifdef __ANDROID__
#include <linux/incrementalfs.h>
#endif
@@ -54,15 +57,25 @@
// This always uses MAP_SHARED.
class IncFsFileMap final {
public:
- IncFsFileMap();
+ IncFsFileMap() noexcept;
IncFsFileMap(IncFsFileMap&&) noexcept;
IncFsFileMap& operator =(IncFsFileMap&&) noexcept;
- ~IncFsFileMap();
+ ~IncFsFileMap() noexcept;
// Initializes the map. Does not take ownership of the file descriptor.
// Returns whether or not the file was able to be memory-mapped.
bool Create(int fd, off64_t offset, size_t length, const char* file_name);
+ // Same thing, but allows verification to be disabled when `verify` is `false`, and enabled when
+ // `verify` is true and the file resides on IncFs.
+ bool Create(int fd, off64_t offset, size_t length, const char* file_name, bool verify);
+
+ // Same thing, but allows verification to be disabled when `verify` is `false`, and enabled when
+ // `verify` is true regardless of whether the file resides on IncFs (used for benchmarks and
+ // testing).
+ bool CreateForceVerification(int fd, off64_t offset, size_t length, const char* file_name,
+ bool verify);
+
template <typename T = void>
map_ptr<T> data() const {
return map_ptr<T>(verification_enabled_ ? this : nullptr,
@@ -74,14 +87,13 @@
off64_t offset() const;
const char* file_name() const;
-private:
- DISALLOW_COPY_AND_ASSIGN(IncFsFileMap);
-
-#ifdef __ANDROID__
+public:
// Returns whether the data range is entirely present on IncFs.
bool Verify(const uint8_t* const& data_start, const uint8_t* const& data_end,
const uint8_t** prev_verified_block) const;
-#endif
+
+private:
+ DISALLOW_COPY_AND_ASSIGN(IncFsFileMap);
using bucket_t = uint8_t;
static constexpr size_t kBucketBits = sizeof(bucket_t) * 8U;
@@ -342,15 +354,15 @@
// Returns true if the elements are completely present; otherwise, returns false.
template <typename T1 = T, NotVoid<T1> = 0, bool V1 = Verified, IsUnverified<V1> = 0>
bool verify(size_t n = 1) const {
+ if (ptr_ == nullptr) {
+ return false;
+ }
+
#ifdef __ANDROID__
if (LIKELY(map_ == nullptr)) {
return ptr_ != nullptr;
}
- if (ptr_ == nullptr) {
- return false;
- }
-
const size_t verify_size = sizeof(T) * n;
LIBINCFS_MAP_PTR_DEBUG_CODE(if (sizeof(T) <= verify_size) verified_ = true;);
diff --git a/incfs/util/map_ptr.cpp b/incfs/util/map_ptr.cpp
index 0b3e08b..f391506 100644
--- a/incfs/util/map_ptr.cpp
+++ b/incfs/util/map_ptr.cpp
@@ -25,10 +25,10 @@
#include "util/map_ptr.h"
namespace android::incfs {
-IncFsFileMap::IncFsFileMap() = default;
+IncFsFileMap::IncFsFileMap() noexcept = default;
IncFsFileMap::IncFsFileMap(IncFsFileMap&&) noexcept = default;
IncFsFileMap& IncFsFileMap::operator =(IncFsFileMap&&) noexcept = default;
-IncFsFileMap::~IncFsFileMap() = default;
+IncFsFileMap::~IncFsFileMap() noexcept = default;
const void* IncFsFileMap::unsafe_data() const {
return map_->getDataPtr();
@@ -46,25 +46,36 @@
return map_->getFileName();
}
+bool IncFsFileMap::Create(int fd, off64_t offset, size_t length, const char* file_name) {
+ return Create(fd, offset, length, file_name, true /* verify */);
+}
+
#ifdef __ANDROID__
-bool IsVerificationEnabled(int fd) {
+static bool IsVerificationEnabled(int fd) {
return isIncFsFd(fd) && isFullyLoaded(fd) != LoadingState::Full;
}
using data_block_index_t = uint32_t;
-data_block_index_t get_block_index(const uint8_t* ptr, const uint8_t* start_block_ptr) {
+static data_block_index_t get_block_index(const uint8_t* ptr, const uint8_t* start_block_ptr) {
return (ptr - start_block_ptr) / INCFS_DATA_FILE_BLOCK_SIZE;
}
-bool IncFsFileMap::Create(int fd, off64_t offset, size_t length, const char* file_name) {
+bool IncFsFileMap::Create(int fd, off64_t offset, size_t length, const char* file_name,
+ bool verify) {
+ return CreateForceVerification(fd, offset, length, file_name,
+ verify && IsVerificationEnabled(fd));
+}
+
+bool IncFsFileMap::CreateForceVerification(int fd, off64_t offset, size_t length,
+ const char* file_name, bool verify) {
map_ = std::make_unique<android::FileMap>();
if (!map_->create(file_name, fd, offset, length, true /* readOnly */)) {
return false;
}
fd_ = fd;
- verification_enabled_ = IsVerificationEnabled(fd);
+ verification_enabled_ = verify;
if (verification_enabled_) {
// Initialize the block cache with enough buckets to hold all of the blocks within the
// memory-mapped region.
@@ -125,10 +136,22 @@
}
#else
-bool IncFsFileMap::Create(int fd, off64_t offset, size_t length, const char* file_name) {
+bool IncFsFileMap::Create(int fd, off64_t offset, size_t length, const char* file_name,
+ bool verify) {
+ return CreateForceVerification(fd, offset, length, file_name, verify);
+}
+
+bool IncFsFileMap::CreateForceVerification(int fd, off64_t offset, size_t length,
+ const char* file_name, bool /* verify */) {
map_ = std::make_unique<android::FileMap>();
return map_->create(file_name, fd, offset, length, true /* readOnly */);
}
+
+bool IncFsFileMap::Verify(const uint8_t* const& /* data_start */,
+ const uint8_t* const& /* data_end */,
+ const uint8_t** /* prev_verified_block */) const {
+ return true;
+}
#endif
} // namespace android::incfs
\ No newline at end of file
diff --git a/libdataloader/Android.bp b/libdataloader/Android.bp
index 7bd2696..eb9adf4 100644
--- a/libdataloader/Android.bp
+++ b/libdataloader/Android.bp
@@ -44,7 +44,9 @@
"liblog",
"libutils",
],
- static_libs: ["libnativehelper_lazy"],
+ static_libs: [
+ "libnativehelper_lazy",
+ ],
tidy: true,
tidy_checks: [
"android-*",
diff --git a/libdataloader/DataLoaderConnector.cpp b/libdataloader/DataLoaderConnector.cpp
index da10484..f5f82ea 100644
--- a/libdataloader/DataLoaderConnector.cpp
+++ b/libdataloader/DataLoaderConnector.cpp
@@ -30,12 +30,14 @@
#include "ManagedDataLoader.h"
#include "dataloader.h"
#include "incfs.h"
+#include "path.h"
namespace {
using namespace std::literals;
using ReadInfo = android::dataloader::ReadInfo;
+using ReadInfoWithUid = android::dataloader::ReadInfoWithUid;
using FileId = android::incfs::FileId;
using RawMetadata = android::incfs::RawMetadata;
@@ -70,6 +72,7 @@
jfieldID controlCmd;
jfieldID controlPendingReads;
jfieldID controlLog;
+ jfieldID controlBlocksWritten;
jfieldID paramsType;
jfieldID paramsPackageName;
@@ -114,8 +117,6 @@
constants.DATA_LOADER_UNRECOVERABLE =
GetStaticIntFieldValueOrDie(env, listener, "DATA_LOADER_UNRECOVERABLE");
- CHECK(constants.DATA_LOADER_UNRECOVERABLE == DATA_LOADER_UNRECOVERABLE);
-
auto packageInstaller = (jclass)FindClassOrDie(env, "android/content/pm/PackageInstaller");
constants.DATA_LOADER_TYPE_NONE =
@@ -161,6 +162,8 @@
controlPendingReads = GetFieldIDOrDie(env, incControl, "pendingReads",
"Landroid/os/ParcelFileDescriptor;");
controlLog = GetFieldIDOrDie(env, incControl, "log", "Landroid/os/ParcelFileDescriptor;");
+ controlBlocksWritten = GetFieldIDOrDie(env, incControl, "blocksWritten",
+ "Landroid/os/ParcelFileDescriptor;");
auto params = FindClassOrDie(env, "android/content/pm/DataLoaderParamsParcel");
paramsType = GetFieldIDOrDie(env, params, "type", "I");
@@ -207,6 +210,17 @@
return ids;
}
+bool checkAndClearJavaException(JNIEnv* env, std::string_view method) {
+ if (!env->ExceptionCheck()) {
+ return false;
+ }
+
+ LOG(ERROR) << "Java exception during DataLoader::" << method;
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ return true;
+}
+
bool reportStatusViaCallback(JNIEnv* env, jobject listener, jint storageId, jint status) {
if (listener == nullptr) {
ALOGE("No listener object to talk to IncrementalService. "
@@ -231,14 +245,10 @@
using DataLoaderConnectorsMap = std::unordered_map<int, DataLoaderConnectorPtr>;
struct Globals {
- Globals() {
- managedDataLoaderFactory =
- new android::dataloader::details::DataLoaderFactoryImpl([](auto jvm, auto) {
- return std::make_unique<android::dataloader::ManagedDataLoader>(jvm);
- });
- }
+ Globals() { managedDataLoaderFactory = new android::dataloader::ManagedDataLoaderFactory(); }
DataLoaderFactory* managedDataLoaderFactory = nullptr;
+ DataLoaderFactory* legacyDataLoaderFactory = nullptr;
DataLoaderFactory* dataLoaderFactory = nullptr;
std::mutex dataLoaderConnectorsLock;
@@ -250,6 +260,8 @@
std::thread logLooperThread;
std::vector<ReadInfo> pendingReads;
std::vector<ReadInfo> pageReads;
+ std::vector<ReadInfoWithUid> pendingReadsWithUid;
+ std::vector<ReadInfoWithUid> pageReadsWithUid;
};
static Globals& globals() {
@@ -306,36 +318,68 @@
DataLoaderConnector(const DataLoaderConnector&) = delete;
DataLoaderConnector(const DataLoaderConnector&&) = delete;
virtual ~DataLoaderConnector() {
+ if (mDataLoader && mDataLoader->onDestroy) {
+ mDataLoader->onDestroy(mDataLoader);
+ checkAndClearJavaException(__func__);
+ }
+ mDataLoader = nullptr;
+
JNIEnv* env = GetOrAttachJNIEnvironment(mJvm);
+ const auto& jni = jniIds(env);
+ reportStatusViaCallback(env, mListener, mStorageId, jni.constants.DATA_LOADER_DESTROYED);
+
env->DeleteGlobalRef(mService);
env->DeleteGlobalRef(mServiceConnector);
env->DeleteGlobalRef(mCallbackControl);
env->DeleteGlobalRef(mListener);
} // to avoid delete-non-virtual-dtor
+ bool tryFactory(DataLoaderFactory* factory, bool withFeatures,
+ const DataLoaderParamsPair& params, jobject managedParams) {
+ if (!factory) {
+ return true;
+ }
+
+ // Let's try the non-default first.
+ mDataLoader = factory->onCreate(factory, ¶ms.ndkDataLoaderParams(), this, this, mJvm,
+ mService, managedParams);
+ if (checkAndClearJavaException(__func__)) {
+ return false;
+ }
+ if (!mDataLoader) {
+ return true;
+ }
+
+ mDataLoaderFeatures = withFeatures && mDataLoader->getFeatures
+ ? mDataLoader->getFeatures(mDataLoader)
+ : DATA_LOADER_FEATURE_NONE;
+ if (mDataLoaderFeatures & DATA_LOADER_FEATURE_UID) {
+ ALOGE("DataLoader supports UID");
+ CHECK(mDataLoader->onPageReadsWithUid);
+ CHECK(mDataLoader->onPendingReadsWithUid);
+ }
+ return true;
+ }
+
bool onCreate(const DataLoaderParamsPair& params, jobject managedParams) {
CHECK(mDataLoader == nullptr);
- if (auto factory = globals().dataLoaderFactory) {
- // Let's try the non-default first.
- mDataLoader = factory->onCreate(factory, ¶ms.ndkDataLoaderParams(), this, this,
- mJvm, mService, managedParams);
- if (checkAndClearJavaException(__func__)) {
- return false;
- }
+ if (!mDataLoader &&
+ !tryFactory(globals().dataLoaderFactory, /*withFeatures=*/true, params,
+ managedParams)) {
+ return false;
}
-
- if (!mDataLoader) {
- // Didn't work, let's try the default.
- auto factory = globals().managedDataLoaderFactory;
- mDataLoader = factory->onCreate(factory, ¶ms.ndkDataLoaderParams(), this, this,
- mJvm, mService, managedParams);
- if (checkAndClearJavaException(__func__)) {
- return false;
- }
+ if (!mDataLoader &&
+ !tryFactory(globals().legacyDataLoaderFactory, /*withFeatures=*/false, params,
+ managedParams)) {
+ return false;
}
-
+ if (!mDataLoader &&
+ !tryFactory(globals().managedDataLoaderFactory, /*withFeatures=*/false, params,
+ managedParams)) {
+ return false;
+ }
if (!mDataLoader) {
return false;
}
@@ -344,7 +388,7 @@
}
bool onStart() {
CHECK(mDataLoader);
- bool result = mDataLoader->onStart(mDataLoader);
+ bool result = !mDataLoader->onStart || mDataLoader->onStart(mDataLoader);
if (checkAndClearJavaException(__func__)) {
result = false;
}
@@ -360,26 +404,21 @@
std::lock_guard{mPendingReadsLooperBusy}; // NOLINT
std::lock_guard{mLogLooperBusy}; // NOLINT
- mDataLoader->onStop(mDataLoader);
- checkAndClearJavaException(__func__);
- }
- void onDestroy() {
- CHECK(mDataLoader);
- mDataLoader->onDestroy(mDataLoader);
+ if (mDataLoader->onStop) {
+ mDataLoader->onStop(mDataLoader);
+ }
checkAndClearJavaException(__func__);
}
bool onPrepareImage(const android::dataloader::DataLoaderInstallationFiles& addedFiles) {
CHECK(mDataLoader);
- bool result =
+ bool result = !mDataLoader->onPrepareImage ||
mDataLoader->onPrepareImage(mDataLoader, addedFiles.data(), addedFiles.size());
- if (checkAndClearJavaException(__func__)) {
- result = false;
- }
return result;
}
- int onPendingReadsLooperEvent(std::vector<ReadInfo>& pendingReads) {
+ template <class ReadInfoType>
+ int onPendingReadsLooperEvent(std::vector<ReadInfoType>& pendingReads) {
CHECK(mDataLoader);
std::lock_guard lock{mPendingReadsLooperBusy};
while (mRunning.load(std::memory_order_relaxed)) {
@@ -389,11 +428,23 @@
pendingReads.empty()) {
return 1;
}
- mDataLoader->onPendingReads(mDataLoader, pendingReads.data(), pendingReads.size());
+ if constexpr (std::is_same_v<ReadInfoType, ReadInfo>) {
+ if (mDataLoader->onPendingReads) {
+ mDataLoader->onPendingReads(mDataLoader, pendingReads.data(),
+ pendingReads.size());
+ }
+ } else {
+ if (mDataLoader->onPendingReadsWithUid) {
+ mDataLoader->onPendingReadsWithUid(mDataLoader, pendingReads.data(),
+ pendingReads.size());
+ }
+ }
}
return 1;
}
- int onLogLooperEvent(std::vector<ReadInfo>& pageReads) {
+
+ template <class ReadInfoType>
+ int onLogLooperEvent(std::vector<ReadInfoType>& pageReads) {
CHECK(mDataLoader);
std::lock_guard lock{mLogLooperBusy};
while (mRunning.load(std::memory_order_relaxed)) {
@@ -403,11 +454,40 @@
pageReads.empty()) {
return 1;
}
- mDataLoader->onPageReads(mDataLoader, pageReads.data(), pageReads.size());
+ if constexpr (std::is_same_v<ReadInfoType, ReadInfo>) {
+ if (mDataLoader->onPageReads) {
+ mDataLoader->onPageReads(mDataLoader, pageReads.data(), pageReads.size());
+ }
+ } else {
+ if (mDataLoader->onPageReadsWithUid) {
+ mDataLoader->onPageReadsWithUid(mDataLoader, pageReads.data(),
+ pageReads.size());
+ }
+ }
}
return 1;
}
+ int onPendingReadsLooperEvent(std::vector<ReadInfo>& pendingReads,
+ std::vector<ReadInfoWithUid>& pendingReadsWithUid) {
+ CHECK(mDataLoader);
+ if (mDataLoaderFeatures & DATA_LOADER_FEATURE_UID) {
+ return this->onPendingReadsLooperEvent(pendingReadsWithUid);
+ } else {
+ return this->onPendingReadsLooperEvent(pendingReads);
+ }
+ }
+
+ int onLogLooperEvent(std::vector<ReadInfo>& pageReads,
+ std::vector<ReadInfoWithUid>& pageReadsWithUid) {
+ CHECK(mDataLoader);
+ if (mDataLoaderFeatures & DATA_LOADER_FEATURE_UID) {
+ return this->onLogLooperEvent(pageReadsWithUid);
+ } else {
+ return this->onLogLooperEvent(pageReads);
+ }
+ }
+
void writeData(jstring name, jlong offsetBytes, jlong lengthBytes, jobject incomingFd) const {
CHECK(mCallbackControl);
JNIEnv* env = GetOrAttachJNIEnvironment(mJvm);
@@ -445,25 +525,28 @@
}
bool reportStatus(DataLoaderStatus status) {
- if (status < DATA_LOADER_FIRST_STATUS || DATA_LOADER_LAST_STATUS < status) {
- ALOGE("Unable to report invalid status. status=%d", status);
- return false;
- }
JNIEnv* env = GetOrAttachJNIEnvironment(mJvm);
- return reportStatusViaCallback(env, mListener, mStorageId, status);
+ const auto& jni = jniIds(env);
+
+ jint osStatus;
+ switch (status) {
+ case DATA_LOADER_UNAVAILABLE:
+ osStatus = jni.constants.DATA_LOADER_UNAVAILABLE;
+ break;
+ case DATA_LOADER_UNRECOVERABLE:
+ osStatus = jni.constants.DATA_LOADER_UNRECOVERABLE;
+ break;
+ default: {
+ ALOGE("Unable to report invalid status. status=%d", status);
+ return false;
+ }
+ }
+ return reportStatusViaCallback(env, mListener, mStorageId, osStatus);
}
bool checkAndClearJavaException(std::string_view method) const {
JNIEnv* env = GetOrAttachJNIEnvironment(mJvm);
-
- if (!env->ExceptionCheck()) {
- return false;
- }
-
- LOG(ERROR) << "Java exception during DataLoader::" << method;
- env->ExceptionDescribe();
- env->ExceptionClear();
- return true;
+ return ::checkAndClearJavaException(env, method);
}
const UniqueControl& control() const { return mControl; }
@@ -480,6 +563,7 @@
UniqueControl const mControl;
::DataLoader* mDataLoader = nullptr;
+ DataLoaderFeatures mDataLoaderFeatures = DATA_LOADER_FEATURE_NONE;
std::mutex mPendingReadsLooperBusy;
std::mutex mLogLooperBusy;
@@ -492,7 +576,8 @@
return 0;
}
auto&& dataLoaderConnector = (DataLoaderConnector*)data;
- return dataLoaderConnector->onPendingReadsLooperEvent(globals().pendingReads);
+ return dataLoaderConnector->onPendingReadsLooperEvent(globals().pendingReads,
+ globals().pendingReadsWithUid);
}
static int onLogLooperEvent(int fd, int events, void* data) {
@@ -501,7 +586,7 @@
return 0;
}
auto&& dataLoaderConnector = (DataLoaderConnector*)data;
- return dataLoaderConnector->onLogLooperEvent(globals().pageReads);
+ return dataLoaderConnector->onLogLooperEvent(globals().pageReads, globals().pageReadsWithUid);
}
static int createFdFromManaged(JNIEnv* env, jobject pfd) {
@@ -534,7 +619,10 @@
auto pr = createFdFromManaged(env,
env->GetObjectField(managedIncControl, jni.controlPendingReads));
auto log = createFdFromManaged(env, env->GetObjectField(managedIncControl, jni.controlLog));
- return android::incfs::createControl(cmd, pr, log);
+ auto blocksWritten =
+ createFdFromManaged(env,
+ env->GetObjectField(managedIncControl, jni.controlBlocksWritten));
+ return android::incfs::createControl(cmd, pr, log, blocksWritten);
}
DataLoaderParamsPair::DataLoaderParamsPair(android::dataloader::DataLoaderParams&& dataLoaderParams)
@@ -568,14 +656,14 @@
}
static void pendingReadsLooperThread() {
- constexpr auto kTimeoutMsecs = 60 * 1000;
+ constexpr auto kTimeoutMsecs = -1;
while (!globals().stopped) {
pendingReadsLooper().pollAll(kTimeoutMsecs);
}
}
static void logLooperThread() {
- constexpr auto kTimeoutMsecs = 60 * 1000;
+ constexpr auto kTimeoutMsecs = -1;
while (!globals().stopped) {
logLooper().pollAll(kTimeoutMsecs);
}
@@ -615,6 +703,11 @@
void DataLoader_Initialize(struct ::DataLoaderFactory* factory) {
CHECK(factory) << "DataLoader factory is invalid.";
+ globals().legacyDataLoaderFactory = factory;
+}
+
+void DataLoader_Initialize_WithFeatures(struct ::DataLoaderFactory* factory) {
+ CHECK(factory) << "DataLoader factory is invalid.";
globals().dataLoaderFactory = factory;
}
@@ -667,15 +760,23 @@
}
}
auto nativeControl = createIncFsControlFromManaged(env, control);
- ALOGI("DataLoader::create1 cmd: %d|%s", nativeControl.cmd(),
- pathFromFd(nativeControl.cmd()).c_str());
- ALOGI("DataLoader::create1 pendingReads: %d|%s", nativeControl.pendingReads(),
- pathFromFd(nativeControl.pendingReads()).c_str());
- ALOGI("DataLoader::create1 log: %d|%s", nativeControl.logs(),
- pathFromFd(nativeControl.logs()).c_str());
+ if (nativeControl) {
+ using namespace android::incfs;
+ ALOGI("DataLoader::create incremental fds: %d/%d/%d/%d", nativeControl.cmd(),
+ nativeControl.pendingReads(), nativeControl.logs(), nativeControl.blocksWritten());
+ auto cmdPath = pathFromFd(nativeControl.cmd());
+ auto dir = path::dirName(cmdPath);
+ ALOGI("DataLoader::create incremental dir: %s, files: %s/%s/%s/%s",
+ details::c_str(dir).get(), details::c_str(path::baseName(cmdPath)).get(),
+ details::c_str(path::baseName(pathFromFd(nativeControl.pendingReads()))).get(),
+ details::c_str(path::baseName(pathFromFd(nativeControl.logs()))).get(),
+ details::c_str(path::baseName(pathFromFd(nativeControl.blocksWritten()))).get());
+ } else {
+ ALOGI("DataLoader::create no incremental control");
+ }
auto nativeParams = DataLoaderParamsPair::createFromManaged(env, params);
- ALOGI("DataLoader::create2: %d|%s|%s|%s", nativeParams.dataLoaderParams().type(),
+ ALOGI("DataLoader::create params: %d|%s|%s|%s", nativeParams.dataLoaderParams().type(),
nativeParams.dataLoaderParams().packageName().c_str(),
nativeParams.dataLoaderParams().className().c_str(),
nativeParams.dataLoaderParams().arguments().c_str());
@@ -694,6 +795,7 @@
auto dataLoaderConnector =
std::make_unique<DataLoaderConnector>(env, service, storageId, std::move(nativeControl),
serviceConnector, callbackControl, listener);
+ bool created = dataLoaderConnector->onCreate(nativeParams, params);
{
std::lock_guard lock{globals().dataLoaderConnectorsLock};
auto [dlIt, dlInserted] =
@@ -703,7 +805,8 @@
ALOGE("id(%d): already exist, skipping creation.", storageId);
return false;
}
- if (!dlIt->second->onCreate(nativeParams, params)) {
+
+ if (!created) {
globals().dataLoaderConnectors.erase(dlIt);
// Enable the reporter.
reportUnavailableOnExit.reset(listener);
@@ -729,8 +832,6 @@
std::unique_ptr<_jobject, decltype(destroyAndReportUnavailable)>
destroyAndReportUnavailableOnExit(nullptr, destroyAndReportUnavailable);
- const UniqueControl* control;
- jobject listener;
DataLoaderConnectorPtr dataLoaderConnector;
{
std::lock_guard lock{globals().dataLoaderConnectorsLock};
@@ -739,41 +840,44 @@
ALOGE("Failed to start id(%d): not found", storageId);
return false;
}
-
- listener = dlIt->second->getListenerLocalRef(env);
-
dataLoaderConnector = dlIt->second;
- if (!dataLoaderConnector->onStart()) {
- ALOGE("Failed to start id(%d): onStart returned false", storageId);
- destroyAndReportUnavailableOnExit.reset(listener);
- return false;
- }
+ }
+ const UniqueControl* control = &(dataLoaderConnector->control());
+ jobject listener = dataLoaderConnector->getListenerLocalRef(env);
- control = &(dataLoaderConnector->control());
-
- // Create loopers while we are under lock.
- if (control->pendingReads() >= 0 && !globals().pendingReadsLooperThread.joinable()) {
- pendingReadsLooper();
- globals().pendingReadsLooperThread = std::thread(&pendingReadsLooperThread);
- }
- if (control->logs() >= 0 && !globals().logLooperThread.joinable()) {
- logLooper();
- globals().logLooperThread = std::thread(&logLooperThread);
- }
+ if (!dataLoaderConnector->onStart()) {
+ ALOGE("Failed to start id(%d): onStart returned false", storageId);
+ destroyAndReportUnavailableOnExit.reset(listener);
+ return false;
}
if (control->pendingReads() >= 0) {
- pendingReadsLooper().addFd(control->pendingReads(), android::Looper::POLL_CALLBACK,
- android::Looper::EVENT_INPUT, &onPendingReadsLooperEvent,
- dataLoaderConnector.get());
- pendingReadsLooper().wake();
+ auto&& looper = pendingReadsLooper();
+ if (!globals().pendingReadsLooperThread.joinable()) {
+ std::lock_guard lock{globals().dataLoaderConnectorsLock};
+ if (!globals().pendingReadsLooperThread.joinable()) {
+ globals().pendingReadsLooperThread = std::thread(&pendingReadsLooperThread);
+ }
+ }
+
+ looper.addFd(control->pendingReads(), android::Looper::POLL_CALLBACK,
+ android::Looper::EVENT_INPUT, &onPendingReadsLooperEvent,
+ dataLoaderConnector.get());
+ looper.wake();
}
if (control->logs() >= 0) {
- logLooper().addFd(control->logs(), android::Looper::POLL_CALLBACK,
- android::Looper::EVENT_INPUT, &onLogLooperEvent,
- dataLoaderConnector.get());
- logLooper().wake();
+ auto&& looper = logLooper();
+ if (!globals().logLooperThread.joinable()) {
+ std::lock_guard lock{globals().dataLoaderConnectorsLock};
+ if (!globals().logLooperThread.joinable()) {
+ globals().logLooperThread = std::thread(&logLooperThread);
+ }
+ }
+
+ looper.addFd(control->logs(), android::Looper::POLL_CALLBACK, android::Looper::EVENT_INPUT,
+ &onLogLooperEvent, dataLoaderConnector.get());
+ looper.wake();
}
const auto& jni = jniIds(env);
@@ -782,17 +886,8 @@
return true;
}
-jobject DataLoaderService_OnStop_NoStatus(JNIEnv* env, jint storageId) {
- const UniqueControl* control;
- {
- std::lock_guard lock{globals().dataLoaderConnectorsLock};
- auto dlIt = globals().dataLoaderConnectors.find(storageId);
- if (dlIt == globals().dataLoaderConnectors.end()) {
- return nullptr;
- }
- control = &(dlIt->second->control());
- }
-
+static void DataLoaderService_OnStop_NoStatus(const UniqueControl* control,
+ const DataLoaderConnectorPtr& dataLoaderConnector) {
if (control->pendingReads() >= 0) {
pendingReadsLooper().removeFd(control->pendingReads());
pendingReadsLooper().wake();
@@ -801,30 +896,25 @@
logLooper().removeFd(control->logs());
logLooper().wake();
}
+ dataLoaderConnector->onStop();
+}
- jobject listener = nullptr;
+bool DataLoaderService_OnStop(JNIEnv* env, jint storageId) {
+ DataLoaderConnectorPtr dataLoaderConnector;
{
std::lock_guard lock{globals().dataLoaderConnectorsLock};
auto dlIt = globals().dataLoaderConnectors.find(storageId);
if (dlIt == globals().dataLoaderConnectors.end()) {
ALOGI("Failed to stop id(%d): not found", storageId);
- return nullptr;
+ return true;
}
-
- listener = dlIt->second->getListenerLocalRef(env);
-
- auto&& dataLoaderConnector = dlIt->second;
- dataLoaderConnector->onStop();
+ dataLoaderConnector = dlIt->second;
}
- return listener;
-}
+ const UniqueControl* control = &(dataLoaderConnector->control());
+ jobject listener = dataLoaderConnector->getListenerLocalRef(env);
-bool DataLoaderService_OnStop(JNIEnv* env, jint storageId) {
- auto listener = DataLoaderService_OnStop_NoStatus(env, storageId);
- if (listener == nullptr) {
- ALOGI("Failed to stop id(%d): not found", storageId);
- return true;
- }
+ // Just stop.
+ DataLoaderService_OnStop_NoStatus(control, dataLoaderConnector);
const auto& jni = jniIds(env);
reportStatusViaCallback(env, listener, storageId, jni.constants.DATA_LOADER_STOPPED);
@@ -832,36 +922,26 @@
return true;
}
-jobject DataLoaderService_OnDestroy_NoStatus(JNIEnv* env, jint storageId) {
- jobject listener = DataLoaderService_OnStop_NoStatus(env, storageId);
- if (!listener) {
- return nullptr;
- }
-
+bool DataLoaderService_OnDestroy(JNIEnv* env, jint storageId) {
+ DataLoaderConnectorPtr dataLoaderConnector;
{
std::lock_guard lock{globals().dataLoaderConnectorsLock};
auto dlIt = globals().dataLoaderConnectors.find(storageId);
if (dlIt == globals().dataLoaderConnectors.end()) {
- return nullptr;
+ ALOGI("Failed to destroy id(%d): not found", storageId);
+ return true;
}
-
- auto&& dataLoaderConnector = dlIt->second;
- dataLoaderConnector->onDestroy();
+ dataLoaderConnector = std::move(dlIt->second);
globals().dataLoaderConnectors.erase(dlIt);
}
+ const UniqueControl* control = &(dataLoaderConnector->control());
- return listener;
-}
-
-bool DataLoaderService_OnDestroy(JNIEnv* env, jint storageId) {
- jobject listener = DataLoaderService_OnDestroy_NoStatus(env, storageId);
- if (!listener) {
- ALOGI("Failed to remove id(%d): not found", storageId);
- return true;
- }
-
- const auto& jni = jniIds(env);
- reportStatusViaCallback(env, listener, storageId, jni.constants.DATA_LOADER_DESTROYED);
+ // Stop/destroy.
+ DataLoaderService_OnStop_NoStatus(control, dataLoaderConnector);
+ // This will destroy the last instance of the DataLoaderConnectorPtr and should trigger the
+ // destruction of the DataLoader. However if there are any hanging instances, the destruction
+ // will be postponed. E.g. OnPrepareImage in progress at the same time we call OnDestroy.
+ dataLoaderConnector = {};
return true;
}
@@ -931,7 +1011,6 @@
bool DataLoaderService_OnPrepareImage(JNIEnv* env, jint storageId, jobjectArray addedFiles,
jobjectArray removedFiles) {
- jobject listener;
DataLoaderConnectorPtr dataLoaderConnector;
{
std::lock_guard lock{globals().dataLoaderConnectorsLock};
@@ -940,14 +1019,20 @@
ALOGE("Failed to handle onPrepareImage for id(%d): not found", storageId);
return false;
}
- listener = dlIt->second->getListenerLocalRef(env);
dataLoaderConnector = dlIt->second;
}
+ jobject listener = dataLoaderConnector->getListenerLocalRef(env);
auto addedFilesPair = DataLoaderInstallationFilesPair::createFromManaged(env, addedFiles);
bool result = dataLoaderConnector->onPrepareImage(addedFilesPair.ndkFiles());
const auto& jni = jniIds(env);
+
+ if (checkAndClearJavaException(env, "onPrepareImage")) {
+ reportStatusViaCallback(env, listener, storageId, jni.constants.DATA_LOADER_UNAVAILABLE);
+ return false;
+ }
+
reportStatusViaCallback(env, listener, storageId,
result ? jni.constants.DATA_LOADER_IMAGE_READY
: jni.constants.DATA_LOADER_IMAGE_NOT_READY);
diff --git a/libdataloader/ManagedDataLoader.cpp b/libdataloader/ManagedDataLoader.cpp
index 8f3e888..c277d45 100644
--- a/libdataloader/ManagedDataLoader.cpp
+++ b/libdataloader/ManagedDataLoader.cpp
@@ -91,18 +91,32 @@
} // namespace
-ManagedDataLoader::ManagedDataLoader(JavaVM* jvm) : mJvm(jvm) {
+ManagedDataLoader::ManagedDataLoader(JavaVM* jvm, jobject dataLoader)
+ : mJvm(jvm), mDataLoader(dataLoader) {
CHECK(mJvm);
+
+ LegacyDataLoader::onStart = [](auto) -> bool { return true; };
+ LegacyDataLoader::onStop = [](auto) {};
+ LegacyDataLoader::onDestroy = [](LegacyDataLoader* self) {
+ auto me = static_cast<ManagedDataLoader*>(self);
+ me->onDestroy();
+ delete me;
+ };
+ LegacyDataLoader::onPrepareImage = [](auto* self, const auto addedFiles[],
+ int addedFilesCount) -> bool {
+ return static_cast<ManagedDataLoader*>(self)->onPrepareImage(
+ DataLoaderInstallationFiles(addedFiles, addedFilesCount));
+ };
+ LegacyDataLoader::onPendingReads = [](auto, auto, auto) {};
+ LegacyDataLoader::onPageReads = [](auto, auto, auto) {};
}
-bool ManagedDataLoader::onCreate(const android::dataloader::DataLoaderParams&,
- android::dataloader::FilesystemConnectorPtr ifs,
- android::dataloader::StatusListenerPtr listener,
- android::dataloader::ServiceConnectorPtr service,
- android::dataloader::ServiceParamsPtr params) {
- CHECK(!mDataLoader);
-
- JNIEnv* env = GetJNIEnvironment(mJvm);
+LegacyDataLoader* ManagedDataLoader::create(JavaVM* jvm,
+ android::dataloader::FilesystemConnectorPtr ifs,
+ android::dataloader::StatusListenerPtr listener,
+ android::dataloader::ServiceConnectorPtr service,
+ android::dataloader::ServiceParamsPtr params) {
+ JNIEnv* env = GetJNIEnvironment(jvm);
const auto& jni = jniIds(env);
jobject dlp = env->NewObject(jni.dataLoaderParams, jni.dataLoaderParamsConstruct, params);
@@ -112,14 +126,16 @@
auto dataLoader = env->CallObjectMethod(service, jni.dataLoaderServiceOnCreateDataLoader, dlp);
if (!dataLoader) {
LOG(ERROR) << "Failed to create Java DataLoader.";
- return false;
+ return nullptr;
}
if (env->ExceptionCheck()) {
- return false;
+ return nullptr;
+ }
+ if (!env->CallBooleanMethod(dataLoader, jni.dataLoaderOnCreate, dlp, ifsc)) {
+ return nullptr;
}
- mDataLoader = env->NewGlobalRef(dataLoader);
- return env->CallBooleanMethod(mDataLoader, jni.dataLoaderOnCreate, dlp, ifsc);
+ return new ManagedDataLoader(jvm, env->NewGlobalRef(dataLoader));
}
void ManagedDataLoader::onDestroy() {
@@ -163,4 +179,18 @@
return env->CallBooleanMethod(mDataLoader, jni.dataLoaderOnPrepareImage, jaddedFiles, nullptr);
}
+ManagedDataLoaderFactory::ManagedDataLoaderFactory() {
+ ::DataLoaderFactory::onCreate =
+ [](::DataLoaderFactory* self, const ::DataLoaderParams* ndkParams,
+ ::DataLoaderFilesystemConnectorPtr fsConnector,
+ ::DataLoaderStatusListenerPtr statusListener, ::DataLoaderServiceVmPtr vm,
+ ::DataLoaderServiceConnectorPtr serviceConnector,
+ ::DataLoaderServiceParamsPtr serviceParams) -> ::DataLoader* {
+ return reinterpret_cast<::DataLoader*>(
+ ManagedDataLoader::create(vm, static_cast<FilesystemConnector*>(fsConnector),
+ static_cast<StatusListener*>(statusListener),
+ serviceConnector, serviceParams));
+ };
+}
+
} // namespace android::dataloader
diff --git a/libdataloader/ManagedDataLoader.h b/libdataloader/ManagedDataLoader.h
index 33d00c6..b9f714f 100644
--- a/libdataloader/ManagedDataLoader.h
+++ b/libdataloader/ManagedDataLoader.h
@@ -17,30 +17,50 @@
#include <dataloader.h>
+__BEGIN_DECLS
+
+// This simulates legacy dataloader (compiled with previous version of libincfs_dataloader).
+// We still need to be able to support them.
+struct LegacyDataLoader {
+ bool (*onStart)(struct LegacyDataLoader* self);
+ void (*onStop)(struct LegacyDataLoader* self);
+ void (*onDestroy)(struct LegacyDataLoader* self);
+
+ bool (*onPrepareImage)(struct LegacyDataLoader* self,
+ const DataLoaderInstallationFile addedFiles[], int addedFilesCount);
+
+ void (*onPendingReads)(struct LegacyDataLoader* self, const IncFsReadInfo pendingReads[],
+ int pendingReadsCount);
+ void (*onPageReads)(struct LegacyDataLoader* self, const IncFsReadInfo pageReads[],
+ int pageReadsCount);
+};
+
+__END_DECLS
+
namespace android::dataloader {
// Default DataLoader redirects everything back to Java.
-struct ManagedDataLoader : public DataLoader {
- ManagedDataLoader(JavaVM* jvm);
+struct ManagedDataLoader : private LegacyDataLoader {
+ static LegacyDataLoader* create(JavaVM* jvm, android::dataloader::FilesystemConnectorPtr ifs,
+ android::dataloader::StatusListenerPtr listener,
+ android::dataloader::ServiceConnectorPtr service,
+ android::dataloader::ServiceParamsPtr params);
private:
+ ManagedDataLoader(JavaVM* jvm, jobject dataLoader);
+
// Lifecycle.
- bool onCreate(const android::dataloader::DataLoaderParams&,
- android::dataloader::FilesystemConnectorPtr ifs,
- android::dataloader::StatusListenerPtr listener,
- android::dataloader::ServiceConnectorPtr service,
- android::dataloader::ServiceParamsPtr params) final;
- bool onStart() final { return true; }
- void onStop() final {}
- void onDestroy() final;
+ void onDestroy();
- bool onPrepareImage(DataLoaderInstallationFiles addedFiles) final;
-
- void onPendingReads(PendingReads pendingReads) final {}
- void onPageReads(PageReads pageReads) final {}
+ // Installation.
+ bool onPrepareImage(DataLoaderInstallationFiles addedFiles);
JavaVM* const mJvm;
jobject mDataLoader = nullptr;
};
+struct ManagedDataLoaderFactory : public ::DataLoaderFactory {
+ ManagedDataLoaderFactory();
+};
+
} // namespace android::dataloader
diff --git a/libdataloader/include/dataloader.h b/libdataloader/include/dataloader.h
index a46167d..2cfd9ff 100644
--- a/libdataloader/include/dataloader.h
+++ b/libdataloader/include/dataloader.h
@@ -36,7 +36,9 @@
struct StatusListener;
using FileId = IncFsFileId;
+using Uid = IncFsUid;
using ReadInfo = IncFsReadInfo;
+using ReadInfoWithUid = IncFsReadInfoWithUid;
using DataBlock = IncFsDataBlock;
using FilesystemConnectorPtr = FilesystemConnector*;
@@ -47,7 +49,9 @@
using DataLoaderPtr = std::unique_ptr<DataLoader>;
using DataLoaderInstallationFiles = Span<const ::DataLoaderInstallationFile>;
using PendingReads = Span<const ReadInfo>;
+using PendingReadsWithUid = Span<const ReadInfoWithUid>;
using PageReads = Span<const ReadInfo>;
+using PageReadsWithUid = Span<const ReadInfoWithUid>;
using RawMetadata = std::vector<char>;
using DataBlocks = Span<const DataBlock>;
@@ -59,6 +63,9 @@
virtual ~DataLoader() {}
+ // Bitmask of supported features.
+ virtual DataLoaderFeatures getFeatures() const = 0;
+
// Lifecycle.
virtual bool onCreate(const DataLoaderParams&, FilesystemConnectorPtr, StatusListenerPtr,
ServiceConnectorPtr, ServiceParamsPtr) = 0;
@@ -72,6 +79,9 @@
// IFS callbacks.
virtual void onPendingReads(PendingReads pendingReads) = 0;
virtual void onPageReads(PageReads pageReads) = 0;
+
+ virtual void onPendingReadsWithUid(PageReadsWithUid pendingReads) = 0;
+ virtual void onPageReadsWithUid(PendingReadsWithUid pageReads) = 0;
};
struct DataLoaderParams {
diff --git a/libdataloader/include/dataloader_inline.h b/libdataloader/include/dataloader_inline.h
index 26da530..10a5c46 100644
--- a/libdataloader/include/dataloader_inline.h
+++ b/libdataloader/include/dataloader_inline.h
@@ -22,6 +22,9 @@
struct DataLoaderImpl : public ::DataLoader {
DataLoaderImpl(DataLoaderPtr&& dataLoader) : mDataLoader(std::move(dataLoader)) {
+ getFeatures = [](DataLoader* self) -> DataLoaderFeatures {
+ return static_cast<DataLoaderImpl*>(self)->mDataLoader->getFeatures();
+ };
onStart = [](DataLoader* self) -> bool {
return static_cast<DataLoaderImpl*>(self)->mDataLoader->onStart();
};
@@ -47,6 +50,16 @@
return static_cast<DataLoaderImpl*>(self)->mDataLoader->onPageReads(
PageReads(pageReads, pageReadsCount));
};
+ onPendingReadsWithUid = [](DataLoader* self, const IncFsReadInfoWithUid pendingReads[],
+ int pendingReadsCount) {
+ return static_cast<DataLoaderImpl*>(self)->mDataLoader->onPendingReadsWithUid(
+ PendingReadsWithUid(pendingReads, pendingReadsCount));
+ };
+ onPageReadsWithUid = [](DataLoader* self, const IncFsReadInfoWithUid pageReads[],
+ int pageReadsCount) {
+ return static_cast<DataLoaderImpl*>(self)->mDataLoader->onPageReadsWithUid(
+ PageReadsWithUid(pageReads, pageReadsCount));
+ };
}
private:
@@ -99,7 +112,7 @@
} // namespace details
inline void DataLoader::initialize(DataLoader::Factory&& factory) {
- DataLoader_Initialize(new details::DataLoaderFactoryImpl(std::move(factory)));
+ DataLoader_Initialize_WithFeatures(new details::DataLoaderFactoryImpl(std::move(factory)));
}
inline DataLoaderParams::DataLoaderParams(DataLoaderType type, std::string&& packageName,
diff --git a/libdataloader/include/dataloader_ndk.h b/libdataloader/include/dataloader_ndk.h
index 67013ec..14dc3df 100644
--- a/libdataloader/include/dataloader_ndk.h
+++ b/libdataloader/include/dataloader_ndk.h
@@ -24,12 +24,9 @@
#define DATALOADER_LIBRARY_NAME "libdataloader.so"
-// Keep in sync with IDataLoaderStatusListener.aidl
typedef enum {
+ DATA_LOADER_UNAVAILABLE = 7,
DATA_LOADER_UNRECOVERABLE = 8,
-
- DATA_LOADER_FIRST_STATUS = DATA_LOADER_UNRECOVERABLE,
- DATA_LOADER_LAST_STATUS = DATA_LOADER_UNRECOVERABLE,
} DataLoaderStatus;
typedef enum {
@@ -44,6 +41,11 @@
DATA_LOADER_LOCATION_MEDIA_DATA = 2,
} DataLoaderLocation;
+typedef enum {
+ DATA_LOADER_FEATURE_NONE = 0,
+ DATA_LOADER_FEATURE_UID = 1 << 0,
+} DataLoaderFeatures;
+
struct DataLoaderParams {
int type;
const char* packageName;
@@ -81,6 +83,7 @@
typedef jobject DataLoaderServiceParamsPtr;
struct DataLoader {
+ // DataLoader v1.
bool (*onStart)(struct DataLoader* self);
void (*onStop)(struct DataLoader* self);
void (*onDestroy)(struct DataLoader* self);
@@ -92,6 +95,15 @@
int pendingReadsCount);
void (*onPageReads)(struct DataLoader* self, const IncFsReadInfo pageReads[],
int pageReadsCount);
+
+ // DataLoader v2, with features.
+ // Use DataLoader_Initialize_WithFeatures to set a factory for v2 DataLoader.
+ DataLoaderFeatures (*getFeatures)(struct DataLoader* self);
+
+ void (*onPendingReadsWithUid)(struct DataLoader* self,
+ const IncFsReadInfoWithUid pendingReads[], int pendingReadsCount);
+ void (*onPageReadsWithUid)(struct DataLoader* self, const IncFsReadInfoWithUid pageReads[],
+ int pageReadsCount);
};
struct DataLoaderFactory {
@@ -101,6 +113,7 @@
DataLoaderServiceParamsPtr);
};
void DataLoader_Initialize(struct DataLoaderFactory*);
+void DataLoader_Initialize_WithFeatures(struct DataLoaderFactory*);
void DataLoader_FilesystemConnector_writeData(DataLoaderFilesystemConnectorPtr, jstring name,
jlong offsetBytes, jlong lengthBytes,