diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2021-07-15 02:07:04 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2021-07-15 02:07:04 +0000 |
commit | 6616caca652e832fb6144d3b428468bc1414c9cb (patch) | |
tree | d57fde808eb29d5bc6d96fa2497d95e4257729ba | |
parent | 5e06a0a2fb83f921ad8c9a25c7b70c241dc675e8 (diff) | |
parent | 3631a30920e393cd9608cc51ed2a3ea49d0bd620 (diff) | |
download | gsid-android12-mainline-captiveportallogin-release.tar.gz |
Snap for 7550930 from 3631a30920e393cd9608cc51ed2a3ea49d0bd620 to mainline-captiveportallogin-releaseandroid-mainline-12.0.0_r6android-mainline-12.0.0_r23android12-mainline-captiveportallogin-release
Change-Id: If53ad79c5d6839eb097c9c4d3501edcc6a53ce7b
-rw-r--r-- | Android.bp | 27 | ||||
-rw-r--r-- | AndroidTest.xml | 30 | ||||
-rw-r--r-- | aidl/android/gsi/IGsiService.aidl | 20 | ||||
-rw-r--r-- | daemon.cpp | 6 | ||||
-rw-r--r-- | gsi_service.cpp | 127 | ||||
-rw-r--r-- | gsi_service.h | 2 | ||||
-rw-r--r-- | gsi_tool.cpp | 148 | ||||
-rw-r--r-- | gsid.rc | 5 | ||||
-rw-r--r-- | include/libgsi/libgsi.h | 40 | ||||
-rw-r--r-- | libgsi.cpp | 4 | ||||
-rw-r--r-- | libgsi_private.h | 3 | ||||
-rw-r--r-- | libgsid.cpp | 1 | ||||
-rw-r--r-- | partition_installer.cpp | 60 | ||||
-rw-r--r-- | partition_installer.h | 15 | ||||
-rw-r--r-- | tests/Android.bp | 14 | ||||
-rw-r--r-- | tests/AndroidManifest.xml | 30 | ||||
-rw-r--r-- | tests/DSUEndtoEndTest.java | 34 | ||||
-rw-r--r-- | tests/LockScreenAutomation.java | 151 |
18 files changed, 382 insertions, 335 deletions
@@ -14,6 +14,10 @@ // limitations under the License. // +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + cc_binary { name: "gsi_tool", shared_libs: [ @@ -55,14 +59,8 @@ cc_library_static { "gsi_aidl_interface-cpp", "libbase", "libbinder", - "libcutils", - "liblog", - "libservices", "libutils", ], - static_libs: [ - "libgsi", - ], export_include_dirs: ["include"], } @@ -103,9 +101,22 @@ cc_binary { "libgsi", "libgsid", "liblp", + "libselinux", "libutils", "libc++fs", + "libvold_binder", + ], + header_libs: [ + "libstorage_literals_headers", ], + target: { + android: { + shared_libs: [ + "libprocessgroup", + "libvndksupport", + ], + }, + }, local_include_dirs: ["include"], } @@ -134,7 +145,3 @@ filegroup { ], path: "aidl", } - -vts_config { - name: "VtsGsiBootTest", -} diff --git a/AndroidTest.xml b/AndroidTest.xml deleted file mode 100644 index 09ec7bd..0000000 --- a/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 VtsGsiBootTest"> - <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="VtsGsiBootTest"/> - <option name="binary-test-source" value="_32bit::DATA/nativetest/gsi_boot_test/gsi_boot_test" /> - <option name="binary-test-source" value="_64bit::DATA/nativetest64/gsi_boot_test/gsi_boot_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/aidl/android/gsi/IGsiService.aidl b/aidl/android/gsi/IGsiService.aidl index 5503493..c889987 100644 --- a/aidl/android/gsi/IGsiService.aidl +++ b/aidl/android/gsi/IGsiService.aidl @@ -90,6 +90,7 @@ interface IGsiService { * Asynchronous enableGsi * @param result callback for result */ + @SuppressWarnings(value={"mixed-oneway"}) oneway void enableGsiAsync(boolean oneShot, @utf8InCpp String dsuSlot, IGsiServiceCallback result); /** @@ -120,6 +121,7 @@ interface IGsiService { * Asynchronous removeGsi * @param result callback for result */ + @SuppressWarnings(value={"mixed-oneway"}) oneway void removeGsiAsync(IGsiServiceCallback result); /** @@ -179,6 +181,16 @@ interface IGsiService { int createPartition(in @utf8InCpp String name, long size, boolean readOnly); /** + * Complete the current partition installation. A partition installation is + * complete after all pending bytes are written successfully. + * Returns an error if current installation still have pending bytes. + * Returns an error if there is any internal filesystem error. + * + * @return 0 on success, an error code on failure. + */ + int closePartition(); + + /** * Wipe a partition. This will not work if the GSI is currently running. * The partition will not be removed, but the first block will be zeroed. * @@ -212,10 +224,16 @@ interface IGsiService { * 2. Open a new partition installer. * 3. Create and map the new partition. * - * In other words, getAvbPublicKey() works between two createPartition() calls. + * In other words, getAvbPublicKey() should be called after + * createPartition() is called and before closePartition() is called. * * @param dst Output the AVB public key. * @return 0 on success, an error code on failure. */ int getAvbPublicKey(out AvbPublicKey dst); + + /** + * Returns the suggested scratch partition size for overlayFS. + */ + long suggestScratchSize(); } @@ -14,8 +14,6 @@ // limitations under the License. // -#include <getopt.h> - #include <iostream> #include <string> @@ -23,7 +21,6 @@ #include <binder/BinderService.h> #include <binder/IPCThreadState.h> #include <binder/ProcessState.h> -#include <libgsi/libgsi.h> #include <libgsi/libgsid.h> #include "gsi_service.h" @@ -52,6 +49,9 @@ static int DumpDeviceMapper() { int main(int argc, char** argv) { android::base::InitLogging(argv, android::base::LogdLogger(android::base::SYSTEM)); + // Create globally readable files. + umask(0022); + if (argc > 1) { if (argv[1] == "run-startup-tasks"s) { android::gsi::GsiService::RunStartupTasks(); diff --git a/gsi_service.cpp b/gsi_service.cpp index ab2339b..939f603 100644 --- a/gsi_service.cpp +++ b/gsi_service.cpp @@ -16,12 +16,7 @@ #include "gsi_service.h" -#include <errno.h> -#include <linux/fs.h> -#include <stdio.h> -#include <sys/ioctl.h> -#include <sys/stat.h> -#include <sys/types.h> +#include <sys/statvfs.h> #include <sys/vfs.h> #include <unistd.h> @@ -38,6 +33,8 @@ #include <android-base/strings.h> #include <android/gsi/BnImageService.h> #include <android/gsi/IGsiService.h> +#include <android/os/IVold.h> +#include <binder/IServiceManager.h> #include <binder/LazyServiceRegistrar.h> #include <ext4_utils/ext4_utils.h> #include <fs_mgr.h> @@ -46,6 +43,8 @@ #include <libfiemap/image_manager.h> #include <openssl/sha.h> #include <private/android_filesystem_config.h> +#include <selinux/android.h> +#include <storage_literals/storage_literals.h> #include "file_paths.h" #include "libgsi_private.h" @@ -56,6 +55,7 @@ namespace gsi { using namespace std::literals; using namespace android::fs_mgr; using namespace android::fiemap; +using namespace android::storage_literals; using android::base::ReadFileToString; using android::base::ReadFullyAtOffset; using android::base::RemoveFileIfExists; @@ -67,13 +67,21 @@ using android::base::WriteStringToFile; using android::binder::LazyServiceRegistrar; using android::dm::DeviceMapper; -static std::mutex sInstanceLock; - // Default userdata image size. static constexpr int64_t kDefaultUserdataSize = int64_t(2) * 1024 * 1024 * 1024; static bool GetAvbPublicKeyFromFd(int fd, AvbPublicKey* dst); +// Fix the file contexts of dsu metadata files. +// By default, newly created files inherit the file contexts of their parent +// directory. Since globally readable public metadata files are labeled with a +// different context, gsi_public_metadata_file, we need to call this function to +// fix their contexts after creating them. +static void RestoreconMetadataFiles() { + auto flags = SELINUX_ANDROID_RESTORECON_RECURSE | SELINUX_ANDROID_RESTORECON_SKIP_SEHASH; + selinux_android_restorecon(DSU_METADATA_PREFIX, flags); +} + GsiService::GsiService() { progress_ = {}; } @@ -119,6 +127,8 @@ int GsiService::SaveInstallation(const std::string& installation) { return INSTALL_OK; } +static bool IsExternalStoragePath(const std::string& path); + binder::Status GsiService::openInstall(const std::string& install_dir, int* _aidl_return) { ENFORCE_SYSTEM; std::lock_guard<std::mutex> guard(lock_); @@ -165,9 +175,6 @@ binder::Status GsiService::createPartition(const ::std::string& name, int64_t si return binder::Status::ok(); } - // Make sure a pending interrupted installations are cleaned up. - installer_ = nullptr; - // Do some precursor validation on the arguments before diving into the // install process. if (size % LP_SECTOR_SIZE) { @@ -179,14 +186,38 @@ binder::Status GsiService::createPartition(const ::std::string& name, int64_t si if (size == 0 && name == "userdata") { size = kDefaultUserdataSize; } + + if (name == "userdata") { + auto dsu_slot = GetDsuSlot(install_dir_); + auto key_dir = DefaultDsuMetadataKeyDir(dsu_slot); + auto key_dir_file = DsuMetadataKeyDirFile(dsu_slot); + if (!android::base::WriteStringToFile(key_dir, key_dir_file)) { + PLOG(ERROR) << "write failed: " << key_dir_file; + *_aidl_return = INSTALL_ERROR_GENERIC; + return binder::Status::ok(); + } + RestoreconMetadataFiles(); + } + installer_ = std::make_unique<PartitionInstaller>(this, install_dir_, name, GetDsuSlot(install_dir_), size, readOnly); progress_ = {}; - int status = installer_->StartInstall(); - if (status != INSTALL_OK) { - installer_ = nullptr; + *_aidl_return = installer_->StartInstall(); + return binder::Status::ok(); +} + +binder::Status GsiService::closePartition(int32_t* _aidl_return) { + ENFORCE_SYSTEM; + std::lock_guard<std::mutex> guard(lock_); + + if (installer_ == nullptr) { + LOG(ERROR) << "createPartition() has to be called before closePartition()"; + *_aidl_return = IGsiService::INSTALL_ERROR_GENERIC; + return binder::Status::ok(); } - *_aidl_return = status; + // It is important to not reset |installer_| here because other methods such + // as enableGsi() relies on the state of |installer_|. + *_aidl_return = installer_->FinishInstall(); return binder::Status::ok(); } @@ -278,6 +309,7 @@ binder::Status GsiService::enableGsi(bool one_shot, const std::string& dsuSlot, *_aidl_return = INSTALL_ERROR_GENERIC; return binder::Status::ok(); } + RestoreconMetadataFiles(); if (installer_) { ENFORCE_SYSTEM; installer_ = {}; @@ -472,6 +504,31 @@ binder::Status GsiService::getAvbPublicKey(AvbPublicKey* dst, int32_t* _aidl_ret return binder::Status::ok(); } +binder::Status GsiService::suggestScratchSize(int64_t* _aidl_return) { + ENFORCE_SYSTEM; + + static constexpr uint64_t kMinScratchSize = 512_MiB; + static constexpr uint64_t kMaxScratchSize = 2_GiB; + + uint64_t size = 0; + struct statvfs info; + if (statvfs(install_dir_.c_str(), &info)) { + PLOG(ERROR) << "Could not statvfs(" << install_dir_ << ")"; + } else { + // Keep the storage device at least 40% free, plus 1% for jitter. + constexpr int jitter = 1; + const uint64_t reserved_blocks = + static_cast<uint64_t>(info.f_blocks) * (kMinimumFreeSpaceThreshold + jitter) / 100; + if (info.f_bavail > reserved_blocks) { + size = (info.f_bavail - reserved_blocks) * info.f_frsize; + } + } + + // We can safely downcast the result here, since we clamped the result within int64_t range. + *_aidl_return = std::clamp(size, kMinScratchSize, kMaxScratchSize); + return binder::Status::ok(); +} + bool GsiService::CreateInstallStatusFile() { if (!android::base::WriteStringToFile("0", kDsuInstallStatusFile)) { PLOG(ERROR) << "write " << kDsuInstallStatusFile; @@ -707,6 +764,8 @@ bool ImageService::CheckUid() { binder::Status GsiService::openImageService(const std::string& prefix, android::sp<IImageService>* _aidl_return) { + using android::base::StartsWith; + static constexpr char kImageMetadataPrefix[] = "/metadata/gsi/"; static constexpr char kImageDataPrefix[] = "/data/gsi/"; @@ -728,9 +787,11 @@ binder::Status GsiService::openImageService(const std::string& prefix, PLOG(ERROR) << "realpath failed for data: " << in_data_dir; return BinderError("Invalid path"); } - if (!android::base::StartsWith(metadata_dir, kImageMetadataPrefix) || - !android::base::StartsWith(data_dir, kImageDataPrefix)) { - return BinderError("Invalid path"); + if (!StartsWith(metadata_dir, kImageMetadataPrefix)) { + return BinderError("Invalid metadata path"); + } + if (!StartsWith(data_dir, kImageDataPrefix) && !StartsWith(data_dir, kDsuSDPrefix)) { + return BinderError("Invalid data path"); } uid_t uid = IPCThreadState::self()->getCallingUid(); @@ -763,7 +824,7 @@ binder::Status GsiService::CheckUid(AccessLevel level) { } static bool IsExternalStoragePath(const std::string& path) { - if (!android::base::StartsWith(path, "/mnt/media_rw/")) { + if (!android::base::StartsWith(path, kDsuSDPrefix)) { return false; } unique_fd fd(open(path.c_str(), O_RDONLY | O_CLOEXEC | O_NOFOLLOW)); @@ -788,6 +849,14 @@ int GsiService::ValidateInstallParams(std::string& install_dir) { install_dir = kDefaultDsuImageFolder; } + if (access(install_dir.c_str(), F_OK) != 0 && (errno == ENOENT)) { + if (android::base::StartsWith(install_dir, kDsuSDPrefix)) { + if (mkdir(install_dir.c_str(), 0755) != 0) { + PLOG(ERROR) << "Failed to create " << install_dir; + return INSTALL_ERROR_GENERIC; + } + } + } // Normalize the path and add a trailing slash. std::string origInstallDir = install_dir; if (!android::base::Realpath(origInstallDir, &install_dir)) { @@ -878,6 +947,10 @@ int GsiService::ReenableGsi(bool one_shot) { return IGsiService::INSTALL_OK; } +static android::sp<android::os::IVold> GetVoldService() { + return android::waitForService<android::os::IVold>(android::String16("vold")); +} + bool GsiService::RemoveGsiFiles(const std::string& install_dir) { bool ok = true; auto active_dsu = GetDsuSlot(install_dir); @@ -907,6 +980,22 @@ bool GsiService::RemoveGsiFiles(const std::string& install_dir) { ok = false; } } + if (auto vold = GetVoldService()) { + auto status = vold->destroyDsuMetadataKey(dsu_slot); + if (status.isOk()) { + std::string message; + if (!RemoveFileIfExists(DsuMetadataKeyDirFile(dsu_slot), &message)) { + LOG(ERROR) << message; + ok = false; + } + } else { + LOG(ERROR) << "Failed to destroy DSU metadata encryption key."; + ok = false; + } + } else { + LOG(ERROR) << "Failed to retrieve vold service."; + ok = false; + } if (ok) { SetProperty(kGsiInstalledProp, "0"); } diff --git a/gsi_service.h b/gsi_service.h index 229db36..c9e4e05 100644 --- a/gsi_service.h +++ b/gsi_service.h @@ -42,6 +42,7 @@ class GsiService : public BinderService<GsiService>, public BnGsiService { binder::Status closeInstall(int32_t* _aidl_return) override; binder::Status createPartition(const ::std::string& name, int64_t size, bool readOnly, int32_t* _aidl_return) override; + binder::Status closePartition(int32_t* _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; @@ -67,6 +68,7 @@ class GsiService : public BinderService<GsiService>, public BnGsiService { android::sp<IImageService>* _aidl_return) override; binder::Status dumpDeviceMapperDevices(std::string* _aidl_return) override; binder::Status getAvbPublicKey(AvbPublicKey* dst, int32_t* _aidl_return) override; + binder::Status suggestScratchSize(int64_t* _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. diff --git a/gsi_tool.cpp b/gsi_tool.cpp index 4c246cc..a6a79a5 100644 --- a/gsi_tool.cpp +++ b/gsi_tool.cpp @@ -35,7 +35,6 @@ #include <android-base/strings.h> #include <android-base/unique_fd.h> #include <android/gsi/IGsiService.h> -#include <binder/IServiceManager.h> #include <cutils/android_reboot.h> #include <libgsi/libgsi.h> #include <libgsi/libgsid.h> @@ -51,6 +50,7 @@ using CommandCallback = std::function<int(sp<IGsiService>, int, char**)>; static int Disable(sp<IGsiService> gsid, int argc, char** argv); static int Enable(sp<IGsiService> gsid, int argc, char** argv); static int Install(sp<IGsiService> gsid, int argc, char** argv); +static int CreatePartition(sp<IGsiService> gsid, int argc, char** argv); static int Wipe(sp<IGsiService> gsid, int argc, char** argv); static int WipeData(sp<IGsiService> gsid, int argc, char** argv); static int Status(sp<IGsiService> gsid, int argc, char** argv); @@ -61,6 +61,7 @@ static const std::map<std::string, CommandCallback> kCommandMap = { {"disable", Disable}, {"enable", Enable}, {"install", Install}, + {"create-partition", CreatePartition}, {"wipe", Wipe}, {"wipe-data", WipeData}, {"status", Status}, @@ -256,7 +257,7 @@ static int Install(sp<IGsiService> gsid, int argc, char** argv) { return EX_SOFTWARE; } - android::base::unique_fd input(dup(1)); + android::base::unique_fd input(dup(STDIN_FILENO)); if (input < 0) { std::cerr << "Error duplicating descriptor: " << strerror(errno) << std::endl; return EX_SOFTWARE; @@ -277,6 +278,12 @@ static int Install(sp<IGsiService> gsid, int argc, char** argv) { << "\n"; return EX_SOFTWARE; } + status = gsid->closePartition(&error); + if (!status.isOk() || error != IGsiService::INSTALL_OK) { + std::cerr << "Could not closePartition(userdata): " << ErrorMessage(status, error) + << std::endl; + return EX_SOFTWARE; + } } status = gsid->createPartition(partition, gsiSize, true, &error); @@ -294,6 +301,13 @@ static int Install(sp<IGsiService> gsid, int argc, char** argv) { return EX_SOFTWARE; } + status = gsid->closePartition(&error); + if (!status.isOk() || error != IGsiService::INSTALL_OK) { + std::cerr << "Could not closePartition(" << partition + << "): " << ErrorMessage(status, error) << std::endl; + return EX_SOFTWARE; + } + status = gsid->closeInstall(&error); if (!status.isOk() || error != IGsiService::INSTALL_OK) { std::cerr << "Could not close DSU installation: " << ErrorMessage(status, error) << "\n"; @@ -323,6 +337,134 @@ static int Install(sp<IGsiService> gsid, int argc, char** argv) { return 0; } +// Experimental API +static int CreatePartition(sp<IGsiService> gsid, int argc, char** argv) { + std::string installDir; + std::string partitionName; + bool readOnly = true; + int64_t partitionSize = 0; + + struct option options[] = { + {"install-dir", required_argument, nullptr, 'i'}, + {"partition-name", required_argument, nullptr, 'p'}, + {"readwrite", no_argument, nullptr, 'r'}, + {"size", required_argument, nullptr, 's'}, + {nullptr, 0, nullptr, 0}, + }; + + int rv = 0; + while ((rv = getopt_long_only(argc, argv, "", options, nullptr)) != -1) { + switch (rv) { + case 'i': + installDir = optarg; + break; + case 'p': + partitionName = optarg; + break; + case 'r': + readOnly = false; + break; + case 's': + if (!android::base::ParseInt(optarg, &partitionSize)) { + std::cerr << "Could not parse partition size: " << optarg << std::endl; + return EX_USAGE; + } + break; + default: + return EX_USAGE; + } + } + + if (getuid() != 0) { + std::cerr << "must be root to install a DSU" << std::endl; + return EX_NOPERM; + } + + bool gsiRunning = false; + auto status = gsid->isGsiRunning(&gsiRunning); + if (!status.isOk()) { + std::cerr << "Could not get DSU running status: " << ErrorMessage(status) << std::endl; + return EX_SOFTWARE; + } + if (gsiRunning) { + std::cerr << "Could not install DSU within an active DSU." << std::endl; + return EX_SOFTWARE; + } + + if (partitionSize <= 0) { + std::cerr << "Partition size must be greater than zero: " << partitionSize << std::endl; + return EX_USAGE; + } + + // Note: the progress bar needs to be re-started in between each call. + ProgressBar progress(gsid); + progress.Display(); + + int error; + status = gsid->openInstall(installDir, &error); + if (!status.isOk() || error != IGsiService::INSTALL_OK) { + std::cerr << "Could not open DSU installation: " << ErrorMessage(status, error) + << std::endl; + return EX_SOFTWARE; + } + + status = gsid->createPartition(partitionName, partitionSize, readOnly, &error); + if (!status.isOk() || error != IGsiService::INSTALL_OK) { + std::cerr << "Could not create DSU partition: " << ErrorMessage(status, error) << std::endl; + return EX_SOFTWARE; + } + + if (readOnly) { + android::base::unique_fd input(dup(STDIN_FILENO)); + if (input < 0) { + std::cerr << "Error duplicating descriptor: " << strerror(errno) << std::endl; + return EX_SOFTWARE; + } + android::os::ParcelFileDescriptor stream(std::move(input)); + + bool ok = false; + status = gsid->commitGsiChunkFromStream(stream, partitionSize, &ok); + if (!ok) { + std::cerr << "Could not commit data from stdin: " << ErrorMessage(status) << std::endl; + return EX_SOFTWARE; + } + } + + status = gsid->closePartition(&error); + if (!status.isOk() || error != IGsiService::INSTALL_OK) { + std::cerr << "Could not close DSU partition:" << ErrorMessage(status, error) << std::endl; + return EX_SOFTWARE; + } + + status = gsid->closeInstall(&error); + if (!status.isOk() || error != IGsiService::INSTALL_OK) { + std::cerr << "Could not close DSU installation: " << ErrorMessage(status, error) + << std::endl; + return EX_SOFTWARE; + } + + progress.Finish(); + + std::string dsuSlot; + status = gsid->getActiveDsuSlot(&dsuSlot); + if (!status.isOk()) { + std::cerr << "Could not get the active DSU slot: " << ErrorMessage(status) << std::endl; + return EX_SOFTWARE; + } + + // Immediately enable DSU after a partition is installed to ensure the installation status file + // is created. + status = gsid->enableGsi(/* one_shot = */ true, dsuSlot, &error); + if (!status.isOk() || error != IGsiService::INSTALL_OK) { + std::cerr << "Could not make DSU bootable: " << ErrorMessage(status, error) << std::endl; + return EX_SOFTWARE; + } + + std::cout << "Enabled DSU slot: " << dsuSlot << std::endl; + std::cout << "Please reboot to use the DSU." << std::endl; + return 0; +} + static int Wipe(sp<IGsiService> gsid, int argc, char** /* argv */) { if (argc > 1) { std::cerr << "Unrecognized arguments to wipe." << std::endl; @@ -570,7 +712,7 @@ static int usage(int /* argc */, char* argv[]) { } int main(int argc, char** argv) { - android::base::InitLogging(argv, android::base::StdioLogger, android::base::DefaultAborter); + android::base::InitLogging(argv, android::base::StderrLogger, android::base::DefaultAborter); android::sp<IGsiService> service = GetGsiService(); if (!service) { @@ -10,8 +10,13 @@ on post-fs mkdir /metadata/gsi/dsu 0771 root system mkdir /metadata/gsi/ota 0771 root system mkdir /metadata/gsi/remount 0771 root system + chmod 0664 /metadata/gsi/dsu/active + chmod 0664 /metadata/gsi/dsu/booted + chmod 0664 /metadata/gsi/dsu/lp_names on post-fs-data + write /data/gsi_persistent_data 0 + chown system system /data/gsi_persistent_data mkdir /data/gsi 0700 root root encryption=None mkdir /data/gsi/dsu 0700 root root mkdir /data/gsi/ota 0700 root root diff --git a/include/libgsi/libgsi.h b/include/libgsi/libgsi.h index fbe2f17..41898df 100644 --- a/include/libgsi/libgsi.h +++ b/include/libgsi/libgsi.h @@ -18,6 +18,9 @@ #include <string> +#include <android-base/file.h> +#include <android-base/strings.h> + namespace android { namespace gsi { @@ -25,6 +28,14 @@ static constexpr char kGsiServiceName[] = "gsiservice"; #define DSU_METADATA_PREFIX "/metadata/gsi/dsu/" +// These files need to be globally readable so that fs_mgr_fstab, which is +// statically linked into processes, can return consistent result for non-root +// processes: +// * kDsuActiveFile +// * kGsiBootedIndicatorFile +// * kGsiLpNamesFile +// * DsuMetadataKeyDirFile(slot) + static constexpr char kGsiBootedIndicatorFile[] = DSU_METADATA_PREFIX "booted"; static constexpr char kGsiLpNamesFile[] = DSU_METADATA_PREFIX "lp_names"; @@ -33,6 +44,10 @@ static constexpr char kDsuActiveFile[] = DSU_METADATA_PREFIX "active"; static constexpr char kDsuAvbKeyDir[] = DSU_METADATA_PREFIX "avb/"; +static constexpr char kDsuMetadataKeyDirPrefix[] = "/metadata/vold/metadata_encryption/dsu/"; + +static constexpr char kDsuSDPrefix[] = "/mnt/media_rw/"; + static inline std::string DsuLpMetadataFile(const std::string& dsu_slot) { return DSU_METADATA_PREFIX + dsu_slot + "/lp_metadata"; } @@ -41,6 +56,24 @@ static inline std::string DsuInstallDirFile(const std::string& dsu_slot) { return DSU_METADATA_PREFIX + dsu_slot + "/install_dir"; } +static inline std::string DsuMetadataKeyDirFile(const std::string& dsu_slot) { + return DSU_METADATA_PREFIX + dsu_slot + "/metadata_encryption_dir"; +} + +static inline std::string DefaultDsuMetadataKeyDir(const std::string& dsu_slot) { + return kDsuMetadataKeyDirPrefix + dsu_slot; +} + +static inline std::string GetDsuMetadataKeyDir(const std::string& dsu_slot) { + auto key_dir_file = DsuMetadataKeyDirFile(dsu_slot); + std::string key_dir; + if (android::base::ReadFileToString(key_dir_file, &key_dir) && + android::base::StartsWith(key_dir, kDsuMetadataKeyDirPrefix)) { + return key_dir; + } + return DefaultDsuMetadataKeyDir(dsu_slot); +} + // install_dir "/data/gsi/dsu/dsu" has a slot name "dsu" // install_dir "/data/gsi/dsu/dsu2" has a slot name "dsu2" std::string GetDsuSlot(const std::string& install_dir); @@ -51,11 +84,16 @@ static constexpr char kGsiInstalledProp[] = "gsid.image_installed"; static constexpr char kDsuPostfix[] = "_gsi"; +inline constexpr char kDsuScratch[] = "scratch_gsi"; +inline constexpr char kDsuUserdata[] = "userdata_gsi"; + static constexpr int kMaxBootAttempts = 1; // Get the currently active dsu slot // Return true on success -bool GetActiveDsu(std::string* active_dsu); +static inline bool GetActiveDsu(std::string* active_dsu) { + return android::base::ReadFileToString(kDsuActiveFile, active_dsu); +} // Returns true if the currently running system image is a live GSI. bool IsGsiRunning(); @@ -37,10 +37,6 @@ using android::base::ReadFileToString; using android::base::Split; using android::base::unique_fd; -bool GetActiveDsu(std::string* active_dsu) { - return android::base::ReadFileToString(kDsuActiveFile, active_dsu); -} - bool IsGsiRunning() { return !access(kGsiBootedIndicatorFile, F_OK); } diff --git a/libgsi_private.h b/libgsi_private.h index 51c7915..82814a9 100644 --- a/libgsi_private.h +++ b/libgsi_private.h @@ -28,5 +28,8 @@ static constexpr char kInstallStatusOk[] = "ok"; static constexpr char kInstallStatusWipe[] = "wipe"; static constexpr char kInstallStatusDisabled[] = "disabled"; +// We are looking for /data to have at least 40% free space. +static constexpr uint32_t kMinimumFreeSpaceThreshold = 40; + } // namespace gsi } // namespace android diff --git a/libgsid.cpp b/libgsid.cpp index b42833c..23eeae4 100644 --- a/libgsid.cpp +++ b/libgsid.cpp @@ -15,7 +15,6 @@ // #include <android-base/logging.h> -#include <android-base/properties.h> #include <android/gsi/IGsiService.h> #include <binder/IServiceManager.h> #include <libgsi/libgsi.h> diff --git a/partition_installer.cpp b/partition_installer.cpp index 357df50..79af71a 100644 --- a/partition_installer.cpp +++ b/partition_installer.cpp @@ -39,10 +39,6 @@ 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; - PartitionInstaller::PartitionInstaller(GsiService* service, const std::string& install_dir, const std::string& name, const std::string& active_dsu, int64_t size, bool read_only) @@ -56,33 +52,32 @@ PartitionInstaller::PartitionInstaller(GsiService* service, const std::string& i } PartitionInstaller::~PartitionInstaller() { - Finish(); - if (!succeeded_) { - // Close open handles before we remove files. - system_device_ = nullptr; - PostInstallCleanup(images_.get()); + if (FinishInstall() != IGsiService::INSTALL_OK) { + LOG(ERROR) << "Installation failed: install_dir=" << install_dir_ + << ", dsu_slot=" << active_dsu_ << ", partition_name=" << name_; } if (IsAshmemMapped()) { UnmapAshmem(); } } -void PartitionInstaller::PostInstallCleanup() { - auto manager = ImageManager::Open(MetadataDir(active_dsu_), install_dir_); - if (!manager) { - LOG(ERROR) << "Could not open image manager"; - return; - } - return PostInstallCleanup(manager.get()); -} - -void PartitionInstaller::PostInstallCleanup(ImageManager* manager) { - std::string file = GetBackingFile(name_); - if (manager->IsImageMapped(file)) { - LOG(ERROR) << "unmap " << file; - manager->UnmapImageDevice(file); +int PartitionInstaller::FinishInstall() { + if (finished_) { + return finished_status_; + } + finished_ = true; + finished_status_ = CheckInstallState(); + system_device_ = nullptr; + if (finished_status_ != IGsiService::INSTALL_OK) { + auto file = GetBackingFile(name_); + LOG(ERROR) << "Installation failed, clean up: " << file; + if (images_->IsImageMapped(file)) { + LOG(ERROR) << "unmap " << file; + images_->UnmapImageDevice(file); + } + images_->DeleteBackingImage(file); } - manager->DeleteBackingImage(file); + return finished_status_; } int PartitionInstaller::StartInstall() { @@ -96,7 +91,6 @@ int PartitionInstaller::StartInstall() { if (!Format()) { return IGsiService::INSTALL_ERROR_GENERIC; } - succeeded_ = true; } else { // Map ${name}_gsi so we can write to it. system_device_ = OpenPartition(GetBackingFile(name_)); @@ -132,8 +126,8 @@ int PartitionInstaller::PerformSanityChecks() { // 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; + uint64_t free_space = static_cast<uint64_t>(sb.f_bavail) * sb.f_frsize; + uint64_t fs_size = static_cast<uint64_t>(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; @@ -308,26 +302,22 @@ bool PartitionInstaller::Format() { return true; } -int PartitionInstaller::Finish() { - if (readOnly_ && gsi_bytes_written_ != size_) { +int PartitionInstaller::CheckInstallState() { + if (readOnly_ && !IsFinishedWriting()) { // 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 (system_device_ != nullptr && fsync(system_device_->fd())) { - PLOG(ERROR) << "fsync failed for " << name_ << "_gsi"; + if (system_device_ != nullptr && fsync(GetPartitionFd())) { + PLOG(ERROR) << "fsync failed for " << GetBackingFile(name_); 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; } - - succeeded_ = true; return IGsiService::INSTALL_OK; } diff --git a/partition_installer.h b/partition_installer.h index 1503648..920af47 100644 --- a/partition_installer.h +++ b/partition_installer.h @@ -53,14 +53,17 @@ class PartitionInstaller final { static int WipeWritable(const std::string& active_dsu, const std::string& install_dir, const std::string& name); - // Clean up install state if gsid crashed and restarted. - void PostInstallCleanup(); - void PostInstallCleanup(ImageManager* manager); + // Finish a partition installation and release resources. + // If the installation is incomplete or corrupted, the backing image would + // be cleaned up and an error code is returned. + // No method other than FinishInstall() and ~PartitionInstaller() should be + // called after calling this method. + // This method is also called by the destructor to free up resources. + int FinishInstall(); const std::string& install_dir() const { return install_dir_; } private: - int Finish(); int PerformSanityChecks(); int Preallocate(); bool Format(); @@ -82,10 +85,12 @@ class PartitionInstaller final { bool readOnly_; // 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; + bool finished_ = false; + int finished_status_ = 0; + std::unique_ptr<MappedDevice> system_device_; }; diff --git a/tests/Android.bp b/tests/Android.bp index 034209f..2cb67ab 100644 --- a/tests/Android.bp +++ b/tests/Android.bp @@ -14,6 +14,10 @@ // limitations under the License. // +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + cc_defaults { name: "gsi_boot_defaults", shared_libs: [ @@ -58,13 +62,3 @@ java_test_host { 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 index 69d40ad..e717079 100644 --- a/tests/DSUEndtoEndTest.java +++ b/tests/DSUEndtoEndTest.java @@ -47,11 +47,6 @@ import java.util.concurrent.TimeUnit; @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 LPUNPACK_PATH = "bin/lpunpack"; private static final String SIMG2IMG_PATH = "bin/simg2img"; @@ -74,7 +69,6 @@ public class DSUEndtoEndTest extends BaseHostJUnit4Test { @After public void teardown() throws Exception { - uninstallPackage(PACKAGE); if (mUnsparseSystemImage != null) { mUnsparseSystemImage.delete(); } @@ -129,13 +123,6 @@ public class DSUEndtoEndTest extends BaseHostJUnit4Test { 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 " + mUserdataSize + @@ -145,7 +132,7 @@ public class DSUEndtoEndTest extends BaseHostJUnit4Test { expectGsiStatus("running"); - rebootAndUnlock(); + getDevice().rebootUntilOnline(); expectGsiStatus("installed"); @@ -162,16 +149,10 @@ public class DSUEndtoEndTest extends BaseHostJUnit4Test { getDevice().executeShellV2Command("gsi_tool wipe"); - rebootAndUnlock(); + getDevice().rebootUntilOnline(); 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(); } @@ -182,16 +163,5 @@ public class DSUEndtoEndTest extends BaseHostJUnit4Test { 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); - } -} |