Merge "fiemap_image_test: libcrypto_utils as static lib"
diff --git a/fastboot/device/commands.cpp b/fastboot/device/commands.cpp
index b8eee4a..2553353 100644
--- a/fastboot/device/commands.cpp
+++ b/fastboot/device/commands.cpp
@@ -625,7 +625,7 @@
         if (!sm) {
             return device->WriteFail("Unable to create SnapshotManager");
         }
-        if (!sm->HandleImminentDataWipe()) {
+        if (!sm->FinishMergeInRecovery()) {
             return device->WriteFail("Unable to finish snapshot merge");
         }
     } else {
diff --git a/fs_mgr/fs_mgr_remount.cpp b/fs_mgr/fs_mgr_remount.cpp
index 24cbad7..052efa7 100644
--- a/fs_mgr/fs_mgr_remount.cpp
+++ b/fs_mgr/fs_mgr_remount.cpp
@@ -105,20 +105,23 @@
 
 }  // namespace
 
+enum RemountStatus {
+    REMOUNT_SUCCESS = 0,
+    NOT_USERDEBUG,
+    BADARG,
+    NOT_ROOT,
+    NO_FSTAB,
+    UNKNOWN_PARTITION,
+    INVALID_PARTITION,
+    VERITY_PARTITION,
+    BAD_OVERLAY,
+    NO_MOUNTS,
+    REMOUNT_FAILED,
+    MUST_REBOOT
+};
+
 static int do_remount(int argc, char* argv[]) {
-    enum {
-        SUCCESS = 0,
-        NOT_USERDEBUG,
-        BADARG,
-        NOT_ROOT,
-        NO_FSTAB,
-        UNKNOWN_PARTITION,
-        INVALID_PARTITION,
-        VERITY_PARTITION,
-        BAD_OVERLAY,
-        NO_MOUNTS,
-        REMOUNT_FAILED,
-    } retval = SUCCESS;
+    RemountStatus retval = REMOUNT_SUCCESS;
 
     // If somehow this executable is delivered on a "user" build, it can
     // not function, so providing a clear message to the caller rather than
@@ -304,8 +307,7 @@
     if (partitions.empty() || just_disabled_verity) {
         if (reboot_later) reboot(setup_overlayfs);
         if (user_please_reboot_later) {
-            LOG(INFO) << "Now reboot your device for settings to take effect";
-            return 0;
+            return MUST_REBOOT;
         }
         LOG(WARNING) << "No partitions to remount";
         return retval;
@@ -394,6 +396,12 @@
 int main(int argc, char* argv[]) {
     android::base::InitLogging(argv, MyLogger);
     int result = do_remount(argc, argv);
-    printf("remount %s\n", result ? "failed" : "succeeded");
+    if (result == MUST_REBOOT) {
+        LOG(INFO) << "Now reboot your device for settings to take effect";
+    } else if (result == REMOUNT_SUCCESS) {
+        printf("remount succeeded\n");
+    } else {
+        printf("remount failed\n");
+    }
     return result;
 }
diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp
index 40da1bc..e916693 100644
--- a/fs_mgr/libsnapshot/Android.bp
+++ b/fs_mgr/libsnapshot/Android.bp
@@ -254,34 +254,47 @@
 
     native_coverage : true,
     srcs: [
+        // Compile the protobuf definition again with type full.
+        "android/snapshot/snapshot_fuzz.proto",
+        "update_engine/update_metadata.proto",
+        "fuzz_utils.cpp",
         "snapshot_fuzz.cpp",
         "snapshot_fuzz_utils.cpp",
-        "fuzz_utils.cpp",
+
+        // Compile libsnapshot sources directly to avoid dependency
+        // to update_metadata-protos
+        ":libsnapshot_sources",
     ],
     static_libs: [
         "libbase",
         "libcrypto_static",
         "libcutils",
+        "libext2_uuid",
+        "libext4_utils",
+        "libfstab",
         "libfs_mgr",
         "libgtest", // from libsnapshot_test_helpers
         "libgmock", // from libsnapshot_test_helpers
         "liblog",
         "liblp",
-        "libsnapshot_init", // don't use binder or hwbinder
         "libsnapshot_test_helpers",
-        "libprotobuf-cpp-lite",
-        "update_metadata-protos",
+        "libprotobuf-mutator",
     ],
     header_libs: [
+        "libfiemap_headers",
         "libstorage_literals_headers",
     ],
+    proto: {
+        type: "full",
+        canonical_path_from_root: false,
+        local_include_dirs: ["."],
+    },
 
     fuzz_config: {
         cc: ["android-virtual-ab+bugs@google.com"],
         componentid: 30545,
         hotlists: ["1646452"],
         fuzz_on_haiku_host: false,
-        // TODO(b/154633114): set to true to run this automatically.
-        fuzz_on_haiku_device: false,
+        fuzz_on_haiku_device: true,
     },
 }
diff --git a/fs_mgr/libsnapshot/android/snapshot/snapshot_fuzz.proto b/fs_mgr/libsnapshot/android/snapshot/snapshot_fuzz.proto
new file mode 100644
index 0000000..91fbb60
--- /dev/null
+++ b/fs_mgr/libsnapshot/android/snapshot/snapshot_fuzz.proto
@@ -0,0 +1,103 @@
+// Copyright (C) 2020 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.
+
+syntax = "proto3";
+package android.snapshot;
+
+import "update_engine/update_metadata.proto";
+
+// Controls the behavior of IDeviceInfo.
+// Next: 6
+message FuzzDeviceInfoData {
+    bool slot_suffix_is_a = 1;
+    bool is_overlayfs_setup = 2;
+    bool allow_set_boot_control_merge_status = 3;
+    bool allow_set_slot_as_unbootable = 4;
+    bool is_recovery = 5;
+}
+
+// Controls the behavior of the test SnapshotManager.
+// Next: 2
+message FuzzSnapshotManagerData {
+    bool is_local_image_manager = 1;
+}
+
+// A simplified version of CreateLogicalPartitionParams for fuzzing.
+// Next: 9
+message CreateLogicalPartitionParamsProto {
+    bool use_correct_super = 1;
+    string block_device = 2;
+    bool has_metadata_slot = 3;
+    uint32 metadata_slot = 4;
+    string partition_name = 5;
+    bool force_writable = 6;
+    int64 timeout_millis = 7;
+    string device_name = 8;
+}
+
+// Mimics the API of ISnapshotManager. Defines one action on the snapshot
+// manager.
+// Next: 18
+message SnapshotManagerActionProto {
+    message NoArgs {}
+    message ProcessUpdateStateArgs {
+        bool has_before_cancel = 1;
+        bool fail_before_cancel = 2;
+    }
+    message CreateLogicalAndSnapshotPartitionsArgs {
+        bool use_correct_super = 1;
+        string super = 2;
+        int64 timeout_millis = 3;
+    }
+    message RecoveryCreateSnapshotDevicesArgs {
+        bool has_metadata_device_object = 1;
+        bool metadata_mounted = 2;
+    }
+    oneof value {
+        NoArgs begin_update = 1;
+        NoArgs cancel_update = 2;
+        bool finished_snapshot_writes = 3;
+        NoArgs initiate_merge = 4;
+        ProcessUpdateStateArgs process_update_state = 5;
+        bool get_update_state = 6;
+        chromeos_update_engine.DeltaArchiveManifest create_update_snapshots = 7;
+        CreateLogicalPartitionParamsProto map_update_snapshot = 8;
+        string unmap_update_snapshot = 9;
+        NoArgs need_snapshots_in_first_stage_mount = 10;
+        CreateLogicalAndSnapshotPartitionsArgs create_logical_and_snapshot_partitions = 11;
+        bool handle_imminent_data_wipe = 12;
+        NoArgs recovery_create_snapshot_devices = 13;
+        RecoveryCreateSnapshotDevicesArgs recovery_create_snapshot_devices_with_metadata = 14;
+        NoArgs dump = 15;
+        NoArgs ensure_metadata_mounted = 16;
+        NoArgs get_snapshot_merge_stats_instance = 17;
+    }
+}
+
+// Includes all data that needs to be fuzzed.
+message SnapshotFuzzData {
+    FuzzDeviceInfoData device_info_data = 1;
+    FuzzSnapshotManagerData manager_data = 2;
+
+    // If true:
+    // - if super_data is empty, create empty super partition metadata.
+    // - otherwise, create super partition metadata accordingly.
+    // If false, no valid super partition metadata (it is zeroed)
+    bool is_super_metadata_valid = 3;
+    chromeos_update_engine.DeltaArchiveManifest super_data = 4;
+
+    // More data used to prep the test before running actions.
+    reserved 5 to 9999;
+    repeated SnapshotManagerActionProto actions = 10000;
+}
diff --git a/fs_mgr/libsnapshot/fuzz.sh b/fs_mgr/libsnapshot/fuzz.sh
index 64d8224..2910129 100755
--- a/fs_mgr/libsnapshot/fuzz.sh
+++ b/fs_mgr/libsnapshot/fuzz.sh
@@ -18,8 +18,8 @@
 
 build_cov() {
     pushd $(gettop)
-    ret=$?
     NATIVE_COVERAGE="true" NATIVE_LINE_COVERAGE="true" COVERAGE_PATHS="${PROJECT_PATH}" m ${FUZZ_TARGET}
+    ret=$?
     popd
     return ${ret}
 }
@@ -46,7 +46,7 @@
 }
 
 # run_snapshot_fuzz -runs=10000
-generate_corpse() {
+generate_corpus() {
     [[ "$@" ]] || { echo "run with -runs=X"; return 1; }
 
     prepare_device &&
diff --git a/fs_mgr/libsnapshot/fuzz_utils.cpp b/fs_mgr/libsnapshot/fuzz_utils.cpp
index 509eb1b..0263f7e 100644
--- a/fs_mgr/libsnapshot/fuzz_utils.cpp
+++ b/fs_mgr/libsnapshot/fuzz_utils.cpp
@@ -22,4 +22,17 @@
     CHECK(value) << msg;
 }
 
+const google::protobuf::OneofDescriptor* GetProtoValueDescriptor(
+        const google::protobuf::Descriptor* action_desc) {
+    CHECK(action_desc);
+    CHECK(action_desc->oneof_decl_count() == 1)
+            << action_desc->oneof_decl_count() << " oneof fields found in " << action_desc->name()
+            << "; only one is expected.";
+    auto* oneof_value_desc = action_desc->oneof_decl(0);
+    CHECK(oneof_value_desc);
+    CHECK(oneof_value_desc->name() == "value")
+            << "oneof field has name " << oneof_value_desc->name();
+    return oneof_value_desc;
+}
+
 }  // namespace android::fuzz
diff --git a/fs_mgr/libsnapshot/fuzz_utils.h b/fs_mgr/libsnapshot/fuzz_utils.h
index 4e14b9c..4dc6cdc 100644
--- a/fs_mgr/libsnapshot/fuzz_utils.h
+++ b/fs_mgr/libsnapshot/fuzz_utils.h
@@ -12,256 +12,254 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include <optional>
+#pragma once
+
+#include <map>
 #include <string>
 #include <string_view>
-#include <vector>
 
