Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2feb47a2 authored by Yifan Hong's avatar Yifan Hong
Browse files

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
parent e71efc3d
Loading
Loading
Loading
Loading
+23 −0
Original line number Diff line number Diff line
@@ -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",
    ],
}
+3 −0
Original line number Diff line number Diff line
@@ -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);
+43 −1
Original line number Diff line number Diff line
@@ -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<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
+115 −0
Original line number Diff line number Diff line
//
// 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();
}