Merge "libsnapshot: add snapshotctl"
diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp
index bb941dd..0c32c15 100644
--- a/fs_mgr/libsnapshot/Android.bp
+++ b/fs_mgr/libsnapshot/Android.bp
@@ -114,3 +114,26 @@
         "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 6130a10..0dd275a 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
@@ -192,6 +192,9 @@
     // 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 803bdb5..9d0272b 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -1482,7 +1482,7 @@
         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 @@
     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<std::string> 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 0000000..d65320c
--- /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 <sysexits.h>
+
+#include <chrono>
+#include <iostream>
+#include <map>
+
+#include <android-base/logging.h>
+#include <libsnapshot/snapshot.h>
+
+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<std::chrono::milliseconds>(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<std::string, std::function<bool(int, char**)>> 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();
+}