From 2feb47a29b19f950e6633301c78ec828ac88585f Mon Sep 17 00:00:00 2001 From: Yifan Hong Date: Mon, 16 Sep 2019 18:49:43 -0700 Subject: [PATCH] libsnapshot: add snapshotctl ... which handles merge when boot completed. It also dumps debug information when requested. Bug: 135752105 Test: adb shell su 0 snapshotctl dump Test: call snapshotctl merge at different stage of OTA: - before OTA (exit normally) - during OTA is applied (exit with error) - after OTA is applied but before reboot (exit with error) - after reboot (finish merge and exit normally) - Manually stop its execution during merge and run again (finish merge and exit normally) Change-Id: Idcc6aea8d7bbeb9a1a288c966b8f5e14b3f6a3e7 --- fs_mgr/libsnapshot/Android.bp | 23 ++++ .../include/libsnapshot/snapshot.h | 3 + fs_mgr/libsnapshot/snapshot.cpp | 44 ++++++- fs_mgr/libsnapshot/snapshotctl.cpp | 115 ++++++++++++++++++ 4 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 fs_mgr/libsnapshot/snapshotctl.cpp diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp index bb941dd9b..0c32c15d5 100644 --- a/fs_mgr/libsnapshot/Android.bp +++ b/fs_mgr/libsnapshot/Android.bp @@ -114,3 +114,26 @@ cc_test { "libstorage_literals_headers", ], } + +cc_binary { + name: "snapshotctl", + srcs: [ + "snapshotctl.cpp", + ], + static_libs: [ + "libdm", + "libext2_uuid", + "libfiemap_binder", + "libfstab", + "libsnapshot", + ], + shared_libs: [ + "libbase", + "libbinder", + "libext4_utils", + "libfs_mgr", + "libutils", + "liblog", + "liblp", + ], +} diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h index 6130a10fe..0dd275a1b 100644 --- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h +++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h @@ -192,6 +192,9 @@ class SnapshotManager final { // call to CreateLogicalPartitions when snapshots are present. bool CreateLogicalAndSnapshotPartitions(const std::string& super_device); + // Dump debug information. + bool Dump(std::ostream& os); + private: FRIEND_TEST(SnapshotTest, CleanFirstStageMount); FRIEND_TEST(SnapshotTest, CreateSnapshot); diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp index 803bdb550..9d0272b33 100644 --- a/fs_mgr/libsnapshot/snapshot.cpp +++ b/fs_mgr/libsnapshot/snapshot.cpp @@ -1482,7 +1482,7 @@ auto SnapshotManager::OpenFile(const std::string& file, int open_flags, int lock PLOG(ERROR) << "Open failed: " << file; return nullptr; } - if (flock(fd, lock_flags) < 0) { + if (lock_flags != 0 && flock(fd, lock_flags) < 0) { PLOG(ERROR) << "Acquire flock failed: " << file; return nullptr; } @@ -1962,5 +1962,47 @@ bool SnapshotManager::UnmapUpdateSnapshot(const std::string& target_partition_na return UnmapPartitionWithSnapshot(lock.get(), target_partition_name); } +bool SnapshotManager::Dump(std::ostream& os) { + // Don't actually lock. Dump() is for debugging purposes only, so it is okay + // if it is racy. + auto file = OpenStateFile(O_RDONLY, 0); + if (!file) return false; + + std::stringstream ss; + + ss << "Update state: " << ReadUpdateState(file.get()) << std::endl; + + auto boot_file = GetSnapshotBootIndicatorPath(); + std::string boot_indicator; + if (android::base::ReadFileToString(boot_file, &boot_indicator)) { + ss << "Boot indicator: old slot = " << boot_indicator << std::endl; + } + + bool ok = true; + std::vector snapshots; + if (!ListSnapshots(file.get(), &snapshots)) { + LOG(ERROR) << "Could not list snapshots"; + snapshots.clear(); + ok = false; + } + for (const auto& name : snapshots) { + ss << "Snapshot: " << name << std::endl; + SnapshotStatus status; + if (!ReadSnapshotStatus(file.get(), name, &status)) { + ok = false; + continue; + } + ss << " state: " << to_string(status.state) << std::endl; + ss << " device size (bytes): " << status.device_size << std::endl; + ss << " snapshot size (bytes): " << status.snapshot_size << std::endl; + ss << " cow partition size (bytes): " << status.cow_partition_size << std::endl; + ss << " cow file size (bytes): " << status.cow_file_size << std::endl; + ss << " allocated sectors: " << status.sectors_allocated << std::endl; + ss << " metadata sectors: " << status.metadata_sectors << std::endl; + } + os << ss.rdbuf(); + return ok; +} + } // namespace snapshot } // namespace android diff --git a/fs_mgr/libsnapshot/snapshotctl.cpp b/fs_mgr/libsnapshot/snapshotctl.cpp new file mode 100644 index 000000000..d65320c01 --- /dev/null +++ b/fs_mgr/libsnapshot/snapshotctl.cpp @@ -0,0 +1,115 @@ +// +// 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 + +#include +#include +#include + +#include +#include + +using namespace std::string_literals; + +int Usage() { + std::cerr << "snapshotctl: Control snapshots.\n" + "Usage: snapshotctl [action] [flags]\n" + "Actions:\n" + " dump\n" + " Print snapshot states.\n" + " merge [--logcat]\n" + " Initialize merge and wait for it to be completed.\n" + " If --logcat is specified, log to logcat. Otherwise, log to stdout.\n"; + return EX_USAGE; +} + +namespace android { +namespace snapshot { + +bool DumpCmdHandler(int /*argc*/, char** argv) { + android::base::InitLogging(argv, &android::base::StderrLogger); + return SnapshotManager::New()->Dump(std::cout); +} + +bool MergeCmdHandler(int argc, char** argv) { + auto begin = std::chrono::steady_clock::now(); + + bool log_to_logcat = false; + for (int i = 2; i < argc; ++i) { + if (argv[i] == "--logcat"s) { + log_to_logcat = true; + } + } + if (log_to_logcat) { + android::base::InitLogging(argv); + } else { + android::base::InitLogging(argv, &android::base::StdioLogger); + } + + auto sm = SnapshotManager::New(); + + auto state = sm->GetUpdateState(); + if (state == UpdateState::None) { + LOG(INFO) << "Can't find any snapshot to merge."; + return true; + } + if (state == UpdateState::Unverified) { + if (!sm->InitiateMerge()) { + LOG(ERROR) << "Failed to initiate merge."; + return false; + } + } + + // All other states can be handled by ProcessUpdateState. + LOG(INFO) << "Waiting for any merge to complete. This can take up to 1 minute."; + state = SnapshotManager::New()->ProcessUpdateState(); + + if (state == UpdateState::MergeCompleted) { + auto end = std::chrono::steady_clock::now(); + auto passed = std::chrono::duration_cast(end - begin).count(); + LOG(INFO) << "Snapshot merged in " << passed << " ms."; + return true; + } + + LOG(ERROR) << "Snapshot failed to merge with state \"" << state << "\"."; + return false; +} + +static std::map> kCmdMap = { + // clang-format off + {"dump", DumpCmdHandler}, + {"merge", MergeCmdHandler}, + // clang-format on +}; + +} // namespace snapshot +} // namespace android + +int main(int argc, char** argv) { + using namespace android::snapshot; + if (argc < 2) { + return Usage(); + } + + for (const auto& cmd : kCmdMap) { + if (cmd.first == argv[1]) { + return cmd.second(argc, argv) ? EX_OK : EX_SOFTWARE; + } + } + + return Usage(); +}