Merge "Fix runtime namespace links for vendor processes"
diff --git a/fs_mgr/libfiemap_writer/.clang-format b/fs_mgr/libfiemap_writer/.clang-format
new file mode 120000
index 0000000..8b770a1
--- /dev/null
+++ b/fs_mgr/libfiemap_writer/.clang-format
@@ -0,0 +1 @@
+../../.clang-format-4
\ No newline at end of file
diff --git a/fs_mgr/libfiemap_writer/Android.bp b/fs_mgr/libfiemap_writer/Android.bp
new file mode 100644
index 0000000..33c3cad
--- /dev/null
+++ b/fs_mgr/libfiemap_writer/Android.bp
@@ -0,0 +1,56 @@
+//
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+cc_library_static {
+ name: "libfiemap_writer",
+ defaults: ["fs_mgr_defaults"],
+ recovery_available: true,
+ export_include_dirs: ["include"],
+ cflags: [
+ // TODO(b/121211685): Allows us to create a skeleton of required classes
+ "-Wno-unused-private-field",
+ "-Wno-unused-parameter",
+ ],
+
+ srcs: [
+ "fiemap_writer.cpp",
+ ],
+
+ header_libs: [
+ "libbase_headers",
+ "liblog_headers",
+ ],
+}
+
+cc_test {
+ name: "fiemap_writer_test",
+ static_libs: [
+ "libbase",
+ "libdm",
+ "libfiemap_writer",
+ "liblog",
+ ],
+
+ data: [
+ "testdata/unaligned_file",
+ "testdata/file_4k",
+ "testdata/file_32k",
+ ],
+
+ srcs: [
+ "fiemap_writer_test.cpp",
+ ],
+}
diff --git a/fs_mgr/libfiemap_writer/fiemap_writer.cpp b/fs_mgr/libfiemap_writer/fiemap_writer.cpp
new file mode 100644
index 0000000..4a0d4b5
--- /dev/null
+++ b/fs_mgr/libfiemap_writer/fiemap_writer.cpp
@@ -0,0 +1,632 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <libfiemap_writer/fiemap_writer.h>
+
+#include <dirent.h>
+#include <fcntl.h>
+#include <linux/fs.h>
+#include <stdio.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
+#include <sys/types.h>
+#include <sys/vfs.h>
+#include <unistd.h>
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+
+namespace android {
+namespace fiemap_writer {
+
+// We are expecting no more than 512 extents in a fiemap of the file we create.
+// If we find more, then it is treated as error for now.
+static constexpr const uint32_t kMaxExtents = 512;
+
+// TODO: Fallback to using fibmap if FIEMAP_EXTENT_MERGED is set.
+static constexpr const uint32_t kUnsupportedExtentFlags =
+ FIEMAP_EXTENT_UNKNOWN | FIEMAP_EXTENT_UNWRITTEN | FIEMAP_EXTENT_DELALLOC |
+ FIEMAP_EXTENT_NOT_ALIGNED | FIEMAP_EXTENT_DATA_INLINE | FIEMAP_EXTENT_DATA_TAIL |
+ FIEMAP_EXTENT_UNWRITTEN | FIEMAP_EXTENT_SHARED | FIEMAP_EXTENT_MERGED;
+
+static inline void cleanup(const std::string& file_path, bool created) {
+ if (created) {
+ unlink(file_path.c_str());
+ }
+}
+
+static bool BlockDeviceToName(uint32_t major, uint32_t minor, std::string* bdev_name) {
+ // The symlinks in /sys/dev/block point to the block device node under /sys/device/..
+ // The directory name in the target corresponds to the name of the block device. We use
+ // that to extract the block device name.
+ // e.g for block device name 'ram0', there exists a symlink named '1:0' in /sys/dev/block as
+ // follows.
+ // 1:0 -> ../../devices/virtual/block/ram0
+ std::string sysfs_path = ::android::base::StringPrintf("/sys/dev/block/%u:%u", major, minor);
+ std::string sysfs_bdev;
+
+ if (!::android::base::Readlink(sysfs_path, &sysfs_bdev)) {
+ PLOG(ERROR) << "Failed to read link at: " << sysfs_path;
+ return false;
+ }
+
+ *bdev_name = ::android::base::Basename(sysfs_bdev);
+ // Paranoid sanity check to make sure we just didn't get the
+ // input in return as-is.
+ if (sysfs_bdev == *bdev_name) {
+ LOG(ERROR) << "Malformed symlink for block device: " << sysfs_bdev;
+ return false;
+ }
+
+ return true;
+}
+
+static bool DeviceMapperStackPop(const std::string& bdev, std::string* bdev_raw) {
+ // TODO: Stop popping the device mapper stack if dm-linear target is found
+ if (!::android::base::StartsWith(bdev, "dm-")) {
+ // We are at the bottom of the device mapper stack.
+ *bdev_raw = bdev;
+ return true;
+ }
+
+ std::string dm_leaf_dir = ::android::base::StringPrintf("/sys/block/%s/slaves", bdev.c_str());
+ auto d = std::unique_ptr<DIR, decltype(&closedir)>(opendir(dm_leaf_dir.c_str()), closedir);
+ if (d == nullptr) {
+ PLOG(ERROR) << "Failed to open: " << dm_leaf_dir;
+ return false;
+ }
+
+ struct dirent* de;
+ uint32_t num_leaves = 0;
+ std::string bdev_next = "";
+ while ((de = readdir(d.get())) != nullptr) {
+ if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) {
+ continue;
+ }
+
+ // We set the first name we find here
+ if (bdev_next.empty()) {
+ bdev_next = de->d_name;
+ }
+ num_leaves++;
+ }
+
+ // if we have more than one leaves, we return immediately. We can't continue to create the
+ // file since we don't know how to write it out using fiemap, so it will be readable via the
+ // underlying block devices later. The reader will also have to construct the same device mapper
+ // target in order read the file out.
+ if (num_leaves > 1) {
+ LOG(ERROR) << "Found " << num_leaves << " leaf block devices under device mapper device "
+ << bdev;
+ return false;
+ }
+
+ // recursively call with the block device we found in order to pop the device mapper stack.
+ return DeviceMapperStackPop(bdev_next, bdev_raw);
+}
+
+static bool FileToBlockDevicePath(const std::string& file_path, std::string* bdev_path) {
+ struct stat sb;
+ if (stat(file_path.c_str(), &sb)) {
+ PLOG(ERROR) << "Failed to get stat for: " << file_path;
+ return false;
+ }
+
+ std::string bdev;
+ if (!BlockDeviceToName(major(sb.st_dev), minor(sb.st_dev), &bdev)) {
+ LOG(ERROR) << "Failed to get block device name for " << major(sb.st_dev) << ":"
+ << minor(sb.st_dev);
+ return false;
+ }
+
+ std::string bdev_raw;
+ if (!DeviceMapperStackPop(bdev, &bdev_raw)) {
+ LOG(ERROR) << "Failed to get the bottom of the device mapper stack for device: " << bdev;
+ return false;
+ }
+
+ LOG(DEBUG) << "Popped device (" << bdev_raw << ") from device mapper stack starting with ("
+ << bdev << ")";
+
+ *bdev_path = ::android::base::StringPrintf("/dev/block/%s", bdev_raw.c_str());
+
+ // Make sure we are talking to a block device before calling it a success.
+ if (stat(bdev_path->c_str(), &sb)) {
+ PLOG(ERROR) << "Failed to get stat for block device: " << *bdev_path;
+ return false;
+ }
+
+ if ((sb.st_mode & S_IFMT) != S_IFBLK) {
+ PLOG(ERROR) << "File: " << *bdev_path << " is not a block device";
+ return false;
+ }
+
+ return true;
+}
+
+static bool GetBlockDeviceSize(int bdev_fd, const std::string& bdev_path, uint64_t* bdev_size) {
+ uint64_t size_in_bytes = 0;
+ if (ioctl(bdev_fd, BLKGETSIZE64, &size_in_bytes)) {
+ PLOG(ERROR) << "Failed to get total size for: " << bdev_path;
+ return false;
+ }
+
+ *bdev_size = size_in_bytes;
+
+ return true;
+}
+
+static uint64_t GetFileSize(const std::string& file_path) {
+ struct stat sb;
+ if (stat(file_path.c_str(), &sb)) {
+ PLOG(ERROR) << "Failed to get size for file: " << file_path;
+ return 0;
+ }
+
+ return sb.st_size;
+}
+
+static bool PerformFileChecks(const std::string& file_path, uint64_t file_size, uint64_t* blocksz,
+ uint32_t* fs_type) {
+ struct statfs64 sfs;
+ if (statfs64(file_path.c_str(), &sfs)) {
+ PLOG(ERROR) << "Failed to read file system status at: " << file_path;
+ return false;
+ }
+
+ if (file_size % sfs.f_bsize) {
+ LOG(ERROR) << "File size " << file_size << " is not aligned to optimal block size "
+ << sfs.f_bsize << " for file " << file_path;
+ return false;
+ }
+
+ // Check if the filesystem is of supported types.
+ // Only ext4 and f2fs are tested and supported.
+ if ((sfs.f_type != EXT4_SUPER_MAGIC) && (sfs.f_type != F2FS_SUPER_MAGIC)) {
+ LOG(ERROR) << "Unsupported file system type: 0x" << std::hex << sfs.f_type;
+ return false;
+ }
+
+ uint64_t available_bytes = sfs.f_bsize * sfs.f_bavail;
+ if (available_bytes <= file_size) {
+ LOG(ERROR) << "Not enough free space in file system to create file of size : " << file_size;
+ return false;
+ }
+
+ *blocksz = sfs.f_bsize;
+ *fs_type = sfs.f_type;
+ return true;
+}
+
+static bool AllocateFile(int file_fd, const std::string& file_path, uint64_t blocksz,
+ uint64_t file_size) {
+ // Reserve space for the file on the file system and write it out to make sure the extents
+ // don't come back unwritten. Return from this function with the kernel file offset set to 0.
+ // If the filesystem is f2fs, then we also PIN the file on disk to make sure the blocks
+ // aren't moved around.
+ if (fallocate(file_fd, FALLOC_FL_ZERO_RANGE, 0, file_size)) {
+ PLOG(ERROR) << "Failed to allocate space for file: " << file_path << " size: " << file_size;
+ return false;
+ }
+
+ // write zeroes in 'blocksz' byte increments until we reach file_size to make sure the data
+ // blocks are actually written to by the file system and thus getting rid of the holes in the
+ // file.
+ auto buffer = std::unique_ptr<void, decltype(&free)>(calloc(1, blocksz), free);
+ if (buffer == nullptr) {
+ LOG(ERROR) << "failed to allocate memory for writing file";
+ return false;
+ }
+
+ off64_t offset = lseek64(file_fd, 0, SEEK_SET);
+ if (offset < 0) {
+ PLOG(ERROR) << "Failed to seek at the beginning of : " << file_path;
+ return false;
+ }
+
+ for (; offset < file_size; offset += blocksz) {
+ if (!::android::base::WriteFully(file_fd, buffer.get(), blocksz)) {
+ PLOG(ERROR) << "Failed to write" << blocksz << " bytes at offset" << offset
+ << " in file " << file_path;
+ return false;
+ }
+ }
+
+ if (lseek64(file_fd, 0, SEEK_SET) < 0) {
+ PLOG(ERROR) << "Failed to reset offset at the beginning of : " << file_path;
+ return false;
+ }
+
+ // flush all writes here ..
+ if (fsync(file_fd)) {
+ PLOG(ERROR) << "Failed to synchronize written file:" << file_path;
+ return false;
+ }
+
+ return true;
+}
+
+static bool PinFile(int file_fd, const std::string& file_path, uint32_t fs_type) {
+ if (fs_type == EXT4_SUPER_MAGIC) {
+ // No pinning necessary for ext4. The blocks, once allocated, are expected
+ // to be fixed.
+ return true;
+ }
+
+// F2FS-specific ioctl
+// It requires the below kernel commit merged in v4.16-rc1.
+// 1ad71a27124c ("f2fs: add an ioctl to disable GC for specific file")
+// In android-4.4,
+// 56ee1e817908 ("f2fs: updates on v4.16-rc1")
+// In android-4.9,
+// 2f17e34672a8 ("f2fs: updates on v4.16-rc1")
+// In android-4.14,
+// ce767d9a55bc ("f2fs: updates on v4.16-rc1")
+#ifndef F2FS_IOC_SET_PIN_FILE
+#ifndef F2FS_IOCTL_MAGIC
+#define F2FS_IOCTL_MAGIC 0xf5
+#endif
+#define F2FS_IOC_SET_PIN_FILE _IOW(F2FS_IOCTL_MAGIC, 13, __u32)
+#endif
+
+ uint32_t pin_status = 1;
+ int error = ioctl(file_fd, F2FS_IOC_SET_PIN_FILE, &pin_status);
+ if (error < 0) {
+ if ((errno == ENOTTY) || (errno == ENOTSUP)) {
+ PLOG(ERROR) << "Failed to pin file, not supported by kernel: " << file_path;
+ } else {
+ PLOG(ERROR) << "Failed to pin file: " << file_path;
+ }
+ return false;
+ }
+
+ return true;
+}
+
+#if 0
+static bool IsFilePinned(int file_fd, const std::string& file_path, uint32_t fs_type) {
+ if (fs_type == EXT4_SUPER_MAGIC) {
+ // No pinning necessary for ext4. The blocks, once allocated, are expected
+ // to be fixed.
+ return true;
+ }
+
+// F2FS-specific ioctl
+// It requires the below kernel commit merged in v4.16-rc1.
+// 1ad71a27124c ("f2fs: add an ioctl to disable GC for specific file")
+// In android-4.4,
+// 56ee1e817908 ("f2fs: updates on v4.16-rc1")
+// In android-4.9,
+// 2f17e34672a8 ("f2fs: updates on v4.16-rc1")
+// In android-4.14,
+// ce767d9a55bc ("f2fs: updates on v4.16-rc1")
+#ifndef F2FS_IOC_GET_PIN_FILE
+#ifndef F2FS_IOCTL_MAGIC
+#define F2FS_IOCTL_MAGIC 0xf5
+#endif
+#define F2FS_IOC_GET_PIN_FILE _IOR(F2FS_IOCTL_MAGIC, 14, __u32)
+#endif
+
+ // F2FS_IOC_GET_PIN_FILE returns the number of blocks moved.
+ uint32_t moved_blocks_nr;
+ int error = ioctl(file_fd, F2FS_IOC_GET_PIN_FILE, &moved_blocks_nr);
+ if (error < 0) {
+ if ((errno == ENOTTY) || (errno == ENOTSUP)) {
+ PLOG(ERROR) << "Failed to get file pin status, not supported by kernel: " << file_path;
+ } else {
+ PLOG(ERROR) << "Failed to get file pin status: " << file_path;
+ }
+ return false;
+ }
+
+ if (moved_blocks_nr) {
+ LOG(ERROR) << moved_blocks_nr << " blocks moved in file " << file_path;
+ }
+ return moved_blocks_nr == 0;
+}
+#endif
+
+static void LogExtent(uint32_t num, const struct fiemap_extent& ext) {
+ LOG(INFO) << "Extent #" << num;
+ LOG(INFO) << " fe_logical: " << ext.fe_logical;
+ LOG(INFO) << " fe_physical: " << ext.fe_physical;
+ LOG(INFO) << " fe_length: " << ext.fe_length;
+ LOG(INFO) << " fe_flags: 0x" << std::hex << ext.fe_flags;
+}
+
+static bool ReadFiemap(int file_fd, const std::string& file_path,
+ std::vector<struct fiemap_extent>* extents) {
+ uint64_t fiemap_size =
+ sizeof(struct fiemap_extent) + kMaxExtents * sizeof(struct fiemap_extent);
+ auto buffer = std::unique_ptr<void, decltype(&free)>(calloc(1, fiemap_size), free);
+ if (buffer == nullptr) {
+ LOG(ERROR) << "Failed to allocate memory for fiemap";
+ return false;
+ }
+
+ struct fiemap* fiemap = reinterpret_cast<struct fiemap*>(buffer.get());
+ fiemap->fm_start = 0;
+ fiemap->fm_length = UINT64_MAX;
+ // make sure file is synced to disk before we read the fiemap
+ fiemap->fm_flags = FIEMAP_FLAG_SYNC;
+ fiemap->fm_extent_count = kMaxExtents;
+
+ if (ioctl(file_fd, FS_IOC_FIEMAP, fiemap)) {
+ PLOG(ERROR) << "Failed to get FIEMAP from the kernel for file: " << file_path;
+ return false;
+ }
+
+ if (fiemap->fm_mapped_extents == 0) {
+ LOG(ERROR) << "File " << file_path << " has zero extents";
+ return false;
+ }
+
+ // Iterate through each extent read and make sure its valid before adding it to the vector
+ bool last_extent_seen = false;
+ struct fiemap_extent* extent = &fiemap->fm_extents[0];
+ for (uint32_t i = 0; i < fiemap->fm_mapped_extents; i++, extent++) {
+ // LogExtent(i + 1, *extent);
+ if (extent->fe_flags & kUnsupportedExtentFlags) {
+ LOG(ERROR) << "Extent " << i + 1 << " of file " << file_path
+ << " has unsupported flags";
+ extents->clear();
+ return false;
+ }
+
+ if (extent->fe_flags & FIEMAP_EXTENT_LAST) {
+ last_extent_seen = true;
+ if (i != (fiemap->fm_mapped_extents - 1)) {
+ LOG(WARNING) << "Extents are being received out-of-order";
+ }
+ }
+ extents->emplace_back(std::move(*extent));
+ }
+
+ if (!last_extent_seen) {
+ // The file is possibly too fragmented.
+ if (fiemap->fm_mapped_extents == kMaxExtents) {
+ LOG(ERROR) << "File is too fragmented, needs more than " << kMaxExtents << " extents.";
+ }
+ extents->clear();
+ }
+
+ return last_extent_seen;
+}
+
+FiemapUniquePtr FiemapWriter::Open(const std::string& file_path, uint64_t file_size, bool create) {
+ // if 'create' is false, open an existing file and do not truncate.
+ int open_flags = O_RDWR | O_CLOEXEC;
+ if (create) {
+ if (access(file_path.c_str(), F_OK) == 0) {
+ LOG(WARNING) << "File " << file_path << " already exists, truncating";
+ }
+ open_flags |= O_CREAT | O_TRUNC;
+ }
+ ::android::base::unique_fd file_fd(
+ TEMP_FAILURE_RETRY(open(file_path.c_str(), open_flags, S_IRUSR | S_IWUSR)));
+ if (file_fd < 0) {
+ PLOG(ERROR) << "Failed to create file at: " << file_path;
+ return nullptr;
+ }
+
+ std::string abs_path;
+ if (!::android::base::Realpath(file_path, &abs_path)) {
+ PLOG(ERROR) << "Invalid file path: " << file_path;
+ cleanup(file_path, create);
+ return nullptr;
+ }
+
+ std::string bdev_path;
+ if (!FileToBlockDevicePath(abs_path, &bdev_path)) {
+ LOG(ERROR) << "Failed to get block dev path for file: " << file_path;
+ cleanup(abs_path, create);
+ return nullptr;
+ }
+
+ ::android::base::unique_fd bdev_fd(
+ TEMP_FAILURE_RETRY(open(bdev_path.c_str(), O_RDWR | O_CLOEXEC)));
+ if (bdev_fd < 0) {
+ PLOG(ERROR) << "Failed to open block device: " << bdev_path;
+ cleanup(file_path, create);
+ return nullptr;
+ }
+
+ uint64_t bdevsz;
+ if (!GetBlockDeviceSize(bdev_fd, bdev_path, &bdevsz)) {
+ LOG(ERROR) << "Failed to get block device size for : " << bdev_path;
+ cleanup(file_path, create);
+ return nullptr;
+ }
+
+ if (!create) {
+ file_size = GetFileSize(abs_path);
+ if (file_size == 0) {
+ LOG(ERROR) << "Invalid file size of zero bytes for file: " << abs_path;
+ return nullptr;
+ }
+ }
+
+ uint64_t blocksz;
+ uint32_t fs_type;
+ if (!PerformFileChecks(abs_path, file_size, &blocksz, &fs_type)) {
+ LOG(ERROR) << "Failed to validate file or file system for file:" << abs_path;
+ cleanup(abs_path, create);
+ return nullptr;
+ }
+
+ if (create) {
+ if (!AllocateFile(file_fd, abs_path, blocksz, file_size)) {
+ cleanup(abs_path, create);
+ return nullptr;
+ }
+ }
+
+ // f2fs may move the file blocks around.
+ if (!PinFile(file_fd, file_path, fs_type)) {
+ cleanup(abs_path, create);
+ LOG(ERROR) << "Failed to pin the file in storage";
+ return nullptr;
+ }
+
+ // now allocate the FiemapWriter and start setting it up
+ FiemapUniquePtr fmap(new FiemapWriter());
+ if (!ReadFiemap(file_fd, abs_path, &fmap->extents_)) {
+ LOG(ERROR) << "Failed to read fiemap of file: " << abs_path;
+ cleanup(abs_path, create);
+ return nullptr;
+ }
+
+ fmap->file_path_ = abs_path;
+ fmap->bdev_path_ = bdev_path;
+ fmap->file_fd_ = std::move(file_fd);
+ fmap->bdev_fd_ = std::move(bdev_fd);
+ fmap->file_size_ = file_size;
+ fmap->bdev_size_ = bdevsz;
+ fmap->fs_type_ = fs_type;
+ fmap->block_size_ = blocksz;
+
+ LOG(INFO) << "Successfully created FiemapWriter for file " << abs_path << " on block device "
+ << bdev_path;
+ return fmap;
+}
+
+bool FiemapWriter::Flush() const {
+ if (fsync(bdev_fd_)) {
+ PLOG(ERROR) << "Failed to flush " << bdev_path_ << " with fsync";
+ return false;
+ }
+ return true;
+}
+
+// TODO: Test with fs block_size > bdev block_size
+bool FiemapWriter::Write(off64_t off, uint8_t* buffer, uint64_t size) {
+ if (!size || size > file_size_) {
+ LOG(ERROR) << "Failed write: size " << size << " is invalid for file's size " << file_size_;
+ return false;
+ }
+
+ if (off + size > file_size_) {
+ LOG(ERROR) << "Failed write: Invalid offset " << off << " or size " << size
+ << " for file size " << file_size_;
+ return false;
+ }
+
+ if ((off & (block_size_ - 1)) || (size & (block_size_ - 1))) {
+ LOG(ERROR) << "Failed write: Unaligned offset " << off << " or size " << size
+ << " for block size " << block_size_;
+ return false;
+ }
+
+#if 0
+ // TODO(b/122138114): check why this fails.
+ if (!IsFilePinned(file_fd_, file_path_, fs_type_)) {
+ LOG(ERROR) << "Failed write: file " << file_path_ << " is not pinned";
+ return false;
+ }
+#endif
+ // find extents that must be written to and then write one at a time.
+ uint32_t num_extent = 1;
+ uint32_t buffer_offset = 0;
+ for (auto& extent : extents_) {
+ uint64_t e_start = extent.fe_logical;
+ uint64_t e_end = extent.fe_logical + extent.fe_length;
+ // Do we write in this extent ?
+ if (off >= e_start && off < e_end) {
+ uint64_t written = WriteExtent(extent, buffer + buffer_offset, off, size);
+ if (written == 0) {
+ return false;
+ }
+
+ buffer_offset += written;
+ off += written;
+ size -= written;
+
+ // Paranoid check to make sure we are done with this extent now
+ if (size && (off >= e_start && off < e_end)) {
+ LOG(ERROR) << "Failed to write extent fully";
+ LogExtent(num_extent, extent);
+ return false;
+ }
+
+ if (size == 0) {
+ // done
+ break;
+ }
+ }
+ num_extent++;
+ }
+
+ return true;
+}
+
+bool FiemapWriter::Read(off64_t off, uint8_t* buffer, uint64_t size) {
+ return false;
+}
+
+// private helpers
+
+// WriteExtent() Returns the total number of bytes written. It will always be multiple of
+// block_size_. 0 is returned in one of the two cases.
+// 1. Any write failed between logical_off & logical_off + length.
+// 2. The logical_offset + length doesn't overlap with the extent passed.
+// The function can either partially for fully write the extent depending on the
+// logical_off + length. It is expected that alignment checks for size and offset are
+// performed before calling into this function.
+uint64_t FiemapWriter::WriteExtent(const struct fiemap_extent& ext, uint8_t* buffer,
+ off64_t logical_off, uint64_t length) {
+ uint64_t e_start = ext.fe_logical;
+ uint64_t e_end = ext.fe_logical + ext.fe_length;
+ if (logical_off < e_start || logical_off >= e_end) {
+ LOG(ERROR) << "Failed write extent, invalid offset " << logical_off << " and size "
+ << length;
+ LogExtent(0, ext);
+ return 0;
+ }
+
+ off64_t bdev_offset = ext.fe_physical + (logical_off - e_start);
+ if (bdev_offset >= bdev_size_) {
+ LOG(ERROR) << "Failed write extent, invalid block # " << bdev_offset << " for block device "
+ << bdev_path_ << " of size " << bdev_size_ << " bytes";
+ return 0;
+ }
+ if (TEMP_FAILURE_RETRY(lseek64(bdev_fd_, bdev_offset, SEEK_SET)) == -1) {
+ PLOG(ERROR) << "Failed write extent, seek offset for " << bdev_path_ << " offset "
+ << bdev_offset;
+ return 0;
+ }
+
+ // Determine how much we want to write at once.
+ uint64_t logical_end = logical_off + length;
+ uint64_t write_size = (e_end <= logical_end) ? (e_end - logical_off) : length;
+ if (!android::base::WriteFully(bdev_fd_, buffer, write_size)) {
+ PLOG(ERROR) << "Failed write extent, write " << bdev_path_ << " at " << bdev_offset
+ << " size " << write_size;
+ return 0;
+ }
+
+ return write_size;
+}
+
+} // namespace fiemap_writer
+} // namespace android
diff --git a/fs_mgr/libfiemap_writer/fiemap_writer_test.cpp b/fs_mgr/libfiemap_writer/fiemap_writer_test.cpp
new file mode 100644
index 0000000..6dff0e8
--- /dev/null
+++ b/fs_mgr/libfiemap_writer/fiemap_writer_test.cpp
@@ -0,0 +1,370 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <string>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <android-base/unique_fd.h>
+#include <gtest/gtest.h>
+#include <libdm/loop_control.h>
+
+#include <libfiemap_writer/fiemap_writer.h>
+
+using namespace std;
+using namespace android::fiemap_writer;
+using unique_fd = android::base::unique_fd;
+using LoopDevice = android::dm::LoopDevice;
+
+std::string testbdev = "";
+uint64_t testfile_size = 536870912; // default of 512MiB
+
+class FiemapWriterTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ const ::testing::TestInfo* tinfo = ::testing::UnitTest::GetInstance()->current_test_info();
+ std::string exec_dir = ::android::base::GetExecutableDirectory();
+ testfile = ::android::base::StringPrintf("%s/testdata/%s", exec_dir.c_str(), tinfo->name());
+ }
+
+ // name of the file we use for testing
+ std::string testfile;
+};
+
+TEST_F(FiemapWriterTest, CreateImpossiblyLargeFile) {
+ // Try creating a file of size ~100TB but aligned to
+ // 512 byte to make sure block alignment tests don't
+ // fail.
+ FiemapUniquePtr fptr = FiemapWriter::Open(testfile, 1099511627997184);
+ EXPECT_EQ(fptr, nullptr);
+ EXPECT_EQ(access(testfile.c_str(), F_OK), -1);
+ EXPECT_EQ(errno, ENOENT);
+}
+
+TEST_F(FiemapWriterTest, CreateUnalignedFile) {
+ // Try creating a file of size 4097 bytes which is guaranteed
+ // to be unaligned to all known block sizes. The creation must
+ // fail.
+ FiemapUniquePtr fptr = FiemapWriter::Open(testfile, 4097);
+ EXPECT_EQ(fptr, nullptr);
+ EXPECT_EQ(access(testfile.c_str(), F_OK), -1);
+ EXPECT_EQ(errno, ENOENT);
+}
+
+TEST_F(FiemapWriterTest, CheckFilePath) {
+ FiemapUniquePtr fptr = FiemapWriter::Open(testfile, 4096);
+ ASSERT_NE(fptr, nullptr);
+ EXPECT_EQ(fptr->size(), 4096);
+ EXPECT_EQ(fptr->file_path(), testfile);
+ EXPECT_EQ(access(testfile.c_str(), F_OK), 0);
+}
+
+TEST_F(FiemapWriterTest, CheckBlockDevicePath) {
+ FiemapUniquePtr fptr = FiemapWriter::Open(testfile, 4096);
+ EXPECT_EQ(fptr->size(), 4096);
+ EXPECT_EQ(fptr->bdev_path(), testbdev);
+}
+
+TEST_F(FiemapWriterTest, CheckFileCreated) {
+ FiemapUniquePtr fptr = FiemapWriter::Open(testfile, 32768);
+ ASSERT_NE(fptr, nullptr);
+ unique_fd fd(open(testfile.c_str(), O_RDONLY));
+ EXPECT_GT(fd, -1);
+}
+
+TEST_F(FiemapWriterTest, CheckFileSizeActual) {
+ FiemapUniquePtr fptr = FiemapWriter::Open(testfile, testfile_size);
+ ASSERT_NE(fptr, nullptr);
+
+ struct stat sb;
+ ASSERT_EQ(stat(testfile.c_str(), &sb), 0);
+ EXPECT_EQ(sb.st_size, testfile_size);
+}
+
+TEST_F(FiemapWriterTest, CheckFileExtents) {
+ FiemapUniquePtr fptr = FiemapWriter::Open(testfile, testfile_size);
+ ASSERT_NE(fptr, nullptr);
+ EXPECT_GT(fptr->extents().size(), 0);
+}
+
+TEST_F(FiemapWriterTest, CheckWriteError) {
+ FiemapUniquePtr fptr = FiemapWriter::Open(testfile, testfile_size);
+ ASSERT_NE(fptr, nullptr);
+
+ // prepare buffer for writing the pattern - 0xa0
+ uint64_t blocksize = fptr->block_size();
+ auto buffer = std::unique_ptr<void, decltype(&free)>(calloc(1, blocksize), free);
+ ASSERT_NE(buffer, nullptr);
+ memset(buffer.get(), 0xa0, blocksize);
+
+ uint8_t* p = static_cast<uint8_t*>(buffer.get());
+ for (off64_t off = 0; off < testfile_size; off += blocksize) {
+ ASSERT_TRUE(fptr->Write(off, p, blocksize));
+ }
+
+ EXPECT_TRUE(fptr->Flush());
+}
+
+class TestExistingFile : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ std::string exec_dir = ::android::base::GetExecutableDirectory();
+ std::string unaligned_file = exec_dir + "/testdata/unaligned_file";
+ std::string file_4k = exec_dir + "/testdata/file_4k";
+ std::string file_32k = exec_dir + "/testdata/file_32k";
+ fptr_unaligned = FiemapWriter::Open(unaligned_file, 4097, false);
+ fptr_4k = FiemapWriter::Open(file_4k, 4096, false);
+ fptr_32k = FiemapWriter::Open(file_32k, 32768, false);
+ }
+
+ FiemapUniquePtr fptr_unaligned;
+ FiemapUniquePtr fptr_4k;
+ FiemapUniquePtr fptr_32k;
+};
+
+TEST_F(TestExistingFile, ErrorChecks) {
+ EXPECT_EQ(fptr_unaligned, nullptr);
+ EXPECT_NE(fptr_4k, nullptr);
+ EXPECT_NE(fptr_32k, nullptr);
+
+ EXPECT_EQ(fptr_4k->size(), 4096);
+ EXPECT_EQ(fptr_32k->size(), 32768);
+ EXPECT_GT(fptr_4k->extents().size(), 0);
+ EXPECT_GT(fptr_32k->extents().size(), 0);
+}
+
+TEST_F(TestExistingFile, CheckWriteError) {
+ ASSERT_NE(fptr_4k, nullptr);
+ // prepare buffer for writing the pattern - 0xa0
+ uint64_t blocksize = fptr_4k->block_size();
+ auto buff_4k = std::unique_ptr<void, decltype(&free)>(calloc(1, blocksize), free);
+ ASSERT_NE(buff_4k, nullptr);
+ memset(buff_4k.get(), 0xa0, blocksize);
+
+ uint8_t* p = static_cast<uint8_t*>(buff_4k.get());
+ for (off64_t off = 0; off < 4096; off += blocksize) {
+ ASSERT_TRUE(fptr_4k->Write(off, p, blocksize));
+ }
+ EXPECT_TRUE(fptr_4k->Flush());
+
+ ASSERT_NE(fptr_32k, nullptr);
+ // prepare buffer for writing the pattern - 0xa0
+ blocksize = fptr_32k->block_size();
+ auto buff_32k = std::unique_ptr<void, decltype(&free)>(calloc(1, blocksize), free);
+ ASSERT_NE(buff_32k, nullptr);
+ memset(buff_32k.get(), 0xa0, blocksize);
+ p = static_cast<uint8_t*>(buff_32k.get());
+ for (off64_t off = 0; off < 4096; off += blocksize) {
+ ASSERT_TRUE(fptr_32k->Write(off, p, blocksize));
+ }
+ EXPECT_TRUE(fptr_32k->Flush());
+}
+
+class VerifyBlockWritesExt4 : public ::testing::Test {
+ // 2GB Filesystem and 4k block size by default
+ static constexpr uint64_t block_size = 4096;
+ static constexpr uint64_t fs_size = 2147483648;
+
+ protected:
+ void SetUp() override {
+ fs_path = std::string(getenv("TMPDIR")) + "/ext4_2G.img";
+ uint64_t count = fs_size / block_size;
+ std::string dd_cmd =
+ ::android::base::StringPrintf("/system/bin/dd if=/dev/zero of=%s bs=%" PRIu64
+ " count=%" PRIu64 " > /dev/null 2>&1",
+ fs_path.c_str(), block_size, count);
+ std::string mkfs_cmd =
+ ::android::base::StringPrintf("/system/bin/mkfs.ext4 -q %s", fs_path.c_str());
+ // create mount point
+ mntpoint = std::string(getenv("TMPDIR")) + "/fiemap_mnt";
+ ASSERT_EQ(mkdir(mntpoint.c_str(), S_IRWXU), 0);
+ // create file for the file system
+ int ret = system(dd_cmd.c_str());
+ ASSERT_EQ(ret, 0);
+ // Get and attach a loop device to the filesystem we created
+ LoopDevice loop_dev(fs_path);
+ ASSERT_TRUE(loop_dev.valid());
+ // create file system
+ ret = system(mkfs_cmd.c_str());
+ ASSERT_EQ(ret, 0);
+
+ // mount the file system
+ ASSERT_EQ(mount(loop_dev.device().c_str(), mntpoint.c_str(), "ext4", 0, nullptr), 0);
+ }
+
+ void TearDown() override {
+ umount(mntpoint.c_str());
+ rmdir(mntpoint.c_str());
+ unlink(fs_path.c_str());
+ }
+
+ std::string mntpoint;
+ std::string fs_path;
+};
+
+TEST_F(VerifyBlockWritesExt4, CheckWrites) {
+ EXPECT_EQ(access(fs_path.c_str(), F_OK), 0);
+
+ std::string file_path = mntpoint + "/testfile";
+ uint64_t file_size = 100 * 1024 * 1024;
+ auto buffer = std::unique_ptr<void, decltype(&free)>(calloc(1, getpagesize()), free);
+ ASSERT_NE(buffer, nullptr);
+ memset(buffer.get(), 0xa0, getpagesize());
+ {
+ // scoped fiemap writer
+ FiemapUniquePtr fptr = FiemapWriter::Open(file_path, file_size);
+ ASSERT_NE(fptr, nullptr);
+ uint8_t* p = static_cast<uint8_t*>(buffer.get());
+ for (off64_t off = 0; off < file_size / getpagesize(); off += getpagesize()) {
+ ASSERT_TRUE(fptr->Write(off, p, getpagesize()));
+ }
+ EXPECT_TRUE(fptr->Flush());
+ }
+ // unmount file system here to make sure we invalidated all page cache and
+ // remount the filesystem again for verification
+ ASSERT_EQ(umount(mntpoint.c_str()), 0);
+
+ LoopDevice loop_dev(fs_path);
+ ASSERT_TRUE(loop_dev.valid());
+ ASSERT_EQ(mount(loop_dev.device().c_str(), mntpoint.c_str(), "ext4", 0, nullptr), 0)
+ << "failed to mount: " << loop_dev.device() << " on " << mntpoint << ": "
+ << strerror(errno);
+
+ ::android::base::unique_fd fd(open(file_path.c_str(), O_RDONLY | O_SYNC));
+ ASSERT_NE(fd, -1);
+ auto filebuf = std::unique_ptr<void, decltype(&free)>(calloc(1, getpagesize()), free);
+ ASSERT_NE(filebuf, nullptr);
+ for (off64_t off = 0; off < file_size / getpagesize(); off += getpagesize()) {
+ memset(filebuf.get(), 0x00, getpagesize());
+ ASSERT_EQ(pread64(fd, filebuf.get(), getpagesize(), off), getpagesize());
+ ASSERT_EQ(memcmp(filebuf.get(), buffer.get(), getpagesize()), 0)
+ << "Invalid pattern at offset: " << off << " size " << getpagesize();
+ }
+}
+
+class VerifyBlockWritesF2fs : public ::testing::Test {
+ // 2GB Filesystem and 4k block size by default
+ static constexpr uint64_t block_size = 4096;
+ static constexpr uint64_t fs_size = 2147483648;
+
+ protected:
+ void SetUp() override {
+ fs_path = std::string(getenv("TMPDIR")) + "/f2fs_2G.img";
+ uint64_t count = fs_size / block_size;
+ std::string dd_cmd =
+ ::android::base::StringPrintf("/system/bin/dd if=/dev/zero of=%s bs=%" PRIu64
+ " count=%" PRIu64 " > /dev/null 2>&1",
+ fs_path.c_str(), block_size, count);
+ std::string mkfs_cmd =
+ ::android::base::StringPrintf("/system/bin/make_f2fs -q %s", fs_path.c_str());
+ // create mount point
+ mntpoint = std::string(getenv("TMPDIR")) + "/fiemap_mnt";
+ ASSERT_EQ(mkdir(mntpoint.c_str(), S_IRWXU), 0);
+ // create file for the file system
+ int ret = system(dd_cmd.c_str());
+ ASSERT_EQ(ret, 0);
+ // Get and attach a loop device to the filesystem we created
+ LoopDevice loop_dev(fs_path);
+ ASSERT_TRUE(loop_dev.valid());
+ // create file system
+ ret = system(mkfs_cmd.c_str());
+ ASSERT_EQ(ret, 0);
+
+ // mount the file system
+ ASSERT_EQ(mount(loop_dev.device().c_str(), mntpoint.c_str(), "f2fs", 0, nullptr), 0);
+ }
+
+ void TearDown() override {
+ umount(mntpoint.c_str());
+ rmdir(mntpoint.c_str());
+ unlink(fs_path.c_str());
+ }
+
+ std::string mntpoint;
+ std::string fs_path;
+};
+
+TEST_F(VerifyBlockWritesF2fs, CheckWrites) {
+ EXPECT_EQ(access(fs_path.c_str(), F_OK), 0);
+
+ std::string file_path = mntpoint + "/testfile";
+ uint64_t file_size = 100 * 1024 * 1024;
+ auto buffer = std::unique_ptr<void, decltype(&free)>(calloc(1, getpagesize()), free);
+ ASSERT_NE(buffer, nullptr);
+ memset(buffer.get(), 0xa0, getpagesize());
+ {
+ // scoped fiemap writer
+ FiemapUniquePtr fptr = FiemapWriter::Open(file_path, file_size);
+ ASSERT_NE(fptr, nullptr);
+ uint8_t* p = static_cast<uint8_t*>(buffer.get());
+ for (off64_t off = 0; off < file_size / getpagesize(); off += getpagesize()) {
+ ASSERT_TRUE(fptr->Write(off, p, getpagesize()));
+ }
+ EXPECT_TRUE(fptr->Flush());
+ }
+ // unmount file system here to make sure we invalidated all page cache and
+ // remount the filesystem again for verification
+ ASSERT_EQ(umount(mntpoint.c_str()), 0);
+
+ LoopDevice loop_dev(fs_path);
+ ASSERT_TRUE(loop_dev.valid());
+ ASSERT_EQ(mount(loop_dev.device().c_str(), mntpoint.c_str(), "f2fs", 0, nullptr), 0)
+ << "failed to mount: " << loop_dev.device() << " on " << mntpoint << ": "
+ << strerror(errno);
+
+ ::android::base::unique_fd fd(open(file_path.c_str(), O_RDONLY | O_SYNC));
+ ASSERT_NE(fd, -1);
+ auto filebuf = std::unique_ptr<void, decltype(&free)>(calloc(1, getpagesize()), free);
+ ASSERT_NE(filebuf, nullptr);
+ for (off64_t off = 0; off < file_size / getpagesize(); off += getpagesize()) {
+ memset(filebuf.get(), 0x00, getpagesize());
+ ASSERT_EQ(pread64(fd, filebuf.get(), getpagesize(), off), getpagesize());
+ ASSERT_EQ(memcmp(filebuf.get(), buffer.get(), getpagesize()), 0)
+ << "Invalid pattern at offset: " << off << " size " << getpagesize();
+ }
+}
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ if (argc <= 1) {
+ cerr << "Filepath with its bdev path must be provided as follows:" << endl;
+ cerr << " $ fiemap_writer_test </dev/block/XXXX" << endl;
+ cerr << " where, /dev/block/XXX is the block device where the file resides" << endl;
+ exit(EXIT_FAILURE);
+ }
+ ::android::base::InitLogging(argv, ::android::base::StderrLogger);
+
+ testbdev = argv[1];
+ if (argc > 2) {
+ testfile_size = strtoull(argv[2], NULL, 0);
+ if (testfile_size == ULLONG_MAX) {
+ testfile_size = 512 * 1024 * 1024;
+ }
+ }
+
+ return RUN_ALL_TESTS();
+}
diff --git a/fs_mgr/libfiemap_writer/include/libfiemap_writer/fiemap_writer.h b/fs_mgr/libfiemap_writer/include/libfiemap_writer/fiemap_writer.h
new file mode 100644
index 0000000..ae61344
--- /dev/null
+++ b/fs_mgr/libfiemap_writer/include/libfiemap_writer/fiemap_writer.h
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <linux/fiemap.h>
+#include <stdint.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <string>
+#include <vector>
+
+#include <android-base/unique_fd.h>
+
+namespace android {
+namespace fiemap_writer {
+
+class FiemapWriter;
+using FiemapUniquePtr = std::unique_ptr<FiemapWriter>;
+
+class FiemapWriter final {
+ public:
+ // Factory method for FiemapWriter.
+ // The method returns FiemapUniquePtr that contains all the data necessary to be able to write
+ // to the given file directly using raw block i/o.
+ static FiemapUniquePtr Open(const std::string& file_path, uint64_t file_size,
+ bool create = true);
+
+ // Syncs block device writes.
+ bool Flush() const;
+
+ // Writes the file by using its FIEMAP and performing i/o on the raw block device.
+ // The return value is success / failure. This will happen in particular if the
+ // kernel write returns errors, extents are not writeable or more importantly, if the 'size' is
+ // not aligned to the block device's block size.
+ bool Write(off64_t off, uint8_t* buffer, uint64_t size);
+
+ // The counter part of Write(). It is an error for the offset to be unaligned with
+ // the block device's block size.
+ // In case of error, the contents of buffer MUST be discarded.
+ bool Read(off64_t off, uint8_t* buffer, uint64_t size);
+
+ ~FiemapWriter() = default;
+
+ const std::string& file_path() const { return file_path_; };
+ uint64_t size() const { return file_size_; };
+ const std::string& bdev_path() const { return bdev_path_; };
+ uint64_t block_size() const { return block_size_; };
+ const std::vector<struct fiemap_extent>& extents() { return extents_; };
+
+ // Non-copyable & Non-movable
+ FiemapWriter(const FiemapWriter&) = delete;
+ FiemapWriter& operator=(const FiemapWriter&) = delete;
+ FiemapWriter& operator=(FiemapWriter&&) = delete;
+ FiemapWriter(FiemapWriter&&) = delete;
+
+ private:
+ // Name of the file managed by this class.
+ std::string file_path_;
+ // Block device on which we have created the file.
+ std::string bdev_path_;
+
+ // File descriptors for the file and block device
+ ::android::base::unique_fd file_fd_;
+ ::android::base::unique_fd bdev_fd_;
+
+ // Size in bytes of the file this class is writing
+ uint64_t file_size_;
+
+ // total size in bytes of the block device
+ uint64_t bdev_size_;
+
+ // Filesystem type where the file is being created.
+ // See: <uapi/linux/magic.h> for filesystem magic numbers
+ uint32_t fs_type_;
+
+ // block size as reported by the kernel of the underlying block device;
+ uint64_t block_size_;
+
+ // This file's fiemap
+ std::vector<struct fiemap_extent> extents_;
+
+ FiemapWriter() = default;
+
+ uint64_t WriteExtent(const struct fiemap_extent& ext, uint8_t* buffer, off64_t logical_off,
+ uint64_t length);
+};
+
+} // namespace fiemap_writer
+} // namespace android
diff --git a/fs_mgr/libfiemap_writer/testdata/file_32k b/fs_mgr/libfiemap_writer/testdata/file_32k
new file mode 100644
index 0000000..12f3be4
--- /dev/null
+++ b/fs_mgr/libfiemap_writer/testdata/file_32k
Binary files differ
diff --git a/fs_mgr/libfiemap_writer/testdata/file_4k b/fs_mgr/libfiemap_writer/testdata/file_4k
new file mode 100644
index 0000000..08e7df1
--- /dev/null
+++ b/fs_mgr/libfiemap_writer/testdata/file_4k
Binary files differ
diff --git a/fs_mgr/libfiemap_writer/testdata/unaligned_file b/fs_mgr/libfiemap_writer/testdata/unaligned_file
new file mode 100644
index 0000000..c107c26
--- /dev/null
+++ b/fs_mgr/libfiemap_writer/testdata/unaligned_file
Binary files differ
diff --git a/fs_mgr/libfs_avb/avb_ops.cpp b/fs_mgr/libfs_avb/avb_ops.cpp
index f56a517..c985a97 100644
--- a/fs_mgr/libfs_avb/avb_ops.cpp
+++ b/fs_mgr/libfs_avb/avb_ops.cpp
@@ -170,16 +170,32 @@
AvbSlotVerifyResult FsManagerAvbOps::AvbSlotVerify(const std::string& ab_suffix,
AvbSlotVerifyFlags flags,
- AvbSlotVerifyData** out_data) {
+ std::vector<VBMetaData>* out_vbmeta_images) {
// Invokes avb_slot_verify() to load and verify all vbmeta images.
// Sets requested_partitions to nullptr as it's to copy the contents
// of HASH partitions into handle>avb_slot_data_, which is not required as
// fs_mgr only deals with HASHTREE partitions.
const char* requested_partitions[] = {nullptr};
+
+ // Local resource to store vbmeta images from avb_slot_verify();
+ AvbSlotVerifyData* avb_slot_data;
+
// The |hashtree_error_mode| field doesn't matter as it only
// influences the generated kernel cmdline parameters.
- return avb_slot_verify(&avb_ops_, requested_partitions, ab_suffix.c_str(), flags,
- AVB_HASHTREE_ERROR_MODE_RESTART_AND_INVALIDATE, out_data);
+ auto verify_result =
+ avb_slot_verify(&avb_ops_, requested_partitions, ab_suffix.c_str(), flags,
+ AVB_HASHTREE_ERROR_MODE_RESTART_AND_INVALIDATE, &avb_slot_data);
+
+ // Copies avb_slot_data->vbmeta_images[].
+ for (size_t i = 0; i < avb_slot_data->num_vbmeta_images; i++) {
+ out_vbmeta_images->emplace_back(VBMetaData(avb_slot_data->vbmeta_images[i].vbmeta_data,
+ avb_slot_data->vbmeta_images[i].vbmeta_size));
+ }
+
+ // Free the local resource.
+ avb_slot_verify_data_free(avb_slot_data);
+
+ return verify_result;
}
} // namespace fs_mgr
diff --git a/fs_mgr/libfs_avb/avb_ops.h b/fs_mgr/libfs_avb/avb_ops.h
index e6b33c2..c0f12aa 100644
--- a/fs_mgr/libfs_avb/avb_ops.h
+++ b/fs_mgr/libfs_avb/avb_ops.h
@@ -25,7 +25,9 @@
#pragma once
#include <string>
+#include <vector>
+#include <fs_avb/fs_avb.h>
#include <libavb/libavb.h>
namespace android {
@@ -55,7 +57,7 @@
void* buffer, size_t* out_num_read);
AvbSlotVerifyResult AvbSlotVerify(const std::string& ab_suffix, AvbSlotVerifyFlags flags,
- AvbSlotVerifyData** out_data);
+ std::vector<VBMetaData>* out_vbmeta_images);
private:
AvbOps avb_ops_;
diff --git a/fs_mgr/libfs_avb/fs_avb.cpp b/fs_mgr/libfs_avb/fs_avb.cpp
index 89c755e..cf920f9 100644
--- a/fs_mgr/libfs_avb/fs_avb.cpp
+++ b/fs_mgr/libfs_avb/fs_avb.cpp
@@ -99,14 +99,13 @@
}
template <typename Hasher>
-static std::pair<size_t, bool> verify_vbmeta_digest(const AvbSlotVerifyData& verify_data,
+static std::pair<size_t, bool> verify_vbmeta_digest(const std::vector<VBMetaData>& vbmeta_images,
const uint8_t* expected_digest) {
size_t total_size = 0;
Hasher hasher;
- for (size_t n = 0; n < verify_data.num_vbmeta_images; n++) {
- hasher.update(verify_data.vbmeta_images[n].vbmeta_data,
- verify_data.vbmeta_images[n].vbmeta_size);
- total_size += verify_data.vbmeta_images[n].vbmeta_size;
+ for (size_t n = 0; n < vbmeta_images.size(); n++) {
+ hasher.update(vbmeta_images[n].vbmeta_data(), vbmeta_images[n].vbmeta_size());
+ total_size += vbmeta_images[n].vbmeta_size();
}
bool matched = (memcmp(hasher.finalize(), expected_digest, Hasher::DIGEST_SIZE) == 0);
@@ -123,7 +122,7 @@
public:
// The factory method to return a unique_ptr<AvbVerifier>
static std::unique_ptr<AvbVerifier> Create();
- bool VerifyVbmetaImages(const AvbSlotVerifyData& verify_data);
+ bool VerifyVbmetaImages(const std::vector<VBMetaData>& vbmeta_images);
protected:
AvbVerifier() = default;
@@ -186,8 +185,8 @@
return avb_verifier;
}
-bool AvbVerifier::VerifyVbmetaImages(const AvbSlotVerifyData& verify_data) {
- if (verify_data.num_vbmeta_images == 0) {
+bool AvbVerifier::VerifyVbmetaImages(const std::vector<VBMetaData>& vbmeta_images) {
+ if (vbmeta_images.empty()) {
LERROR << "No vbmeta images";
return false;
}
@@ -197,10 +196,10 @@
if (hash_alg_ == kSHA256) {
std::tie(total_size, digest_matched) =
- verify_vbmeta_digest<SHA256Hasher>(verify_data, digest_);
+ verify_vbmeta_digest<SHA256Hasher>(vbmeta_images, digest_);
} else if (hash_alg_ == kSHA512) {
std::tie(total_size, digest_matched) =
- verify_vbmeta_digest<SHA512Hasher>(verify_data, digest_);
+ verify_vbmeta_digest<SHA512Hasher>(vbmeta_images, digest_);
}
if (total_size != vbmeta_size_) {
@@ -306,18 +305,18 @@
}
static bool get_hashtree_descriptor(const std::string& partition_name,
- const AvbSlotVerifyData& verify_data,
+ const std::vector<VBMetaData>& vbmeta_images,
AvbHashtreeDescriptor* out_hashtree_desc, std::string* out_salt,
std::string* out_digest) {
bool found = false;
const uint8_t* desc_partition_name;
- for (size_t i = 0; i < verify_data.num_vbmeta_images && !found; i++) {
+ for (size_t i = 0; i < vbmeta_images.size() && !found; i++) {
// Get descriptors from vbmeta_images[i].
size_t num_descriptors;
std::unique_ptr<const AvbDescriptor* [], decltype(&avb_free)> descriptors(
- avb_descriptor_get_all(verify_data.vbmeta_images[i].vbmeta_data,
- verify_data.vbmeta_images[i].vbmeta_size, &num_descriptors),
+ avb_descriptor_get_all(vbmeta_images[i].vbmeta_data(),
+ vbmeta_images[i].vbmeta_size(), &num_descriptors),
avb_free);
if (!descriptors || num_descriptors < 1) {
@@ -377,7 +376,7 @@
AvbSlotVerifyFlags flags = is_device_unlocked ? AVB_SLOT_VERIFY_FLAGS_ALLOW_VERIFICATION_ERROR
: AVB_SLOT_VERIFY_FLAGS_NONE;
AvbSlotVerifyResult verify_result =
- avb_ops.AvbSlotVerify(fs_mgr_get_slot_suffix(), flags, &avb_handle->avb_slot_data_);
+ avb_ops.AvbSlotVerify(fs_mgr_get_slot_suffix(), flags, &avb_handle->vbmeta_images_);
// Only allow two verify results:
// - AVB_SLOT_VERIFY_RESULT_OK.
@@ -417,8 +416,7 @@
// and AVB HASHTREE descriptor(s).
AvbVBMetaImageHeader vbmeta_header;
avb_vbmeta_image_header_to_host_byte_order(
- (AvbVBMetaImageHeader*)avb_handle->avb_slot_data_->vbmeta_images[0].vbmeta_data,
- &vbmeta_header);
+ (AvbVBMetaImageHeader*)avb_handle->vbmeta_images_[0].vbmeta_data(), &vbmeta_header);
bool verification_disabled = ((AvbVBMetaImageFlags)vbmeta_header.flags &
AVB_VBMETA_IMAGE_FLAGS_VERIFICATION_DISABLED);
@@ -431,7 +429,7 @@
LERROR << "Failed to create AvbVerifier";
return nullptr;
}
- if (!avb_verifier->VerifyVbmetaImages(*avb_handle->avb_slot_data_)) {
+ if (!avb_verifier->VerifyVbmetaImages(avb_handle->vbmeta_images_)) {
LERROR << "VerifyVbmetaImages failed";
return nullptr;
}
@@ -449,8 +447,7 @@
}
AvbHashtreeResult AvbHandle::SetUpAvbHashtree(FstabEntry* fstab_entry, bool wait_for_verity_dev) {
- if (!fstab_entry || status_ == kAvbHandleUninitialized || !avb_slot_data_ ||
- avb_slot_data_->num_vbmeta_images < 1) {
+ if (!fstab_entry || status_ == kAvbHandleUninitialized || vbmeta_images_.size() < 1) {
return AvbHashtreeResult::kFail;
}
@@ -478,7 +475,7 @@
AvbHashtreeDescriptor hashtree_descriptor;
std::string salt;
std::string root_digest;
- if (!get_hashtree_descriptor(partition_name, *avb_slot_data_, &hashtree_descriptor, &salt,
+ if (!get_hashtree_descriptor(partition_name, vbmeta_images_, &hashtree_descriptor, &salt,
&root_digest)) {
return AvbHashtreeResult::kFail;
}
diff --git a/fs_mgr/libfs_avb/include/fs_avb/fs_avb.h b/fs_mgr/libfs_avb/include/fs_avb/fs_avb.h
index 08bdbdc..0c2b231 100644
--- a/fs_mgr/libfs_avb/include/fs_avb/fs_avb.h
+++ b/fs_mgr/libfs_avb/include/fs_avb/fs_avb.h
@@ -18,6 +18,7 @@
#include <memory>
#include <string>
+#include <vector>
#include <fstab/fstab.h>
#include <libavb/libavb.h>
@@ -31,6 +32,35 @@
kDisabled,
};
+class VBMetaData {
+ public:
+ // Constructors
+ VBMetaData() : vbmeta_ptr_(nullptr), vbmeta_size_(0){};
+
+ VBMetaData(uint8_t* data, size_t size)
+ : vbmeta_ptr_(new (std::nothrow) uint8_t[size]), vbmeta_size_(size) {
+ // The ownership of data is NOT transferred, i.e., the caller still
+ // needs to release the memory as we make a copy here.
+ memcpy(vbmeta_ptr_.get(), data, size * sizeof(uint8_t));
+ }
+
+ explicit VBMetaData(size_t size)
+ : vbmeta_ptr_(new (std::nothrow) uint8_t[size]), vbmeta_size_(size) {}
+
+ // Get methods for each data member.
+ const std::string& device_path() const { return device_path_; }
+ uint8_t* vbmeta_data() const { return vbmeta_ptr_.get(); }
+ const size_t& vbmeta_size() const { return vbmeta_size_; }
+
+ // Maximum size of a vbmeta data - 64 KiB.
+ static const size_t kMaxVBMetaSize = 64 * 1024;
+
+ private:
+ std::string device_path_;
+ std::unique_ptr<uint8_t[]> vbmeta_ptr_;
+ size_t vbmeta_size_;
+};
+
class FsManagerAvbOps;
class AvbHandle;
@@ -43,7 +73,7 @@
public:
// The factory method to return a AvbUniquePtr that holds
// the verified AVB (external/avb) metadata of all verified partitions
- // in avb_slot_data_.vbmeta_images[].
+ // in vbmeta_images_.
//
// The metadata is checked against the following values from /proc/cmdline.
// - androidboot.vbmeta.{hash_alg, size, digest}.
@@ -95,12 +125,6 @@
AvbHandle(AvbHandle&&) noexcept = delete; // no move
AvbHandle& operator=(AvbHandle&&) noexcept = delete; // no move assignment
- ~AvbHandle() {
- if (avb_slot_data_) {
- avb_slot_verify_data_free(avb_slot_data_);
- }
- };
-
private:
enum AvbHandleStatus {
kAvbHandleSuccess = 0,
@@ -110,9 +134,9 @@
kAvbHandleVerificationError,
};
- AvbHandle() : avb_slot_data_(nullptr), status_(kAvbHandleUninitialized) {}
+ AvbHandle() : status_(kAvbHandleUninitialized) {}
- AvbSlotVerifyData* avb_slot_data_;
+ std::vector<VBMetaData> vbmeta_images_;
AvbHandleStatus status_;
std::string avb_version_;
};
diff --git a/fs_mgr/libfs_avb/tests/Android.bp b/fs_mgr/libfs_avb/tests/Android.bp
new file mode 100644
index 0000000..24e1d76
--- /dev/null
+++ b/fs_mgr/libfs_avb/tests/Android.bp
@@ -0,0 +1,40 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+cc_test_host {
+ name: "libfs_avb_host_unittest",
+ required: [
+ "avbtool",
+ ],
+ data: [
+ "data/*",
+ ],
+ static_libs: [
+ "libgtest_host",
+ ],
+ shared_libs: [
+ "libbase",
+ "libchrome",
+ ],
+ srcs: [
+ "fs_avb_unittest_util.cpp",
+ ],
+ target: {
+ darwin: {
+ enabled: false,
+ },
+ },
+}
diff --git a/fs_mgr/libfs_avb/tests/data/testkey_rsa2048.pem b/fs_mgr/libfs_avb/tests/data/testkey_rsa2048.pem
new file mode 100644
index 0000000..867dcff
--- /dev/null
+++ b/fs_mgr/libfs_avb/tests/data/testkey_rsa2048.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAxlVR3TIkouAOvH79vaJTgFhpfvVKQIeVkFRZPVXK/zY0Gvrh
+4JAqGjJoW/PfrQv5sdD36qtHH3a+G5hLZ6Ni+t/mtfjucxZfuLGC3kmJ1T3XqEKZ
+gXXI2IR7vVSoImREvDQGEDyJwtHzLANlkbGg0cghVhWZSCAndO8BenalC2v94/rt
+DfkPekH6dgU3Sf40T0sBSeSY94mOzTaqOR2pfV1rWlLRdWmo33zeHBv52Rlbt0dM
+uXAureXWiHztkm5GCBC1dgM+CaxNtizNEgC91KcD0xuRCCM2WxH+r1lpszyIJDct
+YbrFmVEYl/kjQpafhy7Nsk1fqSTyRdriZSYmTQIDAQABAoIBAQC+kJgaCuX8wYAn
+SXWQ0fmdZlXnMNRpcF0a0pD0SAzGb1RdYBXMaXiqtyhiwc53PPxsCDdNecjayIMd
+jJVXPTwLhTruOgMS/bp3gcgWwV34UHV4LJXGOGAE+jbS0hbDBMiudOYmj6RmVshp
+z9G1zZCSQNMXHaWsEYkX59XpzzoB384nRul2QgEtwzUNR9XlpzgtJBLk3SACkvsN
+mQ/DW8IWHXLg8vLn1LzVJ2e3B16H4MoE2TCHxqfMgr03IDRRJogkenQuQsFhevYT
+o/mJyHSWavVgzMHG9I5m+eepF4Wyhj1Y4WyKAuMI+9dHAX/h7Lt8XFCQCh5DbkVG
+zGr34sWBAoGBAOs7n7YZqNaaguovfIdRRsxxZr1yJAyDsr6w3yGImDZYju4c4WY9
+5esO2kP3FA4p0c7FhQF5oOb1rBuHEPp36cpL4aGeK87caqTfq63WZAujoTZpr9Lp
+BRbkL7w/xG7jpQ/clpA8sHzHGQs/nelxoOtC7E118FiRgvD/jdhlMyL9AoGBANfX
+vyoN1pplfT2xR8QOjSZ+Q35S/+SAtMuBnHx3l0qH2bbBjcvM1MNDWjnRDyaYhiRu
+i+KA7tqfib09+XpB3g5D6Ov7ls/Ldx0S/VcmVWtia2HK8y8iLGtokoBZKQ5AaFX2
+iQU8+tC4h69GnJYQKqNwgCUzh8+gHX5Y46oDiTmRAoGAYpOx8lX+czB8/Da6MNrW
+mIZNT8atZLEsDs2ANEVRxDSIcTCZJId7+m1W+nRoaycLTWNowZ1+2ErLvR10+AGY
+b7Ys79Wg9idYaY9yGn9lnZsMzAiuLeyIvXcSqgjvAKlVWrhOQFOughvNWvFl85Yy
+oWSCMlPiTLtt7CCsCKsgKuECgYBgdIp6GZsIfkgclKe0hqgvRoeU4TR3gcjJlM9A
+lBTo+pKhaBectplx9RxR8AnsPobbqwcaHnIfAuKDzjk5mEvKZjClnFXF4HAHbyAF
+nRzZEy9XkWFhc80T5rRpZO7C7qdxmu2aiKixM3V3L3/0U58qULEDbubHMw9bEhAT
+PudI8QKBgHEEiMm/hr9T41hbQi/LYanWnlFw1ue+osKuF8bXQuxnnHNuFT/c+9/A
+vWhgqG6bOEHu+p/IPrYm4tBMYlwsyh4nXCyGgDJLbLIfzKwKAWCtH9LwnyDVhOow
+GH9shdR+sW3Ew97xef02KAH4VlNANEmBV4sQNqWWvsYrcFm2rOdL
+-----END RSA PRIVATE KEY-----
diff --git a/fs_mgr/libfs_avb/tests/data/testkey_rsa4096.pem b/fs_mgr/libfs_avb/tests/data/testkey_rsa4096.pem
new file mode 100644
index 0000000..26db5c3
--- /dev/null
+++ b/fs_mgr/libfs_avb/tests/data/testkey_rsa4096.pem
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+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
+-----END RSA PRIVATE KEY-----
diff --git a/fs_mgr/libfs_avb/tests/data/testkey_rsa8192.pem b/fs_mgr/libfs_avb/tests/data/testkey_rsa8192.pem
new file mode 100644
index 0000000..a383428
--- /dev/null
+++ b/fs_mgr/libfs_avb/tests/data/testkey_rsa8192.pem
@@ -0,0 +1,99 @@
+-----BEGIN RSA PRIVATE KEY-----
+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
+-----END RSA PRIVATE KEY-----
diff --git a/fs_mgr/libfs_avb/tests/fs_avb_unittest_util.cpp b/fs_mgr/libfs_avb/tests/fs_avb_unittest_util.cpp
new file mode 100644
index 0000000..216d1cb
--- /dev/null
+++ b/fs_mgr/libfs_avb/tests/fs_avb_unittest_util.cpp
@@ -0,0 +1,465 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "fs_avb_unittest_util.h"
+
+#include <stdlib.h>
+
+#include <android-base/file.h>
+#include <base/files/file_util.h>
+#include <base/strings/string_util.h>
+
+namespace fs_avb_host_test {
+
+void BaseFsAvbTest::SetUp() {
+ // Changes current directory to test executable directory so that relative path
+ // references to test dependencies don't rely on being manually run from
+ // the executable directory. With this, we can just open "./data/testkey_rsa2048.pem"
+ // from the source.
+ base::SetCurrentDirectory(base::FilePath(android::base::GetExecutableDirectory()));
+
+ // Creates a temporary directory, e.g., /tmp/libfs_avb-tests.XXXXXX to stash images in.
+ base::FilePath tmp_dir;
+ ASSERT_TRUE(GetTempDir(&tmp_dir));
+ base::CreateTemporaryDirInDir(tmp_dir, "libfs_avb-tests.", &test_dir_);
+}
+
+void BaseFsAvbTest::TearDown() {
+ // Nukes temporary directory.
+ ASSERT_NE(std::string::npos, test_dir_.value().find("libfs_avb-tests"));
+ ASSERT_TRUE(base::DeleteFile(test_dir_, true /* recursive */));
+}
+
+std::string BaseFsAvbTest::CalcVBMetaDigest(const std::string& file_name,
+ const std::string& hash_algorithm) {
+ auto iter = vbmeta_images_.find(file_name);
+ EXPECT_NE(iter, vbmeta_images_.end()); // ensures file_name is generated before.
+
+ // Gets the image path from iterator->second.path: VBMetaImage.path.
+ base::FilePath image_path = iter->second.path;
+ base::FilePath vbmeta_digest_path = test_dir_.Append("vbmeta_digest");
+ EXPECT_COMMAND(0,
+ "avbtool calculate_vbmeta_digest --image %s --hash_algorithm %s"
+ " --output %s",
+ image_path.value().c_str(), hash_algorithm.c_str(),
+ vbmeta_digest_path.value().c_str());
+ // Reads the content of the output digest file.
+ std::string vbmeta_digest_data;
+ EXPECT_TRUE(base::ReadFileToString(vbmeta_digest_path, &vbmeta_digest_data));
+ // Returns the trimmed digest.
+ std::string trimmed_digest_data;
+ base::TrimString(vbmeta_digest_data, " \t\n", &trimmed_digest_data);
+ return trimmed_digest_data;
+}
+
+void BaseFsAvbTest::GenerateVBMetaImage(
+ const std::string& file_name, const std::string& avb_algorithm, uint64_t rollback_index,
+ const base::FilePath& key_path,
+ const std::vector<base::FilePath>& include_descriptor_image_paths,
+ const std::vector<ChainPartitionConfig>& chain_partitions,
+ const std::string& additional_options) {
+ // --algorithm and --key
+ std::string signing_options;
+ if (avb_algorithm == "") {
+ signing_options = " --algorithm NONE ";
+ } else {
+ signing_options =
+ std::string(" --algorithm ") + avb_algorithm + " --key " + key_path.value() + " ";
+ }
+ // --include_descriptors_from_image
+ std::string include_descriptor_options;
+ for (const auto& path : include_descriptor_image_paths) {
+ include_descriptor_options += " --include_descriptors_from_image " + path.value();
+ }
+ // --chain_partitions
+ std::string chain_partition_options;
+ for (const auto& partition : chain_partitions) {
+ chain_partition_options += base::StringPrintf(
+ " --chain_partition %s:%u:%s", partition.partition_name.c_str(),
+ partition.rollback_index_location, partition.key_blob_path.value().c_str());
+ }
+ // Starts to 'make_vbmeta_image'.
+ VBMetaImage vbmeta_image;
+ vbmeta_image.path = test_dir_.Append(file_name);
+ EXPECT_COMMAND(0,
+ "avbtool make_vbmeta_image"
+ " --rollback_index %" PRIu64
+ " %s %s %s %s"
+ " --output %s",
+ rollback_index, signing_options.c_str(), include_descriptor_options.c_str(),
+ chain_partition_options.c_str(), additional_options.c_str(),
+ vbmeta_image.path.value().c_str());
+ int64_t file_size;
+ ASSERT_TRUE(base::GetFileSize(vbmeta_image.path, &file_size));
+ vbmeta_image.content.resize(file_size);
+ ASSERT_TRUE(base::ReadFile(vbmeta_image.path,
+ reinterpret_cast<char*>(vbmeta_image.content.data()), file_size));
+ // Stores the generated vbmeta image into vbmeta_images_ member object.
+ vbmeta_images_.emplace(file_name, std::move(vbmeta_image));
+}
+
+void BaseFsAvbTest::ExtractVBMetaImage(const base::FilePath& image_path,
+ const std::string& output_file_name,
+ const size_t padding_size) {
+ VBMetaImage vbmeta_image;
+ vbmeta_image.path = test_dir_.Append(output_file_name);
+ EXPECT_COMMAND(0,
+ "avbtool extract_vbmeta_image"
+ " --image %s"
+ " --output %s"
+ " --padding_size %zu",
+ image_path.value().c_str(), vbmeta_image.path.value().c_str(), padding_size);
+ int64_t file_size;
+ ASSERT_TRUE(base::GetFileSize(vbmeta_image.path, &file_size));
+ vbmeta_image.content.resize(file_size);
+ ASSERT_TRUE(base::ReadFile(vbmeta_image.path,
+ reinterpret_cast<char*>(vbmeta_image.content.data()), file_size));
+ // Stores the extracted vbmeta image into vbmeta_images_ member object.
+ vbmeta_images_.emplace(output_file_name, std::move(vbmeta_image));
+}
+
+// Generates a file with name |file_name| of size |image_size| with
+// known content (0x00 0x01 0x02 .. 0xff 0x00 0x01 ..).
+base::FilePath BaseFsAvbTest::GenerateImage(const std::string file_name, size_t image_size,
+ uint8_t start_byte) {
+ std::vector<uint8_t> image;
+ image.resize(image_size);
+ for (size_t n = 0; n < image_size; n++) {
+ image[n] = uint8_t(n + start_byte);
+ }
+ base::FilePath image_path = test_dir_.Append(file_name);
+ EXPECT_EQ(image_size,
+ static_cast<const size_t>(base::WriteFile(
+ image_path, reinterpret_cast<const char*>(image.data()), image.size())));
+ return image_path;
+}
+
+void BaseFsAvbTest::AddAvbFooter(const base::FilePath& image_path, const std::string& footer_type,
+ const std::string& partition_name, const uint64_t partition_size,
+ const std::string& avb_algorithm, uint64_t rollback_index,
+ const base::FilePath& key_path, const std::string& salt,
+ const std::string& additional_options) {
+ // 'add_hash_footer' or 'add_hashtree_footer'.
+ EXPECT_TRUE(footer_type == "hash" or footer_type == "hashtree");
+ std::string add_footer_option = "add_" + footer_type + "_footer";
+
+ std::string signing_options;
+ if (avb_algorithm == "") {
+ signing_options = " --algorithm NONE ";
+ } else {
+ signing_options =
+ std::string(" --algorithm ") + avb_algorithm + " --key " + key_path.value() + " ";
+ }
+ EXPECT_COMMAND(0,
+ "avbtool %s"
+ " --image %s"
+ " --partition_name %s "
+ " --partition_size %" PRIu64 " --rollback_index %" PRIu64
+ " --salt %s"
+ " %s %s",
+ add_footer_option.c_str(), image_path.value().c_str(), partition_name.c_str(),
+ partition_size, rollback_index, salt.c_str(), signing_options.c_str(),
+ additional_options.c_str());
+}
+
+std::string BaseFsAvbTest::InfoImage(const base::FilePath& image_path) {
+ base::FilePath tmp_path = test_dir_.Append("info_output.txt");
+ EXPECT_COMMAND(0, "avbtool info_image --image %s --output %s", image_path.value().c_str(),
+ tmp_path.value().c_str());
+ std::string info_data;
+ EXPECT_TRUE(base::ReadFileToString(tmp_path, &info_data));
+ return info_data;
+}
+
+std::string BaseFsAvbTest::InfoImage(const std::string& file_name) {
+ auto iter = vbmeta_images_.find(file_name);
+ EXPECT_NE(iter, vbmeta_images_.end()); // ensures file_name is generated before.
+ // Gets the image path from iterator->second.path: VBMetaImage.path.
+ base::FilePath image_path = iter->second.path;
+ return InfoImage(image_path);
+}
+
+base::FilePath BaseFsAvbTest::ExtractPublicKeyAvb(const base::FilePath& key_path) {
+ std::string file_name = key_path.RemoveExtension().BaseName().value();
+ base::FilePath tmp_path = test_dir_.Append(file_name + "public_key.bin");
+ EXPECT_COMMAND(0,
+ "avbtool extract_public_key --key %s"
+ " --output %s",
+ key_path.value().c_str(), tmp_path.value().c_str());
+ return tmp_path;
+}
+
+std::string BaseFsAvbTest::ExtractPublicKeyAvbBlob(const base::FilePath& key_path) {
+ base::FilePath tmp_path = test_dir_.Append("public_key.bin");
+ EXPECT_COMMAND(0,
+ "avbtool extract_public_key --key %s"
+ " --output %s",
+ key_path.value().c_str(), tmp_path.value().c_str());
+ std::string key_data;
+ EXPECT_TRUE(base::ReadFileToString(tmp_path, &key_data));
+ return key_data;
+}
+
+TEST_F(BaseFsAvbTest, GenerateImage) {
+ const size_t image_size = 5 * 1024 * 1024;
+ base::FilePath boot_path = GenerateImage("boot.img", image_size);
+ EXPECT_NE(0U, boot_path.value().size());
+
+ // Checks file size is as expected.
+ int64_t file_size;
+ ASSERT_TRUE(base::GetFileSize(boot_path, &file_size));
+ EXPECT_EQ(file_size, image_size);
+
+ // Checks file content is as expected.
+ std::vector<uint8_t> expected_content;
+ expected_content.resize(image_size);
+ for (size_t n = 0; n < image_size; n++) {
+ expected_content[n] = uint8_t(n);
+ }
+ std::vector<uint8_t> actual_content;
+ actual_content.resize(image_size);
+ EXPECT_TRUE(
+ base::ReadFile(boot_path, reinterpret_cast<char*>(actual_content.data()), image_size));
+ EXPECT_EQ(expected_content, actual_content);
+}
+
+TEST_F(BaseFsAvbTest, GenerateVBMetaImage) {
+ GenerateVBMetaImage("vbmeta.img", "SHA256_RSA2048", 0,
+ base::FilePath("data/testkey_rsa2048.pem"),
+ {}, /* include_descriptor_image_paths */
+ {}, /* chain_partitions */
+ "--internal_release_string \"unit test\"");
+ EXPECT_EQ("5eba9ad4e775645e7eac441a563c200681ae868158d06f6a6cd36d06c07bd781",
+ CalcVBMetaDigest("vbmeta.img", "sha256"));
+ EXPECT_EQ(
+ "Minimum libavb version: 1.0\n"
+ "Header Block: 256 bytes\n"
+ "Authentication Block: 320 bytes\n"
+ "Auxiliary Block: 576 bytes\n"
+ "Algorithm: SHA256_RSA2048\n"
+ "Rollback Index: 0\n"
+ "Flags: 0\n"
+ "Release String: 'unit test'\n"
+ "Descriptors:\n"
+ " (none)\n",
+ InfoImage("vbmeta.img"));
+}
+
+TEST_F(BaseFsAvbTest, AddHashFooter) {
+ // Generates a raw boot.img
+ const size_t image_size = 5 * 1024 * 1024;
+ const size_t partition_size = 10 * 1024 * 1024;
+ base::FilePath boot_path = GenerateImage("boot.img", image_size);
+ EXPECT_NE(0U, boot_path.value().size());
+ // Checks file size is as expected.
+ int64_t file_size;
+ ASSERT_TRUE(base::GetFileSize(boot_path, &file_size));
+ EXPECT_EQ(file_size, image_size);
+ // Appends AVB Hash Footer.
+ AddAvbFooter(boot_path, "hash", "boot", partition_size, "SHA256_RSA4096", 10,
+ base::FilePath("data/testkey_rsa4096.pem"), "d00df00d",
+ "--internal_release_string \"unit test\"");
+ // Extracts boot vbmeta from boot.img into boot-vbmeta.img.
+ ExtractVBMetaImage(boot_path, "boot-vbmeta.img");
+ EXPECT_EQ(
+ "Minimum libavb version: 1.0\n"
+ "Header Block: 256 bytes\n"
+ "Authentication Block: 576 bytes\n"
+ "Auxiliary Block: 1216 bytes\n"
+ "Algorithm: SHA256_RSA4096\n"
+ "Rollback Index: 10\n"
+ "Flags: 0\n"
+ "Release String: 'unit test'\n"
+ "Descriptors:\n"
+ " Hash descriptor:\n"
+ " Image Size: 5242880 bytes\n"
+ " Hash Algorithm: sha256\n"
+ " Partition Name: boot\n"
+ " Salt: d00df00d\n"
+ " Digest: "
+ "222dd01e98284a1fcd7781f85d1392e43a530511a64eff96db197db90ebc4df1\n"
+ " Flags: 0\n",
+ InfoImage("boot-vbmeta.img"));
+}
+
+TEST_F(BaseFsAvbTest, AddHashtreeFooter) {
+ // Generates a raw system.img
+ const size_t image_size = 50 * 1024 * 1024;
+ const size_t partition_size = 60 * 1024 * 1024;
+ base::FilePath system_path = GenerateImage("system.img", image_size);
+ EXPECT_NE(0U, system_path.value().size());
+ // Checks file size is as expected.
+ int64_t file_size;
+ ASSERT_TRUE(base::GetFileSize(system_path, &file_size));
+ EXPECT_EQ(file_size, image_size);
+ // Appends AVB Hashtree Footer.
+ AddAvbFooter(system_path, "hashtree", "system", partition_size, "SHA512_RSA8192", 20,
+ base::FilePath("data/testkey_rsa8192.pem"), "d00df00d",
+ "--internal_release_string \"unit test\"");
+ // Extracts system vbmeta from system.img into system-vbmeta.img.
+ ExtractVBMetaImage(system_path, "system-vbmeta.img");
+ EXPECT_EQ(
+ "Minimum libavb version: 1.0\n"
+ "Header Block: 256 bytes\n"
+ "Authentication Block: 1088 bytes\n"
+ "Auxiliary Block: 2304 bytes\n"
+ "Algorithm: SHA512_RSA8192\n"
+ "Rollback Index: 20\n"
+ "Flags: 0\n"
+ "Release String: 'unit test'\n"
+ "Descriptors:\n"
+ " Hashtree descriptor:\n"
+ " Version of dm-verity: 1\n"
+ " Image Size: 52428800 bytes\n"
+ " Tree Offset: 52428800\n"
+ " Tree Size: 413696 bytes\n"
+ " Data Block Size: 4096 bytes\n"
+ " Hash Block Size: 4096 bytes\n"
+ " FEC num roots: 2\n"
+ " FEC offset: 52842496\n"
+ " FEC size: 417792 bytes\n"
+ " Hash Algorithm: sha1\n"
+ " Partition Name: system\n"
+ " Salt: d00df00d\n"
+ " Root Digest: d20d40c02298e385ab6d398a61a3b91dc9947d99\n"
+ " Flags: 0\n",
+ InfoImage("system-vbmeta.img"));
+}
+
+TEST_F(BaseFsAvbTest, GenerateVBMetaImageWithDescriptors) {
+ // Generates a raw boot.img
+ const size_t boot_image_size = 5 * 1024 * 1024;
+ const size_t boot_partition_size = 10 * 1024 * 1024;
+ base::FilePath boot_path = GenerateImage("boot.img", boot_image_size);
+ // Adds AVB Hash Footer.
+ AddAvbFooter(boot_path, "hash", "boot", boot_partition_size, "SHA256_RSA4096", 10,
+ base::FilePath("data/testkey_rsa4096.pem"), "d00df00d",
+ "--internal_release_string \"unit test\"");
+
+ // Generates a raw system.img, use a smaller size to speed-up unit test.
+ const size_t system_image_size = 10 * 1024 * 1024;
+ const size_t system_partition_size = 15 * 1024 * 1024;
+ base::FilePath system_path = GenerateImage("system.img", system_image_size);
+ // Adds AVB Hashtree Footer.
+ AddAvbFooter(system_path, "hashtree", "system", system_partition_size, "SHA512_RSA8192", 20,
+ base::FilePath("data/testkey_rsa8192.pem"), "d00df00d",
+ "--internal_release_string \"unit test\"");
+
+ // Makes a vbmeta.img including both 'boot' and 'system' descriptors.
+ GenerateVBMetaImage("vbmeta.img", "SHA256_RSA2048", 0,
+ base::FilePath("data/testkey_rsa2048.pem"),
+ {boot_path, system_path}, /* include_descriptor_image_paths */
+ {}, /* chain_partitions */
+ "--internal_release_string \"unit test\"");
+ EXPECT_EQ("a069cbfc30c816cddf3b53f1ad53b7ca5d61a3d93845eb596bbb1b40caa1c62f",
+ CalcVBMetaDigest("vbmeta.img", "sha256"));
+ EXPECT_EQ(
+ "Minimum libavb version: 1.0\n"
+ "Header Block: 256 bytes\n"
+ "Authentication Block: 320 bytes\n"
+ "Auxiliary Block: 960 bytes\n"
+ "Algorithm: SHA256_RSA2048\n"
+ "Rollback Index: 0\n"
+ "Flags: 0\n"
+ "Release String: 'unit test'\n"
+ "Descriptors:\n"
+ " Hash descriptor:\n"
+ " Image Size: 5242880 bytes\n"
+ " Hash Algorithm: sha256\n"
+ " Partition Name: boot\n"
+ " Salt: d00df00d\n"
+ " Digest: "
+ "222dd01e98284a1fcd7781f85d1392e43a530511a64eff96db197db90ebc4df1\n"
+ " Flags: 0\n"
+ " Hashtree descriptor:\n"
+ " Version of dm-verity: 1\n"
+ " Image Size: 10485760 bytes\n"
+ " Tree Offset: 10485760\n"
+ " Tree Size: 86016 bytes\n"
+ " Data Block Size: 4096 bytes\n"
+ " Hash Block Size: 4096 bytes\n"
+ " FEC num roots: 2\n"
+ " FEC offset: 10571776\n"
+ " FEC size: 90112 bytes\n"
+ " Hash Algorithm: sha1\n"
+ " Partition Name: system\n"
+ " Salt: d00df00d\n"
+ " Root Digest: a3d5dd307341393d85de356c384ff543ec1ed81b\n"
+ " Flags: 0\n",
+ InfoImage("vbmeta.img"));
+}
+
+TEST_F(BaseFsAvbTest, GenerateVBMetaImageWithChainDescriptors) {
+ // Generates a raw boot.img
+ const size_t boot_image_size = 5 * 1024 * 1024;
+ const size_t boot_partition_size = 10 * 1024 * 1024;
+ base::FilePath boot_path = GenerateImage("boot.img", boot_image_size);
+ // Adds AVB Hash Footer.
+ AddAvbFooter(boot_path, "hash", "boot", boot_partition_size, "SHA256_RSA2048", 10,
+ base::FilePath("data/testkey_rsa2048.pem"), "d00df00d",
+ "--internal_release_string \"unit test\"");
+
+ // Generates a raw system.img, use a smaller size to speed-up unit test.
+ const size_t system_image_size = 10 * 1024 * 1024;
+ const size_t system_partition_size = 15 * 1024 * 1024;
+ base::FilePath system_path = GenerateImage("system.img", system_image_size);
+ // Adds AVB Hashtree Footer.
+ AddAvbFooter(system_path, "hashtree", "system", system_partition_size, "SHA512_RSA4096", 20,
+ base::FilePath("data/testkey_rsa4096.pem"), "d00df00d",
+ "--internal_release_string \"unit test\"");
+
+ // Make a vbmeta image with chain partitions.
+ base::FilePath rsa2048_public_key =
+ ExtractPublicKeyAvb(base::FilePath("data/testkey_rsa2048.pem"));
+ base::FilePath rsa4096_public_key =
+ ExtractPublicKeyAvb(base::FilePath("data/testkey_rsa4096.pem"));
+ GenerateVBMetaImage("vbmeta.img", "SHA256_RSA8192", 0,
+ base::FilePath("data/testkey_rsa8192.pem"),
+ {}, /* include_descriptor_image_paths */
+ {{"boot", 1, rsa2048_public_key}, /* chain_partitions */
+ {"system", 2, rsa4096_public_key}},
+ "--internal_release_string \"unit test\"");
+
+ // vbmeta digest calculation includes the chained vbmeta from boot.img and system.img.
+ EXPECT_EQ("abbe11b316901f3336e26630f64c4732dadbe14532186ac8640e4141a403721f",
+ CalcVBMetaDigest("vbmeta.img", "sha256"));
+ EXPECT_EQ(
+ "Minimum libavb version: 1.0\n"
+ "Header Block: 256 bytes\n"
+ "Authentication Block: 1088 bytes\n"
+ "Auxiliary Block: 3840 bytes\n"
+ "Algorithm: SHA256_RSA8192\n"
+ "Rollback Index: 0\n"
+ "Flags: 0\n"
+ "Release String: 'unit test'\n"
+ "Descriptors:\n"
+ " Chain Partition descriptor:\n"
+ " Partition Name: boot\n"
+ " Rollback Index Location: 1\n"
+ " Public key (sha1): cdbb77177f731920bbe0a0f94f84d9038ae0617d\n"
+ " Chain Partition descriptor:\n"
+ " Partition Name: system\n"
+ " Rollback Index Location: 2\n"
+ " Public key (sha1): 2597c218aae470a130f61162feaae70afd97f011\n",
+ InfoImage("vbmeta.img"));
+}
+
+} // namespace fs_avb_host_test
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/fs_mgr/libfs_avb/tests/fs_avb_unittest_util.h b/fs_mgr/libfs_avb/tests/fs_avb_unittest_util.h
new file mode 100644
index 0000000..f329466
--- /dev/null
+++ b/fs_mgr/libfs_avb/tests/fs_avb_unittest_util.h
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <inttypes.h>
+#include <stdlib.h>
+#include <sys/wait.h>
+#include <string>
+#include <vector>
+
+#include <base/files/file_path.h>
+#include <base/strings/stringprintf.h>
+#include <gtest/gtest.h>
+
+// Utility macro to run the command expressed by the printf()-style string
+// |command_format| using the system(3) utility function. Will assert unless
+// the command exits normally with exit status |expected_exit_status|.
+#define EXPECT_COMMAND(expected_exit_status, command_format, ...) \
+ do { \
+ int rc = system(base::StringPrintf(command_format, ##__VA_ARGS__).c_str()); \
+ EXPECT_TRUE(WIFEXITED(rc)); \
+ EXPECT_EQ(WEXITSTATUS(rc), expected_exit_status); \
+ } while (0);
+
+namespace fs_avb_host_test {
+
+struct VBMetaImage {
+ // Path to vbmeta image generated with GenerateVBMetaImage().
+ base::FilePath path;
+ // Contents of the image generated with GenerateVBMetaImage().
+ std::vector<uint8_t> content;
+};
+
+struct ChainPartitionConfig {
+ std::string partition_name;
+ uint32_t rollback_index_location;
+ base::FilePath key_blob_path;
+};
+
+/* Base-class used for unit test. */
+class BaseFsAvbTest : public ::testing::Test {
+ public:
+ BaseFsAvbTest() {}
+
+ protected:
+ virtual ~BaseFsAvbTest() {}
+
+ // Calculates the vbmeta digest using 'avbtool calc_vbmeta_digest' command.
+ // Note that the calculation includes chained vbmeta images.
+ std::string CalcVBMetaDigest(const std::string& file_name, const std::string& hash_algorithm);
+
+ // Generates a vbmeta image with |file_name| by avbtool.
+ // The generated vbmeta image will be written to disk, see the
+ // |vbmeta_images_| variable for its path and the content.
+ void GenerateVBMetaImage(const std::string& file_name, const std::string& avb_algorithm,
+ uint64_t rollback_index, const base::FilePath& key_path,
+ const std::vector<base::FilePath>& include_descriptor_image_paths,
+ const std::vector<ChainPartitionConfig>& chain_partitions,
+ const std::string& additional_options = "");
+ // Similar to above, but extracts a vbmeta image from the given image_path.
+ // The extracted vbmeta image will be written to disk, with |output_file_name|.
+ // See the |vbmeta_images_| variable for its path and the content.
+ void ExtractVBMetaImage(const base::FilePath& image_path, const std::string& output_file_name,
+ const size_t padding_size = 0);
+
+ // Generate a file with name |file_name| of size |image_size| with
+ // known content (0x00 0x01 0x02 .. 0xff 0x00 0x01 ..).
+ base::FilePath GenerateImage(const std::string file_name, size_t image_size,
+ uint8_t start_byte = 0);
+ // Invokes 'avbtool add_hash_footer' or 'avbtool add_hashtree_footer' to sign
+ // the |image_path|. The |footer_type| can be either "hash" or "hashtree".
+ void AddAvbFooter(const base::FilePath& image_path, const std::string& footer_type,
+ const std::string& partition_name, const uint64_t partition_size,
+ const std::string& avb_algorithm, uint64_t rollback_index,
+ const base::FilePath& key_path, const std::string& salt = "d00df00d",
+ const std::string& additional_options = "");
+
+ // Returns the output of 'avbtool info_image' for the |image_path|.
+ std::string InfoImage(const base::FilePath& image_path);
+ // Same as above, but for an internal vbmeta image with |file_name| in |vbmeta_images_|.
+ std::string InfoImage(const std::string& file_name);
+
+ // Extracts public key blob in AVB format for a .pem key, then returns the
+ // file path: a .bin file.
+ base::FilePath ExtractPublicKeyAvb(const base::FilePath& key_path);
+ // Same as above, but returns the key blob binary instead.
+ std::string ExtractPublicKeyAvbBlob(const base::FilePath& key_path);
+
+ void SetUp() override;
+ void TearDown() override;
+
+ // Temporary directory created in SetUp().
+ base::FilePath test_dir_;
+ // Maps vbmeta image name (e.g., vbmeta_a.img, system_a.img) to VBMetaImage.
+ std::map<std::string, VBMetaImage> vbmeta_images_;
+};
+
+} // namespace fs_avb_host_test
diff --git a/fs_mgr/liblp/builder.cpp b/fs_mgr/liblp/builder.cpp
index 07f9d66..07e3c8a 100644
--- a/fs_mgr/liblp/builder.cpp
+++ b/fs_mgr/liblp/builder.cpp
@@ -212,7 +212,7 @@
sABOverrideValue = ab_device;
}
-MetadataBuilder::MetadataBuilder() : auto_slot_suffixing_(false) {
+MetadataBuilder::MetadataBuilder() : auto_slot_suffixing_(false), ignore_slot_suffixing_(false) {
memset(&geometry_, 0, sizeof(geometry_));
geometry_.magic = LP_METADATA_GEOMETRY_MAGIC;
geometry_.struct_size = sizeof(geometry_);
@@ -436,7 +436,7 @@
LERROR << "Could not find partition group: " << group_name;
return nullptr;
}
- if (IsABDevice() && !auto_slot_suffixing_ && name != "scratch" &&
+ if (IsABDevice() && !auto_slot_suffixing_ && name != "scratch" && !ignore_slot_suffixing_ &&
GetPartitionSlotSuffix(name).empty()) {
LERROR << "Unsuffixed partition not allowed on A/B device: " << name;
return nullptr;
@@ -972,6 +972,10 @@
auto_slot_suffixing_ = true;
}
+void MetadataBuilder::IgnoreSlotSuffixing() {
+ ignore_slot_suffixing_ = true;
+}
+
bool MetadataBuilder::IsABDevice() const {
if (sABOverrideSet) {
return sABOverrideValue;
@@ -983,5 +987,18 @@
return GetBlockDevicePartitionName(block_devices_[0]) != LP_METADATA_DEFAULT_PARTITION_NAME;
}
+bool MetadataBuilder::AddLinearExtent(Partition* partition, const std::string& block_device,
+ uint64_t num_sectors, uint64_t physical_sector) {
+ uint32_t device_index;
+ if (!FindBlockDeviceByName(block_device, &device_index)) {
+ LERROR << "Could not find backing block device for extent: " << block_device;
+ return false;
+ }
+
+ auto extent = std::make_unique<LinearExtent>(num_sectors, device_index, physical_sector);
+ partition->AddExtent(std::move(extent));
+ return true;
+}
+
} // namespace fs_mgr
} // namespace android
diff --git a/fs_mgr/liblp/include/liblp/builder.h b/fs_mgr/liblp/include/liblp/builder.h
index f477b4b..57cce21 100644
--- a/fs_mgr/liblp/include/liblp/builder.h
+++ b/fs_mgr/liblp/include/liblp/builder.h
@@ -219,6 +219,10 @@
// Find a group by name. If no group is found, nullptr is returned.
PartitionGroup* FindGroup(const std::string& name);
+ // Add a predetermined extent to a partition.
+ bool AddLinearExtent(Partition* partition, const std::string& block_device,
+ uint64_t num_sectors, uint64_t physical_sector);
+
// Grow or shrink a partition to the requested size. This size will be
// rounded UP to the nearest block (512 bytes).
//
@@ -244,6 +248,9 @@
// Set the LP_METADATA_AUTO_SLOT_SUFFIXING flag.
void SetAutoSlotSuffixing();
+ // If set, checks for slot suffixes will be ignored internally.
+ void IgnoreSlotSuffixing();
+
bool GetBlockDeviceInfo(const std::string& partition_name, BlockDeviceInfo* info) const;
bool UpdateBlockDeviceInfo(const std::string& partition_name, const BlockDeviceInfo& info);
@@ -306,6 +313,7 @@
std::vector<std::unique_ptr<PartitionGroup>> groups_;
std::vector<LpMetadataBlockDevice> block_devices_;
bool auto_slot_suffixing_;
+ bool ignore_slot_suffixing_;
};
// Read BlockDeviceInfo for a given block device. This always returns false
diff --git a/init/devices.cpp b/init/devices.cpp
index 45b17a2..1a77ba1 100644
--- a/init/devices.cpp
+++ b/init/devices.cpp
@@ -320,6 +320,7 @@
auto link_path = "/dev/block/" + type + "/" + device;
+ bool is_boot_device = boot_devices_.find(device) != boot_devices_.end();
if (!uevent.partition_name.empty()) {
std::string partition_name_sanitized(uevent.partition_name);
SanitizePartitionName(&partition_name_sanitized);
@@ -329,9 +330,13 @@
}
links.emplace_back(link_path + "/by-name/" + partition_name_sanitized);
// Adds symlink: /dev/block/by-name/<partition_name>.
- if (boot_devices_.find(device) != boot_devices_.end()) {
+ if (is_boot_device) {
links.emplace_back("/dev/block/by-name/" + partition_name_sanitized);
}
+ } else if (is_boot_device) {
+ // If we don't have a partition name but we are a partition on a boot device, create a
+ // symlink of /dev/block/by-name/<device_name> for symmetry.
+ links.emplace_back("/dev/block/by-name/" + uevent.device_name);
}
auto last_slash = uevent.path.rfind('/');
diff --git a/libmeminfo/tools/librank.cpp b/libmeminfo/tools/librank.cpp
index 7668640..2c2583d 100644
--- a/libmeminfo/tools/librank.cpp
+++ b/libmeminfo/tools/librank.cpp
@@ -40,7 +40,7 @@
using ::android::meminfo::ProcMemInfo;
using ::android::meminfo::Vma;
-[[noreturn]] static void usage(const char* myname, int exit_status) {
+[[noreturn]] static void usage(int exit_status) {
fprintf(stderr,
"Usage: %s [ -P | -L ] [ -v | -r | -p | -u | -s | -h ]\n"
"\n"
@@ -59,7 +59,7 @@
" -C Only show non-cached (ram/swap backed) pages\n"
" -k Only show pages collapsed by KSM\n"
" -h Display this help screen.\n",
- myname);
+ getprogname());
exit(exit_status);
}
@@ -278,7 +278,7 @@
g_pgflags = g_pgflags_mask = (1 << KPF_SWAPBACKED);
break;
case 'h':
- usage(argv[0], EXIT_SUCCESS);
+ usage(EXIT_SUCCESS);
case 'k':
g_pgflags = g_pgflags_mask = (1 << KPF_KSM);
break;
@@ -307,7 +307,7 @@
g_reverse_sort = true;
break;
default:
- usage(argv[0], EXIT_FAILURE);
+ usage(EXIT_FAILURE);
}
}
diff --git a/libmeminfo/tools/procmem.cpp b/libmeminfo/tools/procmem.cpp
index 56de12d..b9b174d 100644
--- a/libmeminfo/tools/procmem.cpp
+++ b/libmeminfo/tools/procmem.cpp
@@ -42,7 +42,7 @@
// Show working set, mutually exclusive with reset_wss;
bool show_wss = false;
-static void usage(const char* cmd) {
+[[noreturn]] static void usage(int exit_status) {
fprintf(stderr,
"Usage: %s [-i] [ -w | -W ] [ -p | -m ] [ -h ] pid\n"
" -i Uses idle page tracking for working set statistics.\n"
@@ -52,7 +52,9 @@
" -u Sort by USS.\n"
" -m Sort by mapping order (as read from /proc).\n"
" -h Hide maps with no RSS.\n",
- cmd);
+ getprogname());
+
+ exit(exit_status);
}
static void print_separator(std::stringstream& ss) {
@@ -154,17 +156,15 @@
show_wss = true;
break;
case '?':
- usage(argv[0]);
- return 0;
+ usage(EXIT_SUCCESS);
default:
- abort();
+ usage(EXIT_FAILURE);
}
}
if (optind != (argc - 1)) {
fprintf(stderr, "Need exactly one pid at the end\n");
- usage(argv[0]);
- exit(EXIT_FAILURE);
+ usage(EXIT_FAILURE);
}
pid_t pid = atoi(argv[optind]);
diff --git a/libmeminfo/tools/procrank.cpp b/libmeminfo/tools/procrank.cpp
index e39e7fa..a751722 100644
--- a/libmeminfo/tools/procrank.cpp
+++ b/libmeminfo/tools/procrank.cpp
@@ -142,8 +142,9 @@
uint64_t total_uswap = 0;
uint64_t total_zswap = 0;
-static void usage(const char* myname) {
- std::cerr << "Usage: " << myname << " [ -W ] [ -v | -r | -p | -u | -s | -h ]" << std::endl
+[[noreturn]] static void usage(int exit_status) {
+ std::cerr << "Usage: " << getprogname() << " [ -W ] [ -v | -r | -p | -u | -s | -h ]"
+ << std::endl
<< " -v Sort by VSS." << std::endl
<< " -r Sort by RSS." << std::endl
<< " -p Sort by PSS." << std::endl
@@ -159,6 +160,7 @@
<< " -o Show and sort by oom score against lowmemorykiller thresholds."
<< std::endl
<< " -h Display this help screen." << std::endl;
+ exit(exit_status);
}
static bool read_all_pids(std::vector<pid_t>* pids, std::function<bool(pid_t pid)> for_each_pid) {
@@ -386,9 +388,7 @@
pgflags_mask = (1 << KPF_SWAPBACKED);
break;
case 'h':
- usage(argv[0]);
- return 0;
- break;
+ usage(EXIT_SUCCESS);
case 'k':
pgflags = (1 << KPF_KSM);
pgflags_mask = (1 << KPF_KSM);
@@ -422,7 +422,7 @@
reset_wss = true;
break;
default:
- abort();
+ usage(EXIT_FAILURE);
}
}
diff --git a/libutils/include/utils/CallStack.h b/libutils/include/utils/CallStack.h
index 56004fe..7a4a345 100644
--- a/libutils/include/utils/CallStack.h
+++ b/libutils/include/utils/CallStack.h
@@ -123,13 +123,14 @@
if (reinterpret_cast<uintptr_t>(logStackInternal) != 0 && stack != nullptr) {
logStackInternal(logtag, stack, priority);
} else {
- ALOGW("CallStack::logStackInternal not linked");
+ ALOG(LOG_WARN, logtag, "CallStack::logStackInternal not linked");
}
}
#else
- static void ALWAYS_INLINE logStack(const char*, CallStack* = getCurrent().get(),
+ static void ALWAYS_INLINE logStack(const char* logtag, CallStack* = getCurrent().get(),
android_LogPriority = ANDROID_LOG_DEBUG) {
+ ALOG(LOG_WARN, logtag, "CallStack::logStackInternal not linked");
}
#endif // !WEAKS_AVAILABLE
@@ -139,13 +140,13 @@
if (reinterpret_cast<uintptr_t>(stackToStringInternal) != 0 && stack != nullptr) {
return stackToStringInternal(prefix, stack);
} else {
- return String8("<CallStack package not linked>");
+ return String8::format("%s<CallStack package not linked>", (prefix ? prefix : ""));
}
}
#else // !WEAKS_AVAILABLE
- static String8 ALWAYS_INLINE stackToString(const char* = nullptr,
+ static String8 ALWAYS_INLINE stackToString(const char* prefix = nullptr,
const CallStack* = getCurrent().get()) {
- return String8("<CallStack package not linked>");
+ return String8::format("%s<CallStack package not linked>", (prefix ? prefix : ""));
}
#endif // !WEAKS_AVAILABLE
diff --git a/llkd/README.md b/llkd/README.md
index 224e184..43bb94a 100644
--- a/llkd/README.md
+++ b/llkd/README.md
@@ -86,7 +86,13 @@
Android Properties
------------------
-Android Properties llkd respond to (*prop*_ms parms are in milliseconds):
+The following are the Android Properties llkd respond to.
+*prop*_ms named properties are in milliseconds.
+Properties that use comma (*,*) separator for lists, use a leading separator to
+preserve default and add or subtract entries with (*optional*) plus (*+*) and
+minus (*-*) prefixes respectively.
+For these lists, the string "*false*" is synonymous with an *empty* list,
+and *blank* or *missing* resorts to the specified *default* value.
#### ro.config.low_ram
device is configured with limited memory.
@@ -135,8 +141,8 @@
default 2 minutes samples of threads for D or Z.
#### ro.llk.stack
-default cma_alloc,__get_user_pages,bit_wait_io comma separated list of kernel
-symbols. The string "*false*" is the equivalent to an *empty* list.
+default cma_alloc,__get_user_pages,bit_wait_io,wait_on_page_bit_killable
+comma separated list of kernel symbols.
Look for kernel stack symbols that if ever persistently present can
indicate a subsystem is locked up.
Beware, check does not on purpose do forward scheduling ABA except by polling
@@ -153,7 +159,6 @@
default 0,1,2 (kernel, init and [kthreadd]) plus process names
init,[kthreadd],[khungtaskd],lmkd,llkd,watchdogd,
[watchdogd],[watchdogd/0],...,[watchdogd/***get_nprocs**-1*].
-The string "*false*" is the equivalent to an *empty* list.
Do not watch these processes. A process can be comm, cmdline or pid reference.
NB: automated default here can be larger than the current maximum property
size of 92.
@@ -161,18 +166,15 @@
#### ro.llk.blacklist.parent
default 0,2,adbd (kernel, [kthreadd] and adbd).
-The string "*false*" is the equivalent to an *empty* list.
Do not watch processes that have this parent.
A parent process can be comm, cmdline or pid reference.
#### ro.llk.blacklist.uid
default *empty* or false, comma separated list of uid numbers or names.
-The string "*false*" is the equivalent to an *empty* list.
Do not watch processes that match this uid.
#### ro.llk.blacklist.process.stack
default process names init,lmkd.llkd,llkd,keystore,ueventd,apexd,logd.
-The string "*false*" is the equivalent to an *empty* list.
This subset of processes are not monitored for live lock stack signatures.
Also prevents the sepolicy violation associated with processes that block
ptrace, as these can not be checked anyways.
diff --git a/llkd/include/llkd.h b/llkd/include/llkd.h
index 1efa32b..7b7dbf9 100644
--- a/llkd/include/llkd.h
+++ b/llkd/include/llkd.h
@@ -50,7 +50,8 @@
/* LLK_CHECK_MS_DEFAULT = actual timeout_ms / LLK_CHECKS_PER_TIMEOUT_DEFAULT */
#define LLK_CHECKS_PER_TIMEOUT_DEFAULT 5
#define LLK_CHECK_STACK_PROPERTY "ro.llk.stack"
-#define LLK_CHECK_STACK_DEFAULT "cma_alloc,__get_user_pages,bit_wait_io"
+#define LLK_CHECK_STACK_DEFAULT \
+ "cma_alloc,__get_user_pages,bit_wait_io,wait_on_page_bit_killable"
#define LLK_BLACKLIST_PROCESS_PROPERTY "ro.llk.blacklist.process"
#define LLK_BLACKLIST_PROCESS_DEFAULT \
"0,1,2,init,[kthreadd],[khungtaskd],lmkd,llkd,watchdogd,[watchdogd],[watchdogd/0]"
diff --git a/llkd/libllkd.cpp b/llkd/libllkd.cpp
index 267da4a..3a593ec 100644
--- a/llkd/libllkd.cpp
+++ b/llkd/libllkd.cpp
@@ -24,6 +24,7 @@
#include <pwd.h> // getpwuid()
#include <signal.h>
#include <stdint.h>
+#include <string.h>
#include <sys/cdefs.h> // ___STRING, __predict_true() and _predict_false()
#include <sys/mman.h> // mlockall()
#include <sys/prctl.h>
@@ -617,17 +618,24 @@
std::string llkFormat(const std::unordered_set<std::string>& blacklist) {
std::string ret;
for (const auto& entry : blacklist) {
- if (ret.size()) {
- ret += ",";
- }
+ if (!ret.empty()) ret += ",";
ret += entry;
}
return ret;
}
+// This function parses the properties as a list, incorporating the supplied
+// default. A leading comma separator means preserve the defaults and add
+// entries (with an optional leading + sign), or removes entries with a leading
+// - sign.
+//
// We only officially support comma separators, but wetware being what they
// are will take some liberty and I do not believe they should be punished.
-std::unordered_set<std::string> llkSplit(const std::string& s) {
+std::unordered_set<std::string> llkSplit(const std::string& prop, const std::string& def) {
+ auto s = android::base::GetProperty(prop, def);
+ constexpr char separators[] = ", \t:;";
+ if (!s.empty() && (s != def) && strchr(separators, s[0])) s = def + s;
+
std::unordered_set<std::string> result;
// Special case, allow boolean false to empty the list, otherwise expected
@@ -637,9 +645,29 @@
size_t base = 0;
while (s.size() > base) {
- auto found = s.find_first_of(", \t:", base);
- // Only emplace content, empty entries are not an option
- if (found != base) result.emplace(s.substr(base, found - base));
+ auto found = s.find_first_of(separators, base);
+ // Only emplace unique content, empty entries are not an option
+ if (found != base) {
+ switch (s[base]) {
+ case '-':
+ ++base;
+ if (base >= s.size()) break;
+ if (base != found) {
+ auto have = result.find(s.substr(base, found - base));
+ if (have != result.end()) result.erase(have);
+ }
+ break;
+ case '+':
+ ++base;
+ if (base >= s.size()) break;
+ if (base == found) break;
+ // FALLTHRU (for gcc, lint, pcc, etc; following for clang)
+ FALLTHROUGH_INTENDED;
+ default:
+ result.emplace(s.substr(base, found - base));
+ break;
+ }
+ }
if (found == s.npos) break;
base = found + 1;
}
@@ -648,13 +676,21 @@
bool llkSkipName(const std::string& name,
const std::unordered_set<std::string>& blacklist = llkBlacklistProcess) {
- if ((name.size() == 0) || (blacklist.size() == 0)) {
- return false;
- }
+ if (name.empty() || blacklist.empty()) return false;
return blacklist.find(name) != blacklist.end();
}
+bool llkSkipProc(proc* procp,
+ const std::unordered_set<std::string>& blacklist = llkBlacklistProcess) {
+ if (!procp) return false;
+ if (llkSkipName(std::to_string(procp->pid), blacklist)) return true;
+ if (llkSkipName(procp->getComm(), blacklist)) return true;
+ if (llkSkipName(procp->getCmdline(), blacklist)) return true;
+ if (llkSkipName(android::base::Basename(procp->getCmdline()), blacklist)) return true;
+ return false;
+}
+
bool llkSkipPid(pid_t pid) {
return llkSkipName(std::to_string(pid), llkBlacklistProcess);
}
@@ -730,26 +766,24 @@
}
// Don't check process that are known to block ptrace, save sepolicy noise.
- if (llkSkipName(std::to_string(procp->pid), llkBlacklistStack)) return false;
- if (llkSkipName(procp->getComm(), llkBlacklistStack)) return false;
- if (llkSkipName(procp->getCmdline(), llkBlacklistStack)) return false;
- if (llkSkipName(android::base::Basename(procp->getCmdline()), llkBlacklistStack)) return false;
-
+ if (llkSkipProc(procp, llkBlacklistStack)) return false;
auto kernel_stack = ReadFile(piddir + "/stack");
if (kernel_stack.empty()) {
- LOG(INFO) << piddir << "/stack empty comm=" << procp->getComm()
- << " cmdline=" << procp->getCmdline();
+ LOG(VERBOSE) << piddir << "/stack empty comm=" << procp->getComm()
+ << " cmdline=" << procp->getCmdline();
return false;
}
// A scheduling incident that should not reset count_stack
if (kernel_stack.find(" cpu_worker_pools+0x") != std::string::npos) return false;
char idx = -1;
char match = -1;
+ std::string matched_stack_symbol = "<unknown>";
for (const auto& stack : llkCheckStackSymbols) {
if (++idx < 0) break;
if ((kernel_stack.find(" "s + stack + "+0x") != std::string::npos) ||
(kernel_stack.find(" "s + stack + ".cfi+0x") != std::string::npos)) {
match = idx;
+ matched_stack_symbol = stack;
break;
}
}
@@ -760,7 +794,9 @@
}
if (match == char(-1)) return false;
procp->count_stack += llkCycle;
- return procp->count_stack >= llkStateTimeoutMs[llkStateStack];
+ if (procp->count_stack < llkStateTimeoutMs[llkStateStack]) return false;
+ LOG(WARNING) << "Found " << matched_stack_symbol << " in stack for pid " << procp->pid;
+ return true;
}
#endif
@@ -776,12 +812,12 @@
// but if there are problems we assume at least a few
// samples of reads occur before we take any real action.
std::string schedString = ReadFile(piddir + "/sched");
- if (schedString.size() == 0) {
+ if (schedString.empty()) {
// /schedstat is not as standardized, but in 3.1+
// Android devices, the third field is nr_switches
// from /sched:
schedString = ReadFile(piddir + "/schedstat");
- if (schedString.size() == 0) {
+ if (schedString.empty()) {
return;
}
auto val = static_cast<unsigned long long>(-1);
@@ -939,7 +975,7 @@
// Get the process stat
std::string stat = ReadFile(piddir + "/stat");
- if (stat.size() == 0) {
+ if (stat.empty()) {
continue;
}
unsigned tid = -1;
@@ -1028,11 +1064,10 @@
if (pprocp == nullptr) {
pprocp = llkTidAlloc(ppid, ppid, 0, "", 0, '?');
}
- if ((pprocp != nullptr) &&
- (llkSkipName(pprocp->getComm(), llkBlacklistParent) ||
- llkSkipName(pprocp->getCmdline(), llkBlacklistParent) ||
- llkSkipName(android::base::Basename(pprocp->getCmdline()), llkBlacklistParent))) {
- break;
+ if (pprocp) {
+ if (llkSkipProc(pprocp, llkBlacklistParent)) break;
+ } else {
+ if (llkSkipName(std::to_string(ppid), llkBlacklistParent)) break;
}
if ((llkBlacklistUid.size() != 0) && llkSkipUid(procp->getUid())) {
@@ -1131,21 +1166,15 @@
if (!p->second.updated) {
IF_ALOG(LOG_VERBOSE, LOG_TAG) {
std::string ppidCmdline = llkProcGetName(p->second.ppid, nullptr, nullptr);
- if (ppidCmdline.size()) {
- ppidCmdline = "(" + ppidCmdline + ")";
- }
+ if (!ppidCmdline.empty()) ppidCmdline = "(" + ppidCmdline + ")";
std::string pidCmdline;
if (p->second.pid != p->second.tid) {
pidCmdline = llkProcGetName(p->second.pid, nullptr, p->second.getCmdline());
- if (pidCmdline.size()) {
- pidCmdline = "(" + pidCmdline + ")";
- }
+ if (!pidCmdline.empty()) pidCmdline = "(" + pidCmdline + ")";
}
std::string tidCmdline =
llkProcGetName(p->second.tid, p->second.getComm(), p->second.getCmdline());
- if (tidCmdline.size()) {
- tidCmdline = "(" + tidCmdline + ")";
- }
+ if (!tidCmdline.empty()) tidCmdline = "(" + tidCmdline + ")";
LOG(VERBOSE) << "thread " << p->second.ppid << ppidCmdline << "->" << p->second.pid
<< pidCmdline << "->" << p->second.tid << tidCmdline << " removed";
}
@@ -1222,13 +1251,11 @@
llkValidate(); // validate all (effectively minus llkTimeoutMs)
#ifdef __PTRACE_ENABLED__
if (debuggable) {
- llkCheckStackSymbols = llkSplit(
- android::base::GetProperty(LLK_CHECK_STACK_PROPERTY, LLK_CHECK_STACK_DEFAULT));
+ llkCheckStackSymbols = llkSplit(LLK_CHECK_STACK_PROPERTY, LLK_CHECK_STACK_DEFAULT);
}
std::string defaultBlacklistStack(LLK_BLACKLIST_STACK_DEFAULT);
if (!debuggable) defaultBlacklistStack += ",logd,/system/bin/logd";
- llkBlacklistStack = llkSplit(
- android::base::GetProperty(LLK_BLACKLIST_STACK_PROPERTY, defaultBlacklistStack));
+ llkBlacklistStack = llkSplit(LLK_BLACKLIST_STACK_PROPERTY, defaultBlacklistStack);
#endif
std::string defaultBlacklistProcess(
std::to_string(kernelPid) + "," + std::to_string(initPid) + "," +
@@ -1240,17 +1267,14 @@
for (int cpu = 1; cpu < get_nprocs_conf(); ++cpu) {
defaultBlacklistProcess += ",[watchdog/" + std::to_string(cpu) + "]";
}
- defaultBlacklistProcess =
- android::base::GetProperty(LLK_BLACKLIST_PROCESS_PROPERTY, defaultBlacklistProcess);
- llkBlacklistProcess = llkSplit(defaultBlacklistProcess);
+ llkBlacklistProcess = llkSplit(LLK_BLACKLIST_PROCESS_PROPERTY, defaultBlacklistProcess);
if (!llkSkipName("[khungtaskd]")) { // ALWAYS ignore as special
llkBlacklistProcess.emplace("[khungtaskd]");
}
- llkBlacklistParent = llkSplit(android::base::GetProperty(
- LLK_BLACKLIST_PARENT_PROPERTY, std::to_string(kernelPid) + "," + std::to_string(kthreaddPid) +
- "," LLK_BLACKLIST_PARENT_DEFAULT));
- llkBlacklistUid =
- llkSplit(android::base::GetProperty(LLK_BLACKLIST_UID_PROPERTY, LLK_BLACKLIST_UID_DEFAULT));
+ llkBlacklistParent = llkSplit(LLK_BLACKLIST_PARENT_PROPERTY,
+ std::to_string(kernelPid) + "," + std::to_string(kthreaddPid) +
+ "," LLK_BLACKLIST_PARENT_DEFAULT);
+ llkBlacklistUid = llkSplit(LLK_BLACKLIST_UID_PROPERTY, LLK_BLACKLIST_UID_DEFAULT);
// internal watchdog
::signal(SIGALRM, llkAlarmHandler);
diff --git a/llkd/tests/llkd_test.cpp b/llkd/tests/llkd_test.cpp
index f54932b..d738935 100644
--- a/llkd/tests/llkd_test.cpp
+++ b/llkd/tests/llkd_test.cpp
@@ -87,7 +87,8 @@
execute("stop llkd-1");
rest();
std::string setprop("setprop ");
- execute((setprop + LLK_CHECK_STACK_PROPERTY + " SyS_openat").c_str());
+ // Manually check that SyS_openat is _added_ to the list when restarted
+ execute((setprop + LLK_CHECK_STACK_PROPERTY + " ,SyS_openat").c_str());
rest();
execute((setprop + LLK_ENABLE_WRITEABLE_PROPERTY + " false").c_str());
rest();
diff --git a/rootdir/etc/ld.config.txt b/rootdir/etc/ld.config.txt
index b0ba939..5601e53 100644
--- a/rootdir/etc/ld.config.txt
+++ b/rootdir/etc/ld.config.txt
@@ -105,6 +105,7 @@
namespace.default.asan.permitted.paths += /%PRODUCT_SERVICES%/app
namespace.default.asan.permitted.paths += /%PRODUCT_SERVICES%/priv-app
namespace.default.asan.permitted.paths += /mnt/expand
+namespace.default.asan.permitted.paths += /apex/com.android.resolv/${LIB}
# Keep in sync with ld.config.txt in the com.android.runtime APEX.
namespace.default.links = runtime
diff --git a/trusty/utils/trusty-ut-ctrl/Android.bp b/trusty/utils/trusty-ut-ctrl/Android.bp
new file mode 100644
index 0000000..77d1f70
--- /dev/null
+++ b/trusty/utils/trusty-ut-ctrl/Android.bp
@@ -0,0 +1,30 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_test {
+ name: "trusty-ut-ctrl",
+ vendor: true,
+
+ srcs: ["ut-ctrl.c"],
+ shared_libs: [
+ "libc",
+ "liblog",
+ "libtrusty",
+ ],
+ gtest: false,
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+}
diff --git a/trusty/utils/trusty-ut-ctrl/ut-ctrl.c b/trusty/utils/trusty-ut-ctrl/ut-ctrl.c
new file mode 100644
index 0000000..9e72af3
--- /dev/null
+++ b/trusty/utils/trusty-ut-ctrl/ut-ctrl.c
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <errno.h>
+#include <getopt.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/uio.h>
+#include <unistd.h>
+
+#include <trusty/tipc.h>
+
+#define TIPC_DEFAULT_DEVNAME "/dev/trusty-ipc-dev0"
+
+static const char* dev_name = NULL;
+static const char* ut_app = NULL;
+
+static const char* _sopts = "hD:";
+static const struct option _lopts[] = {
+ {"help", no_argument, 0, 'h'},
+ {"dev", required_argument, 0, 'D'},
+ {0, 0, 0, 0},
+};
+
+static const char* usage =
+ "Usage: %s [options] unittest-app\n"
+ "\n"
+ "options:\n"
+ " -h, --help prints this message and exit\n"
+ " -D, --dev name Trusty device name\n"
+ "\n";
+
+static const char* usage_long = "\n";
+
+static bool opt_silent = false;
+
+static void print_usage_and_exit(const char* prog, int code, bool verbose) {
+ fprintf(stderr, usage, prog);
+ if (verbose) {
+ fprintf(stderr, "%s", usage_long);
+ }
+ exit(code);
+}
+
+static void parse_options(int argc, char** argv) {
+ int c;
+ int oidx = 0;
+
+ while (1) {
+ c = getopt_long(argc, argv, _sopts, _lopts, &oidx);
+ if (c == -1) {
+ break; /* done */
+ }
+
+ switch (c) {
+ case 'D':
+ dev_name = strdup(optarg);
+ break;
+
+ case 's':
+ opt_silent = true;
+ break;
+
+ case 'h':
+ print_usage_and_exit(argv[0], EXIT_SUCCESS, true);
+ break;
+
+ default:
+ print_usage_and_exit(argv[0], EXIT_FAILURE, false);
+ }
+ }
+
+ if (optind < argc) {
+ ut_app = strdup(argv[optind]);
+ }
+}
+
+enum test_message_header {
+ TEST_PASSED = 0,
+ TEST_FAILED = 1,
+ TEST_MESSAGE = 2,
+};
+
+static int run_trusty_unitest(const char* utapp) {
+ int fd;
+ int rc;
+ char rx_buf[1024];
+
+ /* connect to unitest app */
+ fd = tipc_connect(dev_name, utapp);
+ if (fd < 0) {
+ fprintf(stderr, "failed to connect to '%s' app: %s\n", utapp, strerror(-fd));
+ return fd;
+ }
+
+ /* wait for test to complete */
+ for (;;) {
+ rc = read(fd, rx_buf, sizeof(rx_buf));
+ if (rc <= 0 || rc >= (int)sizeof(rx_buf)) {
+ fprintf(stderr, "%s: Read failed: %d\n", __func__, rc);
+ tipc_close(fd);
+ return -1;
+ }
+
+ if (rx_buf[0] == TEST_PASSED) {
+ break;
+ } else if (rx_buf[0] == TEST_FAILED) {
+ break;
+ } else if (rx_buf[0] == TEST_MESSAGE) {
+ write(STDOUT_FILENO, rx_buf + 1, rc - 1);
+ } else {
+ fprintf(stderr, "%s: Bad message header: %d\n", __func__, rx_buf[0]);
+ break;
+ }
+ }
+
+ /* close connection to unitest app */
+ tipc_close(fd);
+
+ return rx_buf[0] == TEST_PASSED ? 0 : -1;
+}
+
+int main(int argc, char** argv) {
+ int rc = 0;
+
+ if (argc <= 1) {
+ print_usage_and_exit(argv[0], EXIT_FAILURE, false);
+ }
+
+ parse_options(argc, argv);
+
+ if (!dev_name) {
+ dev_name = TIPC_DEFAULT_DEVNAME;
+ }
+
+ if (!ut_app) {
+ fprintf(stderr, "Unittest app must be specified\n");
+ print_usage_and_exit(argv[0], EXIT_FAILURE, false);
+ }
+
+ rc = run_trusty_unitest(ut_app);
+
+ return rc == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+}