-// Generic classes for fuzzing a collection of APIs.
+#include <google/protobuf/descriptor.h>
+#include <google/protobuf/message.h>
+#include <google/protobuf/repeated_field.h>
+
+// Utilities for using a protobuf definition to fuzz APIs in a class.
+// Terms:
+// The "fuzzed class" is the C++ class definition whose functions are fuzzed.
+// The "fuzzed object" is an instantiated object of the fuzzed class. It is
+//   typically created and destroyed for each test run.
+// An "action" is an operation on the fuzzed object that may mutate its state.
+//   This typically involves one function call into the fuzzed object.
 
 namespace android::fuzz {
 
-// My custom boolean type -- to avoid conflict with (u)int8_t and char.
-struct Bool {
-    bool value;
-    operator bool() const { return value; }
-};
-
-// Helper for FuzzData.
-// A wrapper over an optional const object T. The buffer is maintained elsewhere.
-template <typename T>
-class Optional {
-  public:
-    Optional(const T* ptr) : ptr_(ptr) {}
-    const T& operator*() const { return *ptr_; }
-    const T& value() const { return *ptr_; }
-    bool has_value() const { return ptr_; }
-
-  private:
-    const T* ptr_;
-};
-
-// Helper for FuzzData.
-// A wrapper over an optional boolean. The boolean is owned by this object.
-template <>
-class Optional<Bool> {
-  public:
-    Optional(std::optional<Bool>&& val) : val_(std::move(val)) {}
-    const Bool& operator*() const { return *val_; }
-    const Bool& value() const { return val_.value(); }
-    bool has_value() const { return val_.has_value(); }
-
-  private:
-    std::optional<Bool> val_;
-};
-
-// Helper for FuzzData.
-// A view on a raw data buffer. Client is responsible for maintaining the lifetime of the data
-// buffer.
-class DataView {
-  public:
-    DataView(const uint8_t* data, uint64_t size) : data_(data), size_(size) {}
-    DataView(const void* data, uint64_t size) : DataView(static_cast<const uint8_t*>(data), size) {}
-    inline uint64_t size() const { return size_; }
-    inline const uint8_t* data() const { return data_; }
-    inline bool CanConsume(uint64_t size) { return size_ >= size; }
-    // Consume the first |size| bytes from |this| and return a DataView object that represents
-    // the consumed data. Data pointer in |this| is incremented by |size| bytes.
-    // If not enough bytes, return nullopt.
-    std::optional<DataView> Consume(uint64_t size) {
-        if (!CanConsume(size)) return std::nullopt;
-        DataView ret(data_, size);
-        size_ -= size;
-        data_ += size;
-        return ret;
-    }
-
-  private:
-    const uint8_t* data_;
-    uint64_t size_;
-};
-
-// A view on the fuzz data. Provides APIs to consume typed objects.
-class FuzzData : public DataView {
-  public:
-    // Inherit constructors.
-    using DataView::DataView;
-    // Consume a data object T and return the pointer (into the buffer). No copy is done.
-    // If not enough bytes, return nullptr.
-    template <typename T>
-    inline Optional<T> Consume() {
-        auto data_view = DataView::Consume(sizeof(T));
-        if (!data_view.has_value()) return nullptr;
-        return reinterpret_cast<const T*>(data_view->data());
-    }
-    // To provide enough entropy for booleans, they are consumed bit by bit.
-    // Hence, the returned value is not indexed into the buffer. See Optional<Bool>.
-    template <>
-    Optional<Bool> Consume<Bool>() {
-        if (!boolean_buffer_.has_value() || boolean_bit_offset_ >= sizeof(*boolean_buffer_)) {
-            boolean_buffer_ = Consume<uint8_t>();
-            boolean_bit_offset_ = 0;
-        }
-        if (!boolean_buffer_.has_value()) {
-            return Optional<Bool>(std::nullopt);
-        }
-        const auto& byte = *boolean_buffer_;
-        bool ret = (byte >> boolean_bit_offset_) & 0x1;
-        boolean_bit_offset_++;
-        return Optional<Bool>(Bool{ret});
-    }
-
-  private:
-    // Separate buffer for booleans.
-    Optional<uint8_t> boolean_buffer_ = nullptr;
-    uint8_t boolean_bit_offset_ = 0;
-};
-
-enum class CallResult {
-    SUCCESS,
-    NOT_ENOUGH_DATA,
-};
-
-inline bool AllArgsHasValue() {
-    return true;
-}
-template <typename T>
-inline bool AllArgsHasValue(const Optional<T>& t) {
-    return t.has_value();
-}
-template <typename First, typename... Remaining>
-inline bool AllArgsHasValue(const Optional<First>& first, const Optional<Remaining>&... remaining) {
-    return first.has_value() && AllArgsHasValue(remaining...);
-}
-
-// Base class of FuzzFunction.
-class FuzzFunctionBase {
-  public:
-    virtual ~FuzzFunctionBase() = default;
-    virtual CallResult Call(FuzzData* fuzz_data) const = 0;
-};
-
-template <typename T>
-class FuzzFunction;  // undefined
-
-// A wrapper over a fuzzed function.
-template <typename R, typename... Args>
-class FuzzFunction<R(Args...)> : public FuzzFunctionBase {
-  public:
-    using Function = std::function<R(Args...)>;
-    FuzzFunction(Function&& function) : function_(std::move(function)) {}
-    // Eat necessary data in |fuzz_data| and invoke the function.
-    CallResult Call(FuzzData* fuzz_data) const override {
-        return CallWithOptionalArgs(fuzz_data->Consume<std::remove_reference_t<Args>>()...);
-    }
-
-  private:
-    Function function_;
-
-    CallResult CallWithOptionalArgs(const Optional<std::remove_reference_t<Args>>&... args) const {
-        if (!AllArgsHasValue(args...)) {
-            return CallResult::NOT_ENOUGH_DATA;
-        }
-        (void)function_(args.value()...);  // ignore returned value
-        return CallResult::SUCCESS;
-    }
-};
-
 // CHECK(value) << msg
 void CheckInternal(bool value, std::string_view msg);
 
-// A collection of FuzzFunction's.
-// FunctionsSizeType must be an integral type where
-// functions_.size() <= std::numeric_limits<FunctionSizeType>::max().
-template <typename FunctionsSizeType>
-class FuzzFunctions {
+// Get the oneof descriptor inside Action
+const google::protobuf::OneofDescriptor* GetProtoValueDescriptor(
+        const google::protobuf::Descriptor* action_desc);
+
+template <typename Class>
+using FunctionMapImpl =
+        std::map<int, std::function<void(Class*, const google::protobuf::Message& action_proto,
+                                         const google::protobuf::FieldDescriptor* field_desc)>>;
+
+template <typename Class>
+class FunctionMap : public FunctionMapImpl<Class> {
   public:
-    // Subclass should override this to register functions via AddFunction.
-    FuzzFunctions() = default;
-    virtual ~FuzzFunctions() = default;
-    // Eat some amount of data in |fuzz_data| and call one of the |functions_|.
-    CallResult CallOne(FuzzData* fuzz_data) const {
-        auto opt_number = fuzz_data->Consume<FunctionsSizeType>();
-        if (!opt_number.has_value()) {
-            return CallResult::NOT_ENOUGH_DATA;
-        }
-        auto function_index = opt_number.value() % functions_.size();
-        return functions_[function_index]->Call(fuzz_data);
+    void CheckEmplace(typename FunctionMapImpl<Class>::key_type key,
+                      typename FunctionMapImpl<Class>::mapped_type&& value) {
+        auto [it, inserted] = this->emplace(key, std::move(value));
+        CheckInternal(inserted,
+                      "Multiple implementation registered for tag number " + std::to_string(key));
     }
-
-  private:
-    template <typename R, typename... Args>
-    struct FunctionTraits {
-        using Function = std::function<R(Args...)>;
-    };
-
-  public:
-    // There are no deduction guide from lambda to std::function, so the
-    // signature of the lambda must be specified in the template argument.
-    // FuzzFunctions provide the following 3 ways to specify the signature of
-    // the lambda:
-
-    // AddFunction<R(Args...)>, e.g. AddFunction<ReturnType(ArgType, ArgType)>
-    template <typename T>
-    void AddFunction(std::function<T>&& func) {
-        functions_.push_back(std::make_unique<FuzzFunction<T>>(std::move(func)));
-    }
-
-    // AddFunction<R, Args...>, e.g. AddFunction<ReturnType, ArgType, ArgType>
-    template <typename R, typename... Args>
-    void AddFunction(typename FunctionTraits<R, Args...>::Function&& func) {
-        functions_.push_back(std::make_unique<FuzzFunction<R(Args...)>>(std::move(func)));
-    }
-
-    // AddFunction<ArgType...>. Equivalent to AddFunction<void, Args...>
-    template <typename... Args>
-    void AddFunction(typename FunctionTraits<void, Args...>::Function&& func) {
-        functions_.push_back(std::make_unique<FuzzFunction<void(Args...)>>(std::move(func)));
-    }
-
-    // Use |fuzz_data| as a guide to call |functions_| until |fuzz_data| is
-    // depleted. Return
-    void DepleteData(FuzzData* fuzz_data) const {
-        CallResult result;
-        while ((result = CallOne(fuzz_data)) == CallResult::SUCCESS)
-            ;
-        CheckInternal(result == CallResult::NOT_ENOUGH_DATA,
-                      "result is " + std::to_string(static_cast<int>(result)));
-    }
-
-  protected:
-    // Helper for subclass to check that size of |functions_| is actually within
-    // SizeType. Should be called after all functions are registered.
-    void CheckFunctionsSize() const {
-        CheckInternal(functions_.size() <= std::numeric_limits<FunctionsSizeType>::max(),
-                      "Need to extend number of bits for function count; there are " +
-                              std::to_string(functions_.size()) + " functions now.");
-    }
-
-  private:
-    std::vector<std::unique_ptr<FuzzFunctionBase>> functions_;
 };
 
-// An object whose APIs are being fuzzed.
-template <typename T, typename FunctionsSizeType>
-class FuzzObject : public FuzzFunctions<FunctionsSizeType> {
-  public:
-    // Not thread-safe; client is responsible for ensuring only one thread calls DepleteData.
-    void DepleteData(T* obj, FuzzData* fuzz_data) {
-        obj_ = obj;
-        FuzzFunctions<FunctionsSizeType>::DepleteData(fuzz_data);
-        obj_ = nullptr;
+template <typename Action>
+int CheckConsistency() {
+    const auto* function_map = Action::GetFunctionMap();
+    const auto* action_value_desc = GetProtoValueDescriptor(Action::Proto::GetDescriptor());
+
+    for (int field_index = 0; field_index < action_value_desc->field_count(); ++field_index) {
+        const auto* field_desc = action_value_desc->field(field_index);
+        CheckInternal(function_map->find(field_desc->number()) != function_map->end(),
+                      "Missing impl for function " + field_desc->camelcase_name());
+    }
+    return 0;
+}
+
+template <typename Action>
+void ExecuteActionProto(typename Action::Class* module,
+                        const typename Action::Proto& action_proto) {
+    static auto* action_value_desc = GetProtoValueDescriptor(Action::Proto::GetDescriptor());
+
+    auto* action_refl = Action::Proto::GetReflection();
+    if (!action_refl->HasOneof(action_proto, action_value_desc)) {
+        return;
     }
 
-  protected:
-    // Helper for subclass to get the module under test in the added functions.
-    T* get() const {
-        CheckInternal(obj_ != nullptr, "No module under test is found.");
-        return obj_;
+    const auto* field_desc = action_refl->GetOneofFieldDescriptor(action_proto, action_value_desc);
+    auto number = field_desc->number();
+    const auto& map = *Action::GetFunctionMap();
+    auto it = map.find(number);
+    CheckInternal(it != map.end(), "Missing impl for function " + field_desc->camelcase_name());
+    const auto& func = it->second;
+    func(module, action_proto, field_desc);
+}
+
+template <typename Action>
+void ExecuteAllActionProtos(
+        typename Action::Class* module,
+        const google::protobuf::RepeatedPtrField<typename Action::Proto>& action_protos) {
+    for (const auto& proto : action_protos) {
+        ExecuteActionProto<Action>(module, proto);
+    }
+}
+
+// Safely cast message to T. Returns a pointer to message if cast successfully, otherwise nullptr.
+template <typename T>
+const T* SafeCast(const google::protobuf::Message& message) {
+    if (message.GetDescriptor() != T::GetDescriptor()) {
+        return nullptr;
+    }
+    return static_cast<const T*>(&message);
+}
+
+// Cast message to const T&. Abort if type mismatch.
+template <typename T>
+const T& CheckedCast(const google::protobuf::Message& message) {
+    const auto* ptr = SafeCast<T>(message);
+    CheckInternal(ptr, "Cannot cast " + message.GetDescriptor()->name() + " to " +
+                               T::GetDescriptor()->name());
+    return *ptr;
+}
+
+// A templated way to a primitive field from a message using reflection.
+template <typename T>
+struct PrimitiveGetter;
+#define FUZZ_DEFINE_PRIMITIVE_GETTER(type, func_name)                              \
+    template <>                                                                    \
+    struct PrimitiveGetter<type> {                                                 \
+        static constexpr const auto fp = &google::protobuf::Reflection::func_name; \
     }
 
-  private:
-    T* obj_ = nullptr;
+FUZZ_DEFINE_PRIMITIVE_GETTER(bool, GetBool);
+FUZZ_DEFINE_PRIMITIVE_GETTER(uint32_t, GetUInt32);
+FUZZ_DEFINE_PRIMITIVE_GETTER(int32_t, GetInt32);
+FUZZ_DEFINE_PRIMITIVE_GETTER(uint64_t, GetUInt64);
+FUZZ_DEFINE_PRIMITIVE_GETTER(int64_t, GetInt64);
+FUZZ_DEFINE_PRIMITIVE_GETTER(double, GetDouble);
+FUZZ_DEFINE_PRIMITIVE_GETTER(float, GetFloat);
+
+// ActionPerformer extracts arguments from the protobuf message, and then call FuzzFunction
+// with these arguments.
+template <typename FuzzFunction, typename Signature, typename Enabled = void>
+struct ActionPerfomer;  // undefined
+
+template <typename FuzzFunction, typename MessageProto>
+struct ActionPerfomer<
+        FuzzFunction, void(const MessageProto&),
+        typename std::enable_if_t<std::is_base_of_v<google::protobuf::Message, MessageProto>>> {
+    static void Invoke(typename FuzzFunction::Class* module,
+                       const google::protobuf::Message& action_proto,
+                       const google::protobuf::FieldDescriptor* field_desc) {
+        const MessageProto& arg = CheckedCast<std::remove_reference_t<MessageProto>>(
+                action_proto.GetReflection()->GetMessage(action_proto, field_desc));
+        FuzzFunction::ImplBody(module, arg);
+    }
+};
+
+template <typename FuzzFunction, typename Primitive>
+struct ActionPerfomer<FuzzFunction, void(Primitive),
+                      typename std::enable_if_t<std::is_arithmetic_v<Primitive>>> {
+    static void Invoke(typename FuzzFunction::Class* module,
+                       const google::protobuf::Message& action_proto,
+                       const google::protobuf::FieldDescriptor* field_desc) {
+        Primitive arg = std::invoke(PrimitiveGetter<Primitive>::fp, action_proto.GetReflection(),
+                                    action_proto, field_desc);
+        FuzzFunction::ImplBody(module, arg);
+    }
+};
+
+template <typename FuzzFunction>
+struct ActionPerfomer<FuzzFunction, void()> {
+    static void Invoke(typename FuzzFunction::Class* module, const google::protobuf::Message&,
+                       const google::protobuf::FieldDescriptor*) {
+        FuzzFunction::ImplBody(module);
+    }
+};
+
+template <typename FuzzFunction>
+struct ActionPerfomer<FuzzFunction, void(const std::string&)> {
+    static void Invoke(typename FuzzFunction::Class* module,
+                       const google::protobuf::Message& action_proto,
+                       const google::protobuf::FieldDescriptor* field_desc) {
+        std::string scratch;
+        const std::string& arg = action_proto.GetReflection()->GetStringReference(
+                action_proto, field_desc, &scratch);
+        FuzzFunction::ImplBody(module, arg);
+    }
 };
 
 }  // namespace android::fuzz
+
+// Fuzz existing C++ class, ClassType, with a collection of functions under the name Action.
+//
+// Prerequisite: ActionProto must be defined in Protobuf to describe possible actions:
+// message FooActionProto {
+//     message NoArgs {}
+//     oneof value {
+//         bool do_foo = 1;
+//         NoArgs do_bar = 1;
+//     }
+// }
+// Use it to fuzz a C++ class Foo by doing the following:
+//   FUZZ_CLASS(Foo, FooAction)
+// After linking functions of Foo to FooAction, execute all actions by:
+//   FooAction::ExecuteAll(foo_object, action_protos)
+#define FUZZ_CLASS(ClassType, Action)                                                            \
+    class Action {                                                                               \
+      public:                                                                                    \
+        using Proto = Action##Proto;                                                             \
+        using Class = ClassType;                                                                 \
+        using FunctionMap = android::fuzz::FunctionMap<Class>;                                   \
+        static FunctionMap* GetFunctionMap() {                                                   \
+            static Action::FunctionMap map;                                                      \
+            return &map;                                                                         \
+        }                                                                                        \
+        static void ExecuteAll(Class* module,                                                    \
+                               const google::protobuf::RepeatedPtrField<Proto>& action_protos) { \
+            [[maybe_unused]] static int consistent = android::fuzz::CheckConsistency<Action>();  \
+            android::fuzz::ExecuteAllActionProtos<Action>(module, action_protos);                \
+        }                                                                                        \
+    }
+
+#define FUZZ_FUNCTION_CLASS_NAME(Action, FunctionName) Action##_##FunctionName
+#define FUZZ_FUNCTION_TAG_NAME(FunctionName) k##FunctionName
+
+// Implement an action defined in protobuf. Example:
+// message FooActionProto {
+//     oneof value {
+//         bool do_foo = 1;
+//     }
+// }
+// class Foo { public: void DoAwesomeFoo(bool arg); };
+// FUZZ_OBJECT(FooAction, Foo);
+// FUZZ_FUNCTION(FooAction, DoFoo, module, bool arg) {
+//   module->DoAwesomeFoo(arg);
+// }
+// The name DoFoo is the camel case name of the action in protobuf definition of FooActionProto.
+#define FUZZ_FUNCTION(Action, FunctionName, module, ...)                                         \
+    class FUZZ_FUNCTION_CLASS_NAME(Action, FunctionName) {                                       \
+      public:                                                                                    \
+        using Class = Action::Class;                                                             \
+        static void ImplBody(Action::Class*, ##__VA_ARGS__);                                     \
+                                                                                                 \
+      private:                                                                                   \
+        static bool registered_;                                                                 \
+    };                                                                                           \
+    auto FUZZ_FUNCTION_CLASS_NAME(Action, FunctionName)::registered_ = ([] {                     \
+        auto tag = Action::Proto::ValueCase::FUZZ_FUNCTION_TAG_NAME(FunctionName);               \
+        auto func =                                                                              \
+                &::android::fuzz::ActionPerfomer<FUZZ_FUNCTION_CLASS_NAME(Action, FunctionName), \
+                                                 void(__VA_ARGS__)>::Invoke;                     \
+        Action::GetFunctionMap()->CheckEmplace(tag, func);                                       \
+        return true;                                                                             \
+    })();                                                                                        \
+    void FUZZ_FUNCTION_CLASS_NAME(Action, FunctionName)::ImplBody(Action::Class* module,         \
+                                                                  ##__VA_ARGS__)
+
+// Implement a simple action by linking it to the function with the same name. Example:
+// message FooActionProto {
+//     message NoArgs {}
+//     oneof value {
+//         NoArgs do_bar = 1;
+//     }
+// }
+// class Foo { public void DoBar(); };
+// FUZZ_OBJECT(FooAction, Foo);
+// FUZZ_FUNCTION(FooAction, DoBar);
+// The name DoBar is the camel case name of the action in protobuf definition of FooActionProto, and
+// also the name of the function of Foo.
+#define FUZZ_SIMPLE_FUNCTION(Action, FunctionName) \
+    FUZZ_FUNCTION(Action, FunctionName, module) { (void)module->FunctionName(); }
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h
index 758d66c..cf0b085 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h
@@ -43,6 +43,7 @@
                 (const std::string& super_device, const std::chrono::milliseconds& timeout_ms),
                 (override));
     MOCK_METHOD(bool, HandleImminentDataWipe, (const std::function<void()>& callback), (override));
+    MOCK_METHOD(bool, FinishMergeInRecovery, (), (override));
     MOCK_METHOD(CreateResult, RecoveryCreateSnapshotDevices, (), (override));
     MOCK_METHOD(CreateResult, RecoveryCreateSnapshotDevices,
                 (const std::unique_ptr<AutoDevice>& metadata_device), (override));
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
index 8081866..5acd039 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
@@ -173,6 +173,7 @@
 
     // Map a snapshotted partition for OTA clients to write to. Write-protected regions are
     // determined previously in CreateSnapshots.
+    // |snapshot_path| must not be nullptr.
     virtual bool MapUpdateSnapshot(const android::fs_mgr::CreateLogicalPartitionParams& params,
                                    std::string* snapshot_path) = 0;
 
@@ -201,6 +202,10 @@
     // optional callback fires periodically to query progress via GetUpdateState.
     virtual bool HandleImminentDataWipe(const std::function<void()>& callback = {}) = 0;
 
+    // Force a merge to complete in recovery. This is similar to HandleImminentDataWipe
+    // but does not expect a data wipe after.
+    virtual bool FinishMergeInRecovery() = 0;
+
     // This method is only allowed in recovery and is used as a helper to
     // initialize the snapshot devices as a requirement to mount a snapshotted
     // /system in recovery.
@@ -289,6 +294,7 @@
             const std::string& super_device,
             const std::chrono::milliseconds& timeout_ms = {}) override;
     bool HandleImminentDataWipe(const std::function<void()>& callback = {}) override;
+    bool FinishMergeInRecovery() override;
     CreateResult RecoveryCreateSnapshotDevices() override;
     CreateResult RecoveryCreateSnapshotDevices(
             const std::unique_ptr<AutoDevice>& metadata_device) override;
@@ -579,6 +585,7 @@
     std::unique_ptr<IDeviceInfo> device_;
     std::unique_ptr<IImageManager> images_;
     bool has_local_image_manager_ = false;
+    bool in_factory_data_reset_ = false;
 };
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h
index 9b2590c..9c82906 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h
@@ -41,6 +41,7 @@
             const std::string& super_device,
             const std::chrono::milliseconds& timeout_ms = {}) override;
     bool HandleImminentDataWipe(const std::function<void()>& callback = {}) override;
+    bool FinishMergeInRecovery() override;
     CreateResult RecoveryCreateSnapshotDevices() override;
     CreateResult RecoveryCreateSnapshotDevices(
             const std::unique_ptr<AutoDevice>& metadata_device) override;
diff --git a/fs_mgr/libsnapshot/partition_cow_creator.cpp b/fs_mgr/libsnapshot/partition_cow_creator.cpp
index efdb59f..0df5664 100644
--- a/fs_mgr/libsnapshot/partition_cow_creator.cpp
+++ b/fs_mgr/libsnapshot/partition_cow_creator.cpp
@@ -181,6 +181,13 @@
     ret.snapshot_status.set_device_size(target_partition->size());
     ret.snapshot_status.set_snapshot_size(target_partition->size());
 
+    if (ret.snapshot_status.snapshot_size() == 0) {
+        LOG(INFO) << "Not creating snapshot for partition " << ret.snapshot_status.name();
+        ret.snapshot_status.set_cow_partition_size(0);
+        ret.snapshot_status.set_cow_file_size(0);
+        return ret;
+    }
+
     // Being the COW partition virtual, its size doesn't affect the storage
     // memory that will be occupied by the target.
     // The actual storage space is affected by the COW file, whose size depends
diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp
index eafeea2..03efd68 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -577,8 +577,16 @@
         return false;
     }
 
+    auto other_suffix = device_->GetOtherSlotSuffix();
+
     auto& dm = DeviceMapper::Instance();
     for (const auto& snapshot : snapshots) {
+        if (android::base::EndsWith(snapshot, other_suffix)) {
+            // Allow the merge to continue, but log this unexpected case.
+            LOG(ERROR) << "Unexpected snapshot found during merge: " << snapshot;
+            continue;
+        }
+
         // The device has to be mapped, since everything should be merged at
         // the same time. This is a fairly serious error. We could forcefully
         // map everything here, but it should have been mapped during first-
@@ -1008,6 +1016,15 @@
 }
 
 void SnapshotManager::AcknowledgeMergeSuccess(LockedFile* lock) {
+    // It's not possible to remove update state in recovery, so write an
+    // indicator that cleanup is needed on reboot. If a factory data reset
+    // was requested, it doesn't matter, everything will get wiped anyway.
+    // To make testing easier we consider a /data wipe as cleaned up.
+    if (device_->IsRecovery() && !in_factory_data_reset_) {
+        WriteUpdateState(lock, UpdateState::MergeCompleted);
+        return;
+    }
+
     RemoveAllUpdateState(lock);
 }
 
@@ -2528,7 +2545,43 @@
         }
         return true;
     };
