diff options
| author | android-build-team Robot <android-build-team-robot@google.com> | 2020-04-28 20:27:22 +0000 |
|---|---|---|
| committer | android-build-team Robot <android-build-team-robot@google.com> | 2020-04-28 20:27:22 +0000 |
| commit | 1fc176f3f4cf79967329e0c8731a63b459561e44 (patch) | |
| tree | 54b0313105d531de45e5ed94a70317b063097662 | |
| parent | 1a0326594859a8f5624408c63584ab377889261b (diff) | |
| parent | 4d267077aa2a169b410185b1a5f5a78212fb4745 (diff) | |
| download | platform_system_gsid-android10-mainline-tzdata-release.tar.gz platform_system_gsid-android10-mainline-tzdata-release.tar.bz2 platform_system_gsid-android10-mainline-tzdata-release.zip | |
Snap for 6439596 from 4d267077aa2a169b410185b1a5f5a78212fb4745 to qt-aml-tzdata-releaseandroid-mainline-10.0.0_r11android10-mainline-tzdata-release
Change-Id: Ic2a201d3658a3e99884397a228862bcefaa40419
47 files changed, 1154 insertions, 5517 deletions
diff --git a/.clang-format b/.clang-format index d72ca25..55773a2 100644 --- a/.clang-format +++ b/.clang-format @@ -9,4 +9,3 @@ ContinuationIndentWidth: 8 PointerAlignment: Left TabWidth: 4 UseTab: Never -IncludeBlocks: Preserve @@ -18,16 +18,14 @@ cc_binary { name: "gsi_tool", shared_libs: [ "gsi_aidl_interface-cpp", - "libbase", "libbinder", + "libbase", "libcutils", "libgsi", "liblog", + "libservices", "libutils", ], - static_libs: [ - "libgsid", - ], srcs: [ "gsi_tool.cpp", ], @@ -46,26 +44,6 @@ cc_library { export_include_dirs: ["include"], } -cc_library_static { - name: "libgsid", - srcs: [ - "libgsid.cpp", - ], - shared_libs: [ - "gsi_aidl_interface-cpp", - "libbase", - "libbinder", - "libcutils", - "liblog", - "libservices", - "libutils", - ], - static_libs: [ - "libgsi", - ], - export_include_dirs: ["include"], -} - cc_library_headers { name: "libgsi_headers", host_supported: true, @@ -79,7 +57,6 @@ cc_binary { srcs: [ "daemon.cpp", "gsi_service.cpp", - "gsi_installer.cpp", ], required: [ "mke2fs", @@ -88,29 +65,52 @@ cc_binary { "gsid.rc", ], shared_libs: [ + "gsi_aidl_interface-cpp", "libbase", "libbinder", - "libcrypto", - "liblog", - ], - static_libs: [ - "gsi_aidl_interface-cpp", - "libcutils", - "libdm", "libext4_utils", - "libfiemap_passthrough", "libfs_mgr", "libgsi", - "libgsid", + "liblog", "liblp", "libutils", ], + static_libs: [ + "libdm", + "libfiemap_writer", + ], local_include_dirs: ["include"], } +cc_test { + name: "gsi_boot_test", + shared_libs: [ + "libbase", + "libcutils", + "libhardware", + "libhidlbase", + "libhidltransport", + "libhwbinder", + "liblog", + "libutils", + ], + static_libs: [ + "libext4_utils", + "libfstab", + "android.hardware.weaver@1.0", + ], + srcs: [ + "tests/boot_tests.cpp", + ], +} + aidl_interface { name: "gsi_aidl_interface", - srcs: [":gsiservice_aidl"], + srcs: [ + "aidl/android/gsi/GsiInstallParams.aidl", + "aidl/android/gsi/GsiProgress.aidl", + "aidl/android/gsi/IGsiService.aidl", + ], local_include_dir: "aidl", backend: { ndk: { @@ -124,14 +124,6 @@ filegroup { srcs: [ "aidl/android/gsi/GsiInstallParams.aidl", "aidl/android/gsi/GsiProgress.aidl", - "aidl/android/gsi/IImageService.aidl", - "aidl/android/gsi/IGsid.aidl", "aidl/android/gsi/IGsiService.aidl", - "aidl/android/gsi/MappedImage.aidl", ], - path: "aidl", -} - -vts_config { - name: "VtsGsiBootTest", } diff --git a/Android.mk b/Android.mk new file mode 100644 index 0000000..21d0b45 --- /dev/null +++ b/Android.mk @@ -0,0 +1,22 @@ +# +# 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. +# + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_MODULE := VtsGsiBootTest +-include test/vts/tools/build/Android.host_config.mk diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg deleted file mode 100644 index c8dbf77..0000000 --- a/PREUPLOAD.cfg +++ /dev/null @@ -1,5 +0,0 @@ -[Builtin Hooks] -clang_format = true - -[Builtin Hooks Options] -clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp diff --git a/aidl/android/gsi/GsiInstallParams.aidl b/aidl/android/gsi/GsiInstallParams.aidl index a9307c7..6e46da4 100644 --- a/aidl/android/gsi/GsiInstallParams.aidl +++ b/aidl/android/gsi/GsiInstallParams.aidl @@ -25,24 +25,17 @@ parcelable GsiInstallParams { */ @utf8InCpp String installDir; - /** - * The DSU partition name - */ - @utf8InCpp String name; + /* The size of the on-disk GSI image. */ + long gsiSize; - /* The size of the DSU image. */ - long size; + /* The desired size of the userdata partition. */ + long userdataSize; - /* If false, an image is only created if one does not already + /* If false, a userdata image is only created if one does not already * exist. If the size is zero, a default size of 8GiB is used. If there is * an existing image smaller than the desired size, it may be resized * automatically. */ - boolean wipe; - - /** - * Is this partition read only? - */ - boolean readOnly; + boolean wipeUserdata; } diff --git a/aidl/android/gsi/IGsiService.aidl b/aidl/android/gsi/IGsiService.aidl index c480ef4..4ffdf62 100644 --- a/aidl/android/gsi/IGsiService.aidl +++ b/aidl/android/gsi/IGsiService.aidl @@ -18,7 +18,6 @@ package android.gsi; import android.gsi.GsiInstallParams; import android.gsi.GsiProgress; -import android.gsi.IImageService; import android.os.ParcelFileDescriptor; /** {@hide} */ @@ -41,6 +40,25 @@ interface IGsiService { const int INSTALL_ERROR_FILE_SYSTEM_CLUTTERED = 3; /** + * Starts a GSI installation. Use beginGsiInstall() to target external + * media. + * + * If wipeUserData is true, a clean userdata image is always created to the + * desired size. + * + * If wipeUserData is false, a userdata image is only created if one does + * not already exist. If the size is zero, a default size of 8GiB is used. + * If there is an existing image smaller than the desired size, it is + * resized automatically. + * + * @param gsiSize The size of the on-disk GSI image. + * @param userdataSize The desired size of the userdata partition. + * @param wipeUserdata True to wipe destination userdata. + * @return 0 on success, an error code on failure. + */ + int startGsiInstall(long gsiSize, long userdataSize, boolean wipeUserdata); + + /** * Write bytes from a stream to the on-disk GSI. * * @param stream Stream descriptor. @@ -56,21 +74,12 @@ interface IGsiService { GsiProgress getInstallProgress(); /** - * Set the file descriptor that points to a ashmem which will be used - * to fetch data during the commitGsiChunkFromAshmem. - * - * @param stream fd that points to a ashmem - * @param size size of the ashmem file - */ - boolean setGsiAshmem(in ParcelFileDescriptor stream, long size); - - /** - * Write bytes from ashmem previously set with setGsiAshmem to GSI partition + * Write bytes from memory to the on-disk GSI. * - * @param bytes Number of bytes to submit + * @param bytes Byte array. * @return true on success, false otherwise. */ - boolean commitGsiChunkFromAshmem(long bytes); + boolean commitGsiChunkFromMemory(in byte[] bytes); /** * Complete a GSI installation and mark it as bootable. The caller is @@ -80,7 +89,7 @@ interface IGsiService { * It can still be re-enabled again later with setGsiBootable. * @return INSTALL_* error code. */ - int enableGsi(boolean oneShot); + int setGsiBootable(boolean oneShot); /** * @return True if Gsi is enabled @@ -104,28 +113,55 @@ interface IGsiService { * * @return true on success, false otherwise. */ - boolean removeGsi(); + boolean removeGsiInstall(); /** * Disables a GSI install. The image and userdata will be retained, but can * be re-enabled at any time with setGsiBootable. */ - boolean disableGsi(); + boolean disableGsiInstall(); /** - * Returns true if a gsi is installed. + * Return the size of the userdata partition for an installed GSI. If there + * is no image, 0 is returned. On error, -1 is returned. */ - boolean isGsiInstalled(); + long getUserdataImageSize(); + /** * Returns true if the gsi is currently running, false otherwise. */ boolean isGsiRunning(); /** + * Returns true if a gsi is installed. + */ + boolean isGsiInstalled(); + + /* No GSI is installed. */ + const int BOOT_STATUS_NOT_INSTALLED = 0; + /* GSI is installed, but booting is disabled. */ + const int BOOT_STATUS_DISABLED = 1; + /* GSI is installed, but will only boot once. */ + const int BOOT_STATUS_SINGLE_BOOT = 2; + /* GSI is installed and bootable. */ + const int BOOT_STATUS_ENABLED = 3; + /* GSI will be wiped next boot. */ + const int BOOT_STATUS_WILL_WIPE = 4; + + /** + * Returns the boot status of a GSI. See the BOOT_STATUS constants in IGsiService. + * + * GSI_STATE_NOT_INSTALLED will be returned if no GSI installation has been + * fully completed. Any other value indicates a GSI is installed. If a GSI + * currently running, DISABLED or SINGLE_BOOT can still be returned. + */ + int getGsiBootStatus(); + + /** * If a GSI is installed, returns the directory where the installed images * are located. Otherwise, returns an empty string. */ - @utf8InCpp String getInstalledGsiImageDir(); + @utf8InCpp String getInstalledGsiImageDir(); /** * Begin a GSI installation. @@ -145,19 +181,4 @@ interface IGsiService { * @return 0 on success, an error code on failure. */ int wipeGsiUserdata(); - - /** - * Open a handle to an IImageService for the given metadata and data storage paths. - * - * @param prefix A prefix used to organize images. The data path will become - * /data/gsi/{prefix} and the metadata path will become - * /metadata/gsi/{prefix}. - */ - IImageService openImageService(@utf8InCpp String prefix); - - /** - * Dump diagnostic information about device-mapper devices. This is intended - * for dumpstate. - */ - @utf8InCpp String dumpDeviceMapperDevices(); } diff --git a/aidl/android/gsi/IGsid.aidl b/aidl/android/gsi/IGsid.aidl deleted file mode 100644 index 0c1a7b2..0000000 --- a/aidl/android/gsi/IGsid.aidl +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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. - */ - -package android.gsi; - -import android.gsi.IGsiService; - -/** {@hide} */ -interface IGsid { - // Acquire an IGsiService client. gsid automatically shuts down when the - // last client is dropped. To start the daemon: - // - // 1. Check if the "init.svc.gsid" property is "running". If not, continue. - // 2. Set the "ctl.start" property to "gsid". - // 3. Wait for "init.svc.gsid" to be "running". - IGsiService getClient(); -} diff --git a/aidl/android/gsi/IImageService.aidl b/aidl/android/gsi/IImageService.aidl deleted file mode 100644 index 1195c00..0000000 --- a/aidl/android/gsi/IImageService.aidl +++ /dev/null @@ -1,101 +0,0 @@ -/* - * 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. - */ - -package android.gsi; - -import android.gsi.MappedImage; - -/** {@hide} */ -interface IImageService { - /* These flags match fiemap::ImageManager::CreateBackingImage. */ - const int CREATE_IMAGE_DEFAULT = 0x0; - const int CREATE_IMAGE_READONLY = 0x1; - const int CREATE_IMAGE_ZERO_FILL = 0x2; - - /** - * Create an image that can be mapped as a block device. - * - * This call will fail if running a GSI. - * - * @param name Image name. If the image already exists, the call will fail. - * @param size Image size, in bytes. If too large, or not enough space is - * free, the call will fail. - * @param readonly If readonly, MapBackingImage() will configure the device as - * readonly. - * @return True on success, false otherwise. - */ - void createBackingImage(@utf8InCpp String name, long size, int flags); - - /** - * Delete an image created with createBackingImage. - * - * @param name Image name as passed to createBackingImage(). - * @return True on success, false otherwise. - */ - void deleteBackingImage(@utf8InCpp String name); - - /** - * Map an image, created with createBackingImage, such that it is accessible as a - * block device. - * - * @param name Image name as passed to createBackingImage(). - * @param timeout_ms Time to wait for a valid mapping, in milliseconds. This must be more - * than zero; 10 seconds is recommended. - * @param mapping Information about the newly mapped block device. - */ - void mapImageDevice(@utf8InCpp String name, int timeout_ms, out MappedImage mapping); - - /** - * Unmap a block device previously mapped with mapBackingImage. This step is necessary before - * calling deleteBackingImage. - * - * @param name Image name as passed to createBackingImage(). - */ - void unmapImageDevice(@utf8InCpp String name); - - /** - * Returns whether or not a backing image exists. - * - * @param name Image name as passed to createBackingImage(). - */ - boolean backingImageExists(@utf8InCpp String name); - - /** - * Returns whether or not the named image is mapped. - * - * @param name Image name as passed to createBackingImage(). - */ - boolean isImageMapped(@utf8InCpp String name); - - /** - * Get all installed backing image names - * - * @return list of installed backing image names - */ - @utf8InCpp List<String> getAllBackingImages(); - - /** - * Writes a given amount of zeros in image file. - * - * @param name Image name. If the image does not exist, the call - * will fail. - * @param bytes Number of zeros to be written, starting from the - * beginning. If bytes is equal to 0, then the whole - * image file is filled with zeros. - * @return True on success, false otherwise. - */ - void zeroFillNewImage(@utf8InCpp String name, long bytes); -} diff --git a/aidl/android/gsi/MappedImage.aidl b/aidl/android/gsi/MappedImage.aidl deleted file mode 100644 index aa5af0d..0000000 --- a/aidl/android/gsi/MappedImage.aidl +++ /dev/null @@ -1,23 +0,0 @@ -/* - * 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. - */ - -package android.gsi; - -/** {@hide} */ -parcelable MappedImage { - /* Path to the block device. */ - @utf8InCpp String path; -} @@ -14,55 +14,29 @@ // limitations under the License. // +#include "gsi_service.h" + #include <getopt.h> -#include <iostream> #include <string> #include <android-base/logging.h> -#include <binder/BinderService.h> #include <binder/IPCThreadState.h> #include <binder/ProcessState.h> -#include <libgsi/libgsi.h> -#include <libgsi/libgsid.h> - -#include "gsi_service.h" using android::ProcessState; using android::sp; using namespace std::literals; -static int DumpDeviceMapper() { - auto service = android::gsi::GetGsiService(); - if (!service) { - std::cerr << "Could not start IGsiService.\n"; - return 1; - } - std::string output; - auto status = service->dumpDeviceMapperDevices(&output); - if (!status.isOk()) { - std::cerr << "Could not dump device-mapper devices: " << status.exceptionMessage().c_str() - << "\n"; - return 1; - } - std::cout << output; - return 0; -} - int main(int argc, char** argv) { android::base::InitLogging(argv, android::base::LogdLogger(android::base::SYSTEM)); - if (argc > 1) { - if (argv[1] == "run-startup-tasks"s) { - android::gsi::GsiService::RunStartupTasks(); - exit(0); - } else if (argv[1] == "dump-device-mapper"s) { - int rc = DumpDeviceMapper(); - exit(rc); - } + if (argc > 1 && argv[1] == "run-startup-tasks"s) { + android::gsi::GsiService::RunStartupTasks(); + exit(0); } - android::gsi::Gsid::Register(); + android::gsi::GsiService::Register(); { sp<ProcessState> ps(ProcessState::self()); ps->startThreadPool(); diff --git a/file_paths.h b/file_paths.h index b596704..3335195 100644 --- a/file_paths.h +++ b/file_paths.h @@ -19,19 +19,18 @@ namespace android { namespace gsi { -static constexpr char kDefaultDsuImageFolder[] = "/data/gsi/dsu/"; -static constexpr char kUserdataDevice[] = "/dev/block/by-name/userdata"; +static constexpr char kDefaultGsiImageFolder[] = "/data/gsi/dsu/"; -static constexpr char kDsuMetadataDir[] = "/metadata/gsi/dsu"; -static constexpr char kDsuOneShotBootFile[] = "/metadata/gsi/dsu/one_shot_boot"; -static constexpr char kDsuInstallDirFile[] = "/metadata/gsi/dsu/install_dir"; +static constexpr char kGsiLpMetadataFile[] = "/metadata/gsi/dsu/lp_metadata"; +static constexpr char kGsiOneShotBootFile[] = "/metadata/gsi/dsu/one_shot_boot"; +static constexpr char kGsiInstallDirFile[] = "/metadata/gsi/dsu/install_dir"; // This file can contain the following values: // [int] - boot attempt counter, starting from 0 // "ok" - boot was marked successful // "disabled" - boot into GSI no longer allowed // "wipe" - boot into GSI not allowed; next reboot will delete gsi -static constexpr char kDsuInstallStatusFile[] = "/metadata/gsi/dsu/install_status"; +static constexpr char kGsiInstallStatusFile[] = "/metadata/gsi/dsu/install_status"; } // namespace gsi } // namespace android diff --git a/gsi_installer.cpp b/gsi_installer.cpp deleted file mode 100644 index fe6b837..0000000 --- a/gsi_installer.cpp +++ /dev/null @@ -1,403 +0,0 @@ -/* - * 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 "gsi_installer.h" - -#include <sys/statvfs.h> - -#include <android-base/file.h> -#include <android-base/logging.h> -#include <android-base/unique_fd.h> -#include <ext4_utils/ext4_utils.h> -#include <fs_mgr_dm_linear.h> -#include <libdm/dm.h> -#include <libgsi/libgsi.h> - -#include "file_paths.h" -#include "gsi_service.h" -#include "libgsi_private.h" - -namespace android { -namespace gsi { - -using namespace std::literals; -using namespace android::dm; -using namespace android::fiemap; -using namespace android::fs_mgr; -using android::base::unique_fd; - -// The default size of userdata.img for GSI. -// We are looking for /data to have atleast 40% free space -static constexpr uint32_t kMinimumFreeSpaceThreshold = 40; -// Default userdata image size. -static constexpr int64_t kDefaultUserdataSize = int64_t(2) * 1024 * 1024 * 1024; - -GsiInstaller::GsiInstaller(GsiService* service, const GsiInstallParams& params) - : service_(service), - install_dir_(params.installDir), - name_(params.name), - size_(params.size), - readOnly_(params.readOnly), - wipe_(params.wipe) { - size_ = (params.size) ? params.size : kDefaultUserdataSize; - images_ = ImageManager::Open(kDsuMetadataDir, install_dir_); - - // Only rm backing file if one didn't already exist. - if (wipe_ || !images_->BackingImageExists(GetBackingFile(name_))) { - wipe_on_failure_ = true; - } - - // Remember the installation directory before allocate any resource - if (!android::base::WriteStringToFile(install_dir_, kDsuInstallDirFile)) { - PLOG(ERROR) << "write failed: " << kDsuInstallDirFile; - } -} - -GsiInstaller::~GsiInstaller() { - if (!succeeded_) { - // Close open handles before we remove files. - system_device_ = nullptr; - PostInstallCleanup(images_.get()); - - GsiService::RemoveGsiFiles(install_dir_, wipe_on_failure_); - } - if (IsAshmemMapped()) { - UnmapAshmem(); - } -} - -void GsiInstaller::PostInstallCleanup() { - auto manager = ImageManager::Open(kDsuMetadataDir, GsiService::GetInstalledImageDir()); - if (!manager) { - LOG(ERROR) << "Could not open image manager"; - return; - } - return PostInstallCleanup(manager.get()); -} - -void GsiInstaller::PostInstallCleanup(ImageManager* manager) { - std::string file = GetBackingFile(name_); - if (manager->IsImageMapped(file)) { - LOG(ERROR) << "unmap " << file; - manager->UnmapImageDevice(file); - } -} - -int GsiInstaller::StartInstall() { - if (int status = PerformSanityChecks()) { - return status; - } - if (int status = Preallocate()) { - return status; - } - if (!readOnly_) { - if (!Format()) { - return IGsiService::INSTALL_ERROR_GENERIC; - } - succeeded_ = true; - } else { - // Map ${name}_gsi so we can write to it. - system_device_ = OpenPartition(GetBackingFile(name_)); - if (!system_device_) { - return IGsiService::INSTALL_ERROR_GENERIC; - } - - // Clear the progress indicator. - service_->UpdateProgress(IGsiService::STATUS_NO_OPERATION, 0); - } - return IGsiService::INSTALL_OK; -} - -int GsiInstaller::PerformSanityChecks() { - if (!images_) { - LOG(ERROR) << "unable to create image manager"; - return IGsiService::INSTALL_ERROR_GENERIC; - } - if (size_ < 0) { - LOG(ERROR) << "image size " << size_ << " is negative"; - return IGsiService::INSTALL_ERROR_GENERIC; - } - if (android::gsi::IsGsiRunning()) { - LOG(ERROR) << "cannot install gsi inside a live gsi"; - return IGsiService::INSTALL_ERROR_GENERIC; - } - - struct statvfs sb; - if (statvfs(install_dir_.c_str(), &sb)) { - PLOG(ERROR) << "failed to read file system stats"; - return IGsiService::INSTALL_ERROR_GENERIC; - } - - // This is the same as android::vold::GetFreebytes() but we also - // need the total file system size so we open code it here. - uint64_t free_space = 1ULL * sb.f_bavail * sb.f_frsize; - uint64_t fs_size = sb.f_blocks * sb.f_frsize; - if (free_space <= (size_)) { - LOG(ERROR) << "not enough free space (only " << free_space << " bytes available)"; - return IGsiService::INSTALL_ERROR_NO_SPACE; - } - // We are asking for 40% of the /data to be empty. - // TODO: may be not hard code it like this - double free_space_percent = ((1.0 * free_space) / fs_size) * 100; - if (free_space_percent < kMinimumFreeSpaceThreshold) { - LOG(ERROR) << "free space " << static_cast<uint64_t>(free_space_percent) - << "% is below the minimum threshold of " << kMinimumFreeSpaceThreshold << "%"; - return IGsiService::INSTALL_ERROR_FILE_SYSTEM_CLUTTERED; - } - return IGsiService::INSTALL_OK; -} - -int GsiInstaller::Preallocate() { - std::string file = GetBackingFile(name_); - if (wipe_) { - images_->DeleteBackingImage(file); - } - LOG(ERROR) << "is exists:" << name_ << " " << images_->BackingImageExists(file); - if (!images_->BackingImageExists(file)) { - service_->StartAsyncOperation("create " + name_, size_); - if (!CreateImage(file, size_)) { - LOG(ERROR) << "Could not create userdata image"; - return IGsiService::INSTALL_ERROR_GENERIC; - } - } - service_->UpdateProgress(IGsiService::STATUS_COMPLETE, 0); - return IGsiService::INSTALL_OK; -} - -bool GsiInstaller::CreateImage(const std::string& name, uint64_t size) { - auto progress = [this](uint64_t bytes, uint64_t /* total */) -> bool { - service_->UpdateProgress(IGsiService::STATUS_WORKING, bytes); - if (service_->should_abort()) return false; - return true; - }; - int flags = ImageManager::CREATE_IMAGE_DEFAULT; - if (readOnly_) { - flags |= ImageManager::CREATE_IMAGE_READONLY; - } - return images_->CreateBackingImage(name, size, flags, std::move(progress)); -} - -std::unique_ptr<MappedDevice> GsiInstaller::OpenPartition(const std::string& name) { - return MappedDevice::Open(images_.get(), 10s, name); -} - -bool GsiInstaller::CommitGsiChunk(int stream_fd, int64_t bytes) { - service_->StartAsyncOperation("write " + name_, size_); - - if (bytes < 0) { - LOG(ERROR) << "chunk size " << bytes << " is negative"; - return false; - } - - static const size_t kBlockSize = 4096; - auto buffer = std::make_unique<char[]>(kBlockSize); - - int progress = -1; - uint64_t remaining = bytes; - while (remaining) { - size_t max_to_read = std::min(static_cast<uint64_t>(kBlockSize), remaining); - ssize_t rv = TEMP_FAILURE_RETRY(read(stream_fd, buffer.get(), max_to_read)); - if (rv < 0) { - PLOG(ERROR) << "read gsi chunk"; - return false; - } - if (rv == 0) { - LOG(ERROR) << "no bytes left in stream"; - return false; - } - if (!CommitGsiChunk(buffer.get(), rv)) { - return false; - } - CHECK(static_cast<uint64_t>(rv) <= remaining); - remaining -= rv; - - // Only update the progress when the % (or permille, in this case) - // significantly changes. - int new_progress = ((size_ - remaining) * 1000) / size_; - if (new_progress != progress) { - service_->UpdateProgress(IGsiService::STATUS_WORKING, size_ - remaining); - } - } - - service_->UpdateProgress(IGsiService::STATUS_COMPLETE, size_); - return true; -} - -bool GsiInstaller::IsFinishedWriting() { - return gsi_bytes_written_ == size_; -} - -bool GsiInstaller::IsAshmemMapped() { - return ashmem_data_ != MAP_FAILED; -} - -bool GsiInstaller::CommitGsiChunk(const void* data, size_t bytes) { - if (static_cast<uint64_t>(bytes) > size_ - gsi_bytes_written_) { - // We cannot write past the end of the image file. - LOG(ERROR) << "chunk size " << bytes << " exceeds remaining image size (" << size_ - << " expected, " << gsi_bytes_written_ << " written)"; - return false; - } - if (service_->should_abort()) { - return false; - } - if (!android::base::WriteFully(system_device_->fd(), data, bytes)) { - PLOG(ERROR) << "write failed"; - return false; - } - gsi_bytes_written_ += bytes; - return true; -} - -bool GsiInstaller::MapAshmem(int fd, size_t size) { - ashmem_size_ = size; - ashmem_data_ = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - return ashmem_data_ != MAP_FAILED; -} - -void GsiInstaller::UnmapAshmem() { - if (munmap(ashmem_data_, ashmem_size_) != 0) { - PLOG(ERROR) << "cannot munmap"; - return; - } - ashmem_data_ = MAP_FAILED; - ashmem_size_ = -1; -} - -bool GsiInstaller::CommitGsiChunk(size_t bytes) { - if (!IsAshmemMapped()) { - PLOG(ERROR) << "ashmem is not mapped"; - return false; - } - bool success = CommitGsiChunk(ashmem_data_, bytes); - if (success && IsFinishedWriting()) { - UnmapAshmem(); - } - return success; -} - -const std::string GsiInstaller::GetBackingFile(std::string name) { - return name + "_gsi"; -} - -bool GsiInstaller::SetBootMode(bool one_shot) { - if (one_shot) { - if (!android::base::WriteStringToFile("1", kDsuOneShotBootFile)) { - PLOG(ERROR) << "write " << kDsuOneShotBootFile; - return false; - } - } else if (!access(kDsuOneShotBootFile, F_OK)) { - std::string error; - if (!android::base::RemoveFileIfExists(kDsuOneShotBootFile, &error)) { - LOG(ERROR) << error; - return false; - } - } - return true; -} - -bool GsiInstaller::CreateInstallStatusFile() { - if (!android::base::WriteStringToFile("0", kDsuInstallStatusFile)) { - PLOG(ERROR) << "write " << kDsuInstallStatusFile; - return false; - } - return true; -} - -bool GsiInstaller::Format() { - auto file = GetBackingFile(name_); - auto device = OpenPartition(file); - if (!device) { - return false; - } - - // libcutils checks the first 4K, no matter the block size. - std::string zeroes(4096, 0); - if (!android::base::WriteFully(device->fd(), zeroes.data(), zeroes.size())) { - PLOG(ERROR) << "write " << file; - return false; - } - return true; -} - -int GsiInstaller::SetGsiBootable(bool one_shot) { - if (gsi_bytes_written_ != size_) { - // We cannot boot if the image is incomplete. - LOG(ERROR) << "image incomplete; expected " << size_ << " bytes, waiting for " - << (size_ - gsi_bytes_written_) << " bytes"; - return IGsiService::INSTALL_ERROR_GENERIC; - } - - if (fsync(system_device_->fd())) { - PLOG(ERROR) << "fsync failed for " << name_ << "_gsi"; - return IGsiService::INSTALL_ERROR_GENERIC; - } - system_device_ = {}; - - // If files moved (are no longer pinned), the metadata file will be invalid. - // This check can be removed once b/133967059 is fixed. - if (!images_->Validate()) { - return IGsiService::INSTALL_ERROR_GENERIC; - } - - // Note: create the install status file last, since this is the actual boot - // indicator. - if (!SetBootMode(one_shot) || !CreateInstallStatusFile()) { - return IGsiService::INSTALL_ERROR_GENERIC; - } - - succeeded_ = true; - return IGsiService::INSTALL_OK; -} - -int GsiInstaller::ReenableGsi(bool one_shot) { - if (IsGsiRunning()) { - if (!SetBootMode(one_shot) || !CreateInstallStatusFile()) { - return IGsiService::INSTALL_ERROR_GENERIC; - } - return IGsiService::INSTALL_OK; - } - if (!SetBootMode(one_shot) || !CreateInstallStatusFile()) { - return IGsiService::INSTALL_ERROR_GENERIC; - } - return IGsiService::INSTALL_OK; -} - -int GsiInstaller::WipeWritable(const std::string& install_dir, const std::string& name) { - auto image = ImageManager::Open(kDsuMetadataDir, install_dir); - // The device object has to be destroyed before the image object - auto device = MappedDevice::Open(image.get(), 10s, name); - if (!device) { - return IGsiService::INSTALL_ERROR_GENERIC; - } - - // Wipe the first 1MiB of the device, ensuring both the first block and - // the superblock are destroyed. - static constexpr uint64_t kEraseSize = 1024 * 1024; - - std::string zeroes(4096, 0); - uint64_t erase_size = std::min(kEraseSize, get_block_device_size(device->fd())); - for (uint64_t i = 0; i < erase_size; i += zeroes.size()) { - if (!android::base::WriteFully(device->fd(), zeroes.data(), zeroes.size())) { - PLOG(ERROR) << "write userdata_gsi"; - return IGsiService::INSTALL_ERROR_GENERIC; - } - } - return IGsiService::INSTALL_OK; -} - -} // namespace gsi -} // namespace android diff --git a/gsi_installer.h b/gsi_installer.h deleted file mode 100644 index 33f1937..0000000 --- a/gsi_installer.h +++ /dev/null @@ -1,95 +0,0 @@ -/* - * 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 <stdint.h> -#include <sys/mman.h> - -#include <memory> -#include <string> - -#include <android-base/unique_fd.h> -#include <android/gsi/IGsiService.h> -#include <android/gsi/MappedImage.h> -#include <libfiemap/image_manager.h> -#include <liblp/builder.h> - -namespace android { -namespace gsi { - -class GsiService; - -class GsiInstaller final { - using ImageManager = android::fiemap::ImageManager; - using MappedDevice = android::fiemap::MappedDevice; - - public: - // Constructor for a new GSI installation. - GsiInstaller(GsiService* service, const GsiInstallParams& params); - ~GsiInstaller(); - - // Methods for a clean GSI install. - int StartInstall(); - bool CommitGsiChunk(int stream_fd, int64_t bytes); - bool CommitGsiChunk(const void* data, size_t bytes); - bool MapAshmem(int fd, size_t size); - bool CommitGsiChunk(size_t bytes); - int SetGsiBootable(bool one_shot); - - // Methods for interacting with an existing install. - static int ReenableGsi(bool one_shot); - static int WipeWritable(const std::string& install_dir, const std::string& name); - - // Clean up install state if gsid crashed and restarted. - void PostInstallCleanup(); - void PostInstallCleanup(ImageManager* manager); - - const std::string& install_dir() const { return install_dir_; } - - private: - int PerformSanityChecks(); - int Preallocate(); - bool Format(); - bool CreateImage(const std::string& name, uint64_t size); - std::unique_ptr<MappedDevice> OpenPartition(const std::string& name); - int CheckInstallState(); - static bool CreateInstallStatusFile(); - static bool SetBootMode(bool one_shot); - static const std::string GetBackingFile(std::string name); - bool IsFinishedWriting(); - bool IsAshmemMapped(); - void UnmapAshmem(); - - GsiService* service_; - - std::string install_dir_; - std::string name_; - std::unique_ptr<ImageManager> images_; - uint64_t size_ = 0; - bool readOnly_; - bool wipe_ = false; - bool wipe_on_failure_ = false; - // Remaining data we're waiting to receive for the GSI image. - uint64_t gsi_bytes_written_ = 0; - bool succeeded_ = false; - uint64_t ashmem_size_ = -1; - void* ashmem_data_ = MAP_FAILED; - - std::unique_ptr<MappedDevice> system_device_; -}; - -} // namespace gsi -} // namespace android diff --git a/gsi_service.cpp b/gsi_service.cpp index dbf0ea6..3efec40 100644 --- a/gsi_service.cpp +++ b/gsi_service.cpp @@ -20,6 +20,7 @@ #include <linux/fs.h> #include <sys/ioctl.h> #include <sys/stat.h> +#include <sys/statvfs.h> #include <sys/types.h> #include <sys/vfs.h> #include <unistd.h> @@ -32,12 +33,14 @@ #include <android-base/logging.h> #include <android-base/stringprintf.h> #include <android-base/strings.h> -#include <android/gsi/BnImageService.h> #include <android/gsi/IGsiService.h> #include <ext4_utils/ext4_utils.h> #include <fs_mgr.h> +#include <fs_mgr_dm_linear.h> +#include <fstab/fstab.h> #include <libdm/dm.h> -#include <libfiemap/image_manager.h> +#include <libfiemap_writer/fiemap_writer.h> +#include <logwrap/logwrap.h> #include <private/android_filesystem_config.h> #include "file_paths.h" @@ -47,69 +50,66 @@ namespace android { namespace gsi { using namespace std::literals; +using namespace android::dm; using namespace android::fs_mgr; -using namespace android::fiemap; +using namespace android::fiemap_writer; using android::base::StringPrintf; using android::base::unique_fd; -using android::dm::DeviceMapper; -android::wp<GsiService> GsiService::sInstance; +static constexpr char kUserdataDevice[] = "/dev/block/by-name/userdata"; -void Gsid::Register() { - auto ret = android::BinderService<Gsid>::publish(); +// The default size of userdata.img for GSI. +// We are looking for /data to have atleast 40% free space +static constexpr uint32_t kMinimumFreeSpaceThreshold = 40; +// We determine the fragmentation by making sure the files +// we create don't have more than 16 extents. +static constexpr uint32_t kMaximumExtents = 512; +// Default userdata image size. +static constexpr int64_t kDefaultUserdataSize = int64_t(8) * 1024 * 1024 * 1024; +static constexpr std::chrono::milliseconds kDmTimeout = 5000ms; + +void GsiService::Register() { + auto ret = android::BinderService<GsiService>::publish(); if (ret != android::OK) { LOG(FATAL) << "Could not register gsi service: " << ret; } } -binder::Status Gsid::getClient(android::sp<IGsiService>* _aidl_return) { - *_aidl_return = GsiService::Get(this); - return binder::Status::ok(); -} - -GsiService::GsiService(Gsid* parent) : parent_(parent) { +GsiService::GsiService() { progress_ = {}; } GsiService::~GsiService() { - std::lock_guard<std::mutex> guard(parent_->lock()); - - if (sInstance == this) { - // No more consumers, gracefully shut down gsid. - exit(0); - } + PostInstallCleanup(); } -android::sp<IGsiService> GsiService::Get(Gsid* parent) { - std::lock_guard<std::mutex> guard(parent->lock()); - - android::sp<GsiService> service = sInstance.promote(); - if (!service) { - service = new GsiService(parent); - sInstance = service.get(); - } - return service.get(); -} - -#define ENFORCE_SYSTEM \ - do { \ - binder::Status status = CheckUid(); \ - if (!status.isOk()) return status; \ +#define ENFORCE_SYSTEM \ + do { \ + binder::Status status = CheckUid(); \ + if (!status.isOk()) return status; \ } while (0) -#define ENFORCE_SYSTEM_OR_SHELL \ - do { \ - binder::Status status = CheckUid(AccessLevel::SystemOrShell); \ - if (!status.isOk()) return status; \ +#define ENFORCE_SYSTEM_OR_SHELL \ + do { \ + binder::Status status = CheckUid(AccessLevel::SystemOrShell); \ + if (!status.isOk()) return status; \ } while (0) -binder::Status GsiService::beginGsiInstall(const GsiInstallParams& given_params, +binder::Status GsiService::startGsiInstall(int64_t gsiSize, int64_t userdataSize, bool wipeUserdata, int* _aidl_return) { + GsiInstallParams params; + params.gsiSize = gsiSize; + params.userdataSize = userdataSize; + params.wipeUserdata = wipeUserdata; + return beginGsiInstall(params, _aidl_return); +} + +binder::Status GsiService::beginGsiInstall(const GsiInstallParams& given_params, int* _aidl_return) { ENFORCE_SYSTEM; - std::lock_guard<std::mutex> guard(parent_->lock()); + std::lock_guard<std::mutex> guard(main_lock_); // Make sure any interrupted installations are cleaned up. - installer_ = nullptr; + PostInstallCleanup(); // Do some precursor validation on the arguments before diving into the // install process. @@ -119,26 +119,28 @@ binder::Status GsiService::beginGsiInstall(const GsiInstallParams& given_params, return binder::Status::ok(); } - installer_ = std::make_unique<GsiInstaller>(this, params); - int status = installer_->StartInstall(); + int status = StartInstall(params); if (status != INSTALL_OK) { - installer_ = nullptr; + // Perform local cleanup and delete any lingering files. + PostInstallCleanup(); + RemoveGsiFiles(params.installDir, wipe_userdata_on_failure_); } *_aidl_return = status; + + // Clear the progress indicator. + UpdateProgress(STATUS_NO_OPERATION, 0); return binder::Status::ok(); } binder::Status GsiService::commitGsiChunkFromStream(const android::os::ParcelFileDescriptor& stream, int64_t bytes, bool* _aidl_return) { ENFORCE_SYSTEM; - std::lock_guard<std::mutex> guard(parent_->lock()); + std::lock_guard<std::mutex> guard(main_lock_); - if (!installer_) { - *_aidl_return = false; - return binder::Status::ok(); - } + *_aidl_return = CommitGsiChunk(stream.get(), bytes); - *_aidl_return = installer_->CommitGsiChunk(stream.get(), bytes); + // Clear the progress indicator. + UpdateProgress(STATUS_NO_OPERATION, 0); return binder::Status::ok(); } @@ -170,34 +172,24 @@ binder::Status GsiService::getInstallProgress(::android::gsi::GsiProgress* _aidl return binder::Status::ok(); } -binder::Status GsiService::commitGsiChunkFromAshmem(int64_t bytes, bool* _aidl_return) { +binder::Status GsiService::commitGsiChunkFromMemory(const std::vector<uint8_t>& bytes, + bool* _aidl_return) { ENFORCE_SYSTEM; - std::lock_guard<std::mutex> guard(parent_->lock()); - - if (!installer_) { - *_aidl_return = false; - return binder::Status::ok(); - } - *_aidl_return = installer_->CommitGsiChunk(bytes); - return binder::Status::ok(); -} + std::lock_guard<std::mutex> guard(main_lock_); -binder::Status GsiService::setGsiAshmem(const ::android::os::ParcelFileDescriptor& ashmem, - int64_t size, bool* _aidl_return) { - if (!installer_) { - *_aidl_return = false; - return binder::Status::ok(); - } - *_aidl_return = installer_->MapAshmem(ashmem.get(), size); + *_aidl_return = CommitGsiChunk(bytes.data(), bytes.size()); return binder::Status::ok(); } -binder::Status GsiService::enableGsi(bool one_shot, int* _aidl_return) { - std::lock_guard<std::mutex> guard(parent_->lock()); +binder::Status GsiService::setGsiBootable(bool one_shot, int* _aidl_return) { + std::lock_guard<std::mutex> guard(main_lock_); - if (installer_) { + if (installing_) { ENFORCE_SYSTEM; - if (int error = installer_->SetGsiBootable(one_shot)) { + int error = SetGsiBootable(one_shot); + PostInstallCleanup(); + if (error) { + RemoveGsiFiles(install_dir_, wipe_userdata_on_failure_); *_aidl_return = error; } else { *_aidl_return = INSTALL_OK; @@ -205,15 +197,15 @@ binder::Status GsiService::enableGsi(bool one_shot, int* _aidl_return) { } else { ENFORCE_SYSTEM_OR_SHELL; *_aidl_return = ReenableGsi(one_shot); + PostInstallCleanup(); } - installer_ = nullptr; return binder::Status::ok(); } binder::Status GsiService::isGsiEnabled(bool* _aidl_return) { ENFORCE_SYSTEM_OR_SHELL; - std::lock_guard<std::mutex> guard(parent_->lock()); + std::lock_guard<std::mutex> guard(main_lock_); std::string boot_key; if (!GetInstallStatus(&boot_key)) { *_aidl_return = false; @@ -223,11 +215,19 @@ binder::Status GsiService::isGsiEnabled(bool* _aidl_return) { return binder::Status::ok(); } -binder::Status GsiService::removeGsi(bool* _aidl_return) { +binder::Status GsiService::removeGsiInstall(bool* _aidl_return) { ENFORCE_SYSTEM_OR_SHELL; - std::lock_guard<std::mutex> guard(parent_->lock()); + std::lock_guard<std::mutex> guard(main_lock_); + + // Just in case an install was left hanging. + std::string install_dir; + if (installing_) { + install_dir = install_dir_; + PostInstallCleanup(); + } else { + install_dir = GetInstalledImageDir(); + } - std::string install_dir = GetActiveInstalledImageDir(); if (IsGsiRunning()) { // Can't remove gsi files while running. *_aidl_return = UninstallGsi(); @@ -237,9 +237,9 @@ binder::Status GsiService::removeGsi(bool* _aidl_return) { return binder::Status::ok(); } -binder::Status GsiService::disableGsi(bool* _aidl_return) { +binder::Status GsiService::disableGsiInstall(bool* _aidl_return) { ENFORCE_SYSTEM_OR_SHELL; - std::lock_guard<std::mutex> guard(parent_->lock()); + std::lock_guard<std::mutex> guard(main_lock_); *_aidl_return = DisableGsiInstall(); return binder::Status::ok(); @@ -247,7 +247,7 @@ binder::Status GsiService::disableGsi(bool* _aidl_return) { binder::Status GsiService::isGsiRunning(bool* _aidl_return) { ENFORCE_SYSTEM_OR_SHELL; - std::lock_guard<std::mutex> guard(parent_->lock()); + std::lock_guard<std::mutex> guard(main_lock_); *_aidl_return = IsGsiRunning(); return binder::Status::ok(); @@ -255,7 +255,7 @@ binder::Status GsiService::isGsiRunning(bool* _aidl_return) { binder::Status GsiService::isGsiInstalled(bool* _aidl_return) { ENFORCE_SYSTEM_OR_SHELL; - std::lock_guard<std::mutex> guard(parent_->lock()); + std::lock_guard<std::mutex> guard(main_lock_); *_aidl_return = IsGsiInstalled(); return binder::Status::ok(); @@ -263,234 +263,127 @@ binder::Status GsiService::isGsiInstalled(bool* _aidl_return) { binder::Status GsiService::isGsiInstallInProgress(bool* _aidl_return) { ENFORCE_SYSTEM_OR_SHELL; - std::lock_guard<std::mutex> guard(parent_->lock()); + std::lock_guard<std::mutex> guard(main_lock_); - *_aidl_return = !!installer_; + *_aidl_return = installing_; return binder::Status::ok(); } binder::Status GsiService::cancelGsiInstall(bool* _aidl_return) { ENFORCE_SYSTEM; should_abort_ = true; - std::lock_guard<std::mutex> guard(parent_->lock()); + std::lock_guard<std::mutex> guard(main_lock_); should_abort_ = false; - installer_ = nullptr; + if (installing_) { + PostInstallCleanup(); + RemoveGsiFiles(install_dir_, wipe_userdata_on_failure_); + } *_aidl_return = true; return binder::Status::ok(); } -binder::Status GsiService::getInstalledGsiImageDir(std::string* _aidl_return) { - ENFORCE_SYSTEM; - std::lock_guard<std::mutex> guard(parent_->lock()); - - *_aidl_return = GetActiveInstalledImageDir(); - return binder::Status::ok(); -} - -binder::Status GsiService::wipeGsiUserdata(int* _aidl_return) { +binder::Status GsiService::getGsiBootStatus(int* _aidl_return) { ENFORCE_SYSTEM_OR_SHELL; - std::lock_guard<std::mutex> guard(parent_->lock()); + std::lock_guard<std::mutex> guard(main_lock_); - if (IsGsiRunning() || !IsGsiInstalled()) { - *_aidl_return = IGsiService::INSTALL_ERROR_GENERIC; + if (!IsGsiInstalled()) { + *_aidl_return = BOOT_STATUS_NOT_INSTALLED; return binder::Status::ok(); } - std::string install_dir = GetActiveInstalledImageDir(); - *_aidl_return = GsiInstaller::WipeWritable(install_dir, "userdata"); - - return binder::Status::ok(); -} - -static binder::Status BinderError(const std::string& message) { - return binder::Status::fromExceptionCode(binder::Status::EX_SERVICE_SPECIFIC, - String8(message.c_str())); -} - -binder::Status GsiService::dumpDeviceMapperDevices(std::string* _aidl_return) { - ENFORCE_SYSTEM_OR_SHELL; - - auto& dm = DeviceMapper::Instance(); - - std::vector<DeviceMapper::DmBlockDevice> devices; - if (!dm.GetAvailableDevices(&devices)) { - return BinderError("Could not list devices"); + std::string boot_key; + if (!GetInstallStatus(&boot_key)) { + PLOG(ERROR) << "read " << kGsiInstallStatusFile; + *_aidl_return = BOOT_STATUS_NOT_INSTALLED; + return binder::Status::ok(); } - std::stringstream text; - for (const auto& device : devices) { - text << "Device " << device.name() << " (" << device.Major() << ":" << device.Minor() - << ")\n"; - - std::vector<DeviceMapper::TargetInfo> table; - if (!dm.GetTableInfo(device.name(), &table)) { - continue; - } - - for (const auto& target : table) { - const auto& spec = target.spec; - auto target_type = DeviceMapper::GetTargetType(spec); - text << " " << target_type << " " << spec.sector_start << " " << spec.length << " " - << target.data << "\n"; + bool single_boot = !access(kGsiOneShotBootFile, F_OK); + + if (boot_key == kInstallStatusWipe) { + // This overrides all other statuses. + *_aidl_return = BOOT_STATUS_WILL_WIPE; + } else if (boot_key == kInstallStatusDisabled) { + // A single-boot GSI will have a "disabled" status, because it's + // disabled immediately upon reading the one_shot_boot file. However, + // we still want to return SINGLE_BOOT, because it makes the + // transition clearer to the user. + if (single_boot) { + *_aidl_return = BOOT_STATUS_SINGLE_BOOT; + } else { + *_aidl_return = BOOT_STATUS_DISABLED; } - } - - *_aidl_return = text.str(); - return binder::Status::ok(); -} - -static binder::Status UidSecurityError() { - uid_t uid = IPCThreadState::self()->getCallingUid(); - auto message = StringPrintf("UID %d is not allowed", uid); - return binder::Status::fromExceptionCode(binder::Status::EX_SECURITY, String8(message.c_str())); -} - -class ImageService : public BinderService<ImageService>, public BnImageService { - public: - ImageService(GsiService* service, std::unique_ptr<ImageManager>&& impl, uid_t uid); - binder::Status getAllBackingImages(std::vector<std::string>* _aidl_return); - binder::Status createBackingImage(const std::string& name, int64_t size, int flags) override; - binder::Status deleteBackingImage(const std::string& name) override; - binder::Status mapImageDevice(const std::string& name, int32_t timeout_ms, - MappedImage* mapping) override; - binder::Status unmapImageDevice(const std::string& name) override; - binder::Status backingImageExists(const std::string& name, bool* _aidl_return) override; - binder::Status isImageMapped(const std::string& name, bool* _aidl_return) override; - binder::Status zeroFillNewImage(const std::string& name, int64_t bytes) override; - - private: - bool CheckUid(); - - android::sp<GsiService> service_; - android::sp<Gsid> parent_; - std::unique_ptr<ImageManager> impl_; - uid_t uid_; -}; - -ImageService::ImageService(GsiService* service, std::unique_ptr<ImageManager>&& impl, uid_t uid) - : service_(service), parent_(service->parent()), impl_(std::move(impl)), uid_(uid) {} - -binder::Status ImageService::getAllBackingImages(std::vector<std::string>* _aidl_return) { - *_aidl_return = impl_->GetAllBackingImages(); - return binder::Status::ok(); -} - -binder::Status ImageService::createBackingImage(const std::string& name, int64_t size, int flags) { - if (!CheckUid()) return UidSecurityError(); - - std::lock_guard<std::mutex> guard(parent_->lock()); - - if (!impl_->CreateBackingImage(name, size, flags, nullptr)) { - return BinderError("Failed to create"); - } - return binder::Status::ok(); -} - -binder::Status ImageService::deleteBackingImage(const std::string& name) { - if (!CheckUid()) return UidSecurityError(); - - std::lock_guard<std::mutex> guard(parent_->lock()); - - if (!impl_->DeleteBackingImage(name)) { - return BinderError("Failed to delete"); - } - return binder::Status::ok(); -} - -binder::Status ImageService::mapImageDevice(const std::string& name, int32_t timeout_ms, - MappedImage* mapping) { - if (!CheckUid()) return UidSecurityError(); - - std::lock_guard<std::mutex> guard(parent_->lock()); - - if (!impl_->MapImageDevice(name, std::chrono::milliseconds(timeout_ms), &mapping->path)) { - return BinderError("Failed to map"); + } else if (single_boot) { + *_aidl_return = BOOT_STATUS_SINGLE_BOOT; + } else { + *_aidl_return = BOOT_STATUS_ENABLED; } return binder::Status::ok(); } -binder::Status ImageService::unmapImageDevice(const std::string& name) { - if (!CheckUid()) return UidSecurityError(); - - std::lock_guard<std::mutex> guard(parent_->lock()); +binder::Status GsiService::getUserdataImageSize(int64_t* _aidl_return) { + ENFORCE_SYSTEM; + std::lock_guard<std::mutex> guard(main_lock_); + + *_aidl_return = -1; + + if (installing_) { + // Size has already been computed. + *_aidl_return = userdata_size_; + } else if (IsGsiRunning()) { + // :TODO: libdm + unique_fd fd(open(kUserdataDevice, O_RDONLY | O_NOFOLLOW | O_CLOEXEC)); + if (fd < 0) { + PLOG(ERROR) << "open " << kUserdataDevice; + return binder::Status::ok(); + } - if (!impl_->UnmapImageDevice(name)) { - return BinderError("Failed to unmap"); + int64_t size; + if (ioctl(fd, BLKGETSIZE64, &size)) { + PLOG(ERROR) << "BLKGETSIZE64 " << kUserdataDevice; + return binder::Status::ok(); + } + *_aidl_return = size; + } else { + // Stat the size of the userdata file. + auto userdata_gsi = GetInstalledImagePath("userdata_gsi"); + struct stat s; + if (stat(userdata_gsi.c_str(), &s)) { + if (errno != ENOENT) { + PLOG(ERROR) << "open " << userdata_gsi; + return binder::Status::ok(); + } + *_aidl_return = 0; + } else { + *_aidl_return = s.st_size; + } } return binder::Status::ok(); } -binder::Status ImageService::backingImageExists(const std::string& name, bool* _aidl_return) { - if (!CheckUid()) return UidSecurityError(); - - std::lock_guard<std::mutex> guard(parent_->lock()); - - *_aidl_return = impl_->BackingImageExists(name); - return binder::Status::ok(); -} - -binder::Status ImageService::isImageMapped(const std::string& name, bool* _aidl_return) { - if (!CheckUid()) return UidSecurityError(); - - std::lock_guard<std::mutex> guard(parent_->lock()); - - *_aidl_return = impl_->IsImageMapped(name); - return binder::Status::ok(); -} - -binder::Status ImageService::zeroFillNewImage(const std::string& name, int64_t bytes) { - if (!CheckUid()) return UidSecurityError(); - - std::lock_guard<std::mutex> guard(parent_->lock()); +binder::Status GsiService::getInstalledGsiImageDir(std::string* _aidl_return) { + ENFORCE_SYSTEM; + std::lock_guard<std::mutex> guard(main_lock_); - if (bytes < 0) { - return BinderError("Cannot use negative values"); - } - if (!impl_->ZeroFillNewImage(name, bytes)) { - return BinderError("Failed to fill image with zeros"); + if (IsGsiInstalled()) { + *_aidl_return = GetInstalledImageDir(); } return binder::Status::ok(); } -bool ImageService::CheckUid() { - return uid_ == IPCThreadState::self()->getCallingUid(); -} - -binder::Status GsiService::openImageService(const std::string& prefix, - android::sp<IImageService>* _aidl_return) { - static constexpr char kImageMetadataPrefix[] = "/metadata/gsi/"; - static constexpr char kImageDataPrefix[] = "/data/gsi/"; - - auto in_metadata_dir = kImageMetadataPrefix + prefix; - auto in_data_dir = kImageDataPrefix + prefix; - - std::string metadata_dir, data_dir; - if (!android::base::Realpath(in_metadata_dir, &metadata_dir)) { - PLOG(ERROR) << "realpath failed: " << metadata_dir; - return BinderError("Invalid path"); - } - if (!android::base::Realpath(in_data_dir, &data_dir)) { - PLOG(ERROR) << "realpath failed: " << data_dir; - return BinderError("Invalid path"); - } - if (!android::base::StartsWith(metadata_dir, kImageMetadataPrefix) || - !android::base::StartsWith(data_dir, kImageDataPrefix)) { - return BinderError("Invalid path"); - } +binder::Status GsiService::wipeGsiUserdata(int* _aidl_return) { + ENFORCE_SYSTEM_OR_SHELL; + std::lock_guard<std::mutex> guard(main_lock_); - uid_t uid = IPCThreadState::self()->getCallingUid(); - if (uid != AID_ROOT) { - return UidSecurityError(); + if (IsGsiRunning() || !IsGsiInstalled()) { + *_aidl_return = IGsiService::INSTALL_ERROR_GENERIC; + return binder::Status::ok(); } - auto impl = ImageManager::Open(metadata_dir, data_dir); - if (!impl) { - return BinderError("Unknown error"); - } + *_aidl_return = WipeUserdata(); - *_aidl_return = new ImageService(this, std::move(impl), uid); return binder::Status::ok(); } @@ -506,7 +399,26 @@ binder::Status GsiService::CheckUid(AccessLevel level) { return binder::Status::ok(); } } - return UidSecurityError(); + + auto message = StringPrintf("UID %d is not allowed", uid); + return binder::Status::fromExceptionCode(binder::Status::EX_SECURITY, + String8(message.c_str())); +} + +void GsiService::PostInstallCleanup() { + // This must be closed before unmapping partitions. + system_writer_ = nullptr; + + const auto& dm = DeviceMapper::Instance(); + if (dm.GetState("userdata_gsi") != DmDeviceState::INVALID) { + DestroyLogicalPartition("userdata_gsi", kDmTimeout); + } + if (dm.GetState("system_gsi") != DmDeviceState::INVALID) { + DestroyLogicalPartition("system_gsi", kDmTimeout); + } + + installing_ = false; + partitions_ .clear(); } static bool IsExternalStoragePath(const std::string& path) { @@ -532,7 +444,7 @@ int GsiService::ValidateInstallParams(GsiInstallParams* params) { // specifying the top-level folder, and then we choose the correct location // underneath. if (params->installDir.empty() || params->installDir == "/data/gsi") { - params->installDir = kDefaultDsuImageFolder; + params->installDir = kDefaultGsiImageFolder; } // Normalize the path and add a trailing slash. @@ -541,7 +453,8 @@ int GsiService::ValidateInstallParams(GsiInstallParams* params) { PLOG(ERROR) << "realpath failed: " << origInstallDir; return INSTALL_ERROR_GENERIC; } - // Ensure the path ends in / for consistency. + // Ensure the path ends in / for consistency. Even though GetImagePath() + // does this already, we want it to appear this way in install_dir. if (!android::base::EndsWith(params->installDir, "/")) { params->installDir += "/"; } @@ -562,36 +475,415 @@ int GsiService::ValidateInstallParams(GsiInstallParams* params) { LOG(ERROR) << "cannot install GSIs to external media if verity uses check_at_most_once"; return INSTALL_ERROR_GENERIC; } - } else if (params->installDir != kDefaultDsuImageFolder) { + } else if (params->installDir != kDefaultGsiImageFolder) { LOG(ERROR) << "cannot install GSI to " << params->installDir; return INSTALL_ERROR_GENERIC; } - if (params->size % LP_SECTOR_SIZE) { - LOG(ERROR) << params->name << " size " << params->size << " is not a multiple of " + if (params->gsiSize % LP_SECTOR_SIZE) { + LOG(ERROR) << "GSI size " << params->gsiSize << " is not a multiple of " << LP_SECTOR_SIZE; + return INSTALL_ERROR_GENERIC; + } + if (params->userdataSize % LP_SECTOR_SIZE) { + LOG(ERROR) << "userdata size " << params->userdataSize << " is not a multiple of " << LP_SECTOR_SIZE; return INSTALL_ERROR_GENERIC; } return INSTALL_OK; } -std::string GsiService::GetActiveInstalledImageDir() { - // Just in case an install was left hanging. - if (installer_) { - return installer_->install_dir(); - } else { - return GetInstalledImageDir(); +int GsiService::StartInstall(const GsiInstallParams& params) { + installing_ = true; + userdata_block_size_ = 0; + system_block_size_ = 0; + gsi_size_ = params.gsiSize; + userdata_size_ = (params.userdataSize) ? params.userdataSize : kDefaultUserdataSize; + wipe_userdata_ = params.wipeUserdata; + can_use_devicemapper_ = false; + gsi_bytes_written_ = 0; + install_dir_ = params.installDir; + + userdata_gsi_path_ = GetImagePath(install_dir_, "userdata_gsi"); + system_gsi_path_ = GetImagePath(install_dir_, "system_gsi"); + + // Only rm userdata_gsi if one didn't already exist. + wipe_userdata_on_failure_ = wipe_userdata_ || access(userdata_gsi_path_.c_str(), F_OK); + + if (int status = PerformSanityChecks()) { + return status; + } + if (int status = PreallocateFiles()) { + return status; + } + if (int status = DetermineReadWriteMethod()) { + return status; } + if (!FormatUserdata()) { + return INSTALL_ERROR_GENERIC; + } + + // Map system_gsi so we can write to it. + system_writer_ = OpenPartition("system_gsi"); + if (!system_writer_) { + return INSTALL_ERROR_GENERIC; + } + return INSTALL_OK; +} + +int GsiService::DetermineReadWriteMethod() { + // If there is a device-mapper node wrapping the block device, then we're + // able to create another node around it; the dm layer does not carry the + // exclusion lock down the stack when a mount occurs. + // + // If there is no intermediate device-mapper node, then partitions cannot be + // opened writable due to sepolicy and exclusivity of having a mounted + // filesystem. This should only happen on devices with no encryption, or + // devices with FBE and no metadata encryption. For these cases it suffices + // to perform normal file writes to /data/gsi (which is unencrypted). + std::string block_device; + if (!FiemapWriter::GetBlockDeviceForFile(system_gsi_path_.c_str(), &block_device, + &can_use_devicemapper_)) { + return INSTALL_ERROR_GENERIC; + } + if (install_dir_ != kDefaultGsiImageFolder && can_use_devicemapper_) { + // Never use device-mapper on external media. We don't support adopted + // storage yet, and accidentally using device-mapper could be dangerous + // as we hardcode the userdata device as backing storage. + LOG(ERROR) << "unexpected device-mapper node used to mount external media"; + return INSTALL_ERROR_GENERIC; + } + return INSTALL_OK; +} + +std::string GsiService::GetImagePath(const std::string& image_dir, const std::string& name) { + std::string dir = image_dir; + if (!android::base::EndsWith(dir, "/")) { + dir += "/"; + } + return dir + name + ".img"; } std::string GsiService::GetInstalledImageDir() { // If there's no install left, just return /data/gsi since that's where // installs go by default. std::string dir; - if (android::base::ReadFileToString(kDsuInstallDirFile, &dir)) { + if (android::base::ReadFileToString(kGsiInstallDirFile, &dir)) { return dir; } - return kDefaultDsuImageFolder; + return kDefaultGsiImageFolder; +} + +std::string GsiService::GetInstalledImagePath(const std::string& name) { + return GetImagePath(GetInstalledImageDir(), name); +} + +int GsiService::PerformSanityChecks() { + if (gsi_size_ < 0) { + LOG(ERROR) << "image size " << gsi_size_ << " is negative"; + return INSTALL_ERROR_GENERIC; + } + if (android::gsi::IsGsiRunning()) { + LOG(ERROR) << "cannot install gsi inside a live gsi"; + return INSTALL_ERROR_GENERIC; + } + + struct statvfs sb; + if (statvfs(install_dir_.c_str(), &sb)) { + PLOG(ERROR) << "failed to read file system stats"; + return INSTALL_ERROR_GENERIC; + } + + // This is the same as android::vold::GetFreebytes() but we also + // need the total file system size so we open code it here. + uint64_t free_space = 1ULL * sb.f_bavail * sb.f_frsize; + uint64_t fs_size = sb.f_blocks * sb.f_frsize; + if (free_space <= (gsi_size_ + userdata_size_)) { + LOG(ERROR) << "not enough free space (only " << free_space << " bytes available)"; + return INSTALL_ERROR_NO_SPACE; + } + // We are asking for 40% of the /data to be empty. + // TODO: may be not hard code it like this + double free_space_percent = ((1.0 * free_space) / fs_size) * 100; + if (free_space_percent < kMinimumFreeSpaceThreshold) { + LOG(ERROR) << "free space " << static_cast<uint64_t>(free_space_percent) + << "% is below the minimum threshold of " << kMinimumFreeSpaceThreshold << "%"; + return INSTALL_ERROR_FILE_SYSTEM_CLUTTERED; + } + return INSTALL_OK; +} + +int GsiService::PreallocateFiles() { + if (wipe_userdata_) { + SplitFiemap::RemoveSplitFiles(userdata_gsi_path_); + } + SplitFiemap::RemoveSplitFiles(system_gsi_path_); + + // TODO: trigger GC from fiemap writer. + + // Create fallocated files. + if (int status = PreallocateUserdata()) { + return status; + } + if (int status = PreallocateSystem()) { + return status; + } + + // Save the extent information in liblp. + metadata_ = CreateMetadata(); + if (!metadata_) { + return INSTALL_ERROR_GENERIC; + } + + UpdateProgress(STATUS_COMPLETE, 0); + return INSTALL_OK; +} + +int GsiService::PreallocateUserdata() { + int error; + std::unique_ptr<SplitFiemap> userdata_image; + if (wipe_userdata_ || access(userdata_gsi_path_.c_str(), F_OK)) { + StartAsyncOperation("create userdata", userdata_size_); + userdata_image = CreateFiemapWriter(userdata_gsi_path_, userdata_size_, &error); + if (!userdata_image) { + LOG(ERROR) << "Could not create userdata image: " << userdata_gsi_path_; + return error; + } + // Signal that we need to reformat userdata. + wipe_userdata_ = true; + } else { + userdata_image = CreateFiemapWriter(userdata_gsi_path_, 0, &error); + if (!userdata_image) { + LOG(ERROR) << "Could not open userdata image: " << userdata_gsi_path_; + return error; + } + if (userdata_size_ && userdata_image->size() < userdata_size_) { + // :TODO: need to fallocate more blocks and resizefs. + } + userdata_size_ = userdata_image->size(); + } + + userdata_block_size_ = userdata_image->block_size(); + + Image image = { + .writer = std::move(userdata_image), + .actual_size = userdata_size_, + }; + partitions_.emplace(std::make_pair("userdata_gsi", std::move(image))); + return INSTALL_OK; +} + +int GsiService::PreallocateSystem() { + StartAsyncOperation("create system", gsi_size_); + + int error; + auto system_image = CreateFiemapWriter(system_gsi_path_, gsi_size_, &error); + if (!system_image) { + return error; + } + + system_block_size_ = system_image->block_size(); + + Image image = { + .writer = std::move(system_image), + .actual_size = gsi_size_, + }; + partitions_.emplace(std::make_pair("system_gsi", std::move(image))); + return INSTALL_OK; +} + +std::unique_ptr<SplitFiemap> GsiService::CreateFiemapWriter(const std::string& path, + uint64_t size, int* error) { + bool create = (size != 0); + + std::function<bool(uint64_t, uint64_t)> progress; + if (create) { + // TODO: allow cancelling inside cancelGsiInstall. + progress = [this](uint64_t bytes, uint64_t /* total */) -> bool { + UpdateProgress(STATUS_WORKING, bytes); + if (should_abort_) return false; + return true; + }; + } + + std::unique_ptr<SplitFiemap> file; + if (!size) { + file = SplitFiemap::Open(path); + } else { + file = SplitFiemap::Create(path, size, 0, std::move(progress)); + } + if (!file) { + LOG(ERROR) << "failed to create or open " << path; + *error = INSTALL_ERROR_GENERIC; + return nullptr; + } + + uint64_t extents = file->extents().size(); + if (extents > kMaximumExtents) { + LOG(ERROR) << "file " << path << " has too many extents: " << extents; + *error = INSTALL_ERROR_FILE_SYSTEM_CLUTTERED; + return nullptr; + } + return file; +} + +// Write data through an fd. +class FdWriter final : public GsiService::WriteHelper { + public: + FdWriter(const std::string& path, unique_fd&& fd) : path_(path), fd_(std::move(fd)) {} + + bool Write(const void* data, uint64_t bytes) override { + return android::base::WriteFully(fd_, data, bytes); + } + bool Flush() override { + if (fsync(fd_)) { + PLOG(ERROR) << "fsync failed: " << path_; + return false; + } + return true; + } + uint64_t Size() override { return get_block_device_size(fd_); } + + private: + std::string path_; + unique_fd fd_; +}; + +// Write data through a SplitFiemap. +class SplitFiemapWriter final : public GsiService::WriteHelper { + public: + explicit SplitFiemapWriter(SplitFiemap* writer) : writer_(writer) {} + + bool Write(const void* data, uint64_t bytes) override { + return writer_->Write(data, bytes); + } + bool Flush() override { + return writer_->Flush(); + } + uint64_t Size() override { return writer_->size(); } + + private: + SplitFiemap* writer_; +}; + +std::unique_ptr<GsiService::WriteHelper> GsiService::OpenPartition(const std::string& name) { + if (can_use_devicemapper_) { + std::string path; + if (!CreateLogicalPartition(kUserdataDevice, *metadata_.get(), name, true, kDmTimeout, + &path)) { + LOG(ERROR) << "Error creating device-mapper node for " << name; + return {}; + } + + static const int kOpenFlags = O_RDWR | O_NOFOLLOW | O_CLOEXEC; + unique_fd fd(open(path.c_str(), kOpenFlags)); + if (fd < 0) { + PLOG(ERROR) << "could not open " << path; + } + return std::make_unique<FdWriter>(GetImagePath(install_dir_, name), std::move(fd)); + } + + auto iter = partitions_.find(name); + if (iter == partitions_.end()) { + LOG(ERROR) << "could not find partition " << name; + return {}; + } + return std::make_unique<SplitFiemapWriter>(iter->second.writer.get()); +} + +bool GsiService::CommitGsiChunk(int stream_fd, int64_t bytes) { + StartAsyncOperation("write gsi", gsi_size_); + + if (bytes < 0) { + LOG(ERROR) << "chunk size " << bytes << " is negative"; + return false; + } + + auto buffer = std::make_unique<char[]>(system_block_size_); + + int progress = -1; + uint64_t remaining = bytes; + while (remaining) { + // :TODO: check file pin status! + size_t max_to_read = std::min(system_block_size_, remaining); + ssize_t rv = TEMP_FAILURE_RETRY(read(stream_fd, buffer.get(), max_to_read)); + if (rv < 0) { + PLOG(ERROR) << "read gsi chunk"; + return false; + } + if (rv == 0) { + LOG(ERROR) << "no bytes left in stream"; + return false; + } + if (!CommitGsiChunk(buffer.get(), rv)) { + return false; + } + CHECK(static_cast<uint64_t>(rv) <= remaining); + remaining -= rv; + + // Only update the progress when the % (or permille, in this case) + // significantly changes. + int new_progress = ((gsi_size_ - remaining) * 1000) / gsi_size_; + if (new_progress != progress) { + UpdateProgress(STATUS_WORKING, gsi_size_ - remaining); + } + } + + UpdateProgress(STATUS_COMPLETE, gsi_size_); + return true; +} + +bool GsiService::CommitGsiChunk(const void* data, size_t bytes) { + if (!installing_) { + LOG(ERROR) << "no gsi installation in progress"; + return false; + } + if (static_cast<uint64_t>(bytes) > gsi_size_ - gsi_bytes_written_) { + // We cannot write past the end of the image file. + LOG(ERROR) << "chunk size " << bytes << " exceeds remaining image size (" << gsi_size_ + << " expected, " << gsi_bytes_written_ << " written)"; + return false; + } + + if (!system_writer_->Write(data, bytes)) { + PLOG(ERROR) << "write failed"; + return false; + } + gsi_bytes_written_ += bytes; + return true; +} + +int GsiService::SetGsiBootable(bool one_shot) { + if (gsi_bytes_written_ != gsi_size_) { + // We cannot boot if the image is incomplete. + LOG(ERROR) << "image incomplete; expected " << gsi_size_ << " bytes, waiting for " + << (gsi_size_ - gsi_bytes_written_) << " bytes"; + return INSTALL_ERROR_GENERIC; + } + + if (!system_writer_->Flush()) { + return INSTALL_ERROR_GENERIC; + } + + // If files moved (are no longer pinned), the metadata file will be invalid. + for (const auto& [name, image] : partitions_) { + if (!image.writer->HasPinnedExtents()) { + LOG(ERROR) << name << " no longer has pinned extents"; + return INSTALL_ERROR_GENERIC; + } + } + + // Remember the installation directory. + if (!android::base::WriteStringToFile(install_dir_, kGsiInstallDirFile)) { + PLOG(ERROR) << "write failed: " << kGsiInstallDirFile; + return INSTALL_ERROR_GENERIC; + } + + // Note: create the install status file last, since this is the actual boot + // indicator. + if (!CreateMetadataFile() || !SetBootMode(one_shot) || !CreateInstallStatusFile()) { + return INSTALL_ERROR_GENERIC; + } + return INSTALL_OK; } int GsiService::ReenableGsi(bool one_shot) { @@ -602,7 +894,7 @@ int GsiService::ReenableGsi(bool one_shot) { std::string boot_key; if (!GetInstallStatus(&boot_key)) { - PLOG(ERROR) << "read " << kDsuInstallStatusFile; + PLOG(ERROR) << "read " << kGsiInstallStatusFile; return INSTALL_ERROR_GENERIC; } if (boot_key != kInstallStatusDisabled) { @@ -610,33 +902,164 @@ int GsiService::ReenableGsi(bool one_shot) { return INSTALL_ERROR_GENERIC; } - return GsiInstaller::ReenableGsi(one_shot); + if (IsGsiRunning()) { + if (!SetBootMode(one_shot) || !CreateInstallStatusFile()) { + return INSTALL_ERROR_GENERIC; + } + return INSTALL_OK; + } + // Note: this metadata is only used to recover the original partition sizes. + // We do not trust the extent information, which will get rebuilt later. + auto old_metadata = ReadFromImageFile(kGsiLpMetadataFile); + if (!old_metadata) { + LOG(ERROR) << "GSI install is incomplete"; + return INSTALL_ERROR_GENERIC; + } + + // Set up enough installer state so that we can use various helper + // methods. + // + // TODO(dvander) Extract all of the installer state into a separate + // class so this is more manageable. + install_dir_ = GetInstalledImageDir(); + system_gsi_path_ = GetImagePath(install_dir_, "system_gsi"); + if (int error = DetermineReadWriteMethod()) { + return error; + } + + // Recover parition information. + Image userdata_image; + if (int error = GetExistingImage(*old_metadata.get(), "userdata_gsi", &userdata_image)) { + return error; + } + partitions_.emplace(std::make_pair("userdata_gsi", std::move(userdata_image))); + + Image system_image; + if (int error = GetExistingImage(*old_metadata.get(), "system_gsi", &system_image)) { + return error; + } + partitions_.emplace(std::make_pair("system_gsi", std::move(system_image))); + + metadata_ = CreateMetadata(); + if (!metadata_) { + return INSTALL_ERROR_GENERIC; + } + if (!CreateMetadataFile() || !SetBootMode(one_shot) || !CreateInstallStatusFile()) { + return INSTALL_ERROR_GENERIC; + } + return INSTALL_OK; +} + +int GsiService::WipeUserdata() { + // Note: this metadata is only used to recover the original partition sizes. + // We do not trust the extent information, which will get rebuilt later. + auto old_metadata = ReadFromImageFile(kGsiLpMetadataFile); + if (!old_metadata) { + LOG(ERROR) << "GSI install is incomplete"; + return INSTALL_ERROR_GENERIC; + } + + install_dir_ = GetInstalledImageDir(); + system_gsi_path_ = GetImagePath(install_dir_, "system_gsi"); + if (int error = DetermineReadWriteMethod()) { + return error; + } + + // Recover parition information. + Image userdata_image; + if (int error = GetExistingImage(*old_metadata.get(), "userdata_gsi", &userdata_image)) { + return error; + } + partitions_.emplace(std::make_pair("userdata_gsi", std::move(userdata_image))); + + metadata_ = CreateMetadata(); + if (!metadata_) { + return INSTALL_ERROR_GENERIC; + } + + auto writer = OpenPartition("userdata_gsi"); + if (!writer) { + return IGsiService::INSTALL_ERROR_GENERIC; + } + + // Wipe the first 1MiB of the device, ensuring both the first block and + // the superblock are destroyed. + static constexpr uint64_t kEraseSize = 1024 * 1024; + + std::string zeroes(4096, 0); + uint64_t erase_size = std::min(kEraseSize, writer->Size()); + for (uint64_t i = 0; i < erase_size; i += zeroes.size()) { + if (!writer->Write(zeroes.data(), zeroes.size())) { + PLOG(ERROR) << "write userdata_gsi"; + return IGsiService::INSTALL_ERROR_GENERIC; + } + } + return INSTALL_OK; +} + +static uint64_t GetPartitionSize(const LpMetadata& metadata, const LpMetadataPartition& partition) { + uint64_t total = 0; + for (size_t i = 0; i < partition.num_extents; i++) { + const auto& extent = metadata.extents[partition.first_extent_index + i]; + if (extent.target_type != LP_TARGET_TYPE_LINEAR) { + LOG(ERROR) << "non-linear extent detected"; + return 0; + } + total += extent.num_sectors * LP_SECTOR_SIZE; + } + return total; +} + +static uint64_t GetPartitionSize(const LpMetadata& metadata, const std::string& name) { + for (const auto& partition : metadata.partitions) { + if (GetPartitionName(partition) == name) { + return GetPartitionSize(metadata, partition); + } + } + return 0; +} + +int GsiService::GetExistingImage(const LpMetadata& metadata, const std::string& name, + Image* image) { + int error; + std::string path = GetInstalledImagePath(name); + auto writer = CreateFiemapWriter(path.c_str(), 0, &error); + if (!writer) { + return error; + } + + // Even after recovering the FIEMAP, we also need to know the exact intended + // size of the image, since FiemapWriter may have extended the final block. + uint64_t actual_size = GetPartitionSize(metadata, name); + if (!actual_size) { + LOG(ERROR) << "Could not determine the pre-existing size of " << name; + return INSTALL_ERROR_GENERIC; + } + image->writer = std::move(writer); + image->actual_size = actual_size; + return INSTALL_OK; } bool GsiService::RemoveGsiFiles(const std::string& install_dir, bool wipeUserdata) { bool ok = true; - if (auto manager = ImageManager::Open(kDsuMetadataDir, install_dir)) { - std::vector<std::string> images = manager->GetAllBackingImages(); - for (auto&& image : images) { - if (!android::base::EndsWith(image, "_gsi")) { - continue; - } - if (manager->IsImageMapped(image)) { - ok &= manager->UnmapImageDevice(image); - } - if (!android::base::StartsWith(image, "userdata") || wipeUserdata) { - ok &= manager->DeleteBackingImage(image); - } - } + std::string message; + if (!SplitFiemap::RemoveSplitFiles(GetImagePath(install_dir, "system_gsi"), &message)) { + LOG(ERROR) << message; + ok = false; + } + if (wipeUserdata && + !SplitFiemap::RemoveSplitFiles(GetImagePath(install_dir, "userdata_gsi"), &message)) { + LOG(ERROR) << message; + ok = false; } std::vector<std::string> files{ - kDsuInstallStatusFile, - kDsuOneShotBootFile, - kDsuInstallDirFile, + kGsiInstallStatusFile, + kGsiLpMetadataFile, + kGsiOneShotBootFile, + kGsiInstallDirFile, }; for (const auto& file : files) { - std::string message; if (!android::base::RemoveFileIfExists(file, &message)) { LOG(ERROR) << message; ok = false; @@ -650,7 +1073,7 @@ bool GsiService::DisableGsiInstall() { LOG(ERROR) << "cannot disable gsi install - no install detected"; return false; } - if (installer_) { + if (installing_) { LOG(ERROR) << "cannot disable gsi during GSI installation"; return false; } @@ -661,22 +1084,143 @@ bool GsiService::DisableGsiInstall() { return true; } -void GsiService::CleanCorruptedInstallation() { - auto install_dir = GetInstalledImageDir(); - if (!RemoveGsiFiles(install_dir, true)) { - LOG(ERROR) << "Failed to CleanCorruptedInstallation on " << install_dir; +std::unique_ptr<LpMetadata> GsiService::CreateMetadata() { + std::string data_device_path; + if (install_dir_ == kDefaultGsiImageFolder && !access(kUserdataDevice, F_OK)) { + data_device_path = kUserdataDevice; + } else { + auto writer = partitions_["system_gsi"].writer.get(); + data_device_path = writer->bdev_path(); + } + auto data_device_name = android::base::Basename(data_device_path); + + PartitionOpener opener; + BlockDeviceInfo data_device_info; + if (!opener.GetInfo(data_device_path, &data_device_info)) { + LOG(ERROR) << "Error reading userdata partition"; + return nullptr; + } + + std::vector<BlockDeviceInfo> block_devices = {data_device_info}; + auto builder = MetadataBuilder::New(block_devices, data_device_name, 128 * 1024, 1); + if (!builder) { + LOG(ERROR) << "Error creating metadata builder"; + return nullptr; + } + builder->IgnoreSlotSuffixing(); + + for (const auto& [name, image] : partitions_) { + uint32_t flags = LP_PARTITION_ATTR_NONE; + if (name == "system_gsi") { + flags |= LP_PARTITION_ATTR_READONLY; + } + Partition* partition = builder->AddPartition(name, flags); + if (!partition) { + LOG(ERROR) << "Error adding " << name << " to partition table"; + return nullptr; + } + if (!AddPartitionFiemap(builder.get(), partition, image, data_device_name)) { + return nullptr; + } + } + + auto metadata = builder->Export(); + if (!metadata) { + LOG(ERROR) << "Error exporting partition table"; + return nullptr; + } + return metadata; +} + +bool GsiService::CreateMetadataFile() { + if (!WriteToImageFile(kGsiLpMetadataFile, *metadata_.get())) { + LOG(ERROR) << "Error writing GSI partition table image"; + return false; + } + return true; +} + +bool GsiService::FormatUserdata() { + auto writer = OpenPartition("userdata_gsi"); + if (!writer) { + return false; + } + + // libcutils checks the first 4K, no matter the block size. + std::string zeroes(4096, 0); + if (!writer->Write(zeroes.data(), zeroes.size())) { + PLOG(ERROR) << "write userdata_gsi"; + return false; + } + return true; +} + +bool GsiService::AddPartitionFiemap(MetadataBuilder* builder, Partition* partition, + const Image& image, const std::string& block_device) { + uint64_t sectors_needed = image.actual_size / LP_SECTOR_SIZE; + for (const auto& extent : image.writer->extents()) { + // :TODO: block size check for length, not sector size + if (extent.fe_length % LP_SECTOR_SIZE != 0) { + LOG(ERROR) << "Extent is not sector-aligned: " << extent.fe_length; + return false; + } + if (extent.fe_physical % LP_SECTOR_SIZE != 0) { + LOG(ERROR) << "Extent physical sector is not sector-aligned: " << extent.fe_physical; + return false; + } + + uint64_t num_sectors = + std::min(static_cast<uint64_t>(extent.fe_length / LP_SECTOR_SIZE), sectors_needed); + if (!num_sectors || !sectors_needed) { + // This should never happen, but we include it just in case. It would + // indicate that the last filesystem block had multiple extents. + LOG(WARNING) << "FiemapWriter allocated extra blocks"; + break; + } + + uint64_t physical_sector = extent.fe_physical / LP_SECTOR_SIZE; + if (!builder->AddLinearExtent(partition, block_device, num_sectors, physical_sector)) { + LOG(ERROR) << "Could not add extent to lp metadata"; + return false; + } + + sectors_needed -= num_sectors; + } + return true; +} + +bool GsiService::SetBootMode(bool one_shot) { + if (one_shot) { + if (!android::base::WriteStringToFile("1", kGsiOneShotBootFile)) { + PLOG(ERROR) << "write " << kGsiOneShotBootFile; + return false; + } + } else if (!access(kGsiOneShotBootFile, F_OK)) { + std::string error; + if (!android::base::RemoveFileIfExists(kGsiOneShotBootFile, &error)) { + LOG(ERROR) << error; + return false; + } + } + return true; +} + +bool GsiService::CreateInstallStatusFile() { + if (!android::base::WriteStringToFile("0", kGsiInstallStatusFile)) { + PLOG(ERROR) << "write " << kGsiInstallStatusFile; + return false; } + return true; } void GsiService::RunStartupTasks() { if (!IsGsiInstalled()) { - CleanCorruptedInstallation(); return; } std::string boot_key; if (!GetInstallStatus(&boot_key)) { - PLOG(ERROR) << "read " << kDsuInstallStatusFile; + PLOG(ERROR) << "read " << kGsiInstallStatusFile; return; } @@ -692,8 +1236,8 @@ void GsiService::RunStartupTasks() { int ignore; if (GetBootAttempts(boot_key, &ignore)) { // Mark the GSI as having successfully booted. - if (!android::base::WriteStringToFile(kInstallStatusOk, kDsuInstallStatusFile)) { - PLOG(ERROR) << "write " << kDsuInstallStatusFile; + if (!android::base::WriteStringToFile(kInstallStatusOk, kGsiInstallStatusFile)) { + PLOG(ERROR) << "write " << kGsiInstallStatusFile; } } } diff --git a/gsi_service.h b/gsi_service.h index 0f06b0e..30b5782 100644 --- a/gsi_service.h +++ b/gsi_service.h @@ -24,95 +24,143 @@ #include <android-base/unique_fd.h> #include <android/gsi/BnGsiService.h> -#include <android/gsi/BnGsid.h> #include <binder/BinderService.h> -#include <libfiemap/split_fiemap_writer.h> +#include <libfiemap_writer/split_fiemap_writer.h> #include <liblp/builder.h> #include "libgsi/libgsi.h" -#include "gsi_installer.h" - namespace android { namespace gsi { -class Gsid : public BinderService<Gsid>, public BnGsid { +class GsiService : public BinderService<GsiService>, public BnGsiService { public: static void Register(); - static char const* getServiceName() { return kGsiServiceName; } - - binder::Status getClient(android::sp<IGsiService>* _aidl_return) override; - - private: - friend class GsiService; - friend class ImageService; - - std::mutex& lock() { return lock_; } - - std::mutex lock_; -}; -class GsiService : public BinderService<GsiService>, public BnGsiService { - public: + GsiService(); ~GsiService() override; - static android::sp<IGsiService> Get(Gsid* parent); - + binder::Status startGsiInstall(int64_t gsiSize, int64_t userdataSize, bool wipeUserdata, + int* _aidl_return) override; binder::Status beginGsiInstall(const GsiInstallParams& params, int* _aidl_return) override; binder::Status commitGsiChunkFromStream(const ::android::os::ParcelFileDescriptor& stream, int64_t bytes, bool* _aidl_return) override; binder::Status getInstallProgress(::android::gsi::GsiProgress* _aidl_return) override; - binder::Status setGsiAshmem(const ::android::os::ParcelFileDescriptor& ashmem, int64_t size, - bool* _aidl_return) override; - binder::Status commitGsiChunkFromAshmem(int64_t bytes, bool* _aidl_return) override; + binder::Status commitGsiChunkFromMemory(const ::std::vector<uint8_t>& bytes, + bool* _aidl_return) override; binder::Status cancelGsiInstall(bool* _aidl_return) override; - binder::Status enableGsi(bool oneShot, int* _aidl_return) override; + binder::Status setGsiBootable(bool oneShot, int* _aidl_return) override; binder::Status isGsiEnabled(bool* _aidl_return) override; - binder::Status removeGsi(bool* _aidl_return) override; - binder::Status disableGsi(bool* _aidl_return) override; - binder::Status isGsiInstalled(bool* _aidl_return) override; + binder::Status removeGsiInstall(bool* _aidl_return) override; + binder::Status disableGsiInstall(bool* _aidl_return) override; binder::Status isGsiRunning(bool* _aidl_return) override; + binder::Status isGsiInstalled(bool* _aidl_return) override; binder::Status isGsiInstallInProgress(bool* _aidl_return) override; + binder::Status getUserdataImageSize(int64_t* _aidl_return) override; + binder::Status getGsiBootStatus(int* _aidl_return) override; binder::Status getInstalledGsiImageDir(std::string* _aidl_return) override; binder::Status wipeGsiUserdata(int* _aidl_return) override; - binder::Status openImageService(const std::string& prefix, - android::sp<IImageService>* _aidl_return) override; - binder::Status dumpDeviceMapperDevices(std::string* _aidl_return) override; - - // This is in GsiService, rather than GsiInstaller, since we need to access - // it outside of the main lock which protects the unique_ptr. - void StartAsyncOperation(const std::string& step, int64_t total_bytes); - void UpdateProgress(int status, int64_t bytes_processed); - // Helper methods for GsiInstaller. - static bool RemoveGsiFiles(const std::string& install_dir, bool wipeUserdata); - bool should_abort() const { return should_abort_; } - Gsid* parent() const { return parent_.get(); } + static char const* getServiceName() { return kGsiServiceName; } static void RunStartupTasks(); - static std::string GetInstalledImageDir(); - std::string GetActiveInstalledImageDir(); + + // This helper class will redirect writes to either a SplitFiemap or + // device-mapper. + class WriteHelper { + public: + virtual ~WriteHelper() {}; + virtual bool Write(const void* data, uint64_t bytes) = 0; + virtual bool Flush() = 0; + virtual uint64_t Size() = 0; + + WriteHelper() = default; + WriteHelper(const WriteHelper&) = delete; + WriteHelper& operator=(const WriteHelper&) = delete; + WriteHelper& operator=(WriteHelper&&) = delete; + WriteHelper(WriteHelper&&) = delete; + }; private: - GsiService(Gsid* parent); + using LpMetadata = android::fs_mgr::LpMetadata; + using MetadataBuilder = android::fs_mgr::MetadataBuilder; + using SplitFiemap = android::fiemap_writer::SplitFiemap; + + struct Image { + std::unique_ptr<SplitFiemap> writer; + uint64_t actual_size; + }; + int ValidateInstallParams(GsiInstallParams* params); - bool DisableGsiInstall(); + int StartInstall(const GsiInstallParams& params); + int PerformSanityChecks(); + int PreallocateFiles(); + int PreallocateUserdata(); + int PreallocateSystem(); + int DetermineReadWriteMethod(); + bool FormatUserdata(); + bool CommitGsiChunk(int stream_fd, int64_t bytes); + bool CommitGsiChunk(const void* data, size_t bytes); + int SetGsiBootable(bool one_shot); int ReenableGsi(bool one_shot); - static void CleanCorruptedInstallation(); + int WipeUserdata(); + bool DisableGsiInstall(); + bool AddPartitionFiemap(android::fs_mgr::MetadataBuilder* builder, + android::fs_mgr::Partition* partition, const Image& image, + const std::string& block_device); + std::unique_ptr<LpMetadata> CreateMetadata(); + std::unique_ptr<SplitFiemap> CreateFiemapWriter(const std::string& path, uint64_t size, + int* error); + bool CreateInstallStatusFile(); + bool CreateMetadataFile(); + bool SetBootMode(bool one_shot); + void PostInstallCleanup(); - enum class AccessLevel { System, SystemOrShell }; + void StartAsyncOperation(const std::string& step, int64_t total_bytes); + void UpdateProgress(int status, int64_t bytes_processed); + int GetExistingImage(const LpMetadata& metadata, const std::string& name, Image* image); + std::unique_ptr<WriteHelper> OpenPartition(const std::string& name); + + enum class AccessLevel { + System, + SystemOrShell + }; binder::Status CheckUid(AccessLevel level = AccessLevel::System); - static android::wp<GsiService> sInstance; + static bool RemoveGsiFiles(const std::string& install_dir, bool wipeUserdata); + static std::string GetImagePath(const std::string& image_dir, const std::string& name); + static std::string GetInstalledImagePath(const std::string& name); + static std::string GetInstalledImageDir(); + + std::mutex main_lock_; - android::sp<Gsid> parent_; - std::unique_ptr<GsiInstaller> installer_; + // Set before installation starts, to determine whether or not to delete + // the userdata image if installation fails. + bool wipe_userdata_on_failure_; // These are initialized or set in StartInstall(). + bool installing_ = false; std::atomic<bool> should_abort_ = false; + std::string install_dir_; + std::string userdata_gsi_path_; + std::string system_gsi_path_; + uint64_t userdata_block_size_; + uint64_t system_block_size_; + uint64_t gsi_size_; + uint64_t userdata_size_; + bool can_use_devicemapper_; + bool wipe_userdata_; + // Remaining data we're waiting to receive for the GSI image. + uint64_t gsi_bytes_written_; // Progress bar state. std::mutex progress_lock_; GsiProgress progress_; + + std::unique_ptr<WriteHelper> system_writer_; + + // This is used to track which GSI partitions have been created. + std::map<std::string, Image> partitions_; + std::unique_ptr<LpMetadata> metadata_; }; } // namespace gsi diff --git a/gsi_tool.cpp b/gsi_tool.cpp index 0841e61..ee6094b 100644 --- a/gsi_tool.cpp +++ b/gsi_tool.cpp @@ -17,7 +17,6 @@ #include <getopt.h> #include <stdio.h> #include <sysexits.h> -#include <unistd.h> #include <chrono> #include <condition_variable> @@ -28,16 +27,13 @@ #include <string> #include <thread> -#include <android-base/logging.h> #include <android-base/parseint.h> #include <android-base/properties.h> #include <android-base/unique_fd.h> #include <android/gsi/IGsiService.h> -#include <android/gsi/IGsid.h> #include <binder/IServiceManager.h> #include <cutils/android_reboot.h> #include <libgsi/libgsi.h> -#include <libgsi/libgsid.h> using namespace android::gsi; using namespace std::chrono_literals; @@ -54,7 +50,6 @@ static int Status(sp<IGsiService> gsid, int argc, char** argv); static int Cancel(sp<IGsiService> gsid, int argc, char** argv); static const std::map<std::string, CommandCallback> kCommandMap = { - // clang-format off {"disable", Disable}, {"enable", Enable}, {"install", Install}, @@ -62,11 +57,32 @@ static const std::map<std::string, CommandCallback> kCommandMap = { {"wipe-data", WipeData}, {"status", Status}, {"cancel", Cancel}, - // clang-format on }; -static std::string ErrorMessage(const android::binder::Status& status, - int error_code = IGsiService::INSTALL_ERROR_GENERIC) { +static sp<IGsiService> GetGsiService() { + if (android::base::GetProperty("init.svc.gsid", "") != "running") { + if (!android::base::SetProperty("ctl.start", "gsid") || + !android::base::WaitForProperty("init.svc.gsid", "running", 5s)) { + std::cerr << "Unable to start gsid\n"; + return nullptr; + } + } + + static const int kSleepTimeMs = 50; + static const int kTotalWaitTimeMs = 3000; + for (int i = 0; i < kTotalWaitTimeMs / kSleepTimeMs; i++) { + auto sm = android::defaultServiceManager(); + auto name = android::String16(kGsiServiceName); + android::sp<android::IBinder> res = sm->checkService(name); + if (res) { + return android::interface_cast<IGsiService>(res); + } + usleep(kSleepTimeMs * 1000); + } + return nullptr; +} + +static std::string ErrorMessage(const android::binder::Status& status, int error_code = IGsiService::INSTALL_ERROR_GENERIC) { if (!status.isOk()) { return status.exceptionMessage().string(); } @@ -188,23 +204,21 @@ class ProgressBar { }; static int Install(sp<IGsiService> gsid, int argc, char** argv) { - constexpr const char* kDefaultPartition = "system"; struct option options[] = { {"install-dir", required_argument, nullptr, 'i'}, {"gsi-size", required_argument, nullptr, 's'}, {"no-reboot", no_argument, nullptr, 'n'}, {"userdata-size", required_argument, nullptr, 'u'}, - {"partition-name", required_argument, nullptr, 'p'}, {"wipe", no_argument, nullptr, 'w'}, {nullptr, 0, nullptr, 0}, }; - long gsiSize = 0; - long userdataSize = 0; - bool wipeUserdata = false; + GsiInstallParams params; + params.gsiSize = 0; + params.userdataSize = 0; + params.wipeUserdata = false; bool reboot = true; - std::string installDir = ""; - std::string partition = kDefaultPartition; + if (getuid() != 0) { std::cerr << "must be root to install a GSI" << std::endl; return EX_NOPERM; @@ -213,26 +227,24 @@ static int Install(sp<IGsiService> gsid, int argc, char** argv) { int rv, index; while ((rv = getopt_long_only(argc, argv, "", options, &index)) != -1) { switch (rv) { - case 'p': - partition = optarg; - break; case 's': - if (!android::base::ParseInt(optarg, &gsiSize) || gsiSize <= 0) { + if (!android::base::ParseInt(optarg, ¶ms.gsiSize) || params.gsiSize <= 0) { std::cerr << "Could not parse image size: " << optarg << std::endl; return EX_USAGE; } break; case 'u': - if (!android::base::ParseInt(optarg, &userdataSize) || userdataSize < 0) { + if (!android::base::ParseInt(optarg, ¶ms.userdataSize) || + params.userdataSize < 0) { std::cerr << "Could not parse image size: " << optarg << std::endl; return EX_USAGE; } break; case 'i': - installDir = optarg; + params.installDir = optarg; break; case 'w': - wipeUserdata = true; + params.wipeUserdata = true; break; case 'n': reboot = false; @@ -240,7 +252,7 @@ static int Install(sp<IGsiService> gsid, int argc, char** argv) { } } - if (gsiSize <= 0) { + if (params.gsiSize <= 0) { std::cerr << "Must specify --gsi-size." << std::endl; return EX_USAGE; } @@ -258,42 +270,23 @@ static int Install(sp<IGsiService> gsid, int argc, char** argv) { std::cerr << "Error duplicating descriptor: " << strerror(errno) << std::endl; return EX_SOFTWARE; } + // Note: the progress bar needs to be re-started in between each call. ProgressBar progress(gsid); progress.Display(); - int error; - if (partition == kDefaultPartition) { - GsiInstallParams userdataParams; - userdataParams.installDir = installDir; - userdataParams.name = "userdata"; - userdataParams.size = userdataSize; - userdataParams.wipe = wipeUserdata; - userdataParams.readOnly = false; - - auto status = gsid->beginGsiInstall(userdataParams, &error); - if (!status.isOk() || error != IGsiService::INSTALL_OK) { - std::cerr << "Could not start live image install: " << ErrorMessage(status, error) - << "\n"; - return EX_SOFTWARE; - } - } - GsiInstallParams systemParams; - systemParams.installDir = installDir; - systemParams.name = partition; - systemParams.size = gsiSize; - systemParams.wipe = true; - systemParams.readOnly = true; - auto status = gsid->beginGsiInstall(systemParams, &error); + int error; + auto status = gsid->beginGsiInstall(params, &error); if (!status.isOk() || error != IGsiService::INSTALL_OK) { std::cerr << "Could not start live image install: " << ErrorMessage(status, error) << "\n"; return EX_SOFTWARE; } + android::os::ParcelFileDescriptor stream(std::move(input)); bool ok = false; progress.Display(); - status = gsid->commitGsiChunkFromStream(stream, systemParams.size, &ok); + status = gsid->commitGsiChunkFromStream(stream, params.gsiSize, &ok); if (!ok) { std::cerr << "Could not commit live image data: " << ErrorMessage(status) << "\n"; return EX_SOFTWARE; @@ -301,7 +294,7 @@ static int Install(sp<IGsiService> gsid, int argc, char** argv) { progress.Finish(); - status = gsid->enableGsi(true, &error); + status = gsid->setGsiBootable(true, &error); if (!status.isOk() || error != IGsiService::INSTALL_OK) { std::cerr << "Could not make live image bootable: " << ErrorMessage(status, error) << "\n"; return EX_SOFTWARE; @@ -324,7 +317,7 @@ static int Wipe(sp<IGsiService> gsid, int argc, char** /* argv */) { return EX_USAGE; } bool ok; - auto status = gsid->removeGsi(&ok); + auto status = gsid->removeGsiInstall(&ok); if (!status.isOk() || !ok) { std::cerr << "Could not remove GSI install: " << ErrorMessage(status) << "\n"; return EX_SOFTWARE; @@ -407,25 +400,6 @@ static int Status(sp<IGsiService> gsid, int argc, char** /* argv */) { } else { std::cout << "normal" << std::endl; } - if (getuid() != 0) { - return 0; - } - sp<IImageService> image_service = nullptr; - status = gsid->openImageService("dsu", &image_service); - if (!status.isOk()) { - std::cerr << "error: " << status.exceptionMessage().string() << std::endl; - return EX_SOFTWARE; - } - std::vector<std::string> images; - status = image_service->getAllBackingImages(&images); - if (!status.isOk()) { - std::cerr << "error: " << status.exceptionMessage().string() << std::endl; - return EX_SOFTWARE; - } - - for (auto&& image : images) { - std::cout << "installed: " << image << std::endl; - } return 0; } @@ -477,7 +451,7 @@ static int Enable(sp<IGsiService> gsid, int argc, char** argv) { } int error; - auto status = gsid->enableGsi(one_shot, &error); + auto status = gsid->setGsiBootable(one_shot, &error); if (!status.isOk() || error != IGsiService::INSTALL_OK) { std::cerr << "Error re-enabling GSI: " << ErrorMessage(status, error) << "\n"; return EX_SOFTWARE; @@ -500,7 +474,7 @@ static int Disable(sp<IGsiService> gsid, int argc, char** /* argv */) { } bool ok = false; - gsid->disableGsi(&ok); + gsid->disableGsiInstall(&ok); if (!ok) { std::cerr << "Error disabling GSI" << std::endl; return EX_SOFTWARE; @@ -532,11 +506,10 @@ static int usage(int /* argc */, char* argv[]) { } int main(int argc, char** argv) { - android::base::InitLogging(argv, android::base::StdioLogger, android::base::DefaultAborter); - - android::sp<IGsiService> service = GetGsiService(); - if (!service) { - return EX_SOFTWARE; + auto gsid = GetGsiService(); + if (!gsid) { + std::cerr << "Could not connect to the gsid service." << std::endl; + return EX_NOPERM; } if (1 >= argc) { @@ -552,6 +525,6 @@ int main(int argc, char** argv) { return usage(argc, argv); } - int rc = iter->second(service, argc - 1, argv + 1); + int rc = iter->second(gsid, argc - 1, argv + 1); return rc; } @@ -1,5 +1,4 @@ service gsid /system/bin/gsid - oneshot disabled user root group root system media_rw @@ -7,12 +6,10 @@ service gsid /system/bin/gsid on post-fs mkdir /metadata/gsi 0771 root system mkdir /metadata/gsi/dsu 0771 root system - mkdir /metadata/gsi/ota 0771 root system on post-fs-data - mkdir /data/gsi 0700 root root encryption=None + mkdir /data/gsi 0700 root root mkdir /data/gsi/dsu 0700 root root - mkdir /data/gsi/ota 0700 root root on boot exec_background - root root -- /system/bin/gsid run-startup-tasks diff --git a/include/libgsi/libgsi.h b/include/libgsi/libgsi.h index 9b24f47..94e6f57 100644 --- a/include/libgsi/libgsi.h +++ b/include/libgsi/libgsi.h @@ -25,14 +25,8 @@ static constexpr char kGsiServiceName[] = "gsiservice"; static constexpr char kGsiBootedIndicatorFile[] = "/metadata/gsi/dsu/booted"; -static constexpr char kGsiLpNamesFile[] = "/metadata/gsi/dsu/lp_names"; - -static constexpr char kDsuLpMetadataFile[] = "/metadata/gsi/dsu/lp_metadata"; - static constexpr char kGsiBootedProp[] = "ro.gsid.image_running"; -static constexpr char kDsuPostfix[] = "_gsi"; - static constexpr int kMaxBootAttempts = 1; // Returns true if the currently running system image is a live GSI. @@ -49,10 +43,11 @@ bool UninstallGsi(); bool DisableGsi(); // Returns true if init should attempt to boot into a live GSI image, false -// otherwise. If false, an error message is set. +// otherwise. If true, then the path to the liblp metadata file is set. If +// false, an error message is set instead. // // This is only called by first-stage init. -bool CanBootIntoGsi(std::string* error); +bool CanBootIntoGsi(std::string* metadata_file, std::string* error); // Called by first-stage init to indicate that we're about to boot into a // GSI. diff --git a/include/libgsi/libgsid.h b/include/libgsi/libgsid.h deleted file mode 100644 index 46f8ef5..0000000 --- a/include/libgsi/libgsid.h +++ /dev/null @@ -1,27 +0,0 @@ -// -// 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 <android/gsi/IGsiService.h> - -namespace android { -namespace gsi { - -android::sp<IGsiService> GetGsiService(); - -} // namespace gsi -} // namespace android diff --git a/libfiemap/.clang-format b/libfiemap/.clang-format deleted file mode 120000 index 8b770a1..0000000 --- a/libfiemap/.clang-format +++ /dev/null @@ -1 +0,0 @@ -../../.clang-format-4
\ No newline at end of file diff --git a/libfiemap/Android.bp b/libfiemap/Android.bp deleted file mode 100644 index 8dbbf4c..0000000 --- a/libfiemap/Android.bp +++ /dev/null @@ -1,135 +0,0 @@ -// -// 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_headers { - name: "libfiemap_headers", - recovery_available: true, - export_include_dirs: ["include"], -} - -cc_defaults { - name: "libfiemap_defaults", - defaults: ["fs_mgr_defaults"], - cflags: [ - "-D_FILE_OFFSET_BITS=64", - "-Wall", - "-Werror", - ], - - srcs: [ - "fiemap_writer.cpp", - "image_manager.cpp", - "metadata.cpp", - "split_fiemap_writer.cpp", - "utility.cpp", - ], - - static_libs: [ - "libdm", - "libext2_uuid", - "libext4_utils", - "liblp", - "libfs_mgr", - ], - - shared_libs: [ - "libbase", - ], - - header_libs: [ - "libfiemap_headers", - "liblog_headers", - ], - - export_shared_lib_headers: [ - "libbase", - ], - - export_header_lib_headers: [ - "libfiemap_headers", - ], -} - -// Open up a binder IImageManager interface. -cc_library_static { - name: "libfiemap_binder", - defaults: ["libfiemap_defaults"], - srcs: [ - "binder.cpp", - ], - whole_static_libs: [ - "gsi_aidl_interface-cpp", - "libgsi", - ], - shared_libs: [ - "libbinder", - ], -} - -// Open up a passthrough IImageManager interface. Use libfiemap_binder whenever -// possible. This should only be used when binder is not available. -cc_library_static { - name: "libfiemap_passthrough", - defaults: ["libfiemap_defaults"], - recovery_available: true, - srcs: [ - "passthrough.cpp", - ], -} - -cc_test { - name: "fiemap_writer_test", - defaults: ["libfiemap_defaults"], - static_libs: [ - "libbase", - "libdm", - "liblog", - ], - - data: [ - "testdata/unaligned_file", - "testdata/file_4k", - "testdata/file_32k", - ], - - srcs: [ - "fiemap_writer_test.cpp", - ], -} - -cc_test { - name: "fiemap_image_test", - defaults: ["libfiemap_defaults"], - static_libs: [ - "libdm", - "libext4_utils", - "libfs_mgr", - "liblp", - ], - shared_libs: [ - "libcrypto", - "libcrypto_utils", - "libcutils", - "liblog", - ], - srcs: [ - "image_test.cpp", - ], -} - -vts_config { - name: "VtsFiemapWriterTest", -} diff --git a/libfiemap/AndroidTest.xml b/libfiemap/AndroidTest.xml deleted file mode 100644 index 44c80fc..0000000 --- a/libfiemap/AndroidTest.xml +++ /dev/null @@ -1,30 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- 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. ---> -<configuration description="Config for VTS VtsFiemapWriterTest"> - <option name="config-descriptor:metadata" key="plan" value="vts-kernel" /> - <target_preparer class="com.android.compatibility.common.tradefed.targetprep.VtsFilePusher"> - <option name="abort-on-push-failure" value="false"/> - <option name="push-group" value="HostDrivenTest.push"/> - </target_preparer> - <test class="com.android.tradefed.testtype.VtsMultiDeviceTest"> - <option name="test-module-name" value="VtsFiemapWriterTest"/> - <option name="binary-test-source" value="_32bit::DATA/nativetest/fiemap_writer_test/fiemap_writer_test" /> - <option name="binary-test-source" value="_64bit::DATA/nativetest64/fiemap_writer_test/fiemap_writer_test" /> - <option name="binary-test-type" value="gtest"/> - <option name="precondition-first-api-level" value="29" /> - <option name="test-timeout" value="1m"/> - </test> -</configuration> diff --git a/libfiemap/README.md b/libfiemap/README.md deleted file mode 100644 index 62d610a..0000000 --- a/libfiemap/README.md +++ /dev/null @@ -1,75 +0,0 @@ -libfiemap -============= - -`libfiemap` is a library for creating block-devices that are backed by -storage in read-write partitions. It exists primary for gsid. Generally, the -library works by using `libfiemap_writer` to allocate large files within -filesystem, and then tracks their extents. - -There are three main uses for `libfiemap`: - - Creating images that will act as block devices. For example, gsid needs to - create a `system_gsi` image to store Dynamic System Updates. - - Mapping the image as a block device while /data is mounted. This is fairly - tricky and is described in more detail below. - - Mapping the image as a block device during first-stage init. This is simple - because it uses the same logic from dynamic partitions. - -Image creation is done through `SplitFiemap`. Depending on the file system, -a large image may have to be split into multiple files. On Ext4 the limit is -16GiB and on FAT32 it's 4GiB. Images are saved into `/data/gsi/<name>/` -where `<name>` is chosen by the process requesting the image. - -At the same time, a file called `/metadata/gsi/<name>/lp_metadata` is created. -This is a super partition header that allows first-stage init to create dynamic -partitions from the image files. It also tracks the canonical size of the image, -since the file size may be larger due to alignment. - -Mapping -------- - -It is easy to make block devices out of blocks on `/data` when it is not -mounted, so first-stage init has no issues mapping dynamic partitions from -images. After `/data` is mounted however, there are two problems: - - `/data` is encrypted. - - `/dev/block/by-name/data` may be marked as in-use. - -We break the problem down into three scenarios. - -### FDE and Metadata Encrypted Devices - -When FDE or metadata encryption is used, `/data` is not mounted from -`/dev/block/by-name/data`. Instead, it is mounted from an intermediate -`dm-crypt` or `dm-default-key` device. This means the underlying device is -not marked in use, and we can create new dm-linear devices on top of it. - -On these devices, a block device for an image will consist of a single -device-mapper device with a `dm-linear` table entry for each extent in the -backing file. - -### Unencrypted and FBE-encrypted Devices - -When a device is unencrypted, or is encrypted with FBE but not metadata -encryption, we instead use a loop device with `LOOP_SET_DIRECT_IO` enabled. -Since `/data/gsi` has encryption disabled, this means the raw blocks will be -unencrypted as well. - -### Split Images - -If an image was too large to store a single file on the underlying filesystem, -on an FBE/unencrypted device we will have multiple loop devices. In this case, -we create a device-mapper device as well. For each loop device it will have one -`dm-linear` table entry spanning the length of the device. - -State Tracking --------------- - -It's important that we know whether or not an image is currently in-use by a -block device. It could be catastrophic to write to a dm-linear device if the -underlying blocks are no longer owned by the original file. Thus, when mapping -an image, we create a property called `gsid.mapped_image.<name>` and set it to -the path of the block device. - -Additionally, we create a `/metadata/gsi/<subdir>/<name>.status` file. Each -line in this file denotes a dependency on either a device-mapper node or a loop -device. When deleting a block device, this file is used to release all -resources. diff --git a/libfiemap/binder.cpp b/libfiemap/binder.cpp deleted file mode 100644 index dcb887a..0000000 --- a/libfiemap/binder.cpp +++ /dev/null @@ -1,213 +0,0 @@ -// -// 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. -// - -#if !defined(__ANDROID_RECOVERY__) -#include <android-base/logging.h> -#include <android-base/properties.h> -#include <android/gsi/IGsiService.h> -#include <android/gsi/IGsid.h> -#include <binder/IServiceManager.h> -#include <libfiemap/image_manager.h> -#include <libgsi/libgsi.h> - -namespace android { -namespace fiemap { - -using namespace android::gsi; -using namespace std::chrono_literals; - -class ImageManagerBinder final : public IImageManager { - public: - ImageManagerBinder(android::sp<IGsiService>&& service, android::sp<IImageService>&& manager); - bool CreateBackingImage(const std::string& name, uint64_t size, int flags) override; - bool DeleteBackingImage(const std::string& name) override; - bool MapImageDevice(const std::string& name, const std::chrono::milliseconds& timeout_ms, - std::string* path) override; - bool UnmapImageDevice(const std::string& name) override; - bool BackingImageExists(const std::string& name) override; - bool IsImageMapped(const std::string& name) override; - bool MapImageWithDeviceMapper(const IPartitionOpener& opener, const std::string& name, - std::string* dev) override; - bool ZeroFillNewImage(const std::string& name, uint64_t bytes) override; - - std::vector<std::string> GetAllBackingImages() override; - - private: - android::sp<IGsiService> service_; - android::sp<IImageService> manager_; -}; - -ImageManagerBinder::ImageManagerBinder(android::sp<IGsiService>&& service, - android::sp<IImageService>&& manager) - : service_(std::move(service)), manager_(std::move(manager)) {} - -bool ImageManagerBinder::CreateBackingImage(const std::string& name, uint64_t size, int flags) { - auto status = manager_->createBackingImage(name, size, flags); - if (!status.isOk()) { - LOG(ERROR) << __PRETTY_FUNCTION__ - << " binder returned: " << status.exceptionMessage().string(); - return false; - } - return true; -} - -bool ImageManagerBinder::DeleteBackingImage(const std::string& name) { - auto status = manager_->deleteBackingImage(name); - if (!status.isOk()) { - LOG(ERROR) << __PRETTY_FUNCTION__ - << " binder returned: " << status.exceptionMessage().string(); - return false; - } - return true; -} - -bool ImageManagerBinder::MapImageDevice(const std::string& name, - const std::chrono::milliseconds& timeout_ms, - std::string* path) { - int32_t timeout_ms_count = - static_cast<int32_t>(std::clamp<typename std::chrono::milliseconds::rep>( - timeout_ms.count(), INT32_MIN, INT32_MAX)); - MappedImage map; - auto status = manager_->mapImageDevice(name, timeout_ms_count, &map); - if (!status.isOk()) { - LOG(ERROR) << __PRETTY_FUNCTION__ - << " binder returned: " << status.exceptionMessage().string(); - return false; - } - *path = map.path; - return true; -} - -bool ImageManagerBinder::UnmapImageDevice(const std::string& name) { - auto status = manager_->unmapImageDevice(name); - if (!status.isOk()) { - LOG(ERROR) << __PRETTY_FUNCTION__ - << " binder returned: " << status.exceptionMessage().string(); - return false; - } - return true; -} - -bool ImageManagerBinder::BackingImageExists(const std::string& name) { - bool retval; - auto status = manager_->backingImageExists(name, &retval); - if (!status.isOk()) { - LOG(ERROR) << __PRETTY_FUNCTION__ - << " binder returned: " << status.exceptionMessage().string(); - return false; - } - return retval; -} - -bool ImageManagerBinder::IsImageMapped(const std::string& name) { - bool retval; - auto status = manager_->isImageMapped(name, &retval); - if (!status.isOk()) { - LOG(ERROR) << __PRETTY_FUNCTION__ - << " binder returned: " << status.exceptionMessage().string(); - return false; - } - return retval; -} - -bool ImageManagerBinder::MapImageWithDeviceMapper(const IPartitionOpener& opener, - const std::string& name, std::string* dev) { - (void)opener; - (void)name; - (void)dev; - LOG(ERROR) << "MapImageWithDeviceMapper is not available over binder."; - return false; -} - -std::vector<std::string> ImageManagerBinder::GetAllBackingImages() { - std::vector<std::string> retval; - auto status = manager_->getAllBackingImages(&retval); - if (!status.isOk()) { - LOG(ERROR) << __PRETTY_FUNCTION__ - << " binder returned: " << status.exceptionMessage().string(); - } - return retval; -} - -bool ImageManagerBinder::ZeroFillNewImage(const std::string& name, uint64_t bytes) { - auto status = manager_->zeroFillNewImage(name, bytes); - if (!status.isOk()) { - LOG(ERROR) << __PRETTY_FUNCTION__ - << " binder returned: " << status.exceptionMessage().string(); - return false; - } - return true; -} - -static android::sp<IGsid> AcquireIGsid(const std::chrono::milliseconds& timeout_ms) { - if (android::base::GetProperty("init.svc.gsid", "") != "running") { - if (!android::base::SetProperty("ctl.start", "gsid") || - !android::base::WaitForProperty("init.svc.gsid", "running", timeout_ms)) { - LOG(ERROR) << "Could not start the gsid service"; - return nullptr; - } - // Sleep for 250ms to give the service time to register. - usleep(250 * 1000); - } - auto sm = android::defaultServiceManager(); - auto name = android::String16(kGsiServiceName); - auto service = sm->checkService(name); - return android::interface_cast<IGsid>(service); -} - -static android::sp<IGsid> GetGsiService(const std::chrono::milliseconds& timeout_ms) { - auto start_time = std::chrono::steady_clock::now(); - - std::chrono::milliseconds elapsed = std::chrono::milliseconds::zero(); - do { - if (auto gsid = AcquireIGsid(timeout_ms - elapsed); gsid != nullptr) { - return gsid; - } - auto now = std::chrono::steady_clock::now(); - elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - start_time); - } while (elapsed <= timeout_ms); - - LOG(ERROR) << "Timed out trying to acquire IGsid interface"; - return nullptr; -} - -std::unique_ptr<IImageManager> IImageManager::Open(const std::string& dir, - const std::chrono::milliseconds& timeout_ms) { - auto gsid = GetGsiService(timeout_ms); - if (!gsid) { - return nullptr; - } - - android::sp<IGsiService> service; - auto status = gsid->getClient(&service); - if (!status.isOk() || !service) { - LOG(ERROR) << "Could not acquire IGsiService"; - return nullptr; - } - - android::sp<IImageService> manager; - status = service->openImageService(dir, &manager); - if (!status.isOk() || !manager) { - LOG(ERROR) << "Could not acquire IImageManager: " << status.exceptionMessage().string(); - return nullptr; - } - return std::make_unique<ImageManagerBinder>(std::move(service), std::move(manager)); -} - -} // namespace fiemap -} // namespace android - -#endif // __ANDROID_RECOVERY__ diff --git a/libfiemap/fiemap_writer.cpp b/libfiemap/fiemap_writer.cpp deleted file mode 100644 index 46fcb0e..0000000 --- a/libfiemap/fiemap_writer.cpp +++ /dev/null @@ -1,745 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <libfiemap/fiemap_writer.h> - -#include <dirent.h> -#include <fcntl.h> -#include <linux/fs.h> -#include <stdio.h> -#include <sys/ioctl.h> -#include <sys/stat.h> -#include <sys/sysmacros.h> -#include <sys/types.h> -#include <sys/vfs.h> -#include <unistd.h> - -#include <limits> -#include <string> -#include <utility> -#include <vector> - -#include <android-base/file.h> -#include <android-base/logging.h> -#include <android-base/stringprintf.h> -#include <android-base/strings.h> -#include <android-base/unique_fd.h> -#include <libdm/dm.h> - -namespace android { -namespace fiemap { - -using namespace android::dm; - -// 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; - -// Large file support must be enabled. -static_assert(sizeof(off_t) == sizeof(uint64_t)); - -static inline void cleanup(const std::string& file_path, bool created) { - if (created) { - unlink(file_path.c_str()); - } -} - -static bool 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 ValidateDmTarget(const DeviceMapper::TargetInfo& target) { - const auto& entry = target.spec; - if (entry.sector_start != 0) { - LOG(INFO) << "Stopping at target with non-zero starting sector"; - return false; - } - - auto target_type = DeviceMapper::GetTargetType(entry); - if (target_type == "bow" || target_type == "default-key" || target_type == "crypt") { - return true; - } - if (target_type == "linear") { - auto pieces = android::base::Split(target.data, " "); - if (pieces[1] != "0") { - LOG(INFO) << "Stopping at complex linear target with non-zero starting sector: " - << pieces[1]; - return false; - } - return true; - } - - LOG(INFO) << "Stopping at complex target type " << target_type; - return false; -} - -static bool DeviceMapperStackPop(const std::string& bdev, std::string* bdev_raw) { - *bdev_raw = bdev; - - if (!::android::base::StartsWith(bdev, "dm-")) { - // We are at the bottom of the device mapper stack. - return true; - } - - // Get the device name. - auto dm_name_file = "/sys/block/" + bdev + "/dm/name"; - std::string dm_name; - if (!android::base::ReadFileToString(dm_name_file, &dm_name)) { - PLOG(ERROR) << "Could not read file: " << dm_name_file; - return false; - } - dm_name = android::base::Trim(dm_name); - - auto& dm = DeviceMapper::Instance(); - std::vector<DeviceMapper::TargetInfo> table; - if (!dm.GetTableInfo(dm_name, &table)) { - LOG(ERROR) << "Could not read device-mapper table for " << dm_name << " at " << bdev; - return false; - } - - // The purpose of libfiemap is to provide an extent-based view into - // a file. This is difficult if devices are not layered in a 1:1 manner; - // we would have to translate and break up extents based on the actual - // block mapping. Since this is too complex, we simply stop processing - // the device-mapper stack if we encounter a complex case. - // - // It is up to the caller to decide whether stopping at a virtual block - // device is allowable. In most cases it is not, because we want either - // "userdata" or an external volume. It is useful for tests however. - // Callers can check by comparing the device number to that of userdata, - // or by checking whether is a device-mapper node. - if (table.size() > 1) { - LOG(INFO) << "Stopping at complex table for " << dm_name << " at " << bdev; - return true; - } - if (!ValidateDmTarget(table[0])) { - return true; - } - - auto dm_leaf_dir = "/sys/block/" + bdev + "/slaves"; - auto d = std::unique_ptr<DIR, decltype(&closedir)>(opendir(dm_leaf_dir.c_str()), closedir); - if (d == nullptr) { - PLOG(ERROR) << "Failed to open: " << dm_leaf_dir; - return false; - } - - struct dirent* de; - uint32_t num_leaves = 0; - std::string bdev_next = ""; - while ((de = readdir(d.get())) != nullptr) { - if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) { - continue; - } - - // We set the first name we find here - if (bdev_next.empty()) { - bdev_next = de->d_name; - } - num_leaves++; - } - - // if we have more than one leaves, we return immediately. We can't continue to create the - // file since we don't know how to write it out using fiemap, so it will be readable via the - // underlying block devices later. The reader will also have to construct the same device mapper - // target in order read the file out. - if (num_leaves > 1) { - LOG(ERROR) << "Found " << num_leaves << " leaf block devices under device mapper device " - << bdev; - return false; - } - - // recursively call with the block device we found in order to pop the device mapper stack. - return DeviceMapperStackPop(bdev_next, bdev_raw); -} - -bool FiemapWriter::GetBlockDeviceForFile(const std::string& file_path, std::string* bdev_path, - bool* uses_dm) { - struct stat sb; - if (stat(file_path.c_str(), &sb)) { - PLOG(ERROR) << "Failed to get stat for: " << file_path; - return false; - } - - std::string bdev; - if (!BlockDeviceToName(major(sb.st_dev), minor(sb.st_dev), &bdev)) { - LOG(ERROR) << "Failed to get block device name for " << major(sb.st_dev) << ":" - << minor(sb.st_dev); - return false; - } - - std::string bdev_raw; - if (!DeviceMapperStackPop(bdev, &bdev_raw)) { - LOG(ERROR) << "Failed to get the bottom of the device mapper stack for device: " << bdev; - return false; - } - - if (uses_dm) { - *uses_dm = (bdev_raw != bdev); - } - - LOG(DEBUG) << "Popped device (" << bdev_raw << ") from device mapper stack starting with (" - << bdev << ")"; - - *bdev_path = ::android::base::StringPrintf("/dev/block/%s", bdev_raw.c_str()); - - // Make sure we are talking to a block device before calling it a success. - if (stat(bdev_path->c_str(), &sb)) { - PLOG(ERROR) << "Failed to get stat for block device: " << *bdev_path; - return false; - } - - if ((sb.st_mode & S_IFMT) != S_IFBLK) { - PLOG(ERROR) << "File: " << *bdev_path << " is not a block device"; - return false; - } - - return true; -} - -static bool GetBlockDeviceSize(int bdev_fd, const std::string& bdev_path, uint64_t* bdev_size) { - uint64_t size_in_bytes = 0; - if (ioctl(bdev_fd, BLKGETSIZE64, &size_in_bytes)) { - PLOG(ERROR) << "Failed to get total size for: " << bdev_path; - return false; - } - - *bdev_size = size_in_bytes; - - return true; -} - -static uint64_t GetFileSize(const std::string& file_path) { - struct stat sb; - if (stat(file_path.c_str(), &sb)) { - PLOG(ERROR) << "Failed to get size for file: " << file_path; - return 0; - } - - return sb.st_size; -} - -static bool PerformFileChecks(const std::string& file_path, uint64_t 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 (!sfs.f_bsize) { - LOG(ERROR) << "Unsupported block size: " << sfs.f_bsize; - return false; - } - - // Check if the filesystem is of supported types. - // Only ext4, f2fs, and vfat are tested and supported. - switch (sfs.f_type) { - case EXT4_SUPER_MAGIC: - case F2FS_SUPER_MAGIC: - case MSDOS_SUPER_MAGIC: - break; - default: - LOG(ERROR) << "Unsupported file system type: 0x" << std::hex << sfs.f_type; - return false; - } - - uint64_t available_bytes = sfs.f_bsize * sfs.f_bavail; - if (access(file_path.c_str(), F_OK) != 0 && 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 FallocateFallback(int file_fd, uint64_t block_size, uint64_t file_size, - const std::string& file_path, - const std::function<bool(uint64_t, uint64_t)>& on_progress) { - // Even though this is much faster than writing zeroes, it is still slow - // enough that we need to fire the progress callback periodically. To - // easily achieve this, we seek in chunks. We use 1000 chunks since - // normally we only fire the callback on 1/1000th increments. - uint64_t bytes_per_chunk = std::max(file_size / 1000, block_size); - - // Seek just to the end of each chunk and write a single byte, causing - // the filesystem to allocate blocks. - off_t cursor = 0; - off_t end = static_cast<off_t>(file_size); - while (cursor < end) { - cursor = std::min(static_cast<off_t>(cursor + bytes_per_chunk), end); - auto rv = TEMP_FAILURE_RETRY(lseek(file_fd, cursor - 1, SEEK_SET)); - if (rv < 0) { - PLOG(ERROR) << "Failed to lseek " << file_path; - return false; - } - if (rv != cursor - 1) { - LOG(ERROR) << "Seek returned wrong offset " << rv << " for file " << file_path; - return false; - } - char buffer[] = {0}; - if (!android::base::WriteFully(file_fd, buffer, 1)) { - PLOG(ERROR) << "Write failed: " << file_path; - return false; - } - if (on_progress && !on_progress(cursor, file_size)) { - return false; - } - } - return true; -} - -static bool AllocateFile(int file_fd, const std::string& file_path, uint64_t blocksz, - uint64_t file_size, unsigned int fs_type, - std::function<bool(uint64_t, uint64_t)> on_progress) { - // 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. - switch (fs_type) { - case EXT4_SUPER_MAGIC: - case F2FS_SUPER_MAGIC: - 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; - } - break; - case MSDOS_SUPER_MAGIC: - // fallocate() is not supported, and not needed, since VFAT does not support holes. - // Instead we can perform a much faster allocation. - return FallocateFallback(file_fd, blocksz, file_size, file_path, on_progress); - default: - LOG(ERROR) << "Missing fallocate() support for file system " << fs_type; - return 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; - } - - int permille = -1; - while (offset < file_size) { - if (!::android::base::WriteFully(file_fd, buffer.get(), blocksz)) { - PLOG(ERROR) << "Failed to write" << blocksz << " bytes at offset" << offset - << " in file " << file_path; - return false; - } - - offset += blocksz; - - // Don't invoke the callback every iteration - wait until a significant - // chunk (here, 1/1000th) of the data has been processed. - int new_permille = (static_cast<uint64_t>(offset) * 1000) / file_size; - if (new_permille != permille && static_cast<uint64_t>(offset) != file_size) { - if (on_progress && !on_progress(offset, file_size)) { - return false; - } - permille = new_permille; - } - } - - 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; - } - - // Send one last progress notification. - if (on_progress && !on_progress(file_size, file_size)) { - return false; - } - return true; -} - -static bool PinFile(int file_fd, const std::string& file_path, uint32_t fs_type) { - if (fs_type != F2FS_SUPER_MAGIC) { - // No pinning necessary for ext4/msdos. 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; -} - -static bool IsFilePinned(int file_fd, const std::string& file_path, uint32_t fs_type) { - if (fs_type != F2FS_SUPER_MAGIC) { - // No pinning necessary for ext4 or vfat. The blocks, once allocated, - // are expected to be fixed. - return true; - } - -// F2FS-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: export FS_NOCOW_FL flag to user - uint32_t flags; - int error = ioctl(file_fd, FS_IOC_GETFLAGS, &flags); - if (error < 0) { - if ((errno == ENOTTY) || (errno == ENOTSUP)) { - PLOG(ERROR) << "Failed to get flags, not supported by kernel: " << file_path; - } else { - PLOG(ERROR) << "Failed to get flags: " << file_path; - } - return false; - } - if (!(flags & FS_NOCOW_FL)) { - LOG(ERROR) << "It is not pinned: " << file_path; - return false; - } - - // F2FS_IOC_GET_PIN_FILE returns the number of blocks moved. - uint32_t moved_blocks_nr; - error = ioctl(file_fd, F2FS_IOC_GET_PIN_FILE, &moved_blocks_nr); - if (error < 0) { - if ((errno == ENOTTY) || (errno == ENOTSUP)) { - PLOG(ERROR) << "Failed to get file pin status, not supported by kernel: " << file_path; - } else { - PLOG(ERROR) << "Failed to get file pin status: " << file_path; - } - return false; - } - - if (moved_blocks_nr) { - LOG(ERROR) << moved_blocks_nr << " blocks moved in file " << file_path; - } - return moved_blocks_nr == 0; -} - -bool FiemapWriter::HasPinnedExtents(const std::string& file_path) { - android::base::unique_fd fd(open(file_path.c_str(), O_NOFOLLOW | O_CLOEXEC | O_RDONLY)); - if (fd < 0) { - PLOG(ERROR) << "open: " << file_path; - return false; - } - - struct statfs64 sfs; - if (fstatfs64(fd, &sfs)) { - PLOG(ERROR) << "fstatfs64: " << file_path; - return false; - } - return IsFilePinned(fd, file_path, sfs.f_type); -} - -static bool 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; -} - -static bool ReadFibmap(int file_fd, const std::string& file_path, - std::vector<struct fiemap_extent>* extents) { - struct stat s; - if (fstat(file_fd, &s)) { - PLOG(ERROR) << "Failed to stat " << file_path; - return false; - } - - unsigned int blksize; - if (ioctl(file_fd, FIGETBSZ, &blksize) < 0) { - PLOG(ERROR) << "Failed to get FIGETBSZ for " << file_path; - return false; - } - if (!blksize) { - LOG(ERROR) << "Invalid filesystem block size: " << blksize; - return false; - } - - uint64_t num_blocks = (s.st_size + blksize - 1) / blksize; - if (num_blocks > std::numeric_limits<uint32_t>::max()) { - LOG(ERROR) << "Too many blocks for FIBMAP (" << num_blocks << ")"; - return false; - } - - for (uint32_t last_block, block_number = 0; block_number < num_blocks; block_number++) { - uint32_t block = block_number; - if (ioctl(file_fd, FIBMAP, &block)) { - PLOG(ERROR) << "Failed to get FIBMAP for file " << file_path; - return false; - } - if (!block) { - LOG(ERROR) << "Logical block " << block_number << " is a hole, which is not supported"; - return false; - } - - if (!extents->empty() && block == last_block + 1) { - extents->back().fe_length += blksize; - } else { - extents->push_back(fiemap_extent{.fe_logical = block_number, - .fe_physical = static_cast<uint64_t>(block) * blksize, - .fe_length = static_cast<uint64_t>(blksize), - .fe_flags = 0}); - } - last_block = block; - } - return true; -} - -FiemapUniquePtr FiemapWriter::Open(const std::string& file_path, uint64_t file_size, bool create, - std::function<bool(uint64_t, uint64_t)> progress) { - // 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 (!GetBlockDeviceForFile(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_RDONLY | 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; - } - - // Align up to the nearest block size. - if (file_size % blocksz) { - file_size += blocksz - (file_size % blocksz); - } - - if (create) { - if (!AllocateFile(file_fd, abs_path, blocksz, file_size, fs_type, std::move(progress))) { - LOG(ERROR) << "Failed to allocate file: " << abs_path << " of size: " << file_size - << " bytes"; - cleanup(abs_path, create); - return nullptr; - } - } - - // f2fs may move the file blocks around. - if (!PinFile(file_fd, abs_path, fs_type)) { - cleanup(abs_path, create); - LOG(ERROR) << "Failed to pin the file in storage"; - return nullptr; - } - - // now allocate the FiemapWriter and start setting it up - FiemapUniquePtr fmap(new FiemapWriter()); - switch (fs_type) { - case EXT4_SUPER_MAGIC: - case F2FS_SUPER_MAGIC: - if (!ReadFiemap(file_fd, abs_path, &fmap->extents_)) { - LOG(ERROR) << "Failed to read fiemap of file: " << abs_path; - cleanup(abs_path, create); - return nullptr; - } - break; - case MSDOS_SUPER_MAGIC: - if (!ReadFibmap(file_fd, abs_path, &fmap->extents_)) { - LOG(ERROR) << "Failed to read fibmap of file: " << abs_path; - cleanup(abs_path, create); - return nullptr; - } - break; - } - - fmap->file_path_ = abs_path; - fmap->bdev_path_ = bdev_path; - fmap->file_size_ = file_size; - fmap->bdev_size_ = bdevsz; - fmap->fs_type_ = fs_type; - fmap->block_size_ = blocksz; - - LOG(VERBOSE) << "Successfully created FiemapWriter for file " << abs_path << " on block device " - << bdev_path; - return fmap; -} - -} // namespace fiemap -} // namespace android diff --git a/libfiemap/fiemap_writer_test.cpp b/libfiemap/fiemap_writer_test.cpp deleted file mode 100644 index 4ac7161..0000000 --- a/libfiemap/fiemap_writer_test.cpp +++ /dev/null @@ -1,541 +0,0 @@ -/* - * 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 <sys/vfs.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/fiemap_writer.h> -#include <libfiemap/split_fiemap_writer.h> - -#include "utility.h" - -namespace android { -namespace fiemap { - -using namespace std; -using namespace std::string_literals; -using namespace android::fiemap; -using unique_fd = android::base::unique_fd; -using LoopDevice = android::dm::LoopDevice; - -std::string gTestDir; -uint64_t testfile_size = 536870912; // default of 512MiB -size_t gBlockSize = 0; - -class FiemapWriterTest : public ::testing::Test { - protected: - void SetUp() override { - const ::testing::TestInfo* tinfo = ::testing::UnitTest::GetInstance()->current_test_info(); - testfile = gTestDir + "/"s + tinfo->name(); - } - - void TearDown() override { unlink(testfile.c_str()); } - - // name of the file we use for testing - std::string testfile; -}; - -class SplitFiemapTest : public ::testing::Test { - protected: - void SetUp() override { - const ::testing::TestInfo* tinfo = ::testing::UnitTest::GetInstance()->current_test_info(); - testfile = gTestDir + "/"s + tinfo->name(); - } - - void TearDown() override { - std::string message; - if (!SplitFiemap::RemoveSplitFiles(testfile, &message)) { - cerr << "Could not remove all split files: " << message; - } - } - - // 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. - FiemapUniquePtr fptr = FiemapWriter::Open(testfile, gBlockSize + 1); - ASSERT_NE(fptr, nullptr); - ASSERT_EQ(fptr->size(), gBlockSize * 2); -} - -TEST_F(FiemapWriterTest, CheckFilePath) { - FiemapUniquePtr fptr = FiemapWriter::Open(testfile, gBlockSize); - ASSERT_NE(fptr, nullptr); - EXPECT_EQ(fptr->size(), gBlockSize); - EXPECT_EQ(fptr->file_path(), testfile); - EXPECT_EQ(access(testfile.c_str(), F_OK), 0); -} - -TEST_F(FiemapWriterTest, CheckFileSize) { - // Create a large-ish file and test that the expected size matches. - FiemapUniquePtr fptr = FiemapWriter::Open(testfile, 1024 * 1024 * 16); - ASSERT_NE(fptr, nullptr); - - struct stat s; - ASSERT_EQ(stat(testfile.c_str(), &s), 0); - EXPECT_EQ(static_cast<uint64_t>(s.st_size), fptr->size()); -} - -TEST_F(FiemapWriterTest, CheckProgress) { - std::vector<uint64_t> expected; - size_t invocations = 0; - auto callback = [&](uint64_t done, uint64_t total) -> bool { - if (invocations >= expected.size()) { - return false; - } - EXPECT_EQ(done, expected[invocations]); - EXPECT_EQ(total, gBlockSize); - invocations++; - return true; - }; - - expected.push_back(gBlockSize); - - auto ptr = FiemapWriter::Open(testfile, gBlockSize, true, std::move(callback)); - EXPECT_NE(ptr, nullptr); - EXPECT_EQ(invocations, expected.size()); -} - -TEST_F(FiemapWriterTest, CheckPinning) { - auto ptr = FiemapWriter::Open(testfile, 4096); - ASSERT_NE(ptr, nullptr); - EXPECT_TRUE(FiemapWriter::HasPinnedExtents(testfile)); -} - -TEST_F(FiemapWriterTest, CheckBlockDevicePath) { - FiemapUniquePtr fptr = FiemapWriter::Open(testfile, gBlockSize); - EXPECT_EQ(fptr->size(), gBlockSize); - EXPECT_EQ(fptr->bdev_path().find("/dev/block/"), size_t(0)); - EXPECT_EQ(fptr->bdev_path().find("/dev/block/dm-"), string::npos); -} - -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_GE(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, ExistingFile) { - // Create the file. - { ASSERT_NE(FiemapWriter::Open(testfile, gBlockSize), nullptr); } - // Test that we can still open it. - { - auto ptr = FiemapWriter::Open(testfile, 0, false); - ASSERT_NE(ptr, nullptr); - EXPECT_GT(ptr->extents().size(), 0); - } -} - -TEST_F(FiemapWriterTest, FileDeletedOnError) { - auto callback = [](uint64_t, uint64_t) -> bool { return false; }; - auto ptr = FiemapWriter::Open(testfile, gBlockSize, true, std::move(callback)); - EXPECT_EQ(ptr, nullptr); - EXPECT_EQ(access(testfile.c_str(), F_OK), -1); - EXPECT_EQ(errno, ENOENT); -} - -TEST_F(FiemapWriterTest, MaxBlockSize) { - ASSERT_GT(DetermineMaximumFileSize(testfile), 0); -} - -TEST_F(FiemapWriterTest, FibmapBlockAddressing) { - FiemapUniquePtr fptr = FiemapWriter::Open(testfile, gBlockSize); - ASSERT_NE(fptr, nullptr); - - switch (fptr->fs_type()) { - case F2FS_SUPER_MAGIC: - case EXT4_SUPER_MAGIC: - // Skip the test for FIEMAP supported filesystems. This is really - // because f2fs/ext4 have caches that seem to defeat reading back - // directly from the block device, and writing directly is too - // dangerous. - std::cout << "Skipping test, filesystem does not use FIBMAP\n"; - return; - } - - bool uses_dm; - std::string bdev_path; - ASSERT_TRUE(FiemapWriter::GetBlockDeviceForFile(testfile, &bdev_path, &uses_dm)); - - if (uses_dm) { - // We could use a device-mapper wrapper here to bypass encryption, but - // really this test is for FIBMAP correctness on VFAT (where encryption - // is never used), so we don't bother. - std::cout << "Skipping test, block device is metadata encrypted\n"; - return; - } - - std::string data(fptr->size(), '\0'); - for (size_t i = 0; i < data.size(); i++) { - data[i] = 'A' + static_cast<char>(data.size() % 26); - } - - { - unique_fd fd(open(testfile.c_str(), O_WRONLY | O_CLOEXEC)); - ASSERT_GE(fd, 0); - ASSERT_TRUE(android::base::WriteFully(fd, data.data(), data.size())); - ASSERT_EQ(fsync(fd), 0); - } - - ASSERT_FALSE(fptr->extents().empty()); - const auto& first_extent = fptr->extents()[0]; - - unique_fd bdev(open(fptr->bdev_path().c_str(), O_RDONLY | O_CLOEXEC)); - ASSERT_GE(bdev, 0); - - off_t where = first_extent.fe_physical; - ASSERT_EQ(lseek(bdev, where, SEEK_SET), where); - - // Note: this will fail on encrypted folders. - std::string actual(data.size(), '\0'); - ASSERT_GE(first_extent.fe_length, data.size()); - ASSERT_TRUE(android::base::ReadFully(bdev, actual.data(), actual.size())); - EXPECT_EQ(memcmp(actual.data(), data.data(), data.size()), 0); -} - -TEST_F(SplitFiemapTest, Create) { - auto ptr = SplitFiemap::Create(testfile, 1024 * 768, 1024 * 32); - ASSERT_NE(ptr, nullptr); - - auto extents = ptr->extents(); - - // Destroy the fiemap, closing file handles. This should not delete them. - ptr = nullptr; - - std::vector<std::string> files; - ASSERT_TRUE(SplitFiemap::GetSplitFileList(testfile, &files)); - for (const auto& path : files) { - EXPECT_EQ(access(path.c_str(), F_OK), 0); - } - - ASSERT_GE(extents.size(), files.size()); -} - -TEST_F(SplitFiemapTest, Open) { - { - auto ptr = SplitFiemap::Create(testfile, 1024 * 768, 1024 * 32); - ASSERT_NE(ptr, nullptr); - } - - auto ptr = SplitFiemap::Open(testfile); - ASSERT_NE(ptr, nullptr); - - auto extents = ptr->extents(); - ASSERT_GE(extents.size(), 24); -} - -TEST_F(SplitFiemapTest, DeleteOnFail) { - auto ptr = SplitFiemap::Create(testfile, 1024 * 1024 * 100, 1); - ASSERT_EQ(ptr, nullptr); - - std::string first_file = testfile + ".0001"; - ASSERT_NE(access(first_file.c_str(), F_OK), 0); - ASSERT_EQ(errno, ENOENT); - ASSERT_NE(access(testfile.c_str(), F_OK), 0); - ASSERT_EQ(errno, ENOENT); -} - -static string ReadSplitFiles(const std::string& base_path, size_t num_files) { - std::string result; - for (int i = 0; i < num_files; i++) { - std::string path = base_path + android::base::StringPrintf(".%04d", i); - std::string data; - if (!android::base::ReadFileToString(path, &data)) { - return {}; - } - result += data; - } - return result; -} - -TEST_F(SplitFiemapTest, WriteWholeFile) { - static constexpr size_t kChunkSize = 32768; - static constexpr size_t kSize = kChunkSize * 3; - auto ptr = SplitFiemap::Create(testfile, kSize, kChunkSize); - ASSERT_NE(ptr, nullptr); - - auto buffer = std::make_unique<int[]>(kSize / sizeof(int)); - for (size_t i = 0; i < kSize / sizeof(int); i++) { - buffer[i] = i; - } - ASSERT_TRUE(ptr->Write(buffer.get(), kSize)); - - std::string expected(reinterpret_cast<char*>(buffer.get()), kSize); - auto actual = ReadSplitFiles(testfile, 3); - ASSERT_EQ(expected.size(), actual.size()); - EXPECT_EQ(memcmp(expected.data(), actual.data(), actual.size()), 0); -} - -TEST_F(SplitFiemapTest, WriteFileInChunks1) { - static constexpr size_t kChunkSize = 32768; - static constexpr size_t kSize = kChunkSize * 3; - auto ptr = SplitFiemap::Create(testfile, kSize, kChunkSize); - ASSERT_NE(ptr, nullptr); - - auto buffer = std::make_unique<int[]>(kSize / sizeof(int)); - for (size_t i = 0; i < kSize / sizeof(int); i++) { - buffer[i] = i; - } - - // Write in chunks of 1000 (so some writes straddle the boundary of two - // files). - size_t bytes_written = 0; - while (bytes_written < kSize) { - size_t to_write = std::min(kSize - bytes_written, (size_t)1000); - char* data = reinterpret_cast<char*>(buffer.get()) + bytes_written; - ASSERT_TRUE(ptr->Write(data, to_write)); - bytes_written += to_write; - } - - std::string expected(reinterpret_cast<char*>(buffer.get()), kSize); - auto actual = ReadSplitFiles(testfile, 3); - ASSERT_EQ(expected.size(), actual.size()); - EXPECT_EQ(memcmp(expected.data(), actual.data(), actual.size()), 0); -} - -TEST_F(SplitFiemapTest, WriteFileInChunks2) { - static constexpr size_t kChunkSize = 32768; - static constexpr size_t kSize = kChunkSize * 3; - auto ptr = SplitFiemap::Create(testfile, kSize, kChunkSize); - ASSERT_NE(ptr, nullptr); - - auto buffer = std::make_unique<int[]>(kSize / sizeof(int)); - for (size_t i = 0; i < kSize / sizeof(int); i++) { - buffer[i] = i; - } - - // Write in chunks of 32KiB so every write is exactly at the end of the - // current file. - size_t bytes_written = 0; - while (bytes_written < kSize) { - size_t to_write = std::min(kSize - bytes_written, kChunkSize); - char* data = reinterpret_cast<char*>(buffer.get()) + bytes_written; - ASSERT_TRUE(ptr->Write(data, to_write)); - bytes_written += to_write; - } - - std::string expected(reinterpret_cast<char*>(buffer.get()), kSize); - auto actual = ReadSplitFiles(testfile, 3); - ASSERT_EQ(expected.size(), actual.size()); - EXPECT_EQ(memcmp(expected.data(), actual.data(), actual.size()), 0); -} - -TEST_F(SplitFiemapTest, WritePastEnd) { - static constexpr size_t kChunkSize = 32768; - static constexpr size_t kSize = kChunkSize * 3; - auto ptr = SplitFiemap::Create(testfile, kSize, kChunkSize); - ASSERT_NE(ptr, nullptr); - - auto buffer = std::make_unique<int[]>(kSize / sizeof(int)); - for (size_t i = 0; i < kSize / sizeof(int); i++) { - buffer[i] = i; - } - ASSERT_TRUE(ptr->Write(buffer.get(), kSize)); - ASSERT_FALSE(ptr->Write(buffer.get(), kSize)); -} - -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, 10s); - 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; -}; - -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, 10s); - 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; -}; - -bool DetermineBlockSize() { - struct statfs s; - if (statfs(gTestDir.c_str(), &s)) { - std::cerr << "Could not call statfs: " << strerror(errno) << "\n"; - return false; - } - if (!s.f_bsize) { - std::cerr << "Invalid block size: " << s.f_bsize << "\n"; - return false; - } - - gBlockSize = s.f_bsize; - return true; -} - -} // namespace fiemap -} // namespace android - -using namespace android::fiemap; - -int main(int argc, char** argv) { - ::testing::InitGoogleTest(&argc, argv); - if (argc > 1 && argv[1] == "-h"s) { - cerr << "Usage: [test_dir] [file_size]\n"; - cerr << "\n"; - cerr << "Note: test_dir must be a writable, unencrypted directory.\n"; - exit(EXIT_FAILURE); - } - ::android::base::InitLogging(argv, ::android::base::StderrLogger); - - std::string root_dir = "/data/local/unencrypted"; - if (access(root_dir.c_str(), F_OK)) { - root_dir = "/data"; - } - - std::string tempdir = root_dir + "/XXXXXX"s; - if (!mkdtemp(tempdir.data())) { - cerr << "unable to create tempdir on " << root_dir << "\n"; - exit(EXIT_FAILURE); - } - if (!android::base::Realpath(tempdir, &gTestDir)) { - cerr << "unable to find realpath for " << tempdir; - exit(EXIT_FAILURE); - } - - if (argc > 2) { - testfile_size = strtoull(argv[2], NULL, 0); - if (testfile_size == ULLONG_MAX) { - testfile_size = 512 * 1024 * 1024; - } - } - - if (!DetermineBlockSize()) { - exit(EXIT_FAILURE); - } - - auto result = RUN_ALL_TESTS(); - - std::string cmd = "rm -rf " + gTestDir; - system(cmd.c_str()); - - return result; -} diff --git a/libfiemap/image_manager.cpp b/libfiemap/image_manager.cpp deleted file mode 100644 index 3e32dcf..0000000 --- a/libfiemap/image_manager.cpp +++ /dev/null @@ -1,649 +0,0 @@ -// -// 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 <libfiemap/image_manager.h> - -#include <android-base/file.h> -#include <android-base/logging.h> -#include <android-base/properties.h> -#include <android-base/strings.h> -#include <android-base/unique_fd.h> -#include <ext4_utils/ext4_utils.h> -#include <fs_mgr/file_wait.h> -#include <fs_mgr_dm_linear.h> -#include <libdm/loop_control.h> -#include <libfiemap/split_fiemap_writer.h> - -#include "metadata.h" -#include "utility.h" - -namespace android { -namespace fiemap { - -using namespace std::literals; -using android::base::unique_fd; -using android::dm::DeviceMapper; -using android::dm::DmDeviceState; -using android::dm::DmTable; -using android::dm::DmTargetLinear; -using android::dm::LoopControl; -using android::fs_mgr::CreateLogicalPartition; -using android::fs_mgr::CreateLogicalPartitionParams; -using android::fs_mgr::DestroyLogicalPartition; -using android::fs_mgr::GetPartitionName; - -static constexpr char kTestImageMetadataDir[] = "/metadata/gsi/test"; - -std::unique_ptr<ImageManager> ImageManager::Open(const std::string& dir_prefix) { - auto metadata_dir = "/metadata/gsi/" + dir_prefix; - auto data_dir = "/data/gsi/" + dir_prefix; - return Open(metadata_dir, data_dir); -} - -std::unique_ptr<ImageManager> ImageManager::Open(const std::string& metadata_dir, - const std::string& data_dir) { - return std::unique_ptr<ImageManager>(new ImageManager(metadata_dir, data_dir)); -} - -ImageManager::ImageManager(const std::string& metadata_dir, const std::string& data_dir) - : metadata_dir_(metadata_dir), data_dir_(data_dir) { - partition_opener_ = std::make_unique<android::fs_mgr::PartitionOpener>(); -} - -std::string ImageManager::GetImageHeaderPath(const std::string& name) { - return JoinPaths(data_dir_, name) + ".img"; -} - -// The status file has one entry per line, with each entry formatted as one of: -// dm:<name> -// loop:<path> -// -// This simplifies the process of tearing down a mapping, since we can simply -// unmap each entry in the order it appears. -std::string ImageManager::GetStatusFilePath(const std::string& image_name) { - return JoinPaths(metadata_dir_, image_name) + ".status"; -} - -static std::string GetStatusPropertyName(const std::string& image_name) { - // Note: we don't prefix |image_name|, because CreateLogicalPartition won't - // prefix the name either. There are no plans to change this at the moment, - // consumers of the image API must take care to use globally-unique image - // names. - return "gsid.mapped_image." + image_name; -} - -void ImageManager::set_partition_opener(std::unique_ptr<IPartitionOpener>&& opener) { - partition_opener_ = std::move(opener); -} - -bool ImageManager::IsImageMapped(const std::string& image_name) { - auto prop_name = GetStatusPropertyName(image_name); - if (android::base::GetProperty(prop_name, "").empty()) { - // If mapped in first-stage init, the dm-device will exist but not the - // property. - auto& dm = DeviceMapper::Instance(); - return dm.GetState(image_name) != DmDeviceState::INVALID; - } - return true; -} - -std::vector<std::string> ImageManager::GetAllBackingImages() { - std::vector<std::string> images; - auto metadata = OpenMetadata(metadata_dir_); - if (metadata) { - for (auto&& partition : metadata->partitions) { - images.push_back(partition.name); - } - } - return images; -} - -bool ImageManager::PartitionExists(const std::string& name) { - if (!MetadataExists(metadata_dir_)) { - return false; - } - auto metadata = OpenMetadata(metadata_dir_); - if (!metadata) { - return false; - } - return !!FindPartition(*metadata.get(), name); -} - -bool ImageManager::BackingImageExists(const std::string& name) { - auto header_file = GetImageHeaderPath(name); - return access(header_file.c_str(), F_OK) == 0; -} - -bool ImageManager::CreateBackingImage(const std::string& name, uint64_t size, int flags) { - return CreateBackingImage(name, size, flags, nullptr); -} - -bool ImageManager::CreateBackingImage(const std::string& name, uint64_t size, int flags, - std::function<bool(uint64_t, uint64_t)>&& on_progress) { - auto data_path = GetImageHeaderPath(name); - auto fw = SplitFiemap::Create(data_path, size, 0, on_progress); - if (!fw) { - return false; - } - - // Except for testing, we do not allow persisting metadata that references - // device-mapper devices. It just doesn't make sense, because the device - // numbering may change on reboot. We allow it for testing since the images - // are not meant to survive reboot. Outside of tests, this can only happen - // if device-mapper is stacked in some complex way not supported by - // FiemapWriter. - auto device_path = GetDevicePathForFile(fw.get()); - if (android::base::StartsWith(device_path, "/dev/block/dm-") && - !android::base::StartsWith(metadata_dir_, kTestImageMetadataDir)) { - LOG(ERROR) << "Cannot persist images against device-mapper device: " << device_path; - - fw = {}; - SplitFiemap::RemoveSplitFiles(data_path); - return false; - } - - bool readonly = !!(flags & CREATE_IMAGE_READONLY); - if (!UpdateMetadata(metadata_dir_, name, fw.get(), size, readonly)) { - return false; - } - - if (flags & CREATE_IMAGE_ZERO_FILL) { - if (!ZeroFillNewImage(name, 0)) { - DeleteBackingImage(name); - return false; - } - } - return true; -} - -bool ImageManager::ZeroFillNewImage(const std::string& name, uint64_t bytes) { - auto data_path = GetImageHeaderPath(name); - - // See the comment in MapImageDevice() about how this works. - std::string block_device; - bool can_use_devicemapper; - if (!FiemapWriter::GetBlockDeviceForFile(data_path, &block_device, &can_use_devicemapper)) { - LOG(ERROR) << "Could not determine block device for " << data_path; - return false; - } - - if (!can_use_devicemapper) { - // We've backed with loop devices, and since we store files in an - // unencrypted folder, the initial zeroes we wrote will suffice. - return true; - } - - // data is dm-crypt, or FBE + dm-default-key. This means the zeroes written - // by libfiemap were encrypted, so we need to map the image in and correct - // this. - auto device = MappedDevice::Open(this, 10s, name); - if (!device) { - return false; - } - - static constexpr size_t kChunkSize = 4096; - std::string zeroes(kChunkSize, '\0'); - - uint64_t remaining; - if (bytes) { - remaining = bytes; - } else { - remaining = get_block_device_size(device->fd()); - if (!remaining) { - PLOG(ERROR) << "Could not get block device size for " << device->path(); - return false; - } - } - while (remaining) { - uint64_t to_write = std::min(static_cast<uint64_t>(zeroes.size()), remaining); - if (!android::base::WriteFully(device->fd(), zeroes.data(), - static_cast<size_t>(to_write))) { - PLOG(ERROR) << "write failed: " << device->path(); - return false; - } - remaining -= to_write; - } - return true; -} - -bool ImageManager::DeleteBackingImage(const std::string& name) { - // For dm-linear devices sitting on top of /data, we cannot risk deleting - // the file. The underlying blocks could be reallocated by the filesystem. - if (IsImageMapped(name)) { - LOG(ERROR) << "Backing image " << name << " is currently mapped to a block device"; - return false; - } - - std::string message; - auto header_file = GetImageHeaderPath(name); - if (!SplitFiemap::RemoveSplitFiles(header_file, &message)) { - // This is fatal, because we don't want to leave these files dangling. - LOG(ERROR) << "Error removing image " << name << ": " << message; - return false; - } - - auto status_file = GetStatusFilePath(name); - if (!android::base::RemoveFileIfExists(status_file)) { - LOG(ERROR) << "Error removing " << status_file << ": " << message; - } - return RemoveImageMetadata(metadata_dir_, name); -} - -// Create a block device for an image file, using its extents in its -// lp_metadata. -bool ImageManager::MapWithDmLinear(const IPartitionOpener& opener, const std::string& name, - const std::chrono::milliseconds& timeout_ms, std::string* path) { - // :TODO: refresh extents in metadata file until f2fs is fixed. - auto metadata = OpenMetadata(metadata_dir_); - if (!metadata) { - return false; - } - - auto super = android::fs_mgr::GetMetadataSuperBlockDevice(*metadata.get()); - auto block_device = android::fs_mgr::GetBlockDevicePartitionName(*super); - - CreateLogicalPartitionParams params = { - .block_device = block_device, - .metadata = metadata.get(), - .partition_name = name, - .force_writable = true, - .timeout_ms = timeout_ms, - .partition_opener = &opener, - }; - if (!CreateLogicalPartition(params, path)) { - LOG(ERROR) << "Error creating device-mapper node for image " << name; - return false; - } - - auto status_string = "dm:" + name; - auto status_file = GetStatusFilePath(name); - if (!android::base::WriteStringToFile(status_string, status_file)) { - PLOG(ERROR) << "Could not write status file: " << status_file; - DestroyLogicalPartition(name); - return false; - } - return true; -} - -// Helper to create a loop device for a file. -static bool CreateLoopDevice(LoopControl& control, const std::string& file, - const std::chrono::milliseconds& timeout_ms, std::string* path) { - static constexpr int kOpenFlags = O_RDWR | O_NOFOLLOW | O_CLOEXEC; - android::base::unique_fd file_fd(open(file.c_str(), kOpenFlags)); - if (file_fd < 0) { - PLOG(ERROR) << "Could not open file: " << file; - return false; - } - if (!control.Attach(file_fd, timeout_ms, path)) { - LOG(ERROR) << "Could not create loop device for: " << file; - return false; - } - LOG(INFO) << "Created loop device " << *path << " for file " << file; - return true; -} - -class AutoDetachLoopDevices final { - public: - AutoDetachLoopDevices(LoopControl& control, const std::vector<std::string>& devices) - : control_(control), devices_(devices), commit_(false) {} - - ~AutoDetachLoopDevices() { - if (commit_) return; - for (const auto& device : devices_) { - control_.Detach(device); - } - } - - void Commit() { commit_ = true; } - - private: - LoopControl& control_; - const std::vector<std::string>& devices_; - bool commit_; -}; - -// If an image is stored across multiple files, this takes a list of loop -// devices and joins them together using device-mapper. -bool ImageManager::MapWithLoopDeviceList(const std::vector<std::string>& device_list, - const std::string& name, - const std::chrono::milliseconds& timeout_ms, - std::string* path) { - auto metadata = OpenMetadata(metadata_dir_); - if (!metadata) { - return false; - } - auto partition = FindPartition(*metadata.get(), name); - if (!partition) { - LOG(ERROR) << "Could not find image in metadata: " << name; - return false; - } - - // Since extent lengths are in sector units, the size should be a multiple - // of the sector size. - uint64_t partition_size = GetPartitionSize(*metadata.get(), *partition); - if (partition_size % LP_SECTOR_SIZE != 0) { - LOG(ERROR) << "Partition size not sector aligned: " << name << ", " << partition_size - << " bytes"; - return false; - } - - DmTable table; - - uint64_t start_sector = 0; - uint64_t sectors_needed = partition_size / LP_SECTOR_SIZE; - for (const auto& block_device : device_list) { - // The final block device must be == partition_size, otherwise we - // can't find the AVB footer on verified partitions. - static constexpr int kOpenFlags = O_RDWR | O_NOFOLLOW | O_CLOEXEC; - unique_fd fd(open(block_device.c_str(), kOpenFlags)); - if (fd < 0) { - PLOG(ERROR) << "Open failed: " << block_device; - return false; - } - - uint64_t file_size = get_block_device_size(fd); - uint64_t file_sectors = file_size / LP_SECTOR_SIZE; - uint64_t segment_size = std::min(file_sectors, sectors_needed); - - table.Emplace<DmTargetLinear>(start_sector, segment_size, block_device, 0); - - start_sector += segment_size; - sectors_needed -= segment_size; - if (sectors_needed == 0) { - break; - } - } - - auto& dm = DeviceMapper::Instance(); - if (!dm.CreateDevice(name, table, path, timeout_ms)) { - LOG(ERROR) << "Could not create device-mapper device over loop set"; - return false; - } - - // Build the status file. - std::vector<std::string> lines; - lines.emplace_back("dm:" + name); - for (const auto& block_device : device_list) { - lines.emplace_back("loop:" + block_device); - } - auto status_message = android::base::Join(lines, "\n"); - auto status_file = GetStatusFilePath(name); - if (!android::base::WriteStringToFile(status_message, status_file)) { - PLOG(ERROR) << "Write failed: " << status_file; - dm.DeleteDevice(name); - return false; - } - return true; -} - -static bool OptimizeLoopDevices(const std::vector<std::string>& device_list) { - for (const auto& device : device_list) { - unique_fd fd(open(device.c_str(), O_RDWR | O_CLOEXEC | O_NOFOLLOW)); - if (fd < 0) { - PLOG(ERROR) << "Open failed: " << device; - return false; - } - if (!LoopControl::EnableDirectIo(fd)) { - return false; - } - } - return true; -} - -// Helper to use one or more loop devices around image files. -bool ImageManager::MapWithLoopDevice(const std::string& name, - const std::chrono::milliseconds& timeout_ms, - std::string* path) { - auto image_header = GetImageHeaderPath(name); - - std::vector<std::string> file_list; - if (!SplitFiemap::GetSplitFileList(image_header, &file_list)) { - LOG(ERROR) << "Could not get image file list"; - return false; - } - - // Map each image file as a loopback device. - LoopControl control; - std::vector<std::string> loop_devices; - AutoDetachLoopDevices auto_detach(control, loop_devices); - - auto start_time = std::chrono::steady_clock::now(); - for (const auto& file : file_list) { - auto now = std::chrono::steady_clock::now(); - auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - start_time); - - std::string loop_device; - if (!CreateLoopDevice(control, file, timeout_ms - elapsed, &loop_device)) { - break; - } - loop_devices.emplace_back(loop_device); - } - if (loop_devices.size() != file_list.size()) { - // The number of devices will mismatch if CreateLoopDevice() failed. - return false; - } - - // If OptimizeLoopDevices fails, we'd use double the memory. - if (!OptimizeLoopDevices(loop_devices)) { - return false; - } - - // If there's only one loop device (by far the most common case, splits - // will normally only happen on sdcards with FAT32), then just return that - // as the block device. Otherwise, we need to use dm-linear to stitch - // together all the loop devices we just created. - if (loop_devices.size() > 1) { - if (!MapWithLoopDeviceList(loop_devices, name, timeout_ms, path)) { - return false; - } - } - - auto status_message = "loop:" + loop_devices.back(); - auto status_file = GetStatusFilePath(name); - if (!android::base::WriteStringToFile(status_message, status_file)) { - PLOG(ERROR) << "Write failed: " << status_file; - return false; - } - - auto_detach.Commit(); - - *path = loop_devices.back(); - return true; -} - -bool ImageManager::MapImageDevice(const std::string& name, - const std::chrono::milliseconds& timeout_ms, std::string* path) { - if (IsImageMapped(name)) { - LOG(ERROR) << "Backing image " << name << " is already mapped"; - return false; - } - - auto image_header = GetImageHeaderPath(name); - - // If there is a device-mapper node wrapping the block device, then we're - // able to create another node around it; the dm layer does not carry the - // exclusion lock down the stack when a mount occurs. - // - // If there is no intermediate device-mapper node, then partitions cannot be - // opened writable due to sepolicy and exclusivity of having a mounted - // filesystem. This should only happen on devices with no encryption, or - // devices with FBE and no metadata encryption. For these cases it suffices - // to perform normal file writes to /data/gsi (which is unencrypted). - std::string block_device; - bool can_use_devicemapper; - if (!FiemapWriter::GetBlockDeviceForFile(image_header, &block_device, &can_use_devicemapper)) { - LOG(ERROR) << "Could not determine block device for " << image_header; - return false; - } - - if (can_use_devicemapper) { - if (!MapWithDmLinear(*partition_opener_.get(), name, timeout_ms, path)) { - return false; - } - } else if (!MapWithLoopDevice(name, timeout_ms, path)) { - return false; - } - - // Set a property so we remember this is mapped. - auto prop_name = GetStatusPropertyName(name); - if (!android::base::SetProperty(prop_name, *path)) { - UnmapImageDevice(name, true); - return false; - } - return true; -} - -bool ImageManager::MapImageWithDeviceMapper(const IPartitionOpener& opener, const std::string& name, - std::string* dev) { - std::string ignore_path; - if (!MapWithDmLinear(opener, name, {}, &ignore_path)) { - return false; - } - - auto& dm = DeviceMapper::Instance(); - if (!dm.GetDeviceString(name, dev)) { - return false; - } - return true; -} - -bool ImageManager::UnmapImageDevice(const std::string& name) { - return UnmapImageDevice(name, false); -} - -bool ImageManager::UnmapImageDevice(const std::string& name, bool force) { - if (!force && !IsImageMapped(name)) { - LOG(ERROR) << "Backing image " << name << " is not mapped"; - return false; - } - auto& dm = DeviceMapper::Instance(); - LoopControl loop; - - std::string status; - auto status_file = GetStatusFilePath(name); - if (!android::base::ReadFileToString(status_file, &status)) { - PLOG(ERROR) << "Read failed: " << status_file; - return false; - } - - auto lines = android::base::Split(status, "\n"); - for (const auto& line : lines) { - auto pieces = android::base::Split(line, ":"); - if (pieces.size() != 2) { - LOG(ERROR) << "Unknown status line"; - continue; - } - if (pieces[0] == "dm") { - // Failure to remove a dm node is fatal, since we can't safely - // remove the file or loop devices. - const auto& name = pieces[1]; - if (!dm.DeleteDeviceIfExists(name)) { - return false; - } - } else if (pieces[0] == "loop") { - // Failure to remove a loop device is not fatal, since we can still - // remove the backing file if we want. - loop.Detach(pieces[1]); - } else { - LOG(ERROR) << "Unknown status: " << pieces[0]; - } - } - - std::string message; - if (!android::base::RemoveFileIfExists(status_file, &message)) { - LOG(ERROR) << "Could not remove " << status_file << ": " << message; - } - - auto status_prop = GetStatusPropertyName(name); - android::base::SetProperty(status_prop, ""); - return true; -} - -bool ImageManager::RemoveAllImages() { - if (!MetadataExists(metadata_dir_)) { - return true; - } - auto metadata = OpenMetadata(metadata_dir_); - if (!metadata) { - return RemoveAllMetadata(metadata_dir_); - } - - bool ok = true; - for (const auto& partition : metadata->partitions) { - auto partition_name = GetPartitionName(partition); - ok &= DeleteBackingImage(partition_name); - } - return ok && RemoveAllMetadata(metadata_dir_); -} - -bool ImageManager::Validate() { - auto metadata = OpenMetadata(metadata_dir_); - if (!metadata) { - return false; - } - - for (const auto& partition : metadata->partitions) { - auto name = GetPartitionName(partition); - auto image_path = GetImageHeaderPath(name); - auto fiemap = SplitFiemap::Open(image_path); - if (!fiemap || !fiemap->HasPinnedExtents()) { - LOG(ERROR) << "Image is missing or was moved: " << image_path; - return false; - } - } - return true; -} - -std::unique_ptr<MappedDevice> MappedDevice::Open(IImageManager* manager, - const std::chrono::milliseconds& timeout_ms, - const std::string& name) { - std::string path; - if (!manager->MapImageDevice(name, timeout_ms, &path)) { - return nullptr; - } - - auto device = std::unique_ptr<MappedDevice>(new MappedDevice(manager, name, path)); - if (device->fd() < 0) { - return nullptr; - } - return device; -} - -MappedDevice::MappedDevice(IImageManager* manager, const std::string& name, const std::string& path) - : manager_(manager), name_(name), path_(path) { - // The device is already mapped; try and open it. - fd_.reset(open(path.c_str(), O_RDWR | O_CLOEXEC)); -} - -MappedDevice::~MappedDevice() { - fd_ = {}; - manager_->UnmapImageDevice(name_); -} - -bool IImageManager::UnmapImageIfExists(const std::string& name) { - // No lock is needed even though this seems to be vulnerable to TOCTOU. If process A - // calls MapImageDevice() while process B calls UnmapImageIfExists(), and MapImageDevice() - // happens after process B checks IsImageMapped(), it would be as if MapImageDevice() is called - // after process B finishes calling UnmapImageIfExists(), resulting the image to be mapped, - // which is a reasonable sequence. - if (!IsImageMapped(name)) { - return true; - } - return UnmapImageDevice(name); -} - -} // namespace fiemap -} // namespace android diff --git a/libfiemap/image_test.cpp b/libfiemap/image_test.cpp deleted file mode 100644 index f05825c..0000000 --- a/libfiemap/image_test.cpp +++ /dev/null @@ -1,251 +0,0 @@ -// -// 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 <stdlib.h> -#include <string.h> -#include <sys/mount.h> -#include <sys/stat.h> -#include <sys/types.h> - -#include <chrono> -#include <iostream> -#include <thread> - -#include <android-base/file.h> -#include <android-base/properties.h> -#include <android-base/strings.h> -#include <android-base/unique_fd.h> -#include <ext4_utils/ext4_utils.h> -#include <fs_mgr/file_wait.h> -#include <gtest/gtest.h> -#include <libdm/dm.h> -#include <libfiemap/image_manager.h> - -using namespace android::dm; -using namespace std::literals; -using android::base::unique_fd; -using android::fiemap::ImageManager; -using android::fs_mgr::BlockDeviceInfo; -using android::fs_mgr::PartitionOpener; -using android::fs_mgr::WaitForFile; - -static std::string gDataPath; -static std::string gDataMountPath; -static constexpr char kMetadataPath[] = "/metadata/gsi/test"; - -static constexpr uint64_t kTestImageSize = 1024 * 1024; - -class TestPartitionOpener final : public PartitionOpener { - public: - android::base::unique_fd Open(const std::string& partition_name, int flags) const override { - return PartitionOpener::Open(GetPathForBlockDeviceName(partition_name), flags); - } - bool GetInfo(const std::string& partition_name, BlockDeviceInfo* info) const override { - return PartitionOpener::GetInfo(GetPathForBlockDeviceName(partition_name), info); - } - std::string GetDeviceString(const std::string& partition_name) const override { - return PartitionOpener::GetDeviceString(GetPathForBlockDeviceName(partition_name)); - } - - private: - static std::string GetPathForBlockDeviceName(const std::string& name) { - if (android::base::StartsWith(name, "loop") || android::base::StartsWith(name, "dm-")) { - return "/dev/block/"s + name; - } - return name; - } -}; - -// This fixture is for tests against the device's native configuration. -class NativeTest : public ::testing::Test { - protected: - void SetUp() override { - manager_ = ImageManager::Open(kMetadataPath, gDataPath); - ASSERT_NE(manager_, nullptr); - - manager_->set_partition_opener(std::make_unique<TestPartitionOpener>()); - - const ::testing::TestInfo* tinfo = ::testing::UnitTest::GetInstance()->current_test_info(); - base_name_ = tinfo->name(); - } - - void TearDown() override { - manager_->UnmapImageDevice(base_name_); - manager_->DeleteBackingImage(base_name_); - } - - std::string PropertyName() { return "gsid.mapped_image." + base_name_; } - - std::unique_ptr<ImageManager> manager_; - std::string base_name_; -}; - -TEST_F(NativeTest, CreateAndMap) { - ASSERT_TRUE(manager_->CreateBackingImage(base_name_, kTestImageSize, false, nullptr)); - - std::string path; - ASSERT_TRUE(manager_->MapImageDevice(base_name_, 5s, &path)); - ASSERT_TRUE(manager_->IsImageMapped(base_name_)); - ASSERT_EQ(android::base::GetProperty(PropertyName(), ""), path); - - { - unique_fd fd(open(path.c_str(), O_RDWR | O_NOFOLLOW | O_CLOEXEC)); - ASSERT_GE(fd, 0); - ASSERT_EQ(get_block_device_size(fd), kTestImageSize); - } - - ASSERT_TRUE(manager_->UnmapImageDevice(base_name_)); - ASSERT_FALSE(manager_->IsImageMapped(base_name_)); - ASSERT_EQ(android::base::GetProperty(PropertyName(), ""), ""); -} - -// This fixture is for tests against a simulated device environment. Rather -// than use /data, we create an image and then layer a new filesystem within -// it. Each test then decides how to mount and create layered images. This -// allows us to test FBE vs FDE configurations. -class ImageTest : public ::testing::Test { - public: - ImageTest() : dm_(DeviceMapper::Instance()) {} - - void SetUp() override { - manager_ = ImageManager::Open(kMetadataPath, gDataPath); - ASSERT_NE(manager_, nullptr); - - manager_->set_partition_opener(std::make_unique<TestPartitionOpener>()); - - submanager_ = ImageManager::Open(kMetadataPath + "/mnt"s, gDataPath + "/mnt"s); - ASSERT_NE(submanager_, nullptr); - - submanager_->set_partition_opener(std::make_unique<TestPartitionOpener>()); - - // Ensure that metadata is cleared in between runs. - submanager_->RemoveAllImages(); - manager_->RemoveAllImages(); - - const ::testing::TestInfo* tinfo = ::testing::UnitTest::GetInstance()->current_test_info(); - base_name_ = tinfo->name(); - test_image_name_ = base_name_ + "-base"; - wrapper_device_name_ = base_name_ + "-wrapper"; - - ASSERT_TRUE(manager_->CreateBackingImage(base_name_, kTestImageSize * 16, false, nullptr)); - ASSERT_TRUE(manager_->MapImageDevice(base_name_, 5s, &base_device_)); - } - - void TearDown() override { - submanager_->UnmapImageDevice(test_image_name_); - umount(gDataMountPath.c_str()); - dm_.DeleteDeviceIfExists(wrapper_device_name_); - manager_->UnmapImageDevice(base_name_); - manager_->DeleteBackingImage(base_name_); - } - - protected: - bool DoFormat(const std::string& device) { - // clang-format off - std::vector<std::string> mkfs_args = { - "/system/bin/mke2fs", - "-F", - "-b 4096", - "-t ext4", - "-m 0", - "-O has_journal", - device, - ">/dev/null", - "2>/dev/null", - "</dev/null", - }; - // clang-format on - auto command = android::base::Join(mkfs_args, " "); - return system(command.c_str()) == 0; - } - - std::unique_ptr<ImageManager> manager_; - std::unique_ptr<ImageManager> submanager_; - - DeviceMapper& dm_; - std::string base_name_; - std::string base_device_; - std::string test_image_name_; - std::string wrapper_device_name_; -}; - -TEST_F(ImageTest, DirectMount) { - ASSERT_TRUE(DoFormat(base_device_)); - ASSERT_EQ(mount(base_device_.c_str(), gDataMountPath.c_str(), "ext4", 0, nullptr), 0); - ASSERT_TRUE(submanager_->CreateBackingImage(test_image_name_, kTestImageSize, false, nullptr)); - - std::string path; - ASSERT_TRUE(submanager_->MapImageDevice(test_image_name_, 5s, &path)); - ASSERT_TRUE(android::base::StartsWith(path, "/dev/block/loop")); -} - -TEST_F(ImageTest, IndirectMount) { - // Create a simple wrapper around the base device that we'll mount from - // instead. This will simulate the code paths for dm-crypt/default-key/bow - // and force us to use device-mapper rather than loop devices. - uint64_t device_size = 0; - { - unique_fd fd(open(base_device_.c_str(), O_RDWR | O_CLOEXEC)); - ASSERT_GE(fd, 0); - device_size = get_block_device_size(fd); - ASSERT_EQ(device_size, kTestImageSize * 16); - } - uint64_t num_sectors = device_size / 512; - - auto& dm = DeviceMapper::Instance(); - - DmTable table; - table.Emplace<DmTargetLinear>(0, num_sectors, base_device_, 0); - ASSERT_TRUE(dm.CreateDevice(wrapper_device_name_, table)); - - // Format and mount. - std::string wrapper_device; - ASSERT_TRUE(dm.GetDmDevicePathByName(wrapper_device_name_, &wrapper_device)); - ASSERT_TRUE(WaitForFile(wrapper_device, 5s)); - ASSERT_TRUE(DoFormat(wrapper_device)); - ASSERT_EQ(mount(wrapper_device.c_str(), gDataMountPath.c_str(), "ext4", 0, nullptr), 0); - - ASSERT_TRUE(submanager_->CreateBackingImage(test_image_name_, kTestImageSize, false, nullptr)); - - std::string path; - ASSERT_TRUE(submanager_->MapImageDevice(test_image_name_, 5s, &path)); - ASSERT_TRUE(android::base::StartsWith(path, "/dev/block/dm-")); -} - -bool Mkdir(const std::string& path) { - if (mkdir(path.c_str(), 0700) && errno != EEXIST) { - std::cerr << "Could not mkdir " << path << ": " << strerror(errno) << std::endl; - return false; - } - return true; -} - -int main(int argc, char** argv) { - ::testing::InitGoogleTest(&argc, argv); - - if (argc >= 2) { - gDataPath = argv[1]; - } else { - gDataPath = "/data/gsi/test"; - } - gDataMountPath = gDataPath + "/mnt"s; - - if (!Mkdir(gDataPath) || !Mkdir(kMetadataPath) || !Mkdir(gDataMountPath) || - !Mkdir(kMetadataPath + "/mnt"s)) { - return 1; - } - return RUN_ALL_TESTS(); -} diff --git a/libfiemap/include/libfiemap/fiemap_writer.h b/libfiemap/include/libfiemap/fiemap_writer.h deleted file mode 100644 index c692265..0000000 --- a/libfiemap/include/libfiemap/fiemap_writer.h +++ /dev/null @@ -1,118 +0,0 @@ -/* - * 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 <functional> -#include <string> -#include <vector> - -#include <android-base/unique_fd.h> - -namespace android { -namespace fiemap { - -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. The optional progress callback will be - // invoked, if create is true, while the file is being initialized. It receives the bytes - // written and the number of total bytes. If the callback returns false, the operation will - // fail. - // - // Note: when create is true, the file size will be aligned up to the nearest file system - // block. - static FiemapUniquePtr Open(const std::string& file_path, uint64_t file_size, - bool create = true, - std::function<bool(uint64_t, uint64_t)> progress = {}); - - // Check that a file still has the same extents since it was last opened with FiemapWriter, - // assuming the file was not resized outside of FiemapWriter. Returns false either on error - // or if the file was not pinned. - // - // This will always return true on Ext4. On F2FS, it will return true if either of the - // following cases are true: - // - The file was never pinned. - // - The file is pinned and has not been moved by the GC. - // Thus, this method should only be called for pinned files (such as those returned by - // FiemapWriter::Open). - static bool HasPinnedExtents(const std::string& file_path); - - // Returns the underlying block device of a file. This will look past device-mapper layers - // as long as each layer would not change block mappings (i.e., dm-crypt, dm-bow, and dm- - // default-key tables are okay; dm-linear is not). If a mapping such as dm-linear is found, - // it will be returned in place of any physical block device. - // - // It is the caller's responsibility to check whether the returned block device is acceptable. - // Gsid, for example, will only accept /dev/block/by-name/userdata as the bottom device. - // Callers can check the device name (dm- or loop prefix), inspect sysfs, or compare the major - // number against a boot device. - // - // If device-mapper nodes were encountered, then |uses_dm| will be set to true. - static bool GetBlockDeviceForFile(const std::string& file_path, std::string* bdev_path, - bool* uses_dm = nullptr); - - ~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_; }; - uint32_t fs_type() const { return fs_type_; } - - // 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_; - - // 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; -}; - -} // namespace fiemap -} // namespace android diff --git a/libfiemap/include/libfiemap/image_manager.h b/libfiemap/include/libfiemap/image_manager.h deleted file mode 100644 index efbe9bd..0000000 --- a/libfiemap/include/libfiemap/image_manager.h +++ /dev/null @@ -1,184 +0,0 @@ -// -// 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 <stdint.h> - -#include <chrono> -#include <functional> -#include <memory> -#include <string> - -#include <android-base/unique_fd.h> -#include <liblp/partition_opener.h> - -namespace android { -namespace fiemap { - -class IImageManager { - public: - using IPartitionOpener = android::fs_mgr::IPartitionOpener; - - virtual ~IImageManager() {} - - // When linking to libfiemap_binder, the Open() call will use binder. - // Otherwise, the Open() call will use the ImageManager implementation - // below. - static std::unique_ptr<IImageManager> Open(const std::string& dir_prefix, - const std::chrono::milliseconds& timeout_ms); - - // Flags for CreateBackingImage(). - static constexpr int CREATE_IMAGE_DEFAULT = 0x0; - static constexpr int CREATE_IMAGE_READONLY = 0x1; - static constexpr int CREATE_IMAGE_ZERO_FILL = 0x2; - - // Create an image that can be mapped as a block-device. If |force_zero_fill| - // is true, the image will be zero-filled. Otherwise, the initial content - // of the image is undefined. If zero-fill is requested, and the operation - // cannot be completed, the image will be deleted and this function will - // return false. - virtual bool CreateBackingImage(const std::string& name, uint64_t size, int flags) = 0; - - // Delete an image created with CreateBackingImage. Its entry will be - // removed from the associated lp_metadata file. - virtual bool DeleteBackingImage(const std::string& name) = 0; - - // Create a block device for an image previously created with - // CreateBackingImage. This will wait for at most |timeout_ms| milliseconds - // for |path| to be available, and will return false if not available in - // the requested time. If |timeout_ms| is zero, this is NOT guaranteed to - // return true. A timeout of 10s is recommended. - // - // Note that snapshots created with a readonly flag are always mapped - // writable. The flag is persisted in the lp_metadata file however, so if - // fs_mgr::CreateLogicalPartition(s) is used, the flag will be respected. - virtual bool MapImageDevice(const std::string& name, - const std::chrono::milliseconds& timeout_ms, std::string* path) = 0; - - // Unmap a block device previously mapped with mapBackingImage. - virtual bool UnmapImageDevice(const std::string& name) = 0; - - // Returns true whether the named backing image exists. - virtual bool BackingImageExists(const std::string& name) = 0; - - // Returns true if the specified image is mapped to a device. - virtual bool IsImageMapped(const std::string& name) = 0; - - // Map an image using device-mapper. This is not available over binder, and - // is intended only for first-stage init. The returned device is a major:minor - // device string. - virtual bool MapImageWithDeviceMapper(const IPartitionOpener& opener, const std::string& name, - std::string* dev) = 0; - - // Get all backing image names. - virtual std::vector<std::string> GetAllBackingImages() = 0; - - // Writes |bytes| zeros to |name| file. If |bytes| is 0, then the - // whole file if filled with zeros. - virtual bool ZeroFillNewImage(const std::string& name, uint64_t bytes) = 0; - - virtual bool UnmapImageIfExists(const std::string& name); -}; - -class ImageManager final : public IImageManager { - public: - // Return an ImageManager for the given metadata and data directories. Both - // directories must already exist. - static std::unique_ptr<ImageManager> Open(const std::string& metadata_dir, - const std::string& data_dir); - - // Helper function that derives the metadata and data dirs given a single - // prefix. - static std::unique_ptr<ImageManager> Open(const std::string& dir_prefix); - - // Methods that must be implemented from IImageManager. - bool CreateBackingImage(const std::string& name, uint64_t size, int flags) override; - bool DeleteBackingImage(const std::string& name) override; - bool MapImageDevice(const std::string& name, const std::chrono::milliseconds& timeout_ms, - std::string* path) override; - bool UnmapImageDevice(const std::string& name) override; - bool BackingImageExists(const std::string& name) override; - bool IsImageMapped(const std::string& name) override; - bool MapImageWithDeviceMapper(const IPartitionOpener& opener, const std::string& name, - std::string* dev) override; - - std::vector<std::string> GetAllBackingImages(); - // Same as CreateBackingImage, but provides a progress notification. - bool CreateBackingImage(const std::string& name, uint64_t size, int flags, - std::function<bool(uint64_t, uint64_t)>&& on_progress); - - // Find and remove all images and metadata for a given image dir. - bool RemoveAllImages(); - - // Returns true if the named partition exists. This does not check the - // consistency of the backing image/data file. - bool PartitionExists(const std::string& name); - - // Validates that all images still have pinned extents. This will be removed - // once b/134588268 is fixed. - bool Validate(); - - void set_partition_opener(std::unique_ptr<IPartitionOpener>&& opener); - - // Writes |bytes| zeros at the beginning of the passed image - bool ZeroFillNewImage(const std::string& name, uint64_t bytes); - - private: - ImageManager(const std::string& metadata_dir, const std::string& data_dir); - std::string GetImageHeaderPath(const std::string& name); - std::string GetStatusFilePath(const std::string& image_name); - bool MapWithLoopDevice(const std::string& name, const std::chrono::milliseconds& timeout_ms, - std::string* path); - bool MapWithLoopDeviceList(const std::vector<std::string>& device_list, const std::string& name, - const std::chrono::milliseconds& timeout_ms, std::string* path); - bool MapWithDmLinear(const IPartitionOpener& opener, const std::string& name, - const std::chrono::milliseconds& timeout_ms, std::string* path); - bool UnmapImageDevice(const std::string& name, bool force); - - ImageManager(const ImageManager&) = delete; - ImageManager& operator=(const ImageManager&) = delete; - ImageManager& operator=(ImageManager&&) = delete; - ImageManager(ImageManager&&) = delete; - - std::string metadata_dir_; - std::string data_dir_; - std::unique_ptr<IPartitionOpener> partition_opener_; -}; - -// RAII helper class for mapping and opening devices with an ImageManager. -class MappedDevice final { - public: - static std::unique_ptr<MappedDevice> Open(IImageManager* manager, - const std::chrono::milliseconds& timeout_ms, - const std::string& name); - - ~MappedDevice(); - - int fd() const { return fd_; } - const std::string& path() const { return path_; } - - protected: - MappedDevice(IImageManager* manager, const std::string& name, const std::string& path); - - IImageManager* manager_; - std::string name_; - std::string path_; - android::base::unique_fd fd_; -}; - -} // namespace fiemap -} // namespace android diff --git a/libfiemap/include/libfiemap/split_fiemap_writer.h b/libfiemap/include/libfiemap/split_fiemap_writer.h deleted file mode 100644 index feffb3d..0000000 --- a/libfiemap/include/libfiemap/split_fiemap_writer.h +++ /dev/null @@ -1,97 +0,0 @@ -/* - * 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 <stdint.h> - -#include <functional> -#include <memory> -#include <string> -#include <vector> - -#include <android-base/unique_fd.h> - -#include "fiemap_writer.h" - -namespace android { -namespace fiemap { - -// Wrapper around FiemapWriter that is able to split images across files if -// necessary. -class SplitFiemap final { - public: - using ProgressCallback = std::function<bool(uint64_t, uint64_t)>; - - // Create a new split fiemap file. If |max_piece_size| is 0, the number of - // pieces will be determined automatically by detecting the filesystem. - // Otherwise, the file will be split evenly (with the remainder in the - // final file). - static std::unique_ptr<SplitFiemap> Create(const std::string& file_path, uint64_t file_size, - uint64_t max_piece_size, - ProgressCallback progress = {}); - - // Open an existing split fiemap file. - static std::unique_ptr<SplitFiemap> Open(const std::string& file_path); - - ~SplitFiemap(); - - // Return a list of all files created for a split file. - static bool GetSplitFileList(const std::string& file_path, std::vector<std::string>* list); - - // Destroy all components of a split file. If the root file does not exist, - // this returns true and does not report an error. - static bool RemoveSplitFiles(const std::string& file_path, std::string* message = nullptr); - - // Return whether all components of a split file still have pinned extents. - bool HasPinnedExtents() const; - - // Helper method for writing data that spans files. Note there is no seek - // method (yet); this starts at 0 and increments the position by |bytes|. - bool Write(const void* data, uint64_t bytes); - - // Flush all writes to all split files. - bool Flush(); - - const std::vector<struct fiemap_extent>& extents(); - uint32_t block_size() const; - uint64_t size() const { return total_size_; } - const std::string& bdev_path() const; - - // Non-copyable & Non-movable - SplitFiemap(const SplitFiemap&) = delete; - SplitFiemap& operator=(const SplitFiemap&) = delete; - SplitFiemap& operator=(SplitFiemap&&) = delete; - SplitFiemap(SplitFiemap&&) = delete; - - private: - SplitFiemap() = default; - void AddFile(FiemapUniquePtr&& file); - - bool creating_ = false; - std::string list_file_; - std::vector<FiemapUniquePtr> files_; - std::vector<struct fiemap_extent> extents_; - uint64_t total_size_ = 0; - - // Most recently open file and position for Write(). - size_t cursor_index_ = 0; - uint64_t cursor_file_pos_ = 0; - android::base::unique_fd cursor_fd_; -}; - -} // namespace fiemap -} // namespace android diff --git a/libfiemap/metadata.cpp b/libfiemap/metadata.cpp deleted file mode 100644 index 597efe9..0000000 --- a/libfiemap/metadata.cpp +++ /dev/null @@ -1,196 +0,0 @@ -// -// 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 "metadata.h" - -#include <sys/stat.h> -#include <sys/types.h> -#include <unistd.h> - -#include <android-base/file.h> -#include <android-base/logging.h> -#include <liblp/builder.h> - -#include "utility.h" - -namespace android { -namespace fiemap { - -using namespace android::fs_mgr; - -static constexpr uint32_t kMaxMetadataSize = 256 * 1024; - -std::string GetMetadataFile(const std::string& metadata_dir) { - return JoinPaths(metadata_dir, "lp_metadata"); -} - -bool MetadataExists(const std::string& metadata_dir) { - auto metadata_file = GetMetadataFile(metadata_dir); - return access(metadata_file.c_str(), F_OK) == 0; -} - -std::unique_ptr<LpMetadata> OpenMetadata(const std::string& metadata_dir) { - auto metadata_file = GetMetadataFile(metadata_dir); - auto metadata = ReadFromImageFile(metadata_file); - if (!metadata) { - LOG(ERROR) << "Could not read metadata file " << metadata_file; - return nullptr; - } - return metadata; -} - -// :TODO: overwrite on create if open fails -std::unique_ptr<MetadataBuilder> OpenOrCreateMetadata(const std::string& metadata_dir, - SplitFiemap* file) { - auto metadata_file = GetMetadataFile(metadata_dir); - - PartitionOpener opener; - std::unique_ptr<MetadataBuilder> builder; - if (access(metadata_file.c_str(), R_OK)) { - if (errno != ENOENT) { - PLOG(ERROR) << "access " << metadata_file << " failed:"; - return nullptr; - } - - auto data_device = GetDevicePathForFile(file); - - BlockDeviceInfo device_info; - if (!opener.GetInfo(data_device, &device_info)) { - LOG(ERROR) << "Could not read partition: " << data_device; - return nullptr; - } - - std::vector<BlockDeviceInfo> block_devices = {device_info}; - auto super_name = android::base::Basename(data_device); - builder = MetadataBuilder::New(block_devices, super_name, kMaxMetadataSize, 1); - } else { - auto metadata = OpenMetadata(metadata_dir); - if (!metadata) { - return nullptr; - } - builder = MetadataBuilder::New(*metadata.get(), &opener); - } - - if (!builder) { - LOG(ERROR) << "Could not create metadata builder"; - return nullptr; - } - return builder; -} - -bool SaveMetadata(MetadataBuilder* builder, const std::string& metadata_dir) { - auto exported = builder->Export(); - if (!exported) { - LOG(ERROR) << "Unable to export new metadata"; - return false; - } - - // If there are no more partitions in the metadata, just delete the file. - auto metadata_file = GetMetadataFile(metadata_dir); - if (exported->partitions.empty() && android::base::RemoveFileIfExists(metadata_file)) { - return true; - } - if (!WriteToImageFile(metadata_file, *exported.get())) { - LOG(ERROR) << "Unable to save new metadata"; - return false; - } - return true; -} - -bool RemoveAllMetadata(const std::string& dir) { - auto metadata_file = GetMetadataFile(dir); - return android::base::RemoveFileIfExists(metadata_file); -} - -bool FillPartitionExtents(MetadataBuilder* builder, Partition* partition, SplitFiemap* file, - uint64_t partition_size) { - auto block_device = android::base::Basename(GetDevicePathForFile(file)); - - uint64_t sectors_needed = partition_size / LP_SECTOR_SIZE; - for (const auto& extent : file->extents()) { - if (extent.fe_length % LP_SECTOR_SIZE != 0) { - LOG(ERROR) << "Extent is not sector-aligned: " << extent.fe_length; - return false; - } - if (extent.fe_physical % LP_SECTOR_SIZE != 0) { - LOG(ERROR) << "Extent physical sector is not sector-aligned: " << extent.fe_physical; - return false; - } - - uint64_t num_sectors = - std::min(static_cast<uint64_t>(extent.fe_length / LP_SECTOR_SIZE), sectors_needed); - if (!num_sectors || !sectors_needed) { - // This should never happen, but we include it just in case. It would - // indicate that the last filesystem block had multiple extents. - LOG(WARNING) << "FiemapWriter allocated extra blocks"; - break; - } - - uint64_t physical_sector = extent.fe_physical / LP_SECTOR_SIZE; - if (!builder->AddLinearExtent(partition, block_device, num_sectors, physical_sector)) { - LOG(ERROR) << "Could not add extent to lp metadata"; - return false; - } - - sectors_needed -= num_sectors; - } - return true; -} - -bool RemoveImageMetadata(const std::string& metadata_dir, const std::string& partition_name) { - if (!MetadataExists(metadata_dir)) { - return true; - } - auto metadata = OpenMetadata(metadata_dir); - if (!metadata) { - return false; - } - - PartitionOpener opener; - auto builder = MetadataBuilder::New(*metadata.get(), &opener); - if (!builder) { - return false; - } - builder->RemovePartition(partition_name); - return SaveMetadata(builder.get(), metadata_dir); -} - -bool UpdateMetadata(const std::string& metadata_dir, const std::string& partition_name, - SplitFiemap* file, uint64_t partition_size, bool readonly) { - auto builder = OpenOrCreateMetadata(metadata_dir, file); - if (!builder) { - return false; - } - auto partition = builder->FindPartition(partition_name); - if (!partition) { - int attrs = 0; - if (readonly) attrs |= LP_PARTITION_ATTR_READONLY; - - if ((partition = builder->AddPartition(partition_name, attrs)) == nullptr) { - LOG(ERROR) << "Could not add partition " << partition_name << " to metadata"; - return false; - } - } - partition->RemoveExtents(); - - if (!FillPartitionExtents(builder.get(), partition, file, partition_size)) { - return false; - } - return SaveMetadata(builder.get(), metadata_dir); -} - -} // namespace fiemap -} // namespace android diff --git a/libfiemap/metadata.h b/libfiemap/metadata.h deleted file mode 100644 index f0ce23e..0000000 --- a/libfiemap/metadata.h +++ /dev/null @@ -1,36 +0,0 @@ -// -// 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 <stdint.h> - -#include <memory> -#include <string> - -#include <libfiemap/split_fiemap_writer.h> -#include <liblp/liblp.h> - -namespace android { -namespace fiemap { - -bool MetadataExists(const std::string& metadata_dir); -std::unique_ptr<android::fs_mgr::LpMetadata> OpenMetadata(const std::string& metadata_dir); -bool UpdateMetadata(const std::string& metadata_dir, const std::string& partition_name, - SplitFiemap* file, uint64_t partition_size, bool readonly); -bool RemoveImageMetadata(const std::string& metadata_dir, const std::string& partition_name); -bool RemoveAllMetadata(const std::string& dir); - -} // namespace fiemap -} // namespace android diff --git a/libfiemap/passthrough.cpp b/libfiemap/passthrough.cpp deleted file mode 100644 index 1ccd9a0..0000000 --- a/libfiemap/passthrough.cpp +++ /dev/null @@ -1,29 +0,0 @@ -// -// 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 <libfiemap/image_manager.h> - -namespace android { -namespace fiemap { - -std::unique_ptr<IImageManager> IImageManager::Open(const std::string& dir_prefix, - const std::chrono::milliseconds& timeout_ms) { - (void)timeout_ms; - return ImageManager::Open(dir_prefix); -} - -} // namespace fiemap -} // namespace android diff --git a/libfiemap/split_fiemap_writer.cpp b/libfiemap/split_fiemap_writer.cpp deleted file mode 100644 index cc54f20..0000000 --- a/libfiemap/split_fiemap_writer.cpp +++ /dev/null @@ -1,303 +0,0 @@ -/* - * 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 <libfiemap/split_fiemap_writer.h> - -#include <fcntl.h> -#include <stdint.h> -#include <sys/stat.h> -#include <sys/types.h> -#include <unistd.h> - -#include <memory> -#include <string> -#include <vector> - -#include <android-base/file.h> -#include <android-base/logging.h> -#include <android-base/stringprintf.h> -#include <android-base/strings.h> -#include <android-base/unique_fd.h> - -#include "utility.h" - -namespace android { -namespace fiemap { - -using android::base::unique_fd; - -// We use a four-digit suffix at the end of filenames. -static const size_t kMaxFilePieces = 500; - -std::unique_ptr<SplitFiemap> SplitFiemap::Create(const std::string& file_path, uint64_t file_size, - uint64_t max_piece_size, - ProgressCallback progress) { - if (!file_size) { - LOG(ERROR) << "Cannot create a fiemap for a 0-length file: " << file_path; - return nullptr; - } - - if (!max_piece_size) { - max_piece_size = DetermineMaximumFileSize(file_path); - if (!max_piece_size) { - LOG(ERROR) << "Could not determine maximum file size for " << file_path; - return nullptr; - } - } - - // Remove any existing file. - RemoveSplitFiles(file_path); - - // Call |progress| only when the total percentage would significantly change. - int permille = -1; - uint64_t total_bytes_written = 0; - auto on_progress = [&](uint64_t written, uint64_t) -> bool { - uint64_t actual_written = total_bytes_written + written; - int new_permille = (actual_written * 1000) / file_size; - if (new_permille != permille && actual_written < file_size) { - if (progress && !progress(actual_written, file_size)) { - return false; - } - permille = new_permille; - } - return true; - }; - - std::unique_ptr<SplitFiemap> out(new SplitFiemap()); - out->creating_ = true; - out->list_file_ = file_path; - - // Create the split files. - uint64_t remaining_bytes = file_size; - while (remaining_bytes) { - if (out->files_.size() >= kMaxFilePieces) { - LOG(ERROR) << "Requested size " << file_size << " created too many split files"; - return nullptr; - } - std::string chunk_path = - android::base::StringPrintf("%s.%04d", file_path.c_str(), (int)out->files_.size()); - uint64_t chunk_size = std::min(max_piece_size, remaining_bytes); - auto writer = FiemapWriter::Open(chunk_path, chunk_size, true, on_progress); - if (!writer) { - return nullptr; - } - - // To make sure the alignment doesn't create too much inconsistency, we - // account the *actual* size, not the requested size. - total_bytes_written += writer->size(); - - // writer->size() is block size aligned and could be bigger than remaining_bytes - // If remaining_bytes is bigger, set remaining_bytes to 0 to avoid underflow error. - remaining_bytes = remaining_bytes > writer->size() ? (remaining_bytes - writer->size()) : 0; - - out->AddFile(std::move(writer)); - } - - // Create the split file list. - unique_fd fd(open(out->list_file_.c_str(), O_CREAT | O_WRONLY | O_CLOEXEC, 0660)); - if (fd < 0) { - PLOG(ERROR) << "Failed to open " << file_path; - return nullptr; - } - - for (const auto& writer : out->files_) { - std::string line = android::base::Basename(writer->file_path()) + "\n"; - if (!android::base::WriteFully(fd, line.data(), line.size())) { - PLOG(ERROR) << "Write failed " << file_path; - return nullptr; - } - } - - // Unset this bit, so we don't unlink on destruction. - out->creating_ = false; - return out; -} - -std::unique_ptr<SplitFiemap> SplitFiemap::Open(const std::string& file_path) { - std::vector<std::string> files; - if (!GetSplitFileList(file_path, &files)) { - return nullptr; - } - - std::unique_ptr<SplitFiemap> out(new SplitFiemap()); - out->list_file_ = file_path; - - for (const auto& file : files) { - auto writer = FiemapWriter::Open(file, 0, false); - if (!writer) { - // Error was logged in Open(). - return nullptr; - } - out->AddFile(std::move(writer)); - } - return out; -} - -bool SplitFiemap::GetSplitFileList(const std::string& file_path, std::vector<std::string>* list) { - // This is not the most efficient thing, but it is simple and recovering - // the fiemap/fibmap is much more expensive. - std::string contents; - if (!android::base::ReadFileToString(file_path, &contents, true)) { - PLOG(ERROR) << "Error reading file: " << file_path; - return false; - } - - std::vector<std::string> names = android::base::Split(contents, "\n"); - std::string dir = android::base::Dirname(file_path); - for (const auto& name : names) { - if (!name.empty()) { - list->emplace_back(dir + "/" + name); - } - } - return true; -} - -bool SplitFiemap::RemoveSplitFiles(const std::string& file_path, std::string* message) { - // Early exit if this does not exist, and do not report an error. - if (access(file_path.c_str(), F_OK) && errno == ENOENT) { - return true; - } - - bool ok = true; - std::vector<std::string> files; - if (GetSplitFileList(file_path, &files)) { - for (const auto& file : files) { - ok &= android::base::RemoveFileIfExists(file, message); - } - } - ok &= android::base::RemoveFileIfExists(file_path, message); - return ok; -} - -bool SplitFiemap::HasPinnedExtents() const { - for (const auto& file : files_) { - if (!FiemapWriter::HasPinnedExtents(file->file_path())) { - return false; - } - } - return true; -} - -const std::vector<struct fiemap_extent>& SplitFiemap::extents() { - if (extents_.empty()) { - for (const auto& file : files_) { - const auto& extents = file->extents(); - extents_.insert(extents_.end(), extents.begin(), extents.end()); - } - } - return extents_; -} - -bool SplitFiemap::Write(const void* data, uint64_t bytes) { - // Open the current file. - FiemapWriter* file = files_[cursor_index_].get(); - - const uint8_t* data_ptr = reinterpret_cast<const uint8_t*>(data); - uint64_t bytes_remaining = bytes; - while (bytes_remaining) { - // How many bytes can we write into the current file? - uint64_t file_bytes_left = file->size() - cursor_file_pos_; - if (!file_bytes_left) { - if (cursor_index_ == files_.size() - 1) { - LOG(ERROR) << "write past end of file requested"; - return false; - } - - // No space left in the current file, but we have more files to - // use, so prep the next one. - cursor_fd_ = {}; - cursor_file_pos_ = 0; - file = files_[++cursor_index_].get(); - file_bytes_left = file->size(); - } - - // Open the current file if it's not open. - if (cursor_fd_ < 0) { - cursor_fd_.reset(open(file->file_path().c_str(), O_CLOEXEC | O_WRONLY)); - if (cursor_fd_ < 0) { - PLOG(ERROR) << "open failed: " << file->file_path(); - return false; - } - CHECK(cursor_file_pos_ == 0); - } - - if (!FiemapWriter::HasPinnedExtents(file->file_path())) { - LOG(ERROR) << "file is no longer pinned: " << file->file_path(); - return false; - } - - uint64_t bytes_to_write = std::min(file_bytes_left, bytes_remaining); - if (!android::base::WriteFully(cursor_fd_, data_ptr, bytes_to_write)) { - PLOG(ERROR) << "write failed: " << file->file_path(); - return false; - } - data_ptr += bytes_to_write; - bytes_remaining -= bytes_to_write; - cursor_file_pos_ += bytes_to_write; - } - - // If we've reached the end of the current file, close it for sanity. - if (cursor_file_pos_ == file->size()) { - cursor_fd_ = {}; - } - return true; -} - -bool SplitFiemap::Flush() { - for (const auto& file : files_) { - unique_fd fd(open(file->file_path().c_str(), O_RDONLY | O_CLOEXEC)); - if (fd < 0) { - PLOG(ERROR) << "open failed: " << file->file_path(); - return false; - } - if (fsync(fd)) { - PLOG(ERROR) << "fsync failed: " << file->file_path(); - return false; - } - } - return true; -} - -SplitFiemap::~SplitFiemap() { - if (!creating_) { - return; - } - - // We failed to finish creating, so unlink everything. - unlink(list_file_.c_str()); - for (auto&& file : files_) { - std::string path = file->file_path(); - file = nullptr; - - unlink(path.c_str()); - } -} - -void SplitFiemap::AddFile(FiemapUniquePtr&& file) { - total_size_ += file->size(); - files_.emplace_back(std::move(file)); -} - -uint32_t SplitFiemap::block_size() const { - return files_[0]->block_size(); -} - -const std::string& SplitFiemap::bdev_path() const { - return files_[0]->bdev_path(); -} - -} // namespace fiemap -} // namespace android diff --git a/libfiemap/testdata/file_32k b/libfiemap/testdata/file_32k Binary files differdeleted file mode 100644 index 12f3be4..0000000 --- a/libfiemap/testdata/file_32k +++ /dev/null diff --git a/libfiemap/testdata/file_4k b/libfiemap/testdata/file_4k Binary files differdeleted file mode 100644 index 08e7df1..0000000 --- a/libfiemap/testdata/file_4k +++ /dev/null diff --git a/libfiemap/testdata/unaligned_file b/libfiemap/testdata/unaligned_file Binary files differdeleted file mode 100644 index c107c26..0000000 --- a/libfiemap/testdata/unaligned_file +++ /dev/null diff --git a/libfiemap/utility.cpp b/libfiemap/utility.cpp deleted file mode 100644 index 1f6378f..0000000 --- a/libfiemap/utility.cpp +++ /dev/null @@ -1,93 +0,0 @@ -/* - * 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 "utility.h" - -#include <stdint.h> -#include <sys/stat.h> -#include <sys/types.h> -#include <sys/vfs.h> -#include <unistd.h> - -#include <android-base/logging.h> -#include <android-base/strings.h> -#include <libfiemap/fiemap_writer.h> - -namespace android { -namespace fiemap { - -static constexpr char kUserdataDevice[] = "/dev/block/by-name/userdata"; - -uint64_t DetermineMaximumFileSize(const std::string& file_path) { - // Create the smallest file possible (one block). - auto writer = FiemapWriter::Open(file_path, 1); - if (!writer) { - return 0; - } - - uint64_t result = 0; - switch (writer->fs_type()) { - case EXT4_SUPER_MAGIC: - // The minimum is 16GiB, so just report that. If we wanted we could parse the - // superblock and figure out if 64-bit support is enabled. - result = 17179869184ULL; - break; - case F2FS_SUPER_MAGIC: - // Formula is from https://www.kernel.org/doc/Documentation/filesystems/f2fs.txt - // 4KB * (923 + 2 * 1018 + 2 * 1018 * 1018 + 1018 * 1018 * 1018) := 3.94TB. - result = 4329690886144ULL; - break; - case MSDOS_SUPER_MAGIC: - // 4GB-1, which we want aligned to the block size. - result = 4294967295; - result -= (result % writer->block_size()); - break; - default: - LOG(ERROR) << "Unknown file system type: " << writer->fs_type(); - break; - } - - // Close and delete the temporary file. - writer = nullptr; - unlink(file_path.c_str()); - - return result; -} - -// Given a SplitFiemap, this returns a device path that will work during first- -// stage init (i.e., its path can be found by InitRequiredDevices). -std::string GetDevicePathForFile(SplitFiemap* file) { - auto bdev_path = file->bdev_path(); - - struct stat userdata, given; - if (!stat(bdev_path.c_str(), &given) && !stat(kUserdataDevice, &userdata)) { - if (S_ISBLK(given.st_mode) && S_ISBLK(userdata.st_mode) && - given.st_rdev == userdata.st_rdev) { - return kUserdataDevice; - } - } - return bdev_path; -} - -std::string JoinPaths(const std::string& dir, const std::string& file) { - if (android::base::EndsWith(dir, "/")) { - return dir + file; - } - return dir + "/" + file; -} - -} // namespace fiemap -} // namespace android diff --git a/libfiemap/utility.h b/libfiemap/utility.h deleted file mode 100644 index 19b632d..0000000 --- a/libfiemap/utility.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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 <stdint.h> - -#include <string> - -#include <libfiemap/split_fiemap_writer.h> - -namespace android { -namespace fiemap { - -// Given a file that will be created, determine the maximum size its containing -// filesystem allows. Note this is a theoretical maximum size; free space is -// ignored entirely. -uint64_t DetermineMaximumFileSize(const std::string& file_path); - -// Given a SplitFiemap, this returns a device path that will work during first- -// stage init (i.e., its path can be found by InitRequiredDevices). -std::string GetDevicePathForFile(android::fiemap::SplitFiemap* file); - -// Combine two path components into a single path. -std::string JoinPaths(const std::string& dir, const std::string& file); - -} // namespace fiemap -} // namespace android @@ -39,7 +39,7 @@ bool IsGsiRunning() { } bool IsGsiInstalled() { - return !access(kDsuInstallStatusFile, F_OK); + return !access(kGsiInstallStatusFile, F_OK); } static bool WriteAndSyncFile(const std::string& data, const std::string& file) { @@ -53,12 +53,7 @@ static bool WriteAndSyncFile(const std::string& data, const std::string& file) { return fsync(fd) == 0; } -bool CanBootIntoGsi(std::string* error) { - // Always delete this as a safety precaution, so we can return to the - // original system image. If we're confident GSI will boot, this will - // get re-created by MarkSystemAsGsi. - android::base::RemoveFileIfExists(kGsiBootedIndicatorFile); - +static bool CanBootIntoGsi(std::string* error) { if (!IsGsiInstalled()) { *error = "not detected"; return false; @@ -79,7 +74,7 @@ bool CanBootIntoGsi(std::string* error) { } std::string new_key; - if (!access(kDsuOneShotBootFile, F_OK)) { + if (!access(kGsiOneShotBootFile, F_OK)) { // Mark the GSI as disabled. This only affects the next boot, not // the current boot. Note that we leave the one_shot status behind. // This is so IGsiService can still return GSI_STATE_SINGLE_BOOT @@ -88,7 +83,7 @@ bool CanBootIntoGsi(std::string* error) { } else { new_key = std::to_string(attempts + 1); } - if (!WriteAndSyncFile(new_key, kDsuInstallStatusFile)) { + if (!WriteAndSyncFile(new_key, kGsiInstallStatusFile)) { *error = "error ("s + strerror(errno) + ")"; return false; } @@ -102,12 +97,26 @@ bool CanBootIntoGsi(std::string* error) { return true; } +bool CanBootIntoGsi(std::string* metadata_file, std::string* error) { + // Always delete this as a safety precaution, so we can return to the + // original system image. If we're confident GSI will boot, this will + // get re-created by MarkSystemAsGsi. + android::base::RemoveFileIfExists(kGsiBootedIndicatorFile); + + if (!CanBootIntoGsi(error)) { + return false; + } + + *metadata_file = kGsiLpMetadataFile; + return true; +} + bool UninstallGsi() { - return android::base::WriteStringToFile(kInstallStatusWipe, kDsuInstallStatusFile); + return android::base::WriteStringToFile(kInstallStatusWipe, kGsiInstallStatusFile); } bool DisableGsi() { - return android::base::WriteStringToFile(kInstallStatusDisabled, kDsuInstallStatusFile); + return android::base::WriteStringToFile(kInstallStatusDisabled, kGsiInstallStatusFile); } bool MarkSystemAsGsi() { @@ -115,7 +124,7 @@ bool MarkSystemAsGsi() { } bool GetInstallStatus(std::string* status) { - return android::base::ReadFileToString(kDsuInstallStatusFile, status); + return android::base::ReadFileToString(kGsiInstallStatusFile, status); } bool GetBootAttempts(const std::string& boot_key, int* attempts) { diff --git a/libgsid.cpp b/libgsid.cpp deleted file mode 100644 index cfddb60..0000000 --- a/libgsid.cpp +++ /dev/null @@ -1,71 +0,0 @@ -// -// 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 <android-base/logging.h> -#include <android-base/properties.h> -#include <android/gsi/IGsiService.h> -#include <android/gsi/IGsid.h> -#include <binder/IServiceManager.h> -#include <libgsi/libgsi.h> - -namespace android { -namespace gsi { - -using namespace std::chrono_literals; -using android::sp; - -static sp<IGsid> GetGsid() { - if (android::base::GetProperty("init.svc.gsid", "") != "running") { - if (!android::base::SetProperty("ctl.start", "gsid") || - !android::base::WaitForProperty("init.svc.gsid", "running", 5s)) { - LOG(ERROR) << "Unable to start gsid"; - return nullptr; - } - } - - static const int kSleepTimeMs = 50; - static const int kTotalWaitTimeMs = 3000; - for (int i = 0; i < kTotalWaitTimeMs / kSleepTimeMs; i++) { - auto sm = android::defaultServiceManager(); - auto name = android::String16(kGsiServiceName); - android::sp<android::IBinder> res = sm->checkService(name); - if (res) { - return android::interface_cast<IGsid>(res); - } - usleep(kSleepTimeMs * 1000); - } - - LOG(ERROR) << "Timed out trying to start gsid"; - return nullptr; -} - -sp<IGsiService> GetGsiService() { - auto gsid = GetGsid(); - if (!gsid) { - return nullptr; - } - - sp<IGsiService> service; - auto status = gsid->getClient(&service); - if (!status.isOk() || !service) { - LOG(ERROR) << "Error acquiring IGsid: " << status.exceptionMessage().string(); - return nullptr; - } - return service; -} - -} // namespace gsi -} // namespace android diff --git a/tests/Android.bp b/tests/Android.bp deleted file mode 100644 index e162dfb..0000000 --- a/tests/Android.bp +++ /dev/null @@ -1,66 +0,0 @@ -// -// 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_defaults { - name: "gsi_boot_defaults", - shared_libs: [ - "libbase", - "libcutils", - "libhardware", - "libhidlbase", - "liblog", - "libutils", - ], - static_libs: [ - "libext4_utils", - "libfstab", - "android.hardware.weaver@1.0", - ], - srcs: [ - "boot_tests.cpp", - ], -} - -cc_test { - name: "gsi_boot_test", - defaults: ["gsi_boot_defaults"], -} - -cc_test { - name: "vts_gsi_boot_test", - defaults: ["gsi_boot_defaults"], - test_suites: ["vts-core"], - auto_gen_config: true, - test_min_api_level: 29, -} - -java_test_host { - name: "DSUEndtoEndTest", - srcs: ["DSUEndtoEndTest.java"], - libs: ["tradefed"], - test_config: "dsu-test.xml", - test_suites: ["general-tests"], -} - -android_test { - name: "LockScreenAutomation", - srcs: ["LockScreenAutomation.java"], - libs: ["junit", "android.test.base.stubs"], - static_libs: ["androidx.test.uiautomator"], - certificate: "platform", - manifest: "AndroidManifest.xml", - test_suites: ["general-tests"], -} diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml deleted file mode 100644 index 19d0c53..0000000 --- a/tests/AndroidManifest.xml +++ /dev/null @@ -1,30 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2019 Google Inc. - - 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. ---> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.google.android.lockscreenautomation" - android:sharedUserId="android.uid.system" - > - - <uses-sdk android:minSdkVersion="22" android:targetSdkVersion="28"/> - - <application debuggable="true"> - <uses-library android:name="android.test.runner" /> - </application> - - <instrumentation android:name="androidx.test.uiautomator.UiAutomatorInstrumentationTestRunner" - android:targetPackage="com.google.android.lockscreenautomation" - android:label="Lock Screen Automation"/> -</manifest> diff --git a/tests/DSUEndtoEndTest.java b/tests/DSUEndtoEndTest.java deleted file mode 100644 index 175ca54..0000000 --- a/tests/DSUEndtoEndTest.java +++ /dev/null @@ -1,168 +0,0 @@ -/* - * 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. - */ - -package com.android.tests.dsu; - -import com.android.tradefed.build.BuildRetrievalError; -import com.android.tradefed.build.IBuildInfo; -import com.android.tradefed.build.IDeviceBuildInfo; -import com.android.tradefed.config.Option; -import com.android.tradefed.config.Option.Importance; -import com.android.tradefed.device.DeviceNotAvailableException; -import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; -import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; -import com.android.tradefed.util.CommandResult; -import com.android.tradefed.util.ZipUtil2; - -import org.apache.commons.compress.archivers.zip.ZipFile; - -import org.junit.After; -import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.io.File; -import java.io.IOException; -import java.lang.Process; -import java.lang.Runtime; -import java.util.concurrent.TimeUnit; - -/** - * Test Dynamic System Updates by booting in and out of a supplied system image - */ -@RunWith(DeviceJUnit4ClassRunner.class) -public class DSUEndtoEndTest extends BaseHostJUnit4Test { - private static final long kDefaultUserdataSize = 4L * 1024 * 1024 * 1024; - private static final String APK = "LockScreenAutomation.apk"; - private static final String PACKAGE = "com.google.android.lockscreenautomation"; - private static final String UI_AUTOMATOR_INSTRUMENTATION_RUNNER = - "androidx.test.uiautomator.UiAutomatorInstrumentationTestRunner"; - private static final String CLASS = "LockScreenAutomation"; - private static final String SIMG2IMG_PATH = "bin/simg2img"; - - // Example: atest -v DSUEndtoEndTest -- --test-arg \ - // com.android.tradefed.testtype.HostTest:set-option:system_image_path:/full/path/to/system.img - @Option(name="system_image_path", - shortName='s', - description="full path to the system image to use. If not specified, attempt " + - "to download the image from the test infrastructure", - importance=Importance.ALWAYS) - private String mSystemImagePath; - - private File mUnsparseSystemImage; - - @After - public void teardown() throws Exception { - uninstallPackage(PACKAGE); - if (mUnsparseSystemImage != null) { - mUnsparseSystemImage.delete(); - } - } - - @Test - public void testDSU() throws Exception { - String simg2imgPath = "simg2img"; - if (mSystemImagePath == null) { - IBuildInfo buildInfo = getBuild(); - File system = ((IDeviceBuildInfo) buildInfo).getDeviceImageFile(); - Assert.assertNotEquals("Failed to fetch system image. See system_image_path parameter", null, system); - mSystemImagePath = ZipUtil2.extractFileFromZip(new ZipFile(system), "system.img").getAbsolutePath(); - File otaTools = buildInfo.getFile("otatools.zip"); - File tempdir = ZipUtil2.extractZipToTemp(otaTools, "otatools"); - simg2imgPath = new File(tempdir, SIMG2IMG_PATH).getAbsolutePath(); - } - File gsi = new File(mSystemImagePath); - Assert.assertTrue("not a valid file", gsi.isFile()); - String[] cmd = {simg2imgPath, mSystemImagePath, mSystemImagePath + ".raw"}; - Process p = Runtime.getRuntime().exec(cmd); - p.waitFor(); - if (p.exitValue() == 0) { - mUnsparseSystemImage = new File(mSystemImagePath + ".raw"); - gsi = mUnsparseSystemImage; - } - - boolean wasRoot = getDevice().isAdbRoot(); - if (!wasRoot) - Assert.assertTrue("Test requires root", getDevice().enableAdbRoot()); - - expectGsiStatus("normal"); - - installPackage(APK); - String method = "setPin"; - String testClass = PACKAGE + "." + CLASS; - String testMethod = testClass + "." + method; - Assert.assertTrue(testMethod + " failed.", - runDeviceTests(UI_AUTOMATOR_INSTRUMENTATION_RUNNER, PACKAGE, testClass, method)); - - // Sleep after installing to allow time for gsi_tool to reboot. This prevents a race between - // the device rebooting and waitForDeviceAvailable() returning. - getDevice().executeShellV2Command("gsi_tool install --userdata-size " + kDefaultUserdataSize + - " --gsi-size " + gsi.length() + " && sleep 10000000", gsi, null, 10, TimeUnit.MINUTES, 1); - getDevice().waitForDeviceAvailable(); - getDevice().enableAdbRoot(); - - expectGsiStatus("running"); - - rebootAndUnlock(); - - expectGsiStatus("installed"); - - CommandResult result = getDevice().executeShellV2Command("gsi_tool enable"); - Assert.assertEquals("gsi_tool enable failed", 0, result.getExitCode().longValue()); - - getDevice().reboot(); - - expectGsiStatus("running"); - - getDevice().reboot(); - - expectGsiStatus("running"); - - getDevice().executeShellV2Command("gsi_tool wipe"); - - rebootAndUnlock(); - - expectGsiStatus("normal"); - - method = "removePin"; - testClass = PACKAGE + "." + CLASS; - testMethod = testClass + "." + method; - Assert.assertTrue(testMethod + " failed.", - runDeviceTests(UI_AUTOMATOR_INSTRUMENTATION_RUNNER, PACKAGE, testClass, method)); - - if (wasRoot) { - getDevice().enableAdbRoot(); - } - } - - private void expectGsiStatus(String expected) throws Exception { - CommandResult result = getDevice().executeShellV2Command("gsi_tool status"); - String status = result.getStdout().split("\n", 2)[0].trim(); - Assert.assertEquals("Device not in expected DSU state", expected, status); - } - - private void rebootAndUnlock() throws Exception { - getDevice().rebootUntilOnline(); - getDevice().executeShellV2Command("input keyevent 224"); // KeyEvent.KEYCODE_WAKEUP - getDevice().executeShellV2Command("wm dismiss-keyguard"); - getDevice().executeShellV2Command("input keyevent 7"); // KeyEvent.KEYCODE_0 - getDevice().executeShellV2Command("input keyevent 7"); - getDevice().executeShellV2Command("input keyevent 7"); - getDevice().executeShellV2Command("input keyevent 7"); - getDevice().executeShellV2Command("input keyevent 66"); // KeyEvent.KEYCODE_ENTER - } -} - diff --git a/tests/LockScreenAutomation.java b/tests/LockScreenAutomation.java deleted file mode 100644 index afefa1c..0000000 --- a/tests/LockScreenAutomation.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * 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. - */ - -package com.google.android.lockscreenautomation; - -import org.junit.Assert; - -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.res.Resources; -import android.provider.Settings; -import androidx.test.uiautomator.By; -import androidx.test.uiautomator.BySelector; -import androidx.test.uiautomator.UiAutomatorTestCase; -import androidx.test.uiautomator.UiDevice; -import androidx.test.uiautomator.UiObject2; -import androidx.test.uiautomator.UiObjectNotFoundException; -import androidx.test.uiautomator.UiSelector; -import androidx.test.uiautomator.Until; -import android.view.KeyEvent; - -/** - * Methods for configuring lock screen settings - */ -public class LockScreenAutomation extends UiAutomatorTestCase { - - private static final String SETTINGS_PACKAGE = "com.android.settings"; - - private static final long TIMEOUT = 2000L; - - private Context mContext; - private UiDevice mDevice; - - public void setPin() throws Exception { - mContext = getInstrumentation().getContext(); - mDevice = UiDevice.getInstance(getInstrumentation()); - - mDevice.wakeUp(); - mDevice.pressKeyCode(KeyEvent.KEYCODE_MENU); - mDevice.waitForIdle(TIMEOUT); - launchLockScreenSettings(); - - PackageManager pm = mContext.getPackageManager(); - Resources res = pm.getResourcesForApplication(SETTINGS_PACKAGE); - - int resId = res.getIdentifier("unlock_set_unlock_pin_title", "string", SETTINGS_PACKAGE); - findAndClick(By.text(res.getString(resId))); - mDevice.waitForWindowUpdate(SETTINGS_PACKAGE, 5); - mDevice.pressKeyCode(KeyEvent.KEYCODE_0); - mDevice.waitForWindowUpdate(SETTINGS_PACKAGE, 5); - mDevice.pressKeyCode(KeyEvent.KEYCODE_0); - mDevice.waitForWindowUpdate(SETTINGS_PACKAGE, 5); - mDevice.pressKeyCode(KeyEvent.KEYCODE_0); - mDevice.waitForWindowUpdate(SETTINGS_PACKAGE, 5); - mDevice.pressKeyCode(KeyEvent.KEYCODE_0); - mDevice.waitForWindowUpdate(SETTINGS_PACKAGE, 5); - mDevice.pressEnter(); - mDevice.waitForWindowUpdate(SETTINGS_PACKAGE, 5); - - // Re-enter PIN - mDevice.pressKeyCode(KeyEvent.KEYCODE_0); - mDevice.waitForWindowUpdate(SETTINGS_PACKAGE, 5); - mDevice.pressKeyCode(KeyEvent.KEYCODE_0); - mDevice.waitForWindowUpdate(SETTINGS_PACKAGE, 5); - mDevice.pressKeyCode(KeyEvent.KEYCODE_0); - mDevice.waitForWindowUpdate(SETTINGS_PACKAGE, 5); - mDevice.pressKeyCode(KeyEvent.KEYCODE_0); - mDevice.waitForWindowUpdate(SETTINGS_PACKAGE, 5); - mDevice.pressEnter(); - - findAndClick(By.res(SETTINGS_PACKAGE, "redact_sensitive")); - mDevice.waitForWindowUpdate(SETTINGS_PACKAGE, 5); - findAndClick(By.clazz("android.widget.Button")); - mDevice.waitForWindowUpdate(SETTINGS_PACKAGE, 5); - } - - public void unlock() throws Exception { - mContext = getInstrumentation().getContext(); - mDevice = UiDevice.getInstance(getInstrumentation()); - mDevice.pressKeyCode(KeyEvent.KEYCODE_0); - mDevice.pressKeyCode(KeyEvent.KEYCODE_0); - mDevice.pressKeyCode(KeyEvent.KEYCODE_0); - mDevice.pressKeyCode(KeyEvent.KEYCODE_0); - mDevice.pressKeyCode(KeyEvent.KEYCODE_ENTER); - } - - public void removePin() throws Exception { - mContext = getInstrumentation().getContext(); - mDevice = UiDevice.getInstance(getInstrumentation()); - - mDevice.wakeUp(); - mDevice.pressKeyCode(KeyEvent.KEYCODE_MENU); - mDevice.waitForIdle(TIMEOUT); - launchLockScreenSettings(); - - PackageManager pm = mContext.getPackageManager(); - Resources res = pm.getResourcesForApplication(SETTINGS_PACKAGE); - - mDevice.pressKeyCode(KeyEvent.KEYCODE_0); - mDevice.waitForWindowUpdate(SETTINGS_PACKAGE, 5); - mDevice.pressKeyCode(KeyEvent.KEYCODE_0); - mDevice.waitForWindowUpdate(SETTINGS_PACKAGE, 5); - mDevice.pressKeyCode(KeyEvent.KEYCODE_0); - mDevice.waitForWindowUpdate(SETTINGS_PACKAGE, 5); - mDevice.pressKeyCode(KeyEvent.KEYCODE_0); - mDevice.waitForWindowUpdate(SETTINGS_PACKAGE, 5); - mDevice.pressEnter(); - mDevice.waitForWindowUpdate(SETTINGS_PACKAGE, 5); - - int resId = res.getIdentifier("unlock_set_unlock_off_title", "string", SETTINGS_PACKAGE); - findAndClick(By.text(res.getString(resId))); - mDevice.waitForWindowUpdate(SETTINGS_PACKAGE, 5); - - findAndClick(By.res("android", "button1")); - mDevice.waitForIdle(TIMEOUT); - } - - private void findAndClick(BySelector selector) - { - for (int i = 0; i < 3; i++) { - mDevice.wait(Until.findObject(selector), TIMEOUT); - UiObject2 obj = mDevice.findObject(selector); - if (obj != null) { - obj.click(); - return; - } - } - Assert.fail("Could not find and click " + selector); - } - - private void launchLockScreenSettings() { - final Intent intent = new Intent().setClassName(SETTINGS_PACKAGE, "com.android.settings.password.ChooseLockGeneric"); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - mContext.startActivity(intent); - mDevice.wait(Until.hasObject(By.pkg(SETTINGS_PACKAGE).depth(0)), TIMEOUT); - } -} diff --git a/tests/dsu-test.xml b/tests/dsu-test.xml deleted file mode 100644 index bab2dd2..0000000 --- a/tests/dsu-test.xml +++ /dev/null @@ -1,22 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- 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. ---> -<configuration description="Config for DSU test case"> - <option name="test-suite-tag" value="dsu-tests" /> - <test class="com.android.tradefed.testtype.HostTest" > - <option name="jar" value="DSUEndtoEndTest.jar" /> - </test> -</configuration> - |