-    if (!ProcessUpdateStateOnDataWipe(true /* allow_forward_merge */, process_callback)) {
+
+    in_factory_data_reset_ = true;
+    bool ok = ProcessUpdateStateOnDataWipe(true /* allow_forward_merge */, process_callback);
+    in_factory_data_reset_ = false;
+
+    if (!ok) {
+        return false;
+    }
+
+    // Nothing should be depending on partitions now, so unmap them all.
+    if (!UnmapAllPartitions()) {
+        LOG(ERROR) << "Unable to unmap all partitions; fastboot may fail to flash.";
+    }
+    return true;
+}
+
+bool SnapshotManager::FinishMergeInRecovery() {
+    if (!device_->IsRecovery()) {
+        LOG(ERROR) << "Data wipes are only allowed in recovery.";
+        return false;
+    }
+
+    auto mount = EnsureMetadataMounted();
+    if (!mount || !mount->HasDevice()) {
+        return false;
+    }
+
+    auto slot_number = SlotNumberForSlotSuffix(device_->GetSlotSuffix());
+    auto super_path = device_->GetSuperDevice(slot_number);
+    if (!CreateLogicalAndSnapshotPartitions(super_path)) {
+        LOG(ERROR) << "Unable to map partitions to complete merge.";
+        return false;
+    }
+
+    UpdateState state = ProcessUpdateState();
+    if (state != UpdateState::MergeCompleted) {
+        LOG(ERROR) << "Merge returned unexpected status: " << state;
         return false;
     }
 
diff --git a/fs_mgr/libsnapshot/snapshot_fuzz.cpp b/fs_mgr/libsnapshot/snapshot_fuzz.cpp
index 12a0531..421154d 100644
--- a/fs_mgr/libsnapshot/snapshot_fuzz.cpp
+++ b/fs_mgr/libsnapshot/snapshot_fuzz.cpp
@@ -21,6 +21,7 @@
 #include <tuple>
 
 #include <android-base/logging.h>
+#include <src/libfuzzer/libfuzzer_macro.h>
 #include <storage_literals/storage_literals.h>
 
 #include "fuzz_utils.h"
@@ -31,11 +32,12 @@
 using android::base::SetLogger;
 using android::base::StderrLogger;
 using android::base::StdioLogger;
-using android::fuzz::Bool;
-using android::fuzz::FuzzData;
-using android::fuzz::FuzzObject;
+using android::fs_mgr::CreateLogicalPartitionParams;
+using android::fuzz::CheckedCast;
+using android::snapshot::SnapshotFuzzData;
 using android::snapshot::SnapshotFuzzEnv;
-using android::snapshot::SnapshotManagerFuzzData;
+using chromeos_update_engine::DeltaArchiveManifest;
+using google::protobuf::RepeatedPtrField;
 
 // Avoid linking to libgsi since it needs disk I/O.
 namespace android::gsi {
@@ -51,49 +53,106 @@
 
 namespace android::snapshot {
 
-class FuzzSnapshotManager : public FuzzObject<ISnapshotManager, uint8_t> {
-  public:
-    FuzzSnapshotManager();
-};
+const SnapshotFuzzData* current_data = nullptr;
 
-FuzzSnapshotManager::FuzzSnapshotManager() {
-    AddFunction([this]() { (void)get()->BeginUpdate(); });
-    AddFunction([this]() { (void)get()->CancelUpdate(); });
-    AddFunction<Bool>([this](Bool wipe) { (void)get()->FinishedSnapshotWrites(wipe); });
-    AddFunction([this]() { (void)get()->InitiateMerge(); });
-    AddFunction<Bool, Bool>([this](auto has_before_cancel, auto fail_before_cancel) {
-        std::function<bool()> before_cancel;
-        if (has_before_cancel) {
-            before_cancel = [=]() { return fail_before_cancel; };
-        }
-        (void)get()->ProcessUpdateState({}, before_cancel);
-    });
-    AddFunction<Bool>([this](auto has_progress_arg) {
-        double progress;
-        (void)get()->GetUpdateState(has_progress_arg ? &progress : nullptr);
-    });
-    // TODO add CreateUpdateSnapshots according to proto
-    // TODO add MapUpdateSnapshot
-    // TODO add UnmapUpdateSnapshot using names from the dictionary
-    AddFunction([this]() { (void)get()->NeedSnapshotsInFirstStageMount(); });
-    // TODO add CreateLogicalAndSnapshotPartitions
-    AddFunction<Bool>([this](const Bool& has_callback) {
-        std::function<void()> callback;
-        if (has_callback) {
-            callback = []() {};
-        }
-        (void)get()->HandleImminentDataWipe(callback);
-    });
-    AddFunction([this]() { (void)get()->RecoveryCreateSnapshotDevices(); });
-    // TODO add RecoveryCreateSnapshotDevices with metadata_device arg
-    AddFunction([this]() {
-        std::stringstream ss;
-        (void)get()->Dump(ss);
-    });
-    AddFunction([this]() { (void)get()->EnsureMetadataMounted(); });
-    AddFunction([this]() { (void)get()->GetSnapshotMergeStatsInstance(); });
+SnapshotFuzzEnv* GetSnapshotFuzzEnv();
 
-    CheckFunctionsSize();
+FUZZ_CLASS(ISnapshotManager, SnapshotManagerAction);
+
+using ProcessUpdateStateArgs = SnapshotManagerAction::Proto::ProcessUpdateStateArgs;
+using CreateLogicalAndSnapshotPartitionsArgs =
+        SnapshotManagerAction::Proto::CreateLogicalAndSnapshotPartitionsArgs;
+using RecoveryCreateSnapshotDevicesArgs =
+        SnapshotManagerAction::Proto::RecoveryCreateSnapshotDevicesArgs;
+
+FUZZ_SIMPLE_FUNCTION(SnapshotManagerAction, BeginUpdate);
+FUZZ_SIMPLE_FUNCTION(SnapshotManagerAction, CancelUpdate);
+FUZZ_SIMPLE_FUNCTION(SnapshotManagerAction, InitiateMerge);
+FUZZ_SIMPLE_FUNCTION(SnapshotManagerAction, NeedSnapshotsInFirstStageMount);
+FUZZ_SIMPLE_FUNCTION(SnapshotManagerAction, RecoveryCreateSnapshotDevices);
+FUZZ_SIMPLE_FUNCTION(SnapshotManagerAction, EnsureMetadataMounted);
+FUZZ_SIMPLE_FUNCTION(SnapshotManagerAction, GetSnapshotMergeStatsInstance);
+
+#define SNAPSHOT_FUZZ_FUNCTION(FunctionName, ...) \
+    FUZZ_FUNCTION(SnapshotManagerAction, FunctionName, snapshot, ##__VA_ARGS__)
+
+SNAPSHOT_FUZZ_FUNCTION(FinishedSnapshotWrites, bool wipe) {
+    (void)snapshot->FinishedSnapshotWrites(wipe);
+}
+
+SNAPSHOT_FUZZ_FUNCTION(ProcessUpdateState, const ProcessUpdateStateArgs& args) {
+    std::function<bool()> before_cancel;
+    if (args.has_before_cancel()) {
+        before_cancel = [&]() { return args.fail_before_cancel(); };
+    }
+    (void)snapshot->ProcessUpdateState({}, before_cancel);
+}
+
+SNAPSHOT_FUZZ_FUNCTION(GetUpdateState, bool has_progress_arg) {
+    double progress;
+    (void)snapshot->GetUpdateState(has_progress_arg ? &progress : nullptr);
+}
+
+SNAPSHOT_FUZZ_FUNCTION(HandleImminentDataWipe, bool has_callback) {
+    std::function<void()> callback;
+    if (has_callback) {
+        callback = []() {};
+    }
+    (void)snapshot->HandleImminentDataWipe(callback);
+}
+
+SNAPSHOT_FUZZ_FUNCTION(Dump) {
+    std::stringstream ss;
+    (void)snapshot->Dump(ss);
+}
+
+SNAPSHOT_FUZZ_FUNCTION(CreateUpdateSnapshots, const DeltaArchiveManifest& manifest) {
+    (void)snapshot->CreateUpdateSnapshots(manifest);
+}
+
+SNAPSHOT_FUZZ_FUNCTION(UnmapUpdateSnapshot, const std::string& name) {
+    (void)snapshot->UnmapUpdateSnapshot(name);
+}
+
+SNAPSHOT_FUZZ_FUNCTION(CreateLogicalAndSnapshotPartitions,
+                       const CreateLogicalAndSnapshotPartitionsArgs& args) {
+    const std::string* super;
+    if (args.use_correct_super()) {
+        super = &GetSnapshotFuzzEnv()->super();
+    } else {
+        super = &args.super();
+    }
+    (void)snapshot->CreateLogicalAndSnapshotPartitions(
+            *super, std::chrono::milliseconds(args.timeout_millis()));
+}
+
+SNAPSHOT_FUZZ_FUNCTION(RecoveryCreateSnapshotDevicesWithMetadata,
+                       const RecoveryCreateSnapshotDevicesArgs& args) {
+    std::unique_ptr<AutoDevice> device;
+    if (args.has_metadata_device_object()) {
+        device = std::make_unique<DummyAutoDevice>(args.metadata_mounted());
+    }
+    (void)snapshot->RecoveryCreateSnapshotDevices(device);
+}
+
+SNAPSHOT_FUZZ_FUNCTION(MapUpdateSnapshot, const CreateLogicalPartitionParamsProto& params_proto) {
+    auto partition_opener = std::make_unique<TestPartitionOpener>(GetSnapshotFuzzEnv()->super());
+    CreateLogicalPartitionParams params;
+    if (params_proto.use_correct_super()) {
+        params.block_device = GetSnapshotFuzzEnv()->super();
+    } else {
+        params.block_device = params_proto.block_device();
+    }
+    if (params_proto.has_metadata_slot()) {
+        params.metadata_slot = params_proto.metadata_slot();
+    }
+    params.partition_name = params_proto.partition_name();
+    params.force_writable = params_proto.force_writable();
+    params.timeout_ms = std::chrono::milliseconds(params_proto.timeout_millis());
+    params.device_name = params_proto.device_name();
+    params.partition_opener = partition_opener.get();
+    std::string path;
+    (void)snapshot->MapUpdateSnapshot(params, &path);
 }
 
 // During global init, log all messages to stdio. This is only done once.
@@ -107,35 +166,50 @@
                      unsigned int line, const char* message) {
     if (severity == LogSeverity::FATAL) {
         StderrLogger(logid, severity, tag, file, line, message);
+
+        // If test fails by a LOG(FATAL) or CHECK(), log the corpus. If it abort()'s, there's
+        // nothing else we can do.
+        StderrLogger(logid, severity, tag, __FILE__, __LINE__,
+                     "Attempting to dump current corpus:");
+        if (current_data == nullptr) {
+            StderrLogger(logid, severity, tag, __FILE__, __LINE__, "Current corpus is nullptr.");
+        } else {
+            std::string content;
+            if (!google::protobuf::TextFormat::PrintToString(*current_data, &content)) {
+                StderrLogger(logid, severity, tag, __FILE__, __LINE__,
+                             "Failed to print corpus to string.");
+            } else {
+                StderrLogger(logid, severity, tag, __FILE__, __LINE__, content.c_str());
+            }
+        }
     }
 }
 // Stop logging (except fatal messages) after global initialization. This is only done once.
 int StopLoggingAfterGlobalInit() {
+    [[maybe_unused]] static protobuf_mutator::protobuf::LogSilencer log_silincer;
     SetLogger(&FatalOnlyLogger);
     return 0;
 }
 
-}  // namespace android::snapshot
-
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-    using namespace android::snapshot;
-
+SnapshotFuzzEnv* GetSnapshotFuzzEnv() {
     [[maybe_unused]] static auto allow_logging = AllowLoggingDuringGlobalInit();
     static SnapshotFuzzEnv env;
-    static FuzzSnapshotManager fuzz_snapshot_manager;
     [[maybe_unused]] static auto stop_logging = StopLoggingAfterGlobalInit();
+    return &env;
+}
 
-    CHECK(env.InitOk());
-    FuzzData fuzz_data(data, size);
+}  // namespace android::snapshot
 
-    auto snapshot_manager_data = fuzz_data.Consume<SnapshotManagerFuzzData>();
-    if (!snapshot_manager_data.has_value()) {
-        return 0;
-    }
-    auto snapshot_manager = env.CreateSnapshotManager(snapshot_manager_data.value());
+DEFINE_PROTO_FUZZER(const SnapshotFuzzData& snapshot_fuzz_data) {
+    using namespace android::snapshot;
+
+    current_data = &snapshot_fuzz_data;
+
+    auto env = GetSnapshotFuzzEnv();
+    env->CheckSoftReset();
+
+    auto snapshot_manager = env->CheckCreateSnapshotManager(snapshot_fuzz_data);
     CHECK(snapshot_manager);
 
-    fuzz_snapshot_manager.DepleteData(snapshot_manager.get(), &fuzz_data);
-
-    return 0;
+    SnapshotManagerAction::ExecuteAll(snapshot_manager.get(), snapshot_fuzz_data.actions());
 }
diff --git a/fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp b/fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp
index d5e3e96..8101d03 100644
--- a/fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp
+++ b/fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp
@@ -19,6 +19,7 @@
 #include <sys/stat.h>
 #include <sysexits.h>
 
+#include <chrono>
 #include <string>
 
 #include <android-base/file.h>
@@ -29,17 +30,30 @@
 #include <storage_literals/storage_literals.h>
 
 #include "snapshot_fuzz_utils.h"
+#include "utility.h"
+
+// Prepends the errno string, but it is good enough.
+#ifndef PCHECK
+#define PCHECK(x) CHECK(x) << strerror(errno) << ": "
+#endif
 
 using namespace android::storage_literals;
+using namespace std::chrono_literals;
 using namespace std::string_literals;
 
 using android::base::StringPrintf;
 using android::base::unique_fd;
 using android::base::WriteStringToFile;
+using android::dm::LoopControl;
 using android::fiemap::IImageManager;
 using android::fiemap::ImageManager;
+using android::fs_mgr::BlockDeviceInfo;
+using android::fs_mgr::IPartitionOpener;
+using chromeos_update_engine::DynamicPartitionMetadata;
 
-static const char MNT_DIR[] = "/mnt";
+// This directory is exempted from pinning in ImageManager.
+static const char MNT_DIR[] = "/data/gsi/ota/test/";
+
 static const char FAKE_ROOT_NAME[] = "snapshot_fuzz";
 static const auto SUPER_IMAGE_SIZE = 16_MiB;
 static const auto FAKE_ROOT_SIZE = 64_MiB;
@@ -128,6 +142,9 @@
 class AutoMemBasedDir : public AutoDevice {
   public:
     static std::unique_ptr<AutoMemBasedDir> New(const std::string& name, uint64_t size) {
+        if (!Mkdir(MNT_DIR)) {
+            return std::unique_ptr<AutoMemBasedDir>(new AutoMemBasedDir(""));
+        }
         auto ret = std::unique_ptr<AutoMemBasedDir>(new AutoMemBasedDir(name));
         ret->auto_delete_mount_dir_ = AutoDeleteDir::New(ret->mount_path());
         if (!ret->auto_delete_mount_dir_->HasDevice()) {
@@ -137,20 +154,31 @@
         if (!ret->auto_umount_mount_point_->HasDevice()) {
             return std::unique_ptr<AutoMemBasedDir>(new AutoMemBasedDir(""));
         }
-        // path() does not need to be deleted upon destruction, hence it is not wrapped with
-        // AutoDeleteDir.
-        if (!Mkdir(ret->path())) {
+        // tmp_path() and persist_path does not need to be deleted upon destruction, hence it is
+        // not wrapped with AutoDeleteDir.
+        if (!Mkdir(ret->tmp_path())) {
+            return std::unique_ptr<AutoMemBasedDir>(new AutoMemBasedDir(""));
+        }
+        if (!Mkdir(ret->persist_path())) {
             return std::unique_ptr<AutoMemBasedDir>(new AutoMemBasedDir(""));
         }
         return ret;
     }
-    // Return the scratch directory.
-    std::string path() const {
+    // Return the temporary scratch directory.
+    std::string tmp_path() const {
         CHECK(HasDevice());
-        return mount_path() + "/root";
+        return mount_path() + "/tmp";
     }
-    // Delete all contents in path() and start over. path() itself is re-created.
-    bool SoftReset() { return RmdirRecursive(path()) && Mkdir(path()); }
+    // Return the temporary scratch directory.
+    std::string persist_path() const {
+        CHECK(HasDevice());
+        return mount_path() + "/persist";
+    }
+    // Delete all contents in tmp_path() and start over. tmp_path() itself is re-created.
+    void CheckSoftReset() {
+        PCHECK(RmdirRecursive(tmp_path()));
+        PCHECK(Mkdir(tmp_path()));
+    }
 
   private:
     AutoMemBasedDir(const std::string& name) : AutoDevice(name) {}
@@ -164,64 +192,123 @@
 
 SnapshotFuzzEnv::SnapshotFuzzEnv() {
     fake_root_ = AutoMemBasedDir::New(FAKE_ROOT_NAME, FAKE_ROOT_SIZE);
+    CHECK(fake_root_ != nullptr);
+    CHECK(fake_root_->HasDevice());
+    loop_control_ = std::make_unique<LoopControl>();
+    mapped_super_ = CheckMapSuper(fake_root_->persist_path(), loop_control_.get(), &fake_super_);
 }
 
 SnapshotFuzzEnv::~SnapshotFuzzEnv() = default;
 
-bool SnapshotFuzzEnv::InitOk() const {
-    if (fake_root_ == nullptr || !fake_root_->HasDevice()) return false;
-    return true;
+void CheckZeroFill(const std::string& file, size_t size) {
+    std::string zeros(size, '\0');
+    PCHECK(WriteStringToFile(zeros, file)) << "Cannot write zeros to " << file;
 }
 
-bool SnapshotFuzzEnv::SoftReset() {
-    return fake_root_->SoftReset();
+void SnapshotFuzzEnv::CheckSoftReset() {
+    fake_root_->CheckSoftReset();
+    CheckZeroFill(super(), SUPER_IMAGE_SIZE);
 }
 
-std::unique_ptr<IImageManager> SnapshotFuzzEnv::CreateFakeImageManager(
-        const std::string& fake_root) {
-    auto images_dir = fake_root + "/images";
+std::unique_ptr<IImageManager> SnapshotFuzzEnv::CheckCreateFakeImageManager(
+        const std::string& path) {
+    auto images_dir = path + "/images";
     auto metadata_dir = images_dir + "/metadata";
     auto data_dir = images_dir + "/data";
 
-    if (!Mkdir(images_dir) || !Mkdir(metadata_dir) || !Mkdir(data_dir)) {
-        return nullptr;
-    }
+    PCHECK(Mkdir(images_dir));
+    PCHECK(Mkdir(metadata_dir));
+    PCHECK(Mkdir(data_dir));
     return ImageManager::Open(metadata_dir, data_dir);
 }
 
-std::unique_ptr<TestPartitionOpener> SnapshotFuzzEnv::CreatePartitionOpener(
-        const std::string& fake_root) {
-    auto fake_super = fake_root + "/super.img";
-    std::string zeros(SUPER_IMAGE_SIZE, '\0');
-
-    if (!WriteStringToFile(zeros, fake_super)) {
-        PLOG(ERROR) << "Cannot write zeros to " << fake_super;
-        return nullptr;
-    }
-
-    return std::make_unique<TestPartitionOpener>(fake_super);
+// Helper to create a loop device for a file.
+static void CheckCreateLoopDevice(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));
+    PCHECK(file_fd >= 0) << "Could not open file: " << file;
+    CHECK(control->Attach(file_fd, timeout_ms, path))
+            << "Could not create loop device for: " << file;
 }
 
-std::string SnapshotFuzzEnv::root() const {
-    CHECK(InitOk());
-    return fake_root_->path();
+class AutoDetachLoopDevice : public AutoDevice {
+  public:
+    AutoDetachLoopDevice(LoopControl* control, const std::string& device)
+        : AutoDevice(device), control_(control) {}
+    ~AutoDetachLoopDevice() { control_->Detach(name_); }
+
+  private:
+    LoopControl* control_;
+};
+
+std::unique_ptr<AutoDevice> SnapshotFuzzEnv::CheckMapSuper(const std::string& fake_persist_path,
+                                                           LoopControl* control,
+                                                           std::string* fake_super) {
+    auto super_img = fake_persist_path + "/super.img";
+    CheckZeroFill(super_img, SUPER_IMAGE_SIZE);
+    CheckCreateLoopDevice(control, super_img, 1s, fake_super);
+
+    return std::make_unique<AutoDetachLoopDevice>(control, *fake_super);
 }
 
-std::unique_ptr<ISnapshotManager> SnapshotFuzzEnv::CreateSnapshotManager(
-        const SnapshotManagerFuzzData& data) {
-    // TODO(b/154633114): create valid super partition according to fuzz data
-    auto partition_opener = CreatePartitionOpener(root());
-    if (partition_opener == nullptr) return nullptr;
-    auto metadata_dir = root() + "/snapshot_metadata";
-    if (!Mkdir(metadata_dir)) return nullptr;
+std::unique_ptr<ISnapshotManager> SnapshotFuzzEnv::CheckCreateSnapshotManager(
+        const SnapshotFuzzData& data) {
+    auto partition_opener = std::make_unique<TestPartitionOpener>(super());
+    CheckWriteSuperMetadata(data, *partition_opener);
+    auto metadata_dir = fake_root_->tmp_path() + "/snapshot_metadata";
+    PCHECK(Mkdir(metadata_dir));
 
-    auto device_info = new SnapshotFuzzDeviceInfo(data.device_info_data,
+    auto device_info = new SnapshotFuzzDeviceInfo(data.device_info_data(),
                                                   std::move(partition_opener), metadata_dir);
     auto snapshot = SnapshotManager::New(device_info /* takes ownership */);
-    snapshot->images_ = CreateFakeImageManager(root());
-    snapshot->has_local_image_manager_ = data.is_local_image_manager;
+    snapshot->images_ = CheckCreateFakeImageManager(fake_root_->tmp_path());
+    snapshot->has_local_image_manager_ = data.manager_data().is_local_image_manager();
 
     return snapshot;
 }
 
+const std::string& SnapshotFuzzEnv::super() const {
+    return fake_super_;
+}
+
+void SnapshotFuzzEnv::CheckWriteSuperMetadata(const SnapshotFuzzData& data,
+                                              const IPartitionOpener& opener) {
+    if (!data.is_super_metadata_valid()) {
+        // Leave it zero.
+        return;
+    }
+
+    BlockDeviceInfo super_device("super", SUPER_IMAGE_SIZE, 0, 0, 4096);
+    std::vector<BlockDeviceInfo> devices = {super_device};
+    auto builder = MetadataBuilder::New(devices, "super", 65536, 2);
+    CHECK(builder != nullptr);
+
+    // Attempt to create a super partition metadata using proto. All errors are ignored.
+    for (const auto& group_proto : data.super_data().dynamic_partition_metadata().groups()) {
+        (void)builder->AddGroup(group_proto.name(), group_proto.size());
+        for (const auto& partition_name : group_proto.partition_names()) {
+            (void)builder->AddPartition(partition_name, group_proto.name(),
+                                        LP_PARTITION_ATTR_READONLY);
+        }
+    }
+
+    for (const auto& partition_proto : data.super_data().partitions()) {
+        auto p = builder->FindPartition(partition_proto.partition_name());
+        if (p == nullptr) continue;
+        (void)builder->ResizePartition(p, partition_proto.new_partition_info().size());
+    }
+
+    auto metadata = builder->Export();
+    // metadata may be nullptr if it is not valid (e.g. partition name too long).
+    // In this case, just use empty super partition data.
+    if (metadata == nullptr) {
+        builder = MetadataBuilder::New(devices, "super", 65536, 2);
+        CHECK(builder != nullptr);
+        metadata = builder->Export();
+        CHECK(metadata != nullptr);
+    }
+    CHECK(FlashPartitionTable(opener, super(), *metadata.get()));
+}
+
 }  // namespace android::snapshot
diff --git a/fs_mgr/libsnapshot/snapshot_fuzz_utils.h b/fs_mgr/libsnapshot/snapshot_fuzz_utils.h
index 32910a9..5533def 100644
--- a/fs_mgr/libsnapshot/snapshot_fuzz_utils.h
+++ b/fs_mgr/libsnapshot/snapshot_fuzz_utils.h
@@ -16,32 +16,27 @@
 
 #include <android-base/file.h>
 #include <android-base/stringprintf.h>
+#include <android/snapshot/snapshot_fuzz.pb.h>
+#include <libdm/loop_control.h>
 #include <libfiemap/image_manager.h>
+#include <liblp/liblp.h>
 #include <libsnapshot/auto_device.h>
 #include <libsnapshot/test_helpers.h>
 
 // libsnapshot-specific code for fuzzing. Defines fake classes that are depended
 // by SnapshotManager.
 
+#include "android/snapshot/snapshot_fuzz.pb.h"
+
 namespace android::snapshot {
 
-// Controls the behavior of IDeviceInfo.
-typedef struct SnapshotFuzzDeviceInfoData {
-    bool slot_suffix_is_a : 1;
-    bool is_overlayfs_setup : 1;
-    bool allow_set_boot_control_merge_status : 1;
-    bool allow_set_slot_as_unbootable : 1;
-    bool is_recovery : 1;
-} __attribute__((packed)) SnapshotFuzzDeviceInfoData;
-
-// Controls the behavior of the test SnapshotManager.
-typedef struct SnapshotManagerFuzzData {
-    SnapshotFuzzDeviceInfoData device_info_data;
-    bool is_local_image_manager : 1;
-} __attribute__((packed)) SnapshotManagerFuzzData;
-
 class AutoMemBasedDir;
 
+class DummyAutoDevice : public AutoDevice {
+  public:
+    DummyAutoDevice(bool mounted) : AutoDevice(mounted ? "dummy" : "") {}
+};
+
 // Prepare test environment. This has a heavy overhead and should be done once.
 class SnapshotFuzzEnv {
   public:
@@ -52,36 +47,41 @@
     SnapshotFuzzEnv();
     ~SnapshotFuzzEnv();
 
-    // Check if environment is initialized properly.
-    bool InitOk() const;
-
-    // A scratch directory for the test to play around with. The scratch directory
-    // is backed by tmpfs. SoftReset() clears the directory.
-    std::string root() const;
-
     // Soft reset part of the environment before running the next test.
-    bool SoftReset();
+    // Abort if fails.
+    void CheckSoftReset();
 
     // Create a snapshot manager for this test run.
     // Client is responsible for maintaining the lifetime of |data| over the life time of
     // ISnapshotManager.
-    std::unique_ptr<ISnapshotManager> CreateSnapshotManager(const SnapshotManagerFuzzData& data);
+    std::unique_ptr<ISnapshotManager> CheckCreateSnapshotManager(const SnapshotFuzzData& data);
+
+    // Return path to super partition.
+    const std::string& super() const;
 
   private:
     std::unique_ptr<AutoMemBasedDir> fake_root_;
+    std::unique_ptr<android::dm::LoopControl> loop_control_;
+    std::unique_ptr<AutoDevice> mapped_super_;
+    std::string fake_super_;
 
-    static std::unique_ptr<android::fiemap::IImageManager> CreateFakeImageManager(
-            const std::string& fake_root);
-    static std::unique_ptr<TestPartitionOpener> CreatePartitionOpener(const std::string& fake_root);
+    static std::unique_ptr<android::fiemap::IImageManager> CheckCreateFakeImageManager(
+            const std::string& fake_tmp_path);
+    static std::unique_ptr<AutoDevice> CheckMapSuper(const std::string& fake_persist_path,
+                                                     android::dm::LoopControl* control,
+                                                     std::string* fake_super);
+
+    void CheckWriteSuperMetadata(const SnapshotFuzzData& proto,
+                                 const android::fs_mgr::IPartitionOpener& opener);
 };
 
 class SnapshotFuzzDeviceInfo : public ISnapshotManager::IDeviceInfo {
   public:
     // Client is responsible for maintaining the lifetime of |data|.
-    SnapshotFuzzDeviceInfo(const SnapshotFuzzDeviceInfoData& data,
+    SnapshotFuzzDeviceInfo(const FuzzDeviceInfoData& data,
                            std::unique_ptr<TestPartitionOpener>&& partition_opener,
                            const std::string& metadata_dir)
-        : data_(data),
+        : data_(&data),
           partition_opener_(std::move(partition_opener)),
           metadata_dir_(metadata_dir) {}
 
@@ -97,17 +97,21 @@
     }
 
     // Following APIs are fuzzed.
-    std::string GetSlotSuffix() const override { return data_.slot_suffix_is_a ? "_a" : "_b"; }
-    std::string GetOtherSlotSuffix() const override { return data_.slot_suffix_is_a ? "_b" : "_a"; }
-    bool IsOverlayfsSetup() const override { return data_.is_overlayfs_setup; }
-    bool SetBootControlMergeStatus(android::hardware::boot::V1_1::MergeStatus) override {
-        return data_.allow_set_boot_control_merge_status;
+    std::string GetSlotSuffix() const override { return data_->slot_suffix_is_a() ? "_a" : "_b"; }
+    std::string GetOtherSlotSuffix() const override {
+        return data_->slot_suffix_is_a() ? "_b" : "_a";
     }
-    bool SetSlotAsUnbootable(unsigned int) override { return data_.allow_set_slot_as_unbootable; }
-    bool IsRecovery() const override { return data_.is_recovery; }
+    bool IsOverlayfsSetup() const override { return data_->is_overlayfs_setup(); }
+    bool SetBootControlMergeStatus(android::hardware::boot::V1_1::MergeStatus) override {
+        return data_->allow_set_boot_control_merge_status();
+    }
+    bool SetSlotAsUnbootable(unsigned int) override {
+        return data_->allow_set_slot_as_unbootable();
+    }
+    bool IsRecovery() const override { return data_->is_recovery(); }
 
   private:
-    SnapshotFuzzDeviceInfoData data_;
+    const FuzzDeviceInfoData* data_;
     std::unique_ptr<TestPartitionOpener> partition_opener_;
     std::string metadata_dir_;
 };
diff --git a/fs_mgr/libsnapshot/snapshot_stub.cpp b/fs_mgr/libsnapshot/snapshot_stub.cpp
index 2747b03..2aaa78c 100644
--- a/fs_mgr/libsnapshot/snapshot_stub.cpp
+++ b/fs_mgr/libsnapshot/snapshot_stub.cpp
@@ -89,6 +89,11 @@
     return false;
 }
 
+bool SnapshotManagerStub::FinishMergeInRecovery() {
+    LOG(ERROR) << __FUNCTION__ << " should never be called.";
+    return false;
+}
+
 CreateResult SnapshotManagerStub::RecoveryCreateSnapshotDevices() {
     LOG(ERROR) << __FUNCTION__ << " should never be called.";
     return CreateResult::ERROR;
diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp
index 53a37be..2bd0135 100644
--- a/fs_mgr/libsnapshot/snapshot_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_test.cpp
@@ -1459,6 +1459,52 @@
     ASSERT_EQ(new_sm->GetUpdateState(), UpdateState::None);
 }
 
+// Test that a merge does not clear the snapshot state in fastboot.
+TEST_F(SnapshotUpdateTest, MergeInFastboot) {
+    // Execute the first update.
+    ASSERT_TRUE(sm->BeginUpdate());
+    ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
+    ASSERT_TRUE(MapUpdateSnapshots());
+    ASSERT_TRUE(sm->FinishedSnapshotWrites(false));
+
+    // Simulate shutting down the device.
+    ASSERT_TRUE(UnmapAll());
+
+    // After reboot, init does first stage mount.
+    auto init = SnapshotManager::NewForFirstStageMount(new TestDeviceInfo(fake_super, "_b"));
+    ASSERT_NE(init, nullptr);
+    ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount());
+    ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
+    init = nullptr;
+
+    // Initiate the merge and then immediately stop it to simulate a reboot.
+    auto new_sm = SnapshotManager::New(new TestDeviceInfo(fake_super, "_b"));
+    ASSERT_TRUE(new_sm->InitiateMerge());
+    ASSERT_TRUE(UnmapAll());
+
+    // Simulate a reboot into recovery.
+    auto test_device = std::make_unique<TestDeviceInfo>(fake_super, "_b");
+    test_device->set_recovery(true);
+    new_sm = SnapshotManager::NewForFirstStageMount(test_device.release());
+
+    ASSERT_TRUE(new_sm->FinishMergeInRecovery());
+
+    auto mount = new_sm->EnsureMetadataMounted();
+    ASSERT_TRUE(mount && mount->HasDevice());
+    ASSERT_EQ(new_sm->ProcessUpdateState(), UpdateState::MergeCompleted);
+
+    // Finish the merge in a normal boot.
+    test_device = std::make_unique<TestDeviceInfo>(fake_super, "_b");
+    init = SnapshotManager::NewForFirstStageMount(test_device.release());
+    ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
+    init = nullptr;
+
+    test_device = std::make_unique<TestDeviceInfo>(fake_super, "_b");
+    new_sm = SnapshotManager::NewForFirstStageMount(test_device.release());
+    ASSERT_EQ(new_sm->ProcessUpdateState(), UpdateState::MergeCompleted);
+    ASSERT_EQ(new_sm->ProcessUpdateState(), UpdateState::None);
+}
+
 // Test that after an OTA, before a merge, we can wipe data in recovery.
 TEST_F(SnapshotUpdateTest, DataWipeRollbackInRecovery) {
     // Execute the first update.
diff --git a/fs_mgr/libsnapshot/update_engine/update_metadata.proto b/fs_mgr/libsnapshot/update_engine/update_metadata.proto
new file mode 100644
index 0000000..be5e1fe
--- /dev/null
+++ b/fs_mgr/libsnapshot/update_engine/update_metadata.proto
@@ -0,0 +1,75 @@
+//
+// Copyright (C) 2020 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.
+//
+
+// A subset of system/update_engine/update_metadata.proto. A separate file is
+// used here because:
+// - The original file is optimized for LITE_RUNTIME, but fuzzing needs
+// reflection.
+// - The definition here has less fields. libsnapshot only uses fields declared
+// here, and all fields declared here are fuzzed by libsnapshot_fuzzer. If
+// libsnapshot uses more fields in system/update_engine/update_metadata.proto
+// in the future, they must be added here too, otherwise it will fail to
+// compile.
+//
+// It is okay that this file is older than
+// system/update_engine/update_metadata.proto as long as the messages defined
+// here can also be parsed by protobuf defined there. However, it is not
+// okay to add fields here without adding them to
+// system/update_engine/update_metadata.proto. Doing so will cause a compiler
+// error when libsnapshot code starts to use these dangling fields.
+
+syntax = "proto2";
+
+package chromeos_update_engine;
+
+message Extent {
+    optional uint64 start_block = 1;
+    optional uint64 num_blocks = 2;
+}
+
+message PartitionInfo {
+    optional uint64 size = 1;
+}
+
+message InstallOperation {
+    enum Type { SOURCE_COPY = 4; }
+    required Type type = 1;
+    repeated Extent src_extents = 4;
+    repeated Extent dst_extents = 6;
+}
+
+message PartitionUpdate {
+    required string partition_name = 1;
+    optional PartitionInfo new_partition_info = 7;
+    repeated InstallOperation operations = 8;
+    optional Extent hash_tree_extent = 11;
+    optional Extent fec_extent = 15;
+}
+
+message DynamicPartitionGroup {
+    required string name = 1;
+    optional uint64 size = 2;
+    repeated string partition_names = 3;
+}
+
+message DynamicPartitionMetadata {
+    repeated DynamicPartitionGroup groups = 1;
+}
+
+message DeltaArchiveManifest {
+    repeated PartitionUpdate partitions = 13;
+    optional DynamicPartitionMetadata dynamic_partition_metadata = 15;
+}
diff --git a/init/first_stage_console.cpp b/init/first_stage_console.cpp
index cae53f4..cfa0d99 100644
--- a/init/first_stage_console.cpp
+++ b/init/first_stage_console.cpp
@@ -16,6 +16,7 @@
 
 #include "first_stage_console.h"
 
+#include <stdio.h>
 #include <sys/stat.h>
 #include <sys/sysmacros.h>
 #include <sys/types.h>
@@ -87,8 +88,18 @@
     _exit(127);
 }
 
-bool FirstStageConsole(const std::string& cmdline) {
-    return cmdline.find("androidboot.first_stage_console=1") != std::string::npos;
+int FirstStageConsole(const std::string& cmdline) {
+    auto pos = cmdline.find("androidboot.first_stage_console=");
+    if (pos != std::string::npos) {
+        int val = 0;
+        if (sscanf(cmdline.c_str() + pos, "androidboot.first_stage_console=%d", &val) != 1) {
+            return FirstStageConsoleParam::DISABLED;
+        }
+        if (val <= FirstStageConsoleParam::MAX_PARAM_VALUE && val >= 0) {
+            return val;
+        }
+    }
+    return FirstStageConsoleParam::DISABLED;
 }
 
 }  // namespace init
diff --git a/init/first_stage_console.h b/init/first_stage_console.h
index 7485339..8f36a7c 100644
--- a/init/first_stage_console.h
+++ b/init/first_stage_console.h
@@ -21,8 +21,15 @@
 namespace android {
 namespace init {
 
+enum FirstStageConsoleParam {
+    DISABLED = 0,
+    CONSOLE_ON_FAILURE = 1,
+    IGNORE_FAILURE = 2,
+    MAX_PARAM_VALUE = IGNORE_FAILURE,
+};
+
 void StartConsole();
-bool FirstStageConsole(const std::string& cmdline);
+int FirstStageConsole(const std::string& cmdline);
 
 }  // namespace init
 }  // namespace android
diff --git a/init/first_stage_init.cpp b/init/first_stage_init.cpp
index 5eca644..1a608f6 100644
--- a/init/first_stage_init.cpp
+++ b/init/first_stage_init.cpp
@@ -200,16 +200,16 @@
     }
 
     Modprobe m({"/lib/modules"}, module_load_file);
-    auto want_console = ALLOW_FIRST_STAGE_CONSOLE && FirstStageConsole(cmdline);
+    auto want_console = ALLOW_FIRST_STAGE_CONSOLE ? FirstStageConsole(cmdline) : 0;
     if (!m.LoadListedModules(!want_console)) {
-        if (want_console) {
+        if (want_console != FirstStageConsoleParam::DISABLED) {
             LOG(ERROR) << "Failed to load kernel modules, starting console";
         } else {
             LOG(FATAL) << "Failed to load kernel modules";
         }
     }
 
-    if (want_console) {
+    if (want_console == FirstStageConsoleParam::CONSOLE_ON_FAILURE) {
         StartConsole();
     }
 
diff --git a/init/init_test.cpp b/init/init_test.cpp
index caf3e03..07b4724 100644
--- a/init/init_test.cpp
+++ b/init/init_test.cpp
@@ -239,6 +239,28 @@
     EXPECT_EQ(6, num_executed);
 }
 
+TEST(init, RejectsCriticalAndOneshotService) {
+    std::string init_script =
+            R"init(
+service A something
+  class first
+  critical
+  oneshot
+)init";
+
+    TemporaryFile tf;
+    ASSERT_TRUE(tf.fd != -1);
+    ASSERT_TRUE(android::base::WriteStringToFd(init_script, tf.fd));
+
+    ServiceList service_list;
+    Parser parser;
+    parser.AddSectionParser("service",
+                            std::make_unique<ServiceParser>(&service_list, nullptr, std::nullopt));
+
+    ASSERT_TRUE(parser.ParseConfig(tf.path));
+    ASSERT_EQ(1u, parser.parse_error_count());
+}
+
 }  // namespace init
 }  // namespace android
 
diff --git a/init/reboot.cpp b/init/reboot.cpp
index 72f0450..e89f74a 100644
--- a/init/reboot.cpp
+++ b/init/reboot.cpp
@@ -758,20 +758,24 @@
 
 static Result<void> DoUserspaceReboot() {
     LOG(INFO) << "Userspace reboot initiated";
-    auto guard = android::base::make_scope_guard([] {
+    // An ugly way to pass a more precise reason on why fallback to hard reboot was triggered.
+    std::string sub_reason = "";
+    auto guard = android::base::make_scope_guard([&sub_reason] {
         // Leave shutdown so that we can handle a full reboot.
         LeaveShutdown();
-        trigger_shutdown("reboot,userspace_failed,shutdown_aborted");
+        trigger_shutdown("reboot,userspace_failed,shutdown_aborted," + sub_reason);
     });
     // Triggering userspace-reboot-requested will result in a bunch of setprop
     // actions. We should make sure, that all of them are propagated before
     // proceeding with userspace reboot. Synchronously setting sys.init.userspace_reboot.in_progress
     // property is not perfect, but it should do the trick.
     if (!android::sysprop::InitProperties::userspace_reboot_in_progress(true)) {
+        sub_reason = "setprop";
         return Error() << "Failed to set sys.init.userspace_reboot.in_progress property";
     }
     EnterShutdown();
     if (!SetProperty("sys.powerctl", "")) {
+        sub_reason = "resetprop";
         return Error() << "Failed to reset sys.powerctl property";
     }
     std::vector<Service*> stop_first;
@@ -800,18 +804,22 @@
     StopServicesAndLogViolations(stop_first, sigterm_timeout, true /* SIGTERM */);
     if (int r = StopServicesAndLogViolations(stop_first, sigkill_timeout, false /* SIGKILL */);
         r > 0) {
+        sub_reason = "sigkill";
         // TODO(b/135984674): store information about offending services for debugging.
         return Error() << r << " post-data services are still running";
     }
     if (auto result = KillZramBackingDevice(); !result.ok()) {
+        sub_reason = "zram";
         return result;
     }
     if (auto result = CallVdc("volume", "reset"); !result.ok()) {
+        sub_reason = "vold_reset";
         return result;
     }
     if (int r = StopServicesAndLogViolations(GetDebuggingServices(true /* only_post_data */),
                                              sigkill_timeout, false /* SIGKILL */);
         r > 0) {
+        sub_reason = "sigkill_debug";
         // TODO(b/135984674): store information about offending services for debugging.
         return Error() << r << " debugging services are still running";
     }
@@ -822,9 +830,11 @@
         LOG(INFO) << "sync() took " << sync_timer;
     }
     if (auto result = UnmountAllApexes(); !result.ok()) {
+        sub_reason = "apex";
         return result;
     }
     if (!SwitchToBootstrapMountNamespaceIfNeeded()) {
+        sub_reason = "ns_switch";
         return Error() << "Failed to switch to bootstrap namespace";
     }
     // Remove services that were defined in an APEX.
diff --git a/init/service_parser.cpp b/init/service_parser.cpp
index 1444258..bdac077 100644
--- a/init/service_parser.cpp
+++ b/init/service_parser.cpp
@@ -605,6 +605,13 @@
         }
     }
 
+    if (SelinuxGetVendorAndroidVersion() >= __ANDROID_API_R__) {
+        if ((service_->flags() & SVC_CRITICAL) != 0 && (service_->flags() & SVC_ONESHOT) != 0) {
+            return Error() << "service '" << service_->name()
+                           << "' can't be both critical and oneshot";
+        }
+    }
+
     Service* old_service = service_list_->FindService(service_->name());
     if (old_service) {
         if (!service_->is_override()) {
diff --git a/libcutils/Android.bp b/libcutils/Android.bp
index 175b2b7..d7c83a2 100644
--- a/libcutils/Android.bp
+++ b/libcutils/Android.bp
@@ -33,6 +33,7 @@
         "//apex_available:platform",
         "//apex_available:anyapex",
     ],
+    min_sdk_version: "29",
     native_bridge_supported: true,
     export_include_dirs: ["include"],
     target: {
@@ -59,6 +60,7 @@
         "//apex_available:platform",
         "//apex_available:anyapex",
     ],
+    min_sdk_version: "29",
 
     export_include_dirs: ["include"],
 
@@ -142,6 +144,7 @@
         "//apex_available:platform",
         "//apex_available:anyapex",
     ],
+    min_sdk_version: "29",
     native_bridge_supported: true,
     srcs: [
         "config_utils.cpp",
diff --git a/libgrallocusage/Android.bp b/libgrallocusage/Android.bp
index ce0c3c8..33ae13d 100644
--- a/libgrallocusage/Android.bp
+++ b/libgrallocusage/Android.bp
@@ -26,4 +26,5 @@
     export_include_dirs: ["include"],
     shared_libs: ["android.hardware.graphics.allocator@2.0"],
     header_libs: ["libhardware_headers"],
+    min_sdk_version: "29",
 }
diff --git a/libprocessgroup/Android.bp b/libprocessgroup/Android.bp
index 2c1b255..bda11e9 100644
--- a/libprocessgroup/Android.bp
+++ b/libprocessgroup/Android.bp
@@ -17,6 +17,7 @@
         "//apex_available:platform",
         "//apex_available:anyapex",
     ],
+    min_sdk_version: "29",
 }
 
 cc_library {
@@ -60,4 +61,5 @@
         "//apex_available:platform",
         "//apex_available:anyapex",
     ],
+    min_sdk_version: "29",
 }
diff --git a/libunwindstack/Maps.cpp b/libunwindstack/Maps.cpp
index 8f49ad9..670d904 100644
--- a/libunwindstack/Maps.cpp
+++ b/libunwindstack/Maps.cpp
@@ -159,6 +159,8 @@
         search_map_idx = old_map_idx + 1;
         if (new_map_idx + 1 < maps_.size()) {
           maps_[new_map_idx + 1]->prev_map = info.get();
+          maps_[new_map_idx + 1]->prev_real_map =
+              info->IsBlank() ? info->prev_real_map : info.get();
         }
         maps_[new_map_idx] = nullptr;
         total_entries--;
diff --git a/libunwindstack/tests/LocalUpdatableMapsTest.cpp b/libunwindstack/tests/LocalUpdatableMapsTest.cpp
index b816b9a..99afb0b 100644
--- a/libunwindstack/tests/LocalUpdatableMapsTest.cpp
+++ b/libunwindstack/tests/LocalUpdatableMapsTest.cpp
@@ -271,4 +271,103 @@
   EXPECT_TRUE(map_info->name.empty());
 }
 
+TEST_F(LocalUpdatableMapsTest, add_map_prev_name_updated) {
+  TemporaryFile tf;
+  ASSERT_TRUE(
+      android::base::WriteStringToFile("3000-4000 rwxp 00000 00:00 0\n"
+                                       "8000-9000 r-xp 00000 00:00 0\n"
+                                       "9000-a000 r-xp 00000 00:00 0\n",
+                                       tf.path));
+
+  maps_.TestSetMapsFile(tf.path);
+  ASSERT_TRUE(maps_.Reparse());
+  ASSERT_EQ(3U, maps_.Total());
+
+  MapInfo* map_info = maps_.Get(2);
+  ASSERT_TRUE(map_info != nullptr);
+  EXPECT_EQ(0x9000U, map_info->start);
+  EXPECT_EQ(0xA000U, map_info->end);
+  EXPECT_EQ(0U, map_info->offset);
+  EXPECT_EQ(PROT_READ | PROT_EXEC, map_info->flags);
+  EXPECT_TRUE(map_info->name.empty());
+  EXPECT_EQ(maps_.Get(1), map_info->prev_map);
+}
+
+TEST_F(LocalUpdatableMapsTest, add_map_prev_real_name_updated) {
+  TemporaryFile tf;
+  ASSERT_TRUE(
+      android::base::WriteStringToFile("3000-4000 r-xp 00000 00:00 0 /fake/lib.so\n"
+                                       "4000-5000 ---p 00000 00:00 0\n"
+                                       "7000-8000 r-xp 00000 00:00 0 /fake/lib1.so\n"
+                                       "8000-9000 ---p 00000 00:00 0\n",
+                                       tf.path));
+
+  maps_.TestSetMapsFile(tf.path);
+  ASSERT_TRUE(maps_.Reparse());
+  ASSERT_EQ(4U, maps_.Total());
+
+  MapInfo* map_info = maps_.Get(2);
+  ASSERT_TRUE(map_info != nullptr);
+  EXPECT_EQ(0x7000U, map_info->start);
+  EXPECT_EQ(0x8000U, map_info->end);
+  EXPECT_EQ(0U, map_info->offset);
+  EXPECT_EQ(PROT_READ | PROT_EXEC, map_info->flags);
+  EXPECT_EQ(maps_.Get(0), map_info->prev_real_map);
+  EXPECT_EQ(maps_.Get(1), map_info->prev_map);
+  EXPECT_EQ("/fake/lib1.so", map_info->name);
+
+  map_info = maps_.Get(3);
+  ASSERT_TRUE(map_info != nullptr);
+  EXPECT_EQ(0x8000U, map_info->start);
+  EXPECT_EQ(0x9000U, map_info->end);
+  EXPECT_EQ(0U, map_info->offset);
+  EXPECT_TRUE(map_info->IsBlank());
+  EXPECT_EQ(maps_.Get(2), map_info->prev_real_map);
+  EXPECT_EQ(maps_.Get(2), map_info->prev_map);
+  EXPECT_TRUE(map_info->name.empty());
+
+  ASSERT_TRUE(
+      android::base::WriteStringToFile("3000-4000 r-xp 00000 00:00 0 /fake/lib.so\n"
+                                       "4000-5000 ---p 00000 00:00 0\n"
+                                       "7000-8000 r-xp 00000 00:00 0 /fake/lib1.so\n"
+                                       "8000-9000 ---p 00000 00:00 0\n"
+                                       "9000-a000 r-xp 00000 00:00 0 /fake/lib2.so\n"
+                                       "a000-b000 r-xp 00000 00:00 0 /fake/lib3.so\n",
+                                       tf.path));
+
+  maps_.TestSetMapsFile(tf.path);
+  ASSERT_TRUE(maps_.Reparse());
+  ASSERT_EQ(6U, maps_.Total());
+
+  map_info = maps_.Get(2);
+  ASSERT_TRUE(map_info != nullptr);
+  EXPECT_EQ(0x7000U, map_info->start);
+  EXPECT_EQ(0x8000U, map_info->end);
+  EXPECT_EQ(0U, map_info->offset);
+  EXPECT_EQ(PROT_READ | PROT_EXEC, map_info->flags);
+  EXPECT_EQ("/fake/lib1.so", map_info->name);
+  EXPECT_EQ(maps_.Get(1), map_info->prev_map);
+  EXPECT_EQ(maps_.Get(0), map_info->prev_real_map);
+
+  map_info = maps_.Get(4);
+  ASSERT_TRUE(map_info != nullptr);
+  EXPECT_EQ(0x9000U, map_info->start);
+  EXPECT_EQ(0xA000U, map_info->end);
+  EXPECT_EQ(0U, map_info->offset);
+  EXPECT_EQ(PROT_READ | PROT_EXEC, map_info->flags);
+  EXPECT_EQ("/fake/lib2.so", map_info->name);
+  EXPECT_EQ(maps_.Get(3), map_info->prev_map);
+  EXPECT_EQ(maps_.Get(2), map_info->prev_real_map);
+
+  map_info = maps_.Get(5);
+  ASSERT_TRUE(map_info != nullptr);
+  EXPECT_EQ(0xA000U, map_info->start);
+  EXPECT_EQ(0xB000U, map_info->end);
+  EXPECT_EQ(0U, map_info->offset);
+  EXPECT_EQ(PROT_READ | PROT_EXEC, map_info->flags);
+  EXPECT_EQ("/fake/lib3.so", map_info->name);
+  EXPECT_EQ(maps_.Get(4), map_info->prev_map);
+  EXPECT_EQ(maps_.Get(4), map_info->prev_real_map);
+}
+
 }  // namespace unwindstack
diff --git a/rootdir/etc/public.libraries.android.txt b/rootdir/etc/public.libraries.android.txt
index 405f5a9..5de422f 100644
--- a/rootdir/etc/public.libraries.android.txt
+++ b/rootdir/etc/public.libraries.android.txt
@@ -16,6 +16,7 @@
 liblog.so
 libmediandk.so
 libm.so
+libnativehelper.so
 libnativewindow.so
 libneuralnetworks.so nopreload
 libOpenMAXAL.so
diff --git a/rootdir/etc/public.libraries.iot.txt b/rootdir/etc/public.libraries.iot.txt
index b565340..77f8bb8 100644
--- a/rootdir/etc/public.libraries.iot.txt
+++ b/rootdir/etc/public.libraries.iot.txt
@@ -17,6 +17,7 @@
 liblog.so
 libmediandk.so
 libm.so
+libnativehelper.so
 libnativewindow.so
 libneuralnetworks.so
 libOpenMAXAL.so
diff --git a/rootdir/etc/public.libraries.wear.txt b/rootdir/etc/public.libraries.wear.txt
index 7cbda08..82196e4 100644
--- a/rootdir/etc/public.libraries.wear.txt
+++ b/rootdir/etc/public.libraries.wear.txt
@@ -16,6 +16,7 @@
 liblog.so
 libmediandk.so
 libm.so
+libnativehelper.so
 libnativewindow.so
 libneuralnetworks.so
 libOpenMAXAL.so