Merge "[adb] Speed up the streaming install"
diff --git a/adb/adb.cpp b/adb/adb.cpp
index 24d4292..7dff1b8 100644
--- a/adb/adb.cpp
+++ b/adb/adb.cpp
@@ -1243,11 +1243,7 @@
// TODO: Switch handle_forward_request to string_view.
std::string service_str(service);
if (handle_forward_request(
- service_str.c_str(),
- [=](std::string* error) {
- return acquire_one_transport(type, serial, transport_id, nullptr, error);
- },
- reply_fd)) {
+ service_str.c_str(), [=](std::string* error) { return s->transport; }, reply_fd)) {
return HostRequestResult::Handled;
}
diff --git a/adb/client/adb_client.cpp b/adb/client/adb_client.cpp
index 7e408a8..0f621eb 100644
--- a/adb/client/adb_client.cpp
+++ b/adb/client/adb_client.cpp
@@ -149,7 +149,8 @@
return false;
}
-static int _adb_connect(std::string_view service, TransportId* transport, std::string* error) {
+static int _adb_connect(std::string_view service, TransportId* transport, std::string* error,
+ bool force_switch = false) {
LOG(DEBUG) << "_adb_connect: " << service;
if (service.empty() || service.size() > MAX_PAYLOAD) {
*error = android::base::StringPrintf("bad service name length (%zd)", service.size());
@@ -164,7 +165,7 @@
return -2;
}
- if (!service.starts_with("host")) {
+ if (!service.starts_with("host") || force_switch) {
std::optional<TransportId> transport_result = switch_socket_transport(fd.get(), error);
if (!transport_result) {
return -1;
@@ -323,7 +324,8 @@
return result;
}
-int adb_connect(TransportId* transport, std::string_view service, std::string* error) {
+int adb_connect(TransportId* transport, std::string_view service, std::string* error,
+ bool force_switch_device) {
LOG(DEBUG) << "adb_connect: service: " << service;
// Query the adb server's version.
@@ -336,7 +338,7 @@
return 0;
}
- unique_fd fd(_adb_connect(service, transport, error));
+ unique_fd fd(_adb_connect(service, transport, error, force_switch_device));
if (fd == -1) {
D("_adb_connect error: %s", error->c_str());
} else if(fd == -2) {
diff --git a/adb/client/adb_client.h b/adb/client/adb_client.h
index fe1e584..ba53041 100644
--- a/adb/client/adb_client.h
+++ b/adb/client/adb_client.h
@@ -34,7 +34,11 @@
int adb_connect(std::string_view service, std::string* _Nonnull error);
// Same as above, except returning the TransportId for the service that we've connected to.
-int adb_connect(TransportId* _Nullable id, std::string_view service, std::string* _Nonnull error);
+// force_switch_device forces the function to attempt to select a device, even if the service
+// string appears to be a host: service (for use with host services that are device specific, like
+// forward).
+int adb_connect(TransportId* _Nullable id, std::string_view service, std::string* _Nonnull error,
+ bool force_switch_device = false);
// Kill the currently running adb server, if it exists.
bool adb_kill_server();
diff --git a/adb/client/adb_install.cpp b/adb/client/adb_install.cpp
index 32e9512..a6e8998 100644
--- a/adb/client/adb_install.cpp
+++ b/adb/client/adb_install.cpp
@@ -431,7 +431,8 @@
android::base::EndsWithIgnoreCase(file, ".dm") ||
android::base::EndsWithIgnoreCase(file, ".fsv_sig")) {
struct stat sb;
- if (stat(file, &sb) != -1) total_size += sb.st_size;
+ if (stat(file, &sb) == -1) perror_exit("failed to stat \"%s\"", file);
+ total_size += sb.st_size;
first_apk = i;
} else {
break;
@@ -481,13 +482,13 @@
}
// Valid session, now stream the APKs
- int success = 1;
+ bool success = true;
for (int i = first_apk; i < argc; i++) {
const char* file = argv[i];
struct stat sb;
if (stat(file, &sb) == -1) {
- fprintf(stderr, "adb: failed to stat %s: %s\n", file, strerror(errno));
- success = 0;
+ fprintf(stderr, "adb: failed to stat \"%s\": %s\n", file, strerror(errno));
+ success = false;
goto finalize_session;
}
@@ -498,8 +499,8 @@
unique_fd local_fd(adb_open(file, O_RDONLY | O_CLOEXEC));
if (local_fd < 0) {
- fprintf(stderr, "adb: failed to open %s: %s\n", file, strerror(errno));
- success = 0;
+ fprintf(stderr, "adb: failed to open \"%s\": %s\n", file, strerror(errno));
+ success = false;
goto finalize_session;
}
@@ -507,7 +508,7 @@
unique_fd remote_fd(adb_connect(cmd, &error));
if (remote_fd < 0) {
fprintf(stderr, "adb: connect error for write: %s\n", error.c_str());
- success = 0;
+ success = false;
goto finalize_session;
}
@@ -515,15 +516,15 @@
read_status_line(remote_fd.get(), buf, sizeof(buf));
if (strncmp("Success", buf, 7)) {
- fprintf(stderr, "adb: failed to write %s\n", file);
+ fprintf(stderr, "adb: failed to write \"%s\"\n", file);
fputs(buf, stderr);
- success = 0;
+ success = false;
goto finalize_session;
}
}
finalize_session:
- // Commit session if we streamed everything okay; otherwise abandon
+ // Commit session if we streamed everything okay; otherwise abandon.
std::string service = android::base::StringPrintf("%s install-%s %d", install_cmd.c_str(),
success ? "commit" : "abandon", session_id);
{
@@ -534,14 +535,16 @@
}
read_status_line(fd.get(), buf, sizeof(buf));
}
+ if (!success) return EXIT_FAILURE;
- if (!strncmp("Success", buf, 7)) {
- fputs(buf, stdout);
- return 0;
+ if (strncmp("Success", buf, 7)) {
+ fprintf(stderr, "adb: failed to finalize session\n");
+ fputs(buf, stderr);
+ return EXIT_FAILURE;
}
- fprintf(stderr, "adb: failed to finalize session\n");
- fputs(buf, stderr);
- return EXIT_FAILURE;
+
+ fputs(buf, stdout);
+ return EXIT_SUCCESS;
}
int install_multi_package(int argc, const char** argv) {
@@ -761,6 +764,20 @@
}
int delete_device_file(const std::string& filename) {
- std::string cmd = "rm -f " + escape_arg(filename);
- return send_shell_command(cmd);
+ // http://b/17339227 "Sideloading a Readonly File Results in a Prompt to
+ // Delete" caused us to add `-f` here, to avoid the equivalent of the `-i`
+ // prompt that you get from BSD rm (used in Android 5) if you have a
+ // non-writable file and stdin is a tty (which is true for old versions of
+ // adbd).
+ //
+ // Unfortunately, `rm -f` requires Android 4.3, so that workaround broke
+ // earlier Android releases. This was reported as http://b/37704384 "adb
+ // install -r passes invalid argument to rm on Android 4.1" and
+ // http://b/37035817 "ADB Fails: rm failed for -f, No such file or
+ // directory".
+ //
+ // Testing on a variety of devices and emulators shows that redirecting
+ // stdin is sufficient to avoid the pseudo-`-i`, and works on toolbox,
+ // BSD, and toybox versions of rm.
+ return send_shell_command("rm " + escape_arg(filename) + " </dev/null");
}
diff --git a/adb/client/commandline.cpp b/adb/client/commandline.cpp
index 4eb4eb4..599e0e6 100644
--- a/adb/client/commandline.cpp
+++ b/adb/client/commandline.cpp
@@ -153,6 +153,7 @@
" -d: allow version code downgrade (debuggable packages only)\n"
" -p: partial application install (install-multiple only)\n"
" -g: grant all runtime permissions\n"
+ " --abi ABI: override platform's default ABI\n"
" --instant: cause the app to be installed as an ephemeral install app\n"
" --no-streaming: always push APK to device and invoke Package Manager as separate steps\n"
" --streaming: force streaming APK directly into Package Manager\n"
@@ -164,6 +165,7 @@
#ifndef _WIN32
" --local-agent: locate agent files from local source build (instead of SDK location)\n"
#endif
+ " (See also `adb shell pm help` for more options.)\n"
//TODO--installlog <filename>
" uninstall [-k] PACKAGE\n"
" remove this app package from the device\n"
@@ -1737,41 +1739,33 @@
// Determine the <host-prefix> for this command.
std::string host_prefix;
if (reverse) {
- host_prefix = "reverse";
+ host_prefix = "reverse:";
} else {
- if (serial) {
- host_prefix = android::base::StringPrintf("host-serial:%s", serial);
- } else if (transport_type == kTransportUsb) {
- host_prefix = "host-usb";
- } else if (transport_type == kTransportLocal) {
- host_prefix = "host-local";
- } else {
- host_prefix = "host";
- }
+ host_prefix = "host:";
}
std::string cmd, error_message;
if (strcmp(argv[0], "--list") == 0) {
if (argc != 1) error_exit("--list doesn't take any arguments");
- return adb_query_command(host_prefix + ":list-forward");
+ return adb_query_command(host_prefix + "list-forward");
} else if (strcmp(argv[0], "--remove-all") == 0) {
if (argc != 1) error_exit("--remove-all doesn't take any arguments");
- cmd = host_prefix + ":killforward-all";
+ cmd = "killforward-all";
} else if (strcmp(argv[0], "--remove") == 0) {
// forward --remove <local>
if (argc != 2) error_exit("--remove requires an argument");
- cmd = host_prefix + ":killforward:" + argv[1];
+ cmd = std::string("killforward:") + argv[1];
} else if (strcmp(argv[0], "--no-rebind") == 0) {
// forward --no-rebind <local> <remote>
if (argc != 3) error_exit("--no-rebind takes two arguments");
if (forward_targets_are_valid(argv[1], argv[2], &error_message)) {
- cmd = host_prefix + ":forward:norebind:" + argv[1] + ";" + argv[2];
+ cmd = std::string("forward:norebind:") + argv[1] + ";" + argv[2];
}
} else {
// forward <local> <remote>
if (argc != 2) error_exit("forward takes two arguments");
if (forward_targets_are_valid(argv[0], argv[1], &error_message)) {
- cmd = host_prefix + ":forward:" + argv[0] + ";" + argv[1];
+ cmd = std::string("forward:") + argv[0] + ";" + argv[1];
}
}
@@ -1779,7 +1773,7 @@
error_exit("error: %s", error_message.c_str());
}
- unique_fd fd(adb_connect(cmd, &error_message));
+ unique_fd fd(adb_connect(nullptr, host_prefix + cmd, &error_message, true));
if (fd < 0 || !adb_status(fd.get(), &error_message)) {
error_exit("error: %s", error_message.c_str());
}
diff --git a/fs_mgr/fs_mgr_fstab.cpp b/fs_mgr/fs_mgr_fstab.cpp
index 9a0f4fe..bc197cd 100644
--- a/fs_mgr/fs_mgr_fstab.cpp
+++ b/fs_mgr/fs_mgr_fstab.cpp
@@ -46,7 +46,7 @@
namespace fs_mgr {
namespace {
-const std::string kDefaultAndroidDtDir("/proc/device-tree/firmware/android");
+constexpr char kDefaultAndroidDtDir[] = "/proc/device-tree/firmware/android";
struct FlagList {
const char *name;
diff --git a/fs_mgr/libdm/dm_target.cpp b/fs_mgr/libdm/dm_target.cpp
index 9152677..7c9804c 100644
--- a/fs_mgr/libdm/dm_target.cpp
+++ b/fs_mgr/libdm/dm_target.cpp
@@ -210,6 +210,18 @@
return false;
}
+bool DmTargetSnapshot::GetDevicesFromParams(const std::string& params, std::string* base_device,
+ std::string* cow_device) {
+ auto pieces = android::base::Split(params, " ");
+ if (pieces.size() < 2) {
+ LOG(ERROR) << "Parameter string is invalid: " << params;
+ return false;
+ }
+ *base_device = pieces[0];
+ *cow_device = pieces[1];
+ return true;
+}
+
std::string DmTargetCrypt::GetParameterString() const {
std::vector<std::string> argv = {
cipher_,
diff --git a/fs_mgr/libdm/include/libdm/dm.h b/fs_mgr/libdm/include/libdm/dm.h
index c6b37cf..f5783cb 100644
--- a/fs_mgr/libdm/include/libdm/dm.h
+++ b/fs_mgr/libdm/include/libdm/dm.h
@@ -47,6 +47,8 @@
enum class DmDeviceState { INVALID, SUSPENDED, ACTIVE };
+static constexpr uint64_t kSectorSize = 512;
+
class DeviceMapper final {
public:
class DmBlockDevice final {
diff --git a/fs_mgr/libdm/include/libdm/dm_target.h b/fs_mgr/libdm/include/libdm/dm_target.h
index ab7c2db..a66ab7a 100644
--- a/fs_mgr/libdm/include/libdm/dm_target.h
+++ b/fs_mgr/libdm/include/libdm/dm_target.h
@@ -219,6 +219,8 @@
static double MergePercent(const Status& status, uint64_t sectors_initial = 0);
static bool ParseStatusText(const std::string& text, Status* status);
static bool ReportsOverflow(const std::string& target_type);
+ static bool GetDevicesFromParams(const std::string& params, std::string* base_device,
+ std::string* cow_device);
private:
std::string base_device_;
diff --git a/fs_mgr/liblp/Android.bp b/fs_mgr/liblp/Android.bp
index b504161..5b377ae 100644
--- a/fs_mgr/liblp/Android.bp
+++ b/fs_mgr/liblp/Android.bp
@@ -36,6 +36,7 @@
"builder.cpp",
"images.cpp",
"partition_opener.cpp",
+ "property_fetcher.cpp",
"reader.cpp",
"utility.cpp",
"writer.cpp",
diff --git a/fs_mgr/liblp/builder.cpp b/fs_mgr/liblp/builder.cpp
index 8797ea9..777743c 100644
--- a/fs_mgr/liblp/builder.cpp
+++ b/fs_mgr/liblp/builder.cpp
@@ -20,21 +20,16 @@
#include <algorithm>
-#include <android-base/properties.h>
#include <android-base/unique_fd.h>
#include "liblp/liblp.h"
+#include "liblp/property_fetcher.h"
#include "reader.h"
#include "utility.h"
namespace android {
namespace fs_mgr {
-std::optional<bool> MetadataBuilder::sABOverride;
-std::optional<bool> MetadataBuilder::sRetrofitDap;
-
-static const std::string kDefaultGroup = "default";
-
bool LinearExtent::AddTo(LpMetadata* out) const {
if (device_index_ >= out->block_devices.size()) {
LERROR << "Extent references unknown block device.";
@@ -164,16 +159,27 @@
return nullptr;
}
- // On non-retrofit devices there is only one location for metadata: the
- // super partition. update_engine will remove and resize partitions as
- // needed. On the other hand, for retrofit devices, we'll need to
- // translate block device and group names to update their slot suffixes.
+ // On retrofit DAP devices, modify the metadata so that it is suitable for being written
+ // to the target slot later. We detect retrofit DAP devices by checking the super partition
+ // name and system properties.
+ // See comments for UpdateMetadataForOtherSuper.
auto super_device = GetMetadataSuperBlockDevice(*metadata.get());
- if (GetBlockDevicePartitionName(*super_device) == "super" ||
- !IsRetrofitDynamicPartitionsDevice()) {
- return New(*metadata.get(), &opener);
+ if (GetBlockDevicePartitionName(*super_device) != "super" &&
+ IsRetrofitDynamicPartitionsDevice()) {
+ if (!UpdateMetadataForOtherSuper(metadata.get(), source_slot_number, target_slot_number)) {
+ return nullptr;
+ }
}
+ return New(*metadata.get(), &opener);
+}
+
+// For retrofit DAP devices, there are (conceptually) two super partitions. We'll need to translate
+// block device and group names to update their slot suffixes.
+// (On the other hand, On non-retrofit DAP devices there is only one location for metadata: the
+// super partition. update_engine will remove and resize partitions as needed.)
+bool MetadataBuilder::UpdateMetadataForOtherSuper(LpMetadata* metadata, uint32_t source_slot_number,
+ uint32_t target_slot_number) {
// Clear partitions and extents, since they have no meaning on the target
// slot. We also clear groups since they are re-added during OTA.
metadata->partitions.clear();
@@ -193,7 +199,7 @@
// refers to a target or unknown block device.
LERROR << "Invalid block device for slot " << source_slot_suffix << ": "
<< partition_name;
- return nullptr;
+ return false;
}
std::string new_name =
partition_name.substr(0, partition_name.size() - slot_suffix.size()) +
@@ -202,20 +208,12 @@
auto new_device = source_block_device;
if (!UpdateBlockDevicePartitionName(&new_device, new_name)) {
LERROR << "Partition name too long: " << new_name;
- return nullptr;
+ return false;
}
metadata->block_devices.emplace_back(new_device);
}
- return New(*metadata.get(), &opener);
-}
-
-void MetadataBuilder::OverrideABForTesting(bool ab_device) {
- sABOverride = ab_device;
-}
-
-void MetadataBuilder::OverrideRetrofitDynamicParititonsForTesting(bool retrofit) {
- sRetrofitDap = retrofit;
+ return true;
}
MetadataBuilder::MetadataBuilder() : auto_slot_suffixing_(false) {
@@ -414,7 +412,7 @@
geometry_.metadata_slot_count = metadata_slot_count;
geometry_.logical_block_size = logical_block_size;
- if (!AddGroup(kDefaultGroup, 0)) {
+ if (!AddGroup(std::string(kDefaultGroup), 0)) {
return false;
}
return true;
@@ -430,7 +428,7 @@
}
Partition* MetadataBuilder::AddPartition(const std::string& name, uint32_t attributes) {
- return AddPartition(name, kDefaultGroup, attributes);
+ return AddPartition(name, std::string(kDefaultGroup), attributes);
}
Partition* MetadataBuilder::AddPartition(const std::string& name, const std::string& group_name,
@@ -1050,17 +1048,12 @@
}
bool MetadataBuilder::IsABDevice() {
- if (sABOverride.has_value()) {
- return *sABOverride;
- }
- return !android::base::GetProperty("ro.boot.slot_suffix", "").empty();
+ return !IPropertyFetcher::GetInstance()->GetProperty("ro.boot.slot_suffix", "").empty();
}
bool MetadataBuilder::IsRetrofitDynamicPartitionsDevice() {
- if (sRetrofitDap.has_value()) {
- return *sRetrofitDap;
- }
- return android::base::GetBoolProperty("ro.boot.dynamic_partitions_retrofit", false);
+ return IPropertyFetcher::GetInstance()->GetBoolProperty("ro.boot.dynamic_partitions_retrofit",
+ false);
}
bool MetadataBuilder::IsRetrofitMetadata() const {
diff --git a/fs_mgr/liblp/builder_test.cpp b/fs_mgr/liblp/builder_test.cpp
index 377ec68..6d27873 100644
--- a/fs_mgr/liblp/builder_test.cpp
+++ b/fs_mgr/liblp/builder_test.cpp
@@ -19,36 +19,40 @@
#include <gtest/gtest.h>
#include <liblp/builder.h>
+#include "mock_property_fetcher.h"
#include "utility.h"
using namespace std;
using namespace android::fs_mgr;
+using ::android::fs_mgr::MockPropertyFetcher;
+using ::testing::_;
+using ::testing::AnyNumber;
using ::testing::ElementsAre;
+using ::testing::NiceMock;
+using ::testing::Return;
+
+static void ResetPropertyFetcher() {
+ IPropertyFetcher::OverrideForTesting(std::make_unique<NiceMock<MockPropertyFetcher>>());
+}
+
+MockPropertyFetcher* GetMockedInstance() {
+ return static_cast<MockPropertyFetcher*>(IPropertyFetcher::GetInstance());
+}
class Environment : public ::testing::Environment {
public:
- void SetUp() override {
- MetadataBuilder::OverrideABForTesting(false);
- MetadataBuilder::OverrideRetrofitDynamicParititonsForTesting(false);
- }
+ void SetUp() override { ResetPropertyFetcher(); }
};
int main(int argc, char** argv) {
- ::testing::AddGlobalTestEnvironment(new Environment);
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
class BuilderTest : public ::testing::Test {
public:
- void SetUp() override {
- MetadataBuilder::OverrideABForTesting(false);
- MetadataBuilder::OverrideRetrofitDynamicParititonsForTesting(false);
- }
- void TearDown() override {
- MetadataBuilder::OverrideABForTesting(false);
- MetadataBuilder::OverrideRetrofitDynamicParititonsForTesting(false);
- }
+ void SetUp() override { ResetPropertyFetcher(); }
+ void TearDown() override { ResetPropertyFetcher(); }
};
TEST_F(BuilderTest, BuildBasic) {
@@ -785,7 +789,9 @@
// A and B slots should be allocated from separate halves of the partition,
// to mitigate allocating too many extents. (b/120433288)
- MetadataBuilder::OverrideABForTesting(true);
+ ON_CALL(*GetMockedInstance(), GetProperty("ro.boot.slot_suffix", _))
+ .WillByDefault(Return("_a"));
+
auto builder = MetadataBuilder::New(device_info, 65536, 2);
ASSERT_NE(builder, nullptr);
Partition* system_a = builder->AddPartition("system_a", 0);
diff --git a/fs_mgr/liblp/include/liblp/builder.h b/fs_mgr/liblp/include/liblp/builder.h
index a2221ef..3b229bd 100644
--- a/fs_mgr/liblp/include/liblp/builder.h
+++ b/fs_mgr/liblp/include/liblp/builder.h
@@ -24,6 +24,7 @@
#include <memory>
#include <optional>
#include <set>
+#include <string_view>
#include "liblp.h"
#include "partition_opener.h"
@@ -37,6 +38,9 @@
static const uint32_t kDefaultPartitionAlignment = 1024 * 1024;
static const uint32_t kDefaultBlockSize = 4096;
+// Name of the default group in a metadata.
+static constexpr std::string_view kDefaultGroup = "default";
+
// Abstraction around dm-targets that can be encoded into logical partition tables.
class Extent {
public:
@@ -196,12 +200,6 @@
return New(device_info, metadata_max_size, metadata_slot_count);
}
- // Used by the test harness to override whether the device is "A/B".
- static void OverrideABForTesting(bool ab_device);
-
- // Used by the test harness to override whether the device is "retrofitting dynamic partitions".
- static void OverrideRetrofitDynamicParititonsForTesting(bool retrofit);
-
// Define a new partition group. By default there is one group called
// "default", with an unrestricted size. A non-zero size will restrict the
// total space used by all partitions in the group.
@@ -347,8 +345,8 @@
const std::vector<Interval>& free_list,
uint64_t sectors_needed) const;
- static std::optional<bool> sABOverride;
- static std::optional<bool> sRetrofitDap;
+ static bool UpdateMetadataForOtherSuper(LpMetadata* metadata, uint32_t source_slot_number,
+ uint32_t target_slot_number);
LpMetadataGeometry geometry_;
LpMetadataHeader header_;
diff --git a/fs_mgr/liblp/include/liblp/property_fetcher.h b/fs_mgr/liblp/include/liblp/property_fetcher.h
new file mode 100644
index 0000000..e73a1f5
--- /dev/null
+++ b/fs_mgr/liblp/include/liblp/property_fetcher.h
@@ -0,0 +1,42 @@
+//
+// 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.
+//
+
+#pragma once
+
+#include <memory>
+
+namespace android {
+namespace fs_mgr {
+
+class IPropertyFetcher {
+ public:
+ virtual ~IPropertyFetcher() = default;
+ virtual std::string GetProperty(const std::string& key, const std::string& defaultValue) = 0;
+ virtual bool GetBoolProperty(const std::string& key, bool defaultValue) = 0;
+
+ static IPropertyFetcher* GetInstance();
+ static void OverrideForTesting(std::unique_ptr<IPropertyFetcher>&&);
+};
+
+class PropertyFetcher : public IPropertyFetcher {
+ public:
+ ~PropertyFetcher() = default;
+ std::string GetProperty(const std::string& key, const std::string& defaultValue) override;
+ bool GetBoolProperty(const std::string& key, bool defaultValue) override;
+};
+
+} // namespace fs_mgr
+} // namespace android
diff --git a/fs_mgr/liblp/io_test.cpp b/fs_mgr/liblp/io_test.cpp
index 70dd85f..2990863 100644
--- a/fs_mgr/liblp/io_test.cpp
+++ b/fs_mgr/liblp/io_test.cpp
@@ -23,10 +23,12 @@
#include <android-base/unique_fd.h>
#include <fs_mgr.h>
#include <fstab/fstab.h>
+#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <liblp/builder.h>
#include "images.h"
+#include "mock_property_fetcher.h"
#include "reader.h"
#include "test_partition_opener.h"
#include "utility.h"
@@ -34,6 +36,8 @@
using namespace std;
using namespace android::fs_mgr;
+using ::testing::_;
+using ::testing::Return;
using unique_fd = android::base::unique_fd;
// Our tests assume a 128KiB disk with two 512 byte metadata slots.
@@ -664,7 +668,8 @@
}
TEST(liblp, UpdateRetrofit) {
- MetadataBuilder::OverrideRetrofitDynamicParititonsForTesting(true);
+ ON_CALL(*GetMockedInstance(), GetBoolProperty("ro.boot.dynamic_partitions_retrofit", _))
+ .WillByDefault(Return(true));
unique_ptr<MetadataBuilder> builder = CreateDefaultBuilder();
ASSERT_NE(builder, nullptr);
@@ -695,7 +700,8 @@
}
TEST(liblp, UpdateNonRetrofit) {
- MetadataBuilder::OverrideRetrofitDynamicParititonsForTesting(false);
+ ON_CALL(*GetMockedInstance(), GetBoolProperty("ro.boot.dynamic_partitions_retrofit", _))
+ .WillByDefault(Return(false));
unique_fd fd = CreateFlashedDisk();
ASSERT_GE(fd, 0);
diff --git a/fs_mgr/liblp/mock_property_fetcher.h b/fs_mgr/liblp/mock_property_fetcher.h
new file mode 100644
index 0000000..eb91de2
--- /dev/null
+++ b/fs_mgr/liblp/mock_property_fetcher.h
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <gmock/gmock.h>
+
+#include <liblp/property_fetcher.h>
+
+namespace android {
+namespace fs_mgr {
+
+class MockPropertyFetcher : public IPropertyFetcher {
+ public:
+ MOCK_METHOD2(GetProperty, std::string(const std::string&, const std::string&));
+ MOCK_METHOD2(GetBoolProperty, bool(const std::string&, bool));
+
+ // By default, return default_value for all functions.
+ MockPropertyFetcher() {
+ using ::testing::_;
+ using ::testing::Invoke;
+ ON_CALL(*this, GetProperty(_, _)).WillByDefault(Invoke([](const auto&, const auto& def) {
+ return def;
+ }));
+ ON_CALL(*this, GetBoolProperty(_, _)).WillByDefault(Invoke([](const auto&, auto def) {
+ return def;
+ }));
+ }
+};
+
+} // namespace fs_mgr
+} // namespace android
+
+android::fs_mgr::MockPropertyFetcher* GetMockedInstance();
diff --git a/fs_mgr/liblp/property_fetcher.cpp b/fs_mgr/liblp/property_fetcher.cpp
new file mode 100644
index 0000000..038ef4d
--- /dev/null
+++ b/fs_mgr/liblp/property_fetcher.cpp
@@ -0,0 +1,49 @@
+/*
+ * 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 "liblp/property_fetcher.h"
+
+#include <memory>
+
+#include <android-base/properties.h>
+
+namespace android {
+namespace fs_mgr {
+
+std::string PropertyFetcher::GetProperty(const std::string& key, const std::string& default_value) {
+ return android::base::GetProperty(key, default_value);
+}
+
+bool PropertyFetcher::GetBoolProperty(const std::string& key, bool default_value) {
+ return android::base::GetBoolProperty(key, default_value);
+}
+
+static std::unique_ptr<IPropertyFetcher>* GetInstanceAllocation() {
+ static std::unique_ptr<IPropertyFetcher> instance = std::make_unique<PropertyFetcher>();
+ return &instance;
+}
+
+IPropertyFetcher* IPropertyFetcher::GetInstance() {
+ return GetInstanceAllocation()->get();
+}
+
+void IPropertyFetcher::OverrideForTesting(std::unique_ptr<IPropertyFetcher>&& fetcher) {
+ GetInstanceAllocation()->swap(fetcher);
+ fetcher.reset();
+}
+
+} // namespace fs_mgr
+} // namespace android
diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp
index 3a08049..52aad12 100644
--- a/fs_mgr/libsnapshot/Android.bp
+++ b/fs_mgr/libsnapshot/Android.bp
@@ -14,15 +14,13 @@
// limitations under the License.
//
-cc_library {
- name: "libsnapshot",
- recovery_available: true,
+cc_defaults {
+ name: "libsnapshot_defaults",
defaults: ["fs_mgr_defaults"],
- cppflags: [
+ cflags: [
"-D_FILE_OFFSET_BITS=64",
- ],
- srcs: [
- "snapshot.cpp",
+ "-Wall",
+ "-Werror",
],
shared_libs: [
"libbase",
@@ -30,7 +28,53 @@
],
static_libs: [
"libdm",
+ ],
+ whole_static_libs: [
"libext2_uuid",
+ "libext4_utils",
+ "libfiemap",
],
export_include_dirs: ["include"],
}
+
+filegroup {
+ name: "libsnapshot_sources",
+ srcs: [
+ "snapshot.cpp",
+ ],
+}
+
+cc_library_static {
+ name: "libsnapshot",
+ defaults: ["libsnapshot_defaults"],
+ srcs: [":libsnapshot_sources"],
+ static_libs: [
+ "libfiemap_binder",
+ ],
+}
+
+cc_library_static {
+ name: "libsnapshot_nobinder",
+ defaults: ["libsnapshot_defaults"],
+ srcs: [":libsnapshot_sources"],
+ recovery_available: true,
+}
+
+cc_test {
+ name: "libsnapshot_test",
+ defaults: ["libsnapshot_defaults"],
+ srcs: [
+ "snapshot_test.cpp",
+ ],
+ shared_libs: [
+ "libbinder",
+ "libutils",
+ ],
+ static_libs: [
+ "libcutils",
+ "libcrypto",
+ "libfs_mgr",
+ "liblp",
+ "libsnapshot",
+ ],
+}
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
index 5cfd7fa..062e00b 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
@@ -19,14 +19,32 @@
#include <chrono>
#include <memory>
#include <string>
+#include <vector>
+
+#include <android-base/unique_fd.h>
+#include <libdm/dm_target.h>
+
+#ifndef FRIEND_TEST
+#define FRIEND_TEST(test_set_name, individual_test) \
+ friend class test_set_name##_##individual_test##_Test
+#define DEFINED_FRIEND_TEST
+#endif
namespace android {
+
+namespace fiemap {
+class IImageManager;
+} // namespace fiemap
+
namespace snapshot {
-enum class UpdateStatus {
+enum class UpdateState {
// No update or merge is in progress.
None,
+ // An update is applying; snapshots may already exist.
+ Initiated,
+
// An update is pending, but has not been successfully booted yet.
Unverified,
@@ -34,36 +52,41 @@
Merging,
// Merging is complete, and needs to be acknowledged.
- MergeCompleted
+ MergeCompleted,
+
+ // Merging failed due to an unrecoverable error.
+ MergeFailed
};
class SnapshotManager final {
public:
- // Return a new SnapshotManager instance, or null on error.
- static std::unique_ptr<SnapshotManager> New();
+ // Dependency injection for testing.
+ class IDeviceInfo {
+ public:
+ virtual ~IDeviceInfo() {}
+ virtual std::string GetGsidDir() const = 0;
+ virtual std::string GetMetadataDir() const = 0;
- // Create a new snapshot device with the given name, base device, and COW device
- // size. The new device path will be returned in |dev_path|. If timeout_ms is
- // greater than zero, this function will wait the given amount of time for
- // |dev_path| to become available, and fail otherwise. If timeout_ms is 0, then
- // no wait will occur and |dev_path| may not yet exist on return.
- bool CreateSnapshot(const std::string& name, const std::string& base_device, uint64_t cow_size,
- std::string* dev_path, const std::chrono::milliseconds& timeout_ms);
+ // Return true if the device is currently running off snapshot devices,
+ // indicating that we have booted after applying (but not merging) an
+ // OTA.
+ virtual bool IsRunningSnapshot() const = 0;
+ };
- // Map a snapshot device that was previously created with CreateSnapshot.
- // If a merge was previously initiated, the device-mapper table will have a
- // snapshot-merge target instead of a snapshot target. The timeout parameter
- // is the same as in CreateSnapshotDevice.
- bool MapSnapshotDevice(const std::string& name, const std::string& base_device,
- const std::chrono::milliseconds& timeout_ms, std::string* dev_path);
+ ~SnapshotManager();
- // Unmap a snapshot device previously mapped with MapSnapshotDevice().
- bool UnmapSnapshotDevice(const std::string& name);
+ // Return a new SnapshotManager instance, or null on error. The device
+ // pointer is owned for the lifetime of SnapshotManager. If null, a default
+ // instance will be created.
+ static std::unique_ptr<SnapshotManager> New(IDeviceInfo* device = nullptr);
- // Remove the backing copy-on-write image for the named snapshot. If the
- // device is still mapped, this will attempt an Unmap, and fail if the
- // unmap fails.
- bool DeleteSnapshot(const std::string& name);
+ // Begin an update. This must be called before creating any snapshots. It
+ // will fail if GetUpdateState() != None.
+ bool BeginUpdate();
+
+ // Cancel an update; any snapshots will be deleted. This will fail if the
+ // state != Initiated or None.
+ bool CancelUpdate();
// Initiate a merge on all snapshot devices. This should only be used after an
// update has been marked successful after booting.
@@ -77,12 +100,128 @@
// Find the status of the current update, if any.
//
// |progress| depends on the returned status:
- // None: 0
- // Unverified: 0
- // Merging: Value in the range [0, 100)
+ // Merging: Value in the range [0, 100]
// MergeCompleted: 100
- UpdateStatus GetUpdateStatus(double* progress);
+ // Other: 0
+ UpdateState GetUpdateState(double* progress = nullptr);
+
+ private:
+ FRIEND_TEST(SnapshotTest, CreateSnapshot);
+ FRIEND_TEST(SnapshotTest, MapSnapshot);
+ FRIEND_TEST(SnapshotTest, MapPartialSnapshot);
+ friend class SnapshotTest;
+
+ using IImageManager = android::fiemap::IImageManager;
+
+ explicit SnapshotManager(IDeviceInfo* info);
+
+ // This is created lazily since it connects via binder.
+ bool EnsureImageManager();
+
+ // Helper function for tests.
+ IImageManager* image_manager() const { return images_.get(); }
+
+ // Since libsnapshot is included into multiple processes, we flock() our
+ // files for simple synchronization. LockedFile is a helper to assist with
+ // this. It also serves as a proof-of-lock for some functions.
+ class LockedFile final {
+ public:
+ LockedFile(const std::string& path, android::base::unique_fd&& fd)
+ : path_(path), fd_(std::move(fd)) {}
+ ~LockedFile();
+
+ const std::string& path() const { return path_; }
+ int fd() const { return fd_; }
+
+ private:
+ std::string path_;
+ android::base::unique_fd fd_;
+ };
+ std::unique_ptr<LockedFile> OpenFile(const std::string& file, int open_flags, int lock_flags);
+ bool Truncate(LockedFile* file);
+
+ // Create a new snapshot record. This creates the backing COW store and
+ // persists information needed to map the device. The device can be mapped
+ // with MapSnapshot().
+ //
+ // |device_size| should be the size of the base_device that will be passed
+ // via MapDevice(). |snapshot_size| should be the number of bytes in the
+ // base device, starting from 0, that will be snapshotted. The cow_size
+ // should be the amount of space that will be allocated to store snapshot
+ // deltas.
+ //
+ // If |snapshot_size| < device_size, then the device will always
+ // be mapped with two table entries: a dm-snapshot range covering
+ // snapshot_size, and a dm-linear range covering the remainder.
+ //
+ // All sizes are specified in bytes, and the device and snapshot sizes
+ // must be a multiple of the sector size (512 bytes). |cow_size| will
+ // be rounded up to the nearest sector.
+ bool CreateSnapshot(LockedFile* lock, const std::string& name, uint64_t device_size,
+ uint64_t snapshot_size, uint64_t cow_size);
+
+ // Map a snapshot device that was previously created with CreateSnapshot.
+ // If a merge was previously initiated, the device-mapper table will have a
+ // snapshot-merge target instead of a snapshot target. If the timeout
+ // parameter greater than zero, this function will wait the given amount
+ // of time for |dev_path| to become available, and fail otherwise. If
+ // timeout_ms is 0, then no wait will occur and |dev_path| may not yet
+ // exist on return.
+ bool MapSnapshot(LockedFile* lock, const std::string& name, const std::string& base_device,
+ const std::chrono::milliseconds& timeout_ms, std::string* dev_path);
+
+ // Remove the backing copy-on-write image for the named snapshot. The
+ // caller is responsible for ensuring that the snapshot is unmapped.
+ bool DeleteSnapshot(LockedFile* lock, const std::string& name);
+
+ // Unmap a snapshot device previously mapped with MapSnapshotDevice().
+ bool UnmapSnapshot(LockedFile* lock, const std::string& name);
+
+ // Unmap and remove all known snapshots.
+ bool RemoveAllSnapshots(LockedFile* lock);
+
+ // List the known snapshot names.
+ bool ListSnapshots(LockedFile* lock, std::vector<std::string>* snapshots);
+
+ // Interact with /metadata/ota/state.
+ std::unique_ptr<LockedFile> OpenStateFile(int open_flags, int lock_flags);
+ std::unique_ptr<LockedFile> LockShared();
+ std::unique_ptr<LockedFile> LockExclusive();
+ UpdateState ReadUpdateState(LockedFile* file);
+ bool WriteUpdateState(LockedFile* file, UpdateState state);
+
+ // This state is persisted per-snapshot in /metadata/ota/snapshots/.
+ struct SnapshotStatus {
+ std::string state;
+ uint64_t device_size;
+ uint64_t snapshot_size;
+ // These are non-zero when merging.
+ uint64_t sectors_allocated = 0;
+ uint64_t metadata_sectors = 0;
+ };
+
+ // Interact with status files under /metadata/ota/snapshots.
+ std::unique_ptr<LockedFile> OpenSnapshotStatusFile(const std::string& name, int open_flags,
+ int lock_flags);
+ bool WriteSnapshotStatus(LockedFile* file, const SnapshotStatus& status);
+ bool ReadSnapshotStatus(LockedFile* file, SnapshotStatus* status);
+
+ // Return the name of the device holding the "snapshot" or "snapshot-merge"
+ // target. This may not be the final device presented via MapSnapshot(), if
+ // for example there is a linear segment.
+ std::string GetSnapshotDeviceName(const std::string& snapshot_name,
+ const SnapshotStatus& status);
+
+ std::string gsid_dir_;
+ std::string metadata_dir_;
+ std::unique_ptr<IDeviceInfo> device_;
+ std::unique_ptr<IImageManager> images_;
};
} // namespace snapshot
} // namespace android
+
+#ifdef DEFINED_FRIEND_TEST
+#undef DEFINED_FRIEND_TEST
+#undef FRIEND_TEST
+#endif
diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp
index 3e80239..ef56179 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -14,45 +14,309 @@
#include <libsnapshot/snapshot.h>
+#include <dirent.h>
+#include <sys/file.h>
+#include <sys/types.h>
+#include <sys/unistd.h>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+#include <ext4_utils/ext4_utils.h>
+#include <libdm/dm.h>
+#include <libfiemap/image_manager.h>
+
namespace android {
namespace snapshot {
-std::unique_ptr<SnapshotManager> SnapshotManager::New() {
- return std::make_unique<SnapshotManager>();
+using android::base::unique_fd;
+using android::dm::DeviceMapper;
+using android::dm::DmDeviceState;
+using android::dm::DmTable;
+using android::dm::DmTargetLinear;
+using android::dm::DmTargetSnapshot;
+using android::dm::kSectorSize;
+using android::dm::SnapshotStorageMode;
+using android::fiemap::IImageManager;
+using namespace std::chrono_literals;
+using namespace std::string_literals;
+
+// Unit is sectors, this is a 4K chunk.
+static constexpr uint32_t kSnapshotChunkSize = 8;
+
+class DeviceInfo final : public SnapshotManager::IDeviceInfo {
+ public:
+ std::string GetGsidDir() const override { return "ota"s; }
+ std::string GetMetadataDir() const override { return "/metadata/ota"s; }
+ bool IsRunningSnapshot() const override;
+};
+
+bool DeviceInfo::IsRunningSnapshot() const {
+ // :TODO: implement this check.
+ return true;
}
-bool SnapshotManager::CreateSnapshot(const std::string& name, const std::string& base_device,
- uint64_t cow_size, std::string* dev_path,
- const std::chrono::milliseconds& timeout_ms) {
- // (1) Create COW device using libgsi_image.
- // (2) Create snapshot device using libdm + DmTargetSnapshot.
- // (3) Record partition in /metadata/ota.
- (void)name;
- (void)base_device;
- (void)cow_size;
- (void)dev_path;
- (void)timeout_ms;
- return false;
+// Note: IIMageManager is an incomplete type in the header, so the default
+// destructor doesn't work.
+SnapshotManager::~SnapshotManager() {}
+
+std::unique_ptr<SnapshotManager> SnapshotManager::New(IDeviceInfo* info) {
+ if (!info) {
+ info = new DeviceInfo();
+ }
+ return std::unique_ptr<SnapshotManager>(new SnapshotManager(info));
}
-bool SnapshotManager::MapSnapshotDevice(const std::string& name, const std::string& base_device,
- const std::chrono::milliseconds& timeout_ms,
- std::string* dev_path) {
- (void)name;
- (void)base_device;
- (void)dev_path;
- (void)timeout_ms;
- return false;
+SnapshotManager::SnapshotManager(IDeviceInfo* device) : device_(device) {
+ gsid_dir_ = device_->GetGsidDir();
+ metadata_dir_ = device_->GetMetadataDir();
}
-bool SnapshotManager::UnmapSnapshotDevice(const std::string& name) {
- (void)name;
- return false;
+static std::string GetCowName(const std::string& snapshot_name) {
+ return snapshot_name + "-cow";
}
-bool SnapshotManager::DeleteSnapshot(const std::string& name) {
- (void)name;
- return false;
+bool SnapshotManager::BeginUpdate() {
+ auto file = LockExclusive();
+ if (!file) return false;
+
+ auto state = ReadUpdateState(file.get());
+ if (state != UpdateState::None) {
+ LOG(ERROR) << "An update is already in progress, cannot begin a new update";
+ return false;
+ }
+ return WriteUpdateState(file.get(), UpdateState::Initiated);
+}
+
+bool SnapshotManager::CancelUpdate() {
+ auto file = LockExclusive();
+ if (!file) return false;
+
+ UpdateState state = ReadUpdateState(file.get());
+ if (state == UpdateState::None) return true;
+ if (state != UpdateState::Initiated) {
+ LOG(ERROR) << "Cannot cancel update after it has completed or started merging";
+ return false;
+ }
+
+ if (!RemoveAllSnapshots(file.get())) {
+ LOG(ERROR) << "Could not remove all snapshots";
+ return false;
+ }
+
+ if (!WriteUpdateState(file.get(), UpdateState::None)) {
+ LOG(ERROR) << "Could not write new update state";
+ return false;
+ }
+ return true;
+}
+
+bool SnapshotManager::CreateSnapshot(LockedFile* lock, const std::string& name,
+ uint64_t device_size, uint64_t snapshot_size,
+ uint64_t cow_size) {
+ CHECK(lock);
+ if (!EnsureImageManager()) return false;
+
+ // Sanity check these sizes. Like liblp, we guarantee the partition size
+ // is respected, which means it has to be sector-aligned. (This guarantee
+ // is useful for locating avb footers correctly). The COW size, however,
+ // can be arbitrarily larger than specified, so we can safely round it up.
+ if (device_size % kSectorSize != 0) {
+ LOG(ERROR) << "Snapshot " << name
+ << " device size is not a multiple of the sector size: " << device_size;
+ return false;
+ }
+ if (snapshot_size % kSectorSize != 0) {
+ LOG(ERROR) << "Snapshot " << name
+ << " snapshot size is not a multiple of the sector size: " << snapshot_size;
+ return false;
+ }
+
+ // Round the COW size up to the nearest sector.
+ cow_size += kSectorSize - 1;
+ cow_size &= ~(kSectorSize - 1);
+
+ LOG(INFO) << "Snapshot " << name << " will have COW size " << cow_size;
+
+ auto status_file = OpenSnapshotStatusFile(name, O_RDWR | O_CREAT, LOCK_EX);
+ if (!status_file) return false;
+
+ // Note, we leave the status file hanging around if we fail to create the
+ // actual backing image. This is harmless, since it'll get removed when
+ // CancelUpdate is called.
+ SnapshotStatus status = {
+ .state = "created",
+ .device_size = device_size,
+ .snapshot_size = snapshot_size,
+ };
+ if (!WriteSnapshotStatus(status_file.get(), status)) {
+ PLOG(ERROR) << "Could not write snapshot status: " << name;
+ return false;
+ }
+
+ auto cow_name = GetCowName(name);
+ int cow_flags = IImageManager::CREATE_IMAGE_ZERO_FILL;
+ return images_->CreateBackingImage(cow_name, cow_size, cow_flags);
+}
+
+bool SnapshotManager::MapSnapshot(LockedFile* lock, const std::string& name,
+ const std::string& base_device,
+ const std::chrono::milliseconds& timeout_ms,
+ std::string* dev_path) {
+ CHECK(lock);
+ if (!EnsureImageManager()) return false;
+
+ auto status_file = OpenSnapshotStatusFile(name, O_RDWR, LOCK_EX);
+ if (!status_file) return false;
+
+ SnapshotStatus status;
+ if (!ReadSnapshotStatus(status_file.get(), &status)) {
+ return false;
+ }
+
+ // Validate the block device size, as well as the requested snapshot size.
+ // During this we also compute the linear sector region if any.
+ {
+ unique_fd fd(open(base_device.c_str(), O_RDONLY | O_CLOEXEC));
+ if (fd < 0) {
+ PLOG(ERROR) << "open failed: " << base_device;
+ return false;
+ }
+ auto dev_size = get_block_device_size(fd);
+ if (!dev_size) {
+ PLOG(ERROR) << "Could not determine block device size: " << base_device;
+ return false;
+ }
+ if (status.device_size != dev_size) {
+ LOG(ERROR) << "Block device size for " << base_device << " does not match"
+ << "(expected " << status.device_size << ", got " << dev_size << ")";
+ return false;
+ }
+ }
+ if (status.device_size % kSectorSize != 0) {
+ LOG(ERROR) << "invalid blockdev size for " << base_device << ": " << status.device_size;
+ return false;
+ }
+ if (status.snapshot_size % kSectorSize != 0 || status.snapshot_size > status.device_size) {
+ LOG(ERROR) << "Invalid snapshot size for " << base_device << ": " << status.snapshot_size;
+ return false;
+ }
+ uint64_t snapshot_sectors = status.snapshot_size / kSectorSize;
+ uint64_t linear_sectors = (status.device_size - status.snapshot_size) / kSectorSize;
+
+ auto cow_name = GetCowName(name);
+
+ std::string cow_dev;
+ if (!images_->MapImageDevice(cow_name, timeout_ms, &cow_dev)) {
+ return false;
+ }
+
+ auto& dm = DeviceMapper::Instance();
+
+ // Merging is a global state, not per-snapshot. We do however track the
+ // progress of individual snapshots' merges.
+ SnapshotStorageMode mode;
+ UpdateState update_state = ReadUpdateState(lock);
+ if (update_state == UpdateState::Merging || update_state == UpdateState::MergeCompleted) {
+ mode = SnapshotStorageMode::Merge;
+ } else {
+ mode = SnapshotStorageMode::Persistent;
+ }
+
+ // The kernel (tested on 4.19) crashes horribly if a device has both a snapshot
+ // and a linear target in the same table. Instead, we stack them, and give the
+ // snapshot device a different name. It is not exposed to the caller in this
+ // case.
+ auto snap_name = (linear_sectors > 0) ? name + "-inner" : name;
+
+ DmTable table;
+ table.Emplace<DmTargetSnapshot>(0, snapshot_sectors, base_device, cow_dev, mode,
+ kSnapshotChunkSize);
+ if (!dm.CreateDevice(snap_name, table, dev_path, timeout_ms)) {
+ LOG(ERROR) << "Could not create snapshot device: " << snap_name;
+ images_->UnmapImageDevice(cow_name);
+ return false;
+ }
+
+ if (linear_sectors) {
+ // Our stacking will looks like this:
+ // [linear, linear] ; to snapshot, and non-snapshot region of base device
+ // [snapshot-inner]
+ // [base device] [cow]
+ DmTable table;
+ table.Emplace<DmTargetLinear>(0, snapshot_sectors, *dev_path, 0);
+ table.Emplace<DmTargetLinear>(snapshot_sectors, linear_sectors, base_device,
+ snapshot_sectors);
+ if (!dm.CreateDevice(name, table, dev_path, timeout_ms)) {
+ LOG(ERROR) << "Could not create outer snapshot device: " << name;
+ dm.DeleteDevice(snap_name);
+ images_->UnmapImageDevice(cow_name);
+ return false;
+ }
+ }
+
+ // :TODO: when merging is implemented, we need to add an argument to the
+ // status indicating how much progress is left to merge. (device-mapper
+ // does not retain the initial values, so we can't derive them.)
+ return true;
+}
+
+bool SnapshotManager::UnmapSnapshot(LockedFile* lock, const std::string& name) {
+ CHECK(lock);
+ if (!EnsureImageManager()) return false;
+
+ auto status_file = OpenSnapshotStatusFile(name, O_RDWR, LOCK_EX);
+ if (!status_file) return false;
+
+ SnapshotStatus status;
+ if (!ReadSnapshotStatus(status_file.get(), &status)) {
+ return false;
+ }
+
+ auto& dm = DeviceMapper::Instance();
+ if (dm.GetState(name) != DmDeviceState::INVALID && !dm.DeleteDevice(name)) {
+ LOG(ERROR) << "Could not delete snapshot device: " << name;
+ return false;
+ }
+
+ // There may be an extra device, since the kernel doesn't let us have a
+ // snapshot and linear target in the same table.
+ auto dm_name = GetSnapshotDeviceName(name, status);
+ if (name != dm_name && !dm.DeleteDevice(dm_name)) {
+ LOG(ERROR) << "Could not delete inner snapshot device: " << dm_name;
+ return false;
+ }
+
+ auto cow_name = GetCowName(name);
+ if (images_->IsImageMapped(cow_name) && !images_->UnmapImageDevice(cow_name)) {
+ return false;
+ }
+ return true;
+}
+
+bool SnapshotManager::DeleteSnapshot(LockedFile* lock, const std::string& name) {
+ CHECK(lock);
+ if (!EnsureImageManager()) return false;
+
+ // Take the snapshot's lock after Unmap, since it will also try to lock.
+ auto status_file = OpenSnapshotStatusFile(name, O_RDONLY, LOCK_EX);
+ if (!status_file) return false;
+
+ auto cow_name = GetCowName(name);
+ if (!images_->BackingImageExists(cow_name)) {
+ return true;
+ }
+ if (!images_->DeleteBackingImage(cow_name)) {
+ return false;
+ }
+
+ if (!android::base::RemoveFileIfExists(status_file->path())) {
+ LOG(ERROR) << "Failed to remove status file: " << status_file->path();
+ return false;
+ }
+ return true;
}
bool SnapshotManager::InitiateMerge() {
@@ -63,9 +327,242 @@
return false;
}
-UpdateStatus SnapshotManager::GetUpdateStatus(double* progress) {
- *progress = 0.0f;
- return UpdateStatus::None;
+bool SnapshotManager::RemoveAllSnapshots(LockedFile* lock) {
+ std::vector<std::string> snapshots;
+ if (!ListSnapshots(lock, &snapshots)) {
+ LOG(ERROR) << "Could not list snapshots";
+ return false;
+ }
+
+ bool ok = true;
+ for (const auto& name : snapshots) {
+ ok &= DeleteSnapshot(lock, name);
+ }
+ return ok;
+}
+
+UpdateState SnapshotManager::GetUpdateState(double* progress) {
+ auto file = LockShared();
+ if (!file) {
+ return UpdateState::None;
+ }
+
+ auto state = ReadUpdateState(file.get());
+ if (progress) {
+ *progress = 0.0;
+ if (state == UpdateState::Merging) {
+ // :TODO: When merging is implemented, set progress_val.
+ } else if (state == UpdateState::MergeCompleted) {
+ *progress = 100.0;
+ }
+ }
+ return state;
+}
+
+bool SnapshotManager::ListSnapshots(LockedFile* lock, std::vector<std::string>* snapshots) {
+ CHECK(lock);
+
+ auto dir_path = metadata_dir_ + "/snapshots"s;
+ std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(dir_path.c_str()), closedir);
+ if (!dir) {
+ PLOG(ERROR) << "opendir failed: " << dir_path;
+ return false;
+ }
+
+ struct dirent* dp;
+ while ((dp = readdir(dir.get())) != nullptr) {
+ if (dp->d_type != DT_REG) continue;
+ snapshots->emplace_back(dp->d_name);
+ }
+ return true;
+}
+
+auto SnapshotManager::OpenFile(const std::string& file, int open_flags, int lock_flags)
+ -> std::unique_ptr<LockedFile> {
+ unique_fd fd(open(file.c_str(), open_flags | O_CLOEXEC | O_NOFOLLOW | O_SYNC, 0660));
+ if (fd < 0) {
+ PLOG(ERROR) << "Open failed: " << file;
+ return nullptr;
+ }
+ if (flock(fd, lock_flags) < 0) {
+ PLOG(ERROR) << "Acquire flock failed: " << file;
+ return nullptr;
+ }
+ return std::make_unique<LockedFile>(file, std::move(fd));
+}
+
+SnapshotManager::LockedFile::~LockedFile() {
+ if (flock(fd_, LOCK_UN) < 0) {
+ PLOG(ERROR) << "Failed to unlock file: " << path_;
+ }
+}
+
+std::unique_ptr<SnapshotManager::LockedFile> SnapshotManager::OpenStateFile(int open_flags,
+ int lock_flags) {
+ auto state_file = metadata_dir_ + "/state"s;
+ return OpenFile(state_file, open_flags, lock_flags);
+}
+
+std::unique_ptr<SnapshotManager::LockedFile> SnapshotManager::LockShared() {
+ return OpenStateFile(O_RDONLY, LOCK_SH);
+}
+
+std::unique_ptr<SnapshotManager::LockedFile> SnapshotManager::LockExclusive() {
+ return OpenStateFile(O_RDWR | O_CREAT, LOCK_EX);
+}
+
+UpdateState SnapshotManager::ReadUpdateState(LockedFile* file) {
+ // Reset position since some calls read+write.
+ if (lseek(file->fd(), 0, SEEK_SET) < 0) {
+ PLOG(ERROR) << "lseek state file failed";
+ return UpdateState::None;
+ }
+
+ std::string contents;
+ if (!android::base::ReadFdToString(file->fd(), &contents)) {
+ PLOG(ERROR) << "Read state file failed";
+ return UpdateState::None;
+ }
+
+ if (contents.empty() || contents == "none") {
+ return UpdateState::None;
+ } else if (contents == "initiated") {
+ return UpdateState::Initiated;
+ } else if (contents == "unverified") {
+ return UpdateState::Unverified;
+ } else if (contents == "merging") {
+ return UpdateState::Merging;
+ } else if (contents == "merge-completed") {
+ return UpdateState::MergeCompleted;
+ } else {
+ LOG(ERROR) << "Unknown merge state in update state file";
+ return UpdateState::None;
+ }
+}
+
+bool SnapshotManager::WriteUpdateState(LockedFile* file, UpdateState state) {
+ std::string contents;
+ switch (state) {
+ case UpdateState::None:
+ contents = "none";
+ break;
+ case UpdateState::Initiated:
+ contents = "initiated";
+ break;
+ case UpdateState::Unverified:
+ contents = "unverified";
+ break;
+ case UpdateState::Merging:
+ contents = "merging";
+ break;
+ case UpdateState::MergeCompleted:
+ contents = "merge-completed";
+ break;
+ default:
+ LOG(ERROR) << "Unknown update state";
+ return false;
+ }
+
+ if (!Truncate(file)) return false;
+ if (!android::base::WriteStringToFd(contents, file->fd())) {
+ PLOG(ERROR) << "Could not write to state file";
+ return false;
+ }
+ return true;
+}
+
+auto SnapshotManager::OpenSnapshotStatusFile(const std::string& name, int open_flags,
+ int lock_flags) -> std::unique_ptr<LockedFile> {
+ auto file = metadata_dir_ + "/snapshots/"s + name;
+ return OpenFile(file, open_flags, lock_flags);
+}
+
+bool SnapshotManager::ReadSnapshotStatus(LockedFile* file, SnapshotStatus* status) {
+ // Reset position since some calls read+write.
+ if (lseek(file->fd(), 0, SEEK_SET) < 0) {
+ PLOG(ERROR) << "lseek status file failed";
+ return false;
+ }
+
+ std::string contents;
+ if (!android::base::ReadFdToString(file->fd(), &contents)) {
+ PLOG(ERROR) << "read status file failed";
+ return false;
+ }
+ auto pieces = android::base::Split(contents, " ");
+ if (pieces.size() != 5) {
+ LOG(ERROR) << "Invalid status line for snapshot: " << file->path();
+ return false;
+ }
+
+ status->state = pieces[0];
+ if (!android::base::ParseUint(pieces[1], &status->device_size)) {
+ LOG(ERROR) << "Invalid device size in status line for: " << file->path();
+ return false;
+ }
+ if (!android::base::ParseUint(pieces[2], &status->snapshot_size)) {
+ LOG(ERROR) << "Invalid snapshot size in status line for: " << file->path();
+ return false;
+ }
+ if (!android::base::ParseUint(pieces[3], &status->sectors_allocated)) {
+ LOG(ERROR) << "Invalid snapshot size in status line for: " << file->path();
+ return false;
+ }
+ if (!android::base::ParseUint(pieces[4], &status->metadata_sectors)) {
+ LOG(ERROR) << "Invalid snapshot size in status line for: " << file->path();
+ return false;
+ }
+ return true;
+}
+
+bool SnapshotManager::WriteSnapshotStatus(LockedFile* file, const SnapshotStatus& status) {
+ std::vector<std::string> pieces = {
+ status.state,
+ std::to_string(status.device_size),
+ std::to_string(status.snapshot_size),
+ std::to_string(status.sectors_allocated),
+ std::to_string(status.metadata_sectors),
+ };
+ auto contents = android::base::Join(pieces, " ");
+
+ if (!Truncate(file)) return false;
+ if (!android::base::WriteStringToFd(contents, file->fd())) {
+ PLOG(ERROR) << "write to status file failed: " << file->path();
+ return false;
+ }
+ return true;
+}
+
+bool SnapshotManager::Truncate(LockedFile* file) {
+ if (lseek(file->fd(), 0, SEEK_SET) < 0) {
+ PLOG(ERROR) << "lseek file failed: " << file->path();
+ return false;
+ }
+ if (ftruncate(file->fd(), 0) < 0) {
+ PLOG(ERROR) << "truncate failed: " << file->path();
+ return false;
+ }
+ return true;
+}
+
+std::string SnapshotManager::GetSnapshotDeviceName(const std::string& snapshot_name,
+ const SnapshotStatus& status) {
+ if (status.device_size != status.snapshot_size) {
+ return snapshot_name + "-inner";
+ }
+ return snapshot_name;
+}
+
+bool SnapshotManager::EnsureImageManager() {
+ if (images_) return true;
+
+ // For now, use a preset timeout.
+ images_ = android::fiemap::IImageManager::Open(gsid_dir_, 15000ms);
+ if (!images_) {
+ LOG(ERROR) << "Could not open ImageManager";
+ return false;
+ }
+ return true;
}
} // namespace snapshot
diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp
new file mode 100644
index 0000000..9cc9bd7
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapshot_test.cpp
@@ -0,0 +1,191 @@
+// Copyright (C) 2018 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 <libsnapshot/snapshot.h>
+
+#include <fcntl.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <chrono>
+#include <iostream>
+
+#include <android-base/strings.h>
+#include <gtest/gtest.h>
+#include <libfiemap/image_manager.h>
+
+namespace android {
+namespace snapshot {
+
+using namespace std::chrono_literals;
+using namespace std::string_literals;
+
+class TestDeviceInfo : public SnapshotManager::IDeviceInfo {
+ public:
+ std::string GetGsidDir() const override { return "ota/test"s; }
+ std::string GetMetadataDir() const override { return "/metadata/ota/test"s; }
+ bool IsRunningSnapshot() const override { return is_running_snapshot_; }
+
+ void set_is_running_snapshot(bool value) { is_running_snapshot_ = value; }
+
+ private:
+ bool is_running_snapshot_;
+};
+
+std::unique_ptr<SnapshotManager> sm;
+TestDeviceInfo* test_device = nullptr;
+
+class SnapshotTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ test_device->set_is_running_snapshot(false);
+
+ if (sm->GetUpdateState() != UpdateState::None) {
+ ASSERT_TRUE(sm->CancelUpdate());
+ }
+ ASSERT_TRUE(sm->BeginUpdate());
+ ASSERT_TRUE(sm->EnsureImageManager());
+
+ image_manager_ = sm->image_manager();
+ ASSERT_NE(image_manager_, nullptr);
+ }
+
+ void TearDown() override {
+ lock_ = nullptr;
+
+ if (sm->GetUpdateState() != UpdateState::None) {
+ ASSERT_TRUE(sm->CancelUpdate());
+ }
+ for (const auto& temp_image : temp_images_) {
+ image_manager_->UnmapImageDevice(temp_image);
+ image_manager_->DeleteBackingImage(temp_image);
+ }
+ }
+
+ bool AcquireLock() {
+ lock_ = sm->OpenStateFile(O_RDWR, LOCK_EX);
+ return !!lock_;
+ }
+
+ bool CreateTempDevice(const std::string& name, uint64_t size, std::string* path) {
+ if (!image_manager_->CreateBackingImage(name, size, false)) {
+ return false;
+ }
+ temp_images_.emplace_back(name);
+ return image_manager_->MapImageDevice(name, 10s, path);
+ }
+
+ std::unique_ptr<SnapshotManager::LockedFile> lock_;
+ std::vector<std::string> temp_images_;
+ android::fiemap::IImageManager* image_manager_ = nullptr;
+};
+
+TEST_F(SnapshotTest, CreateSnapshot) {
+ ASSERT_TRUE(AcquireLock());
+
+ static const uint64_t kDeviceSize = 1024 * 1024;
+ ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), "test-snapshot", kDeviceSize, kDeviceSize,
+ kDeviceSize));
+
+ std::vector<std::string> snapshots;
+ ASSERT_TRUE(sm->ListSnapshots(lock_.get(), &snapshots));
+ ASSERT_EQ(snapshots.size(), 1);
+ ASSERT_EQ(snapshots[0], "test-snapshot");
+
+ // Scope so delete can re-acquire the snapshot file lock.
+ {
+ auto file = sm->OpenSnapshotStatusFile("test-snapshot", O_RDONLY, LOCK_SH);
+ ASSERT_NE(file, nullptr);
+
+ SnapshotManager::SnapshotStatus status;
+ ASSERT_TRUE(sm->ReadSnapshotStatus(file.get(), &status));
+ ASSERT_EQ(status.state, "created");
+ ASSERT_EQ(status.device_size, kDeviceSize);
+ ASSERT_EQ(status.snapshot_size, kDeviceSize);
+ }
+
+ ASSERT_TRUE(sm->UnmapSnapshot(lock_.get(), "test-snapshot"));
+ ASSERT_TRUE(sm->DeleteSnapshot(lock_.get(), "test-snapshot"));
+}
+
+TEST_F(SnapshotTest, MapSnapshot) {
+ ASSERT_TRUE(AcquireLock());
+
+ static const uint64_t kDeviceSize = 1024 * 1024;
+ ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), "test-snapshot", kDeviceSize, kDeviceSize,
+ kDeviceSize));
+
+ std::string base_device;
+ ASSERT_TRUE(CreateTempDevice("base-device", kDeviceSize, &base_device));
+
+ std::string snap_device;
+ ASSERT_TRUE(sm->MapSnapshot(lock_.get(), "test-snapshot", base_device, 10s, &snap_device));
+ ASSERT_TRUE(android::base::StartsWith(snap_device, "/dev/block/dm-"));
+}
+
+TEST_F(SnapshotTest, MapPartialSnapshot) {
+ ASSERT_TRUE(AcquireLock());
+
+ static const uint64_t kSnapshotSize = 1024 * 1024;
+ static const uint64_t kDeviceSize = 1024 * 1024 * 2;
+ ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), "test-snapshot", kDeviceSize, kSnapshotSize,
+ kSnapshotSize));
+
+ std::string base_device;
+ ASSERT_TRUE(CreateTempDevice("base-device", kDeviceSize, &base_device));
+
+ std::string snap_device;
+ ASSERT_TRUE(sm->MapSnapshot(lock_.get(), "test-snapshot", base_device, 10s, &snap_device));
+ ASSERT_TRUE(android::base::StartsWith(snap_device, "/dev/block/dm-"));
+}
+
+} // namespace snapshot
+} // namespace android
+
+using namespace android::snapshot;
+
+bool Mkdir(const std::string& path) {
+ if (mkdir(path.c_str(), 0700) && errno != EEXIST) {
+ std::cerr << "Could not mkdir " << path << ": " << strerror(errno) << std::endl;
+ return false;
+ }
+ return true;
+}
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+
+ std::vector<std::string> paths = {
+ "/data/gsi/ota/test",
+ "/metadata/gsi/ota/test",
+ "/metadata/ota/test",
+ "/metadata/ota/test/snapshots",
+ };
+ for (const auto& path : paths) {
+ if (!Mkdir(path)) {
+ return 1;
+ }
+ }
+
+ // Create this once, otherwise, gsid will start/stop between each test.
+ test_device = new TestDeviceInfo();
+ sm = SnapshotManager::New(test_device);
+ if (!sm) {
+ std::cerr << "Could not create snapshot manager";
+ return 1;
+ }
+
+ return RUN_ALL_TESTS();
+}
diff --git a/fs_mgr/tools/dmctl.cpp b/fs_mgr/tools/dmctl.cpp
index 4fb0b83..2738457 100644
--- a/fs_mgr/tools/dmctl.cpp
+++ b/fs_mgr/tools/dmctl.cpp
@@ -197,19 +197,12 @@
char** argv_;
};
-static int DmCreateCmdHandler(int argc, char** argv) {
- if (argc < 1) {
- std::cerr << "Usage: dmctl create <dm-name> [-ro] <targets...>" << std::endl;
- return -EINVAL;
- }
- std::string name = argv[0];
-
+static bool parse_table_args(DmTable* table, int argc, char** argv) {
// Parse extended options first.
- DmTable table;
int arg_index = 1;
while (arg_index < argc && argv[arg_index][0] == '-') {
if (strcmp(argv[arg_index], "-ro") == 0) {
- table.set_readonly(true);
+ table->set_readonly(true);
arg_index++;
} else {
std::cerr << "Unrecognized option: " << argv[arg_index] << std::endl;
@@ -221,15 +214,30 @@
TargetParser parser(argc - arg_index, argv + arg_index);
while (parser.More()) {
std::unique_ptr<DmTarget> target = parser.Next();
- if (!target || !table.AddTarget(std::move(target))) {
+ if (!target || !table->AddTarget(std::move(target))) {
return -EINVAL;
}
}
- if (table.num_targets() == 0) {
+ if (table->num_targets() == 0) {
std::cerr << "Must define at least one target." << std::endl;
return -EINVAL;
}
+ return 0;
+}
+
+static int DmCreateCmdHandler(int argc, char** argv) {
+ if (argc < 1) {
+ std::cerr << "Usage: dmctl create <dm-name> [-ro] <targets...>" << std::endl;
+ return -EINVAL;
+ }
+ std::string name = argv[0];
+
+ DmTable table;
+ int ret = parse_table_args(&table, argc, argv);
+ if (ret) {
+ return ret;
+ }
DeviceMapper& dm = DeviceMapper::Instance();
if (!dm.CreateDevice(name, table)) {
@@ -255,6 +263,27 @@
return 0;
}
+static int DmReplaceCmdHandler(int argc, char** argv) {
+ if (argc < 1) {
+ std::cerr << "Usage: dmctl replace <dm-name> <targets...>" << std::endl;
+ return -EINVAL;
+ }
+ std::string name = argv[0];
+
+ DmTable table;
+ int ret = parse_table_args(&table, argc, argv);
+ if (ret) {
+ return ret;
+ }
+
+ DeviceMapper& dm = DeviceMapper::Instance();
+ if (!dm.LoadTableAndActivate(name, table)) {
+ std::cerr << "Failed to replace device-mapper table to: " << name << std::endl;
+ return -EIO;
+ }
+ return 0;
+}
+
static int DmListTargets(DeviceMapper& dm, [[maybe_unused]] int argc,
[[maybe_unused]] char** argv) {
std::vector<DmTargetTypeInfo> targets;
@@ -469,6 +498,7 @@
// clang-format off
{"create", DmCreateCmdHandler},
{"delete", DmDeleteCmdHandler},
+ {"replace", DmReplaceCmdHandler},
{"list", DmListCmdHandler},
{"help", HelpCmdHandler},
{"getpath", GetPathCmdHandler},
diff --git a/init/Android.bp b/init/Android.bp
index 31e4173..3233cc3 100644
--- a/init/Android.bp
+++ b/init/Android.bp
@@ -83,6 +83,7 @@
"libfscrypt",
"libgsi",
"libhidl-gen-utils",
+ "libjsoncpp",
"libkeyutils",
"liblog",
"liblogwrap",
@@ -93,12 +94,16 @@
"libutils",
],
bootstrap: true,
+ visibility: [":__subpackages__"],
}
cc_library_static {
name: "libinit",
recovery_available: true,
- defaults: ["init_defaults", "selinux_policy_version"],
+ defaults: [
+ "init_defaults",
+ "selinux_policy_version",
+ ],
srcs: [
"action.cpp",
"action_manager.cpp",
@@ -114,6 +119,7 @@
"first_stage_mount.cpp",
"import_parser.cpp",
"init.cpp",
+ "interface_utils.cpp",
"keychords.cpp",
"modalias_handler.cpp",
"mount_handler.cpp",
@@ -143,7 +149,10 @@
"ueventd_parser.cpp",
"util.cpp",
],
- whole_static_libs: ["libcap", "com.android.sysprop.apex"],
+ whole_static_libs: [
+ "libcap",
+ "com.android.sysprop.apex",
+ ],
header_libs: ["bootimg_headers"],
proto: {
type: "lite",
@@ -153,7 +162,10 @@
target: {
recovery: {
cflags: ["-DRECOVERY"],
- exclude_shared_libs: ["libbinder", "libutils"],
+ exclude_shared_libs: [
+ "libbinder",
+ "libutils",
+ ],
},
},
}
@@ -182,7 +194,10 @@
target: {
recovery: {
cflags: ["-DRECOVERY"],
- exclude_shared_libs: ["libbinder", "libutils"],
+ exclude_shared_libs: [
+ "libbinder",
+ "libutils",
+ ],
},
},
}
@@ -229,7 +244,10 @@
name: "generated_stub_builtin_function_map",
tool_files: ["host_builtin_map.py"],
out: ["generated_stub_builtin_function_map.h"],
- srcs: ["builtins.cpp", "check_builtins.cpp"],
+ srcs: [
+ "builtins.cpp",
+ "check_builtins.cpp",
+ ],
cmd: "$(location host_builtin_map.py) --builtins $(location builtins.cpp) --check_builtins $(location check_builtins.cpp) > $(out)",
}
@@ -265,6 +283,7 @@
"epoll.cpp",
"keychords.cpp",
"import_parser.cpp",
+ "interface_utils.cpp",
"host_import_parser.cpp",
"host_init_verifier.cpp",
"parser.cpp",
@@ -283,7 +302,7 @@
},
generated_headers: [
"generated_stub_builtin_function_map",
- "generated_android_ids"
+ "generated_android_ids",
],
target: {
android: {
diff --git a/init/action.cpp b/init/action.cpp
index 65ba25d..1a66eee 100644
--- a/init/action.cpp
+++ b/init/action.cpp
@@ -161,18 +161,8 @@
auto result = command.InvokeFunc(subcontext_);
auto duration = t.duration();
- // There are many legacy paths in rootdir/init.rc that will virtually never exist on a new
- // device, such as '/sys/class/leds/jogball-backlight/brightness'. As of this writing, there
- // are 198 such failures on bullhead. Instead of spamming the log reporting them, we do not
- // report such failures unless we're running at the DEBUG log level.
- bool report_failure = !result.has_value();
- if (report_failure && android::base::GetMinimumLogSeverity() > android::base::DEBUG &&
- result.error().code() == ENOENT) {
- report_failure = false;
- }
-
// Any action longer than 50ms will be warned to user as slow operation
- if (report_failure || duration > 50ms ||
+ if (!result.has_value() || duration > 50ms ||
android::base::GetMinimumLogSeverity() <= android::base::DEBUG) {
std::string trigger_name = BuildTriggersString();
std::string cmd_str = command.BuildCommandString();
diff --git a/init/builtins.cpp b/init/builtins.cpp
index e75f5cb..a2d782b 100644
--- a/init/builtins.cpp
+++ b/init/builtins.cpp
@@ -89,6 +89,43 @@
namespace android {
namespace init {
+// There are many legacy paths in rootdir/init.rc that will virtually never exist on a new
+// device, such as '/sys/class/leds/jogball-backlight/brightness'. As of this writing, there
+// are 81 such failures on cuttlefish. Instead of spamming the log reporting them, we do not
+// report such failures unless we're running at the DEBUG log level.
+class ErrorIgnoreEnoent {
+ public:
+ ErrorIgnoreEnoent()
+ : ignore_error_(errno == ENOENT &&
+ android::base::GetMinimumLogSeverity() > android::base::DEBUG) {}
+ explicit ErrorIgnoreEnoent(int errno_to_append)
+ : error_(errno_to_append),
+ ignore_error_(errno_to_append == ENOENT &&
+ android::base::GetMinimumLogSeverity() > android::base::DEBUG) {}
+
+ template <typename T>
+ operator android::base::expected<T, ResultError>() {
+ if (ignore_error_) {
+ return {};
+ }
+ return error_;
+ }
+
+ template <typename T>
+ ErrorIgnoreEnoent& operator<<(T&& t) {
+ error_ << t;
+ return *this;
+ }
+
+ private:
+ Error error_;
+ bool ignore_error_;
+};
+
+inline ErrorIgnoreEnoent ErrnoErrorIgnoreEnoent() {
+ return ErrorIgnoreEnoent(errno);
+}
+
std::vector<std::string> late_import_paths;
static constexpr std::chrono::nanoseconds kCommandRetryTimeout = 5s;
@@ -330,7 +367,7 @@
return ErrnoError() << "fchmodat() failed";
}
} else {
- return ErrnoError() << "mkdir() failed";
+ return ErrnoErrorIgnoreEnoent() << "mkdir() failed";
}
}
@@ -459,7 +496,7 @@
if (wait)
wait_for_file(source, kCommandRetryTimeout);
if (mount(source, target, system, flags, options) < 0) {
- return ErrnoError() << "mount() failed";
+ return ErrnoErrorIgnoreEnoent() << "mount() failed";
}
}
@@ -683,7 +720,7 @@
Service* svc = ServiceList::GetInstance().FindService(args[1]);
if (!svc) return Error() << "service " << args[1] << " not found";
if (auto result = svc->Start(); !result) {
- return Error() << "Could not start service: " << result.error();
+ return ErrorIgnoreEnoent() << "Could not start service: " << result.error();
}
return {};
}
@@ -729,10 +766,7 @@
if (MakeSymlink(args[1], args[2]) < 0) {
// The symlink builtin is often used to create symlinks for older devices to be backwards
// compatible with new paths, therefore we skip reporting this error.
- if (errno == EEXIST && android::base::GetMinimumLogSeverity() > android::base::DEBUG) {
- return {};
- }
- return ErrnoError() << "symlink() failed";
+ return ErrnoErrorIgnoreEnoent() << "symlink() failed";
}
return {};
}
@@ -790,7 +824,8 @@
static Result<void> do_write(const BuiltinArguments& args) {
if (auto result = WriteFile(args[1], args[2]); !result) {
- return Error() << "Unable to write to file '" << args[1] << "': " << result.error();
+ return ErrorIgnoreEnoent()
+ << "Unable to write to file '" << args[1] << "': " << result.error();
}
return {};
@@ -908,7 +943,7 @@
}
if (lchown(path.c_str(), *uid, *gid) == -1) {
- return ErrnoError() << "lchown() failed";
+ return ErrnoErrorIgnoreEnoent() << "lchown() failed";
}
return {};
@@ -930,7 +965,7 @@
static Result<void> do_chmod(const BuiltinArguments& args) {
mode_t mode = get_mode(args[1].c_str());
if (fchmodat(AT_FDCWD, args[2].c_str(), mode, AT_SYMLINK_NOFOLLOW) < 0) {
- return ErrnoError() << "fchmodat() failed";
+ return ErrnoErrorIgnoreEnoent() << "fchmodat() failed";
}
return {};
}
@@ -950,7 +985,7 @@
}
}
- if (ret) return ErrnoError() << "selinux_android_restorecon() failed";
+ if (ret) return ErrnoErrorIgnoreEnoent() << "selinux_android_restorecon() failed";
return {};
}
diff --git a/init/check_builtins.cpp b/init/check_builtins.cpp
index 3bd4774..771f1d7 100644
--- a/init/check_builtins.cpp
+++ b/init/check_builtins.cpp
@@ -29,6 +29,7 @@
#include <android-base/strings.h>
#include "builtin_arguments.h"
+#include "interface_utils.h"
#include "rlimit_parser.h"
#include "service.h"
#include "util.h"
@@ -80,6 +81,21 @@
return check_exec(std::move(args));
}
+Result<void> check_interface_restart(const BuiltinArguments& args) {
+ if (auto result = IsKnownInterface(args[1]); !result) {
+ return result.error();
+ }
+ return {};
+}
+
+Result<void> check_interface_start(const BuiltinArguments& args) {
+ return check_interface_restart(std::move(args));
+}
+
+Result<void> check_interface_stop(const BuiltinArguments& args) {
+ return check_interface_restart(std::move(args));
+}
+
Result<void> check_load_system_props(const BuiltinArguments& args) {
return Error() << "'load_system_props' is deprecated";
}
diff --git a/init/check_builtins.h b/init/check_builtins.h
index c974e88..4ff0d0c 100644
--- a/init/check_builtins.h
+++ b/init/check_builtins.h
@@ -25,6 +25,9 @@
Result<void> check_chown(const BuiltinArguments& args);
Result<void> check_exec(const BuiltinArguments& args);
Result<void> check_exec_background(const BuiltinArguments& args);
+Result<void> check_interface_restart(const BuiltinArguments& args);
+Result<void> check_interface_start(const BuiltinArguments& args);
+Result<void> check_interface_stop(const BuiltinArguments& args);
Result<void> check_load_system_props(const BuiltinArguments& args);
Result<void> check_loglevel(const BuiltinArguments& args);
Result<void> check_mkdir(const BuiltinArguments& args);
diff --git a/init/host_init_verifier.cpp b/init/host_init_verifier.cpp
index a5a5b1b..b2402b3 100644
--- a/init/host_init_verifier.cpp
+++ b/init/host_init_verifier.cpp
@@ -30,7 +30,6 @@
#include <android-base/logging.h>
#include <android-base/parseint.h>
#include <android-base/strings.h>
-#include <json/json.h>
#include "action.h"
#include "action_manager.h"
@@ -38,6 +37,7 @@
#include "check_builtins.h"
#include "host_import_parser.h"
#include "host_init_stubs.h"
+#include "interface_utils.h"
#include "parser.h"
#include "result.h"
#include "service.h"
@@ -132,35 +132,6 @@
return nullptr;
}
-static std::optional<android::init::InterfaceInheritanceHierarchyMap>
-ReadInterfaceInheritanceHierarchy(const std::string& interface_inheritance_hierarchy_file) {
- if (interface_inheritance_hierarchy_file.empty()) {
- LOG(WARNING) << "Missing an interface inheritance hierarchy file.";
- return {};
- }
-
- Json::Value root;
- Json::Reader reader;
- std::ifstream stream(interface_inheritance_hierarchy_file);
- if (!reader.parse(stream, root)) {
- LOG(ERROR) << "Failed to read interface inheritance hierarchy file: "
- << interface_inheritance_hierarchy_file << "\n"
- << reader.getFormattedErrorMessages();
- return {};
- }
-
- android::init::InterfaceInheritanceHierarchyMap result;
- for (const Json::Value& entry : root) {
- std::set<std::string> inherited_interfaces;
- for (const Json::Value& intf : entry["inheritedInterfaces"]) {
- inherited_interfaces.insert(intf.asString());
- }
- result[entry["interface"].asString()] = inherited_interfaces;
- }
-
- return result;
-}
-
namespace android {
namespace init {
@@ -171,12 +142,12 @@
#include "generated_stub_builtin_function_map.h"
void PrintUsage() {
- std::cout << "usage: host_init_verifier [-p FILE] -k FILE <init rc file>\n"
+ std::cout << "usage: host_init_verifier [-p FILE] -i FILE <init rc file>\n"
"\n"
"Tests an init script for correctness\n"
"\n"
"-p FILE\tSearch this passwd file for users and groups\n"
- "-k FILE\tUse this file as a space-separated list of known interfaces\n"
+ "-i FILE\tParse this JSON file for the HIDL interface inheritance hierarchy\n"
<< std::endl;
}
@@ -217,21 +188,26 @@
argc -= optind;
argv += optind;
- if (argc != 1) {
+ if (argc != 1 || interface_inheritance_hierarchy_file.empty()) {
PrintUsage();
return EXIT_FAILURE;
}
+ auto interface_inheritance_hierarchy_map =
+ ReadInterfaceInheritanceHierarchy(interface_inheritance_hierarchy_file);
+ if (!interface_inheritance_hierarchy_map) {
+ LOG(ERROR) << interface_inheritance_hierarchy_map.error();
+ return EXIT_FAILURE;
+ }
+ SetKnownInterfaces(*interface_inheritance_hierarchy_map);
+
const BuiltinFunctionMap& function_map = GetBuiltinFunctionMap();
Action::set_function_map(&function_map);
ActionManager& am = ActionManager::GetInstance();
ServiceList& sl = ServiceList::GetInstance();
Parser parser;
- parser.AddSectionParser(
- "service",
- std::make_unique<ServiceParser>(
- &sl, nullptr,
- ReadInterfaceInheritanceHierarchy(interface_inheritance_hierarchy_file)));
+ parser.AddSectionParser("service", std::make_unique<ServiceParser>(
+ &sl, nullptr, *interface_inheritance_hierarchy_map));
parser.AddSectionParser("on", std::make_unique<ActionParser>(&am, nullptr));
parser.AddSectionParser("import", std::make_unique<HostImportParser>());
@@ -239,7 +215,7 @@
LOG(ERROR) << "Failed to open init rc script '" << *argv << "'";
return EXIT_FAILURE;
}
- size_t failures = parser.parse_error_count() + am.CheckAllCommands();
+ size_t failures = parser.parse_error_count() + am.CheckAllCommands() + sl.CheckAllCommands();
if (failures > 0) {
LOG(ERROR) << "Failed to parse init script '" << *argv << "' with " << failures
<< " errors";
diff --git a/init/interface_utils.cpp b/init/interface_utils.cpp
new file mode 100644
index 0000000..a54860f
--- /dev/null
+++ b/init/interface_utils.cpp
@@ -0,0 +1,149 @@
+/*
+ * 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 "interface_utils.h"
+
+#include <fstream>
+#include <sstream>
+
+#include <android-base/strings.h>
+#include <hidl-util/FqInstance.h>
+#include <json/json.h>
+
+using android::FqInstance;
+using android::FQName;
+using android::base::Error;
+
+namespace android {
+namespace init {
+
+namespace {
+
+std::string FQNamesToString(const std::set<FQName>& fqnames) {
+ std::set<std::string> fqname_strings;
+ for (const FQName& fqname : fqnames) {
+ fqname_strings.insert(fqname.string());
+ }
+ return android::base::Join(fqname_strings, " ");
+}
+
+} // namespace
+
+Result<InterfaceInheritanceHierarchyMap> ReadInterfaceInheritanceHierarchy(
+ const std::string& path) {
+ Json::Value root;
+ Json::Reader reader;
+ std::ifstream stream(path);
+ if (!reader.parse(stream, root)) {
+ return Error() << "Failed to read interface inheritance hierarchy file: " << path << "\n"
+ << reader.getFormattedErrorMessages();
+ }
+
+ InterfaceInheritanceHierarchyMap result;
+ for (const Json::Value& entry : root) {
+ std::set<FQName> inherited_interfaces;
+ for (const Json::Value& intf : entry["inheritedInterfaces"]) {
+ FQName fqname;
+ if (!fqname.setTo(intf.asString())) {
+ return Error() << "Unable to parse interface '" << intf.asString() << "'";
+ }
+ inherited_interfaces.insert(fqname);
+ }
+ std::string intf_string = entry["interface"].asString();
+ FQName fqname;
+ if (!fqname.setTo(intf_string)) {
+ return Error() << "Unable to parse interface '" << intf_string << "'";
+ }
+ result[fqname] = inherited_interfaces;
+ }
+
+ return result;
+}
+
+Result<void> CheckInterfaceInheritanceHierarchy(const std::set<std::string>& instances,
+ const InterfaceInheritanceHierarchyMap& hierarchy) {
+ std::set<FQName> interface_fqnames;
+ for (const std::string& instance : instances) {
+ FqInstance fqinstance;
+ if (!fqinstance.setTo(instance)) {
+ return Error() << "Unable to parse interface instance '" << instance << "'";
+ }
+ interface_fqnames.insert(fqinstance.getFqName());
+ }
+ return CheckInterfaceInheritanceHierarchy(interface_fqnames, hierarchy);
+}
+
+Result<void> CheckInterfaceInheritanceHierarchy(const std::set<FQName>& interfaces,
+ const InterfaceInheritanceHierarchyMap& hierarchy) {
+ std::ostringstream error_stream;
+ for (const FQName& intf : interfaces) {
+ if (hierarchy.count(intf) == 0) {
+ error_stream << "\nInterface is not in the known set of hidl_interfaces: '"
+ << intf.string()
+ << "'. Please ensure the interface is spelled correctly and built "
+ << "by a hidl_interface target.";
+ continue;
+ }
+ const std::set<FQName>& required_interfaces = hierarchy.at(intf);
+ std::set<FQName> diff;
+ std::set_difference(required_interfaces.begin(), required_interfaces.end(),
+ interfaces.begin(), interfaces.end(),
+ std::inserter(diff, diff.begin()));
+ if (!diff.empty()) {
+ error_stream << "\nInterface '" << intf.string() << "' requires its full inheritance "
+ << "hierarchy to be listed in this init_rc file. Missing "
+ << "interfaces: [" << FQNamesToString(diff) << "]";
+ }
+ }
+ const std::string& errors = error_stream.str();
+ if (!errors.empty()) {
+ return Error() << errors;
+ }
+
+ return {};
+}
+
+std::optional<std::set<FQName>> known_interfaces;
+
+void SetKnownInterfaces(const InterfaceInheritanceHierarchyMap& hierarchy) {
+ known_interfaces = std::set<FQName>();
+ for (const auto& [intf, inherited_interfaces] : hierarchy) {
+ known_interfaces->insert(intf);
+ }
+}
+
+Result<void> IsKnownInterface(const std::string& instance) {
+ FqInstance fqinstance;
+ if (!fqinstance.setTo(instance)) {
+ return Error() << "Unable to parse interface instance '" << instance << "'";
+ }
+ return IsKnownInterface(fqinstance.getFqName());
+}
+
+Result<void> IsKnownInterface(const FQName& intf) {
+ if (!known_interfaces) {
+ return Error() << "No known interfaces have been loaded.";
+ }
+ if (known_interfaces->count(intf) == 0) {
+ return Error() << "Interface is not in the known set of hidl_interfaces: '" << intf.string()
+ << "'. Please ensure the interface is spelled correctly and built "
+ << "by a hidl_interface target.";
+ }
+ return {};
+}
+
+} // namespace init
+} // namespace android
diff --git a/init/interface_utils.h b/init/interface_utils.h
new file mode 100644
index 0000000..bd0c104
--- /dev/null
+++ b/init/interface_utils.h
@@ -0,0 +1,53 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <map>
+#include <set>
+#include <string>
+
+#include <hidl-util/FQName.h>
+
+#include "result.h"
+
+namespace android {
+namespace init {
+
+using InterfaceInheritanceHierarchyMap = std::map<android::FQName, std::set<android::FQName>>;
+
+// Reads the HIDL interface inheritance hierarchy JSON file at the given path.
+Result<InterfaceInheritanceHierarchyMap> ReadInterfaceInheritanceHierarchy(const std::string& path);
+
+// For the given set of interfaces / interface instances, checks that each
+// interface's hierarchy of inherited interfaces is also included in the given
+// interface set. Uses the provided hierarchy data.
+Result<void> CheckInterfaceInheritanceHierarchy(const std::set<std::string>& instances,
+ const InterfaceInheritanceHierarchyMap& hierarchy);
+Result<void> CheckInterfaceInheritanceHierarchy(const std::set<android::FQName>& interfaces,
+ const InterfaceInheritanceHierarchyMap& hierarchy);
+
+// Saves the set of known interfaces using the provided HIDL interface
+// inheritance hierarchy.
+void SetKnownInterfaces(const InterfaceInheritanceHierarchyMap& hierarchy);
+
+// Checks if the provided interface is in the set of known interfaces. Returns
+// an empty Result if present, otherwise an Error.
+Result<void> IsKnownInterface(const std::string& instance);
+Result<void> IsKnownInterface(const FQName& intf);
+
+} // namespace init
+} // namespace android
diff --git a/init/service.cpp b/init/service.cpp
index e60c20d..9537843 100644
--- a/init/service.cpp
+++ b/init/service.cpp
@@ -167,6 +167,15 @@
property_set(boottime_property, std::to_string(start_ns));
}
}
+
+ // init.svc_debug_pid.* properties are only for tests, and should not be used
+ // on device for security checks.
+ std::string pid_property = "init.svc_debug_pid." + name_;
+ if (new_state == "running") {
+ property_set(pid_property, std::to_string(pid_));
+ } else if (new_state == "stopped") {
+ property_set(pid_property, "");
+ }
}
void Service::KillProcessGroup(int signal) {
diff --git a/init/service.h b/init/service.h
index 6f79faa..ccefc8e 100644
--- a/init/service.h
+++ b/init/service.h
@@ -97,6 +97,7 @@
void AddReapCallback(std::function<void(const siginfo_t& siginfo)> callback) {
reap_callbacks_.emplace_back(std::move(callback));
}
+ size_t CheckAllCommands() const { return onrestart_.CheckAllCommands(); }
static bool is_exec_service_running() { return is_exec_service_running_; }
diff --git a/init/service_list.cpp b/init/service_list.cpp
index 3a48183..c51a9cf 100644
--- a/init/service_list.cpp
+++ b/init/service_list.cpp
@@ -28,6 +28,14 @@
return instance;
}
+size_t ServiceList::CheckAllCommands() {
+ size_t failures = 0;
+ for (const auto& service : services_) {
+ failures += service->CheckAllCommands();
+ }
+ return failures;
+}
+
void ServiceList::AddService(std::unique_ptr<Service> service) {
services_.emplace_back(std::move(service));
}
diff --git a/init/service_list.h b/init/service_list.h
index 2136a21..ee2c702 100644
--- a/init/service_list.h
+++ b/init/service_list.h
@@ -30,6 +30,7 @@
// Exposed for testing
ServiceList();
+ size_t CheckAllCommands();
void AddService(std::unique_ptr<Service> service);
void RemoveService(const Service& svc);
diff --git a/init/service_parser.cpp b/init/service_parser.cpp
index e45e804..dd552fb 100644
--- a/init/service_parser.cpp
+++ b/init/service_parser.cpp
@@ -568,33 +568,10 @@
}
if (interface_inheritance_hierarchy_) {
- std::set<std::string> interface_names;
- for (const std::string& intf : service_->interfaces()) {
- interface_names.insert(Split(intf, "/")[0]);
- }
- std::ostringstream error_stream;
- for (const std::string& intf : interface_names) {
- if (interface_inheritance_hierarchy_->count(intf) == 0) {
- error_stream << "\nInterface is not in the known set of hidl_interfaces: '" << intf
- << "'. Please ensure the interface is spelled correctly and built "
- << "by a hidl_interface target.";
- continue;
- }
- const std::set<std::string>& required_interfaces =
- (*interface_inheritance_hierarchy_)[intf];
- std::set<std::string> diff;
- std::set_difference(required_interfaces.begin(), required_interfaces.end(),
- interface_names.begin(), interface_names.end(),
- std::inserter(diff, diff.begin()));
- if (!diff.empty()) {
- error_stream << "\nInterface '" << intf << "' requires its full inheritance "
- << "hierarchy to be listed in this init_rc file. Missing "
- << "interfaces: [" << base::Join(diff, " ") << "]";
- }
- }
- const std::string& errors = error_stream.str();
- if (!errors.empty()) {
- return Error() << errors;
+ if (const auto& check_hierarchy_result = CheckInterfaceInheritanceHierarchy(
+ service_->interfaces(), *interface_inheritance_hierarchy_);
+ !check_hierarchy_result) {
+ return Error() << check_hierarchy_result.error();
}
}
diff --git a/init/service_parser.h b/init/service_parser.h
index 98ab15a..4729874 100644
--- a/init/service_parser.h
+++ b/init/service_parser.h
@@ -18,6 +18,7 @@
#include <vector>
+#include "interface_utils.h"
#include "parser.h"
#include "service.h"
#include "service_list.h"
@@ -26,8 +27,6 @@
namespace android {
namespace init {
-using InterfaceInheritanceHierarchyMap = std::map<std::string, std::set<std::string>>;
-
class ServiceParser : public SectionParser {
public:
ServiceParser(
diff --git a/init/test_utils/Android.bp b/init/test_utils/Android.bp
new file mode 100644
index 0000000..1cb05b6
--- /dev/null
+++ b/init/test_utils/Android.bp
@@ -0,0 +1,27 @@
+cc_library_static {
+ name: "libinit_test_utils",
+ cflags: [
+ "-Wall",
+ "-Wextra",
+ "-Wno-unused-parameter",
+ "-Werror",
+ ],
+ srcs: [
+ "service_utils.cpp",
+ ],
+ shared_libs: [
+ "libcutils",
+ "liblog",
+ "libjsoncpp",
+ "libprotobuf-cpp-lite",
+ "libhidl-gen-utils",
+ ],
+ whole_static_libs: [
+ "libinit",
+ "libpropertyinfoparser",
+ ],
+ static_libs: [
+ "libbase",
+ ],
+ export_include_dirs: ["include"], // for tests
+}
diff --git a/init/test_utils/include/init-test-utils/service_utils.h b/init/test_utils/include/init-test-utils/service_utils.h
new file mode 100644
index 0000000..3ec61d4
--- /dev/null
+++ b/init/test_utils/include/init-test-utils/service_utils.h
@@ -0,0 +1,32 @@
+//
+// 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.
+//
+
+#pragma once
+
+#include <map>
+#include <set>
+
+#include <android-base/result.h>
+#include <hidl-util/FqInstance.h>
+
+namespace android {
+namespace init {
+
+using ServiceInterfacesMap = std::map<std::string, std::set<android::FqInstance>>;
+android::base::Result<ServiceInterfacesMap> GetOnDeviceServiceInterfacesMap();
+
+} // namespace init
+} // namespace android
diff --git a/init/test_utils/service_utils.cpp b/init/test_utils/service_utils.cpp
new file mode 100644
index 0000000..bc00702
--- /dev/null
+++ b/init/test_utils/service_utils.cpp
@@ -0,0 +1,63 @@
+//
+// 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 <string>
+
+#include <android-base/logging.h>
+
+#include "../parser.h"
+#include "../service.h"
+#include "../service_list.h"
+#include "../service_parser.h"
+#include "include/init-test-utils/service_utils.h"
+
+namespace android {
+namespace init {
+
+android::base::Result<ServiceInterfacesMap> GetOnDeviceServiceInterfacesMap() {
+ ServiceList& service_list = ServiceList::GetInstance();
+ Parser parser;
+ parser.AddSectionParser("service",
+ std::make_unique<ServiceParser>(&service_list, nullptr, std::nullopt));
+ for (const auto& location : {
+ "/init.rc",
+ "/system/etc/init",
+ "/system_ext/etc/init",
+ "/product/etc/init",
+ "/odm/etc/init",
+ "/vendor/etc/init",
+ }) {
+ parser.ParseConfig(location);
+ }
+
+ ServiceInterfacesMap result;
+ for (const auto& service : service_list.services()) {
+ // Create an entry for all services, including services that may not
+ // have any declared interfaces.
+ result[service->name()] = std::set<android::FqInstance>();
+ for (const auto& intf : service->interfaces()) {
+ android::FqInstance fqInstance;
+ if (!fqInstance.setTo(intf)) {
+ return android::base::Error() << "Unable to parse interface: '" << intf << "'";
+ }
+ result[service->name()].insert(fqInstance);
+ }
+ }
+ return result;
+}
+
+} // namespace init
+} // namespace android
diff --git a/libcutils/Android.bp b/libcutils/Android.bp
index 319a73a..5144fc6 100644
--- a/libcutils/Android.bp
+++ b/libcutils/Android.bp
@@ -68,7 +68,6 @@
"record_stream.cpp",
"sockets.cpp",
"strdup16to8.cpp",
- "strdup8to16.cpp",
"strlcpy.c",
"threads.cpp",
],
diff --git a/libcutils/include/cutils/jstring.h b/libcutils/include/cutils/jstring.h
index a342608..6ede786 100644
--- a/libcutils/include/cutils/jstring.h
+++ b/libcutils/include/cutils/jstring.h
@@ -14,8 +14,7 @@
* limitations under the License.
*/
-#ifndef __CUTILS_STRING16_H
-#define __CUTILS_STRING16_H
+#pragma once
#include <stdint.h>
#include <stddef.h>
@@ -33,14 +32,6 @@
extern size_t strnlen16to8 (const char16_t* s, size_t n);
extern char * strncpy16to8 (char *dest, const char16_t*s, size_t n);
-extern char16_t * strdup8to16 (const char* s, size_t *out_len);
-extern size_t strlen8to16 (const char* utf8Str);
-extern char16_t * strcpy8to16 (char16_t *dest, const char*s, size_t *out_len);
-extern char16_t * strcpylen8to16 (char16_t *dest, const char*s, int length,
- size_t *out_len);
-
#ifdef __cplusplus
}
#endif
-
-#endif /* __CUTILS_STRING16_H */
diff --git a/libcutils/strdup8to16.cpp b/libcutils/strdup8to16.cpp
deleted file mode 100644
index d1e51b9..0000000
--- a/libcutils/strdup8to16.cpp
+++ /dev/null
@@ -1,215 +0,0 @@
-/* libs/cutils/strdup8to16.c
-**
-** Copyright 2006, 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 <cutils/jstring.h>
-
-#include <assert.h>
-#include <limits.h>
-#include <stdlib.h>
-
-/* See http://www.unicode.org/reports/tr22/ for discussion
- * on invalid sequences
- */
-
-#define UTF16_REPLACEMENT_CHAR 0xfffd
-
-/* Clever trick from Dianne that returns 1-4 depending on leading bit sequence*/
-#define UTF8_SEQ_LENGTH(ch) (((0xe5000000 >> (((ch) >> 3) & 0x1e)) & 3) + 1)
-
-/* note: macro expands to multiple lines */
-#define UTF8_SHIFT_AND_MASK(unicode, byte) \
- (unicode)<<=6; (unicode) |= (0x3f & (byte));
-
-#define UNICODE_UPPER_LIMIT 0x10fffd
-
-/**
- * out_len is an out parameter (which may not be null) containing the
- * length of the UTF-16 string (which may contain embedded \0's)
- */
-
-extern char16_t * strdup8to16 (const char* s, size_t *out_len)
-{
- char16_t *ret;
- size_t len;
-
- if (s == NULL) return NULL;
-
- len = strlen8to16(s);
-
- // fail on overflow
- if (len && SIZE_MAX/len < sizeof(char16_t))
- return NULL;
-
- // no plus-one here. UTF-16 strings are not null terminated
- ret = (char16_t *) malloc (sizeof(char16_t) * len);
-
- return strcpy8to16 (ret, s, out_len);
-}
-
-/**
- * Like "strlen", but for strings encoded with Java's modified UTF-8.
- *
- * The value returned is the number of UTF-16 characters required
- * to represent this string.
- */
-extern size_t strlen8to16 (const char* utf8Str)
-{
- size_t len = 0;
- int ic;
- int expected = 0;
-
- while ((ic = *utf8Str++) != '\0') {
- /* bytes that start 0? or 11 are lead bytes and count as characters.*/
- /* bytes that start 10 are extention bytes and are not counted */
-
- if ((ic & 0xc0) == 0x80) {
- /* count the 0x80 extention bytes. if we have more than
- * expected, then start counting them because strcpy8to16
- * will insert UTF16_REPLACEMENT_CHAR's
- */
- expected--;
- if (expected < 0) {
- len++;
- }
- } else {
- len++;
- expected = UTF8_SEQ_LENGTH(ic) - 1;
-
- /* this will result in a surrogate pair */
- if (expected == 3) {
- len++;
- }
- }
- }
-
- return len;
-}
-
-
-
-/*
- * Retrieve the next UTF-32 character from a UTF-8 string.
- *
- * Stops at inner \0's
- *
- * Returns UTF16_REPLACEMENT_CHAR if an invalid sequence is encountered
- *
- * Advances "*pUtf8Ptr" to the start of the next character.
- */
-static inline uint32_t getUtf32FromUtf8(const char** pUtf8Ptr)
-{
- uint32_t ret;
- int seq_len;
- int i;
-
- /* Mask for leader byte for lengths 1, 2, 3, and 4 respectively*/
- static const unsigned char leaderMask[4] = {0xff, 0x1f, 0x0f, 0x07};
-
- /* Bytes that start with bits "10" are not leading characters. */
- if (((**pUtf8Ptr) & 0xc0) == 0x80) {
- (*pUtf8Ptr)++;
- return UTF16_REPLACEMENT_CHAR;
- }
-
- /* note we tolerate invalid leader 11111xxx here */
- seq_len = UTF8_SEQ_LENGTH(**pUtf8Ptr);
-
- ret = (**pUtf8Ptr) & leaderMask [seq_len - 1];
-
- if (**pUtf8Ptr == '\0') return ret;
-
- (*pUtf8Ptr)++;
- for (i = 1; i < seq_len ; i++, (*pUtf8Ptr)++) {
- if ((**pUtf8Ptr) == '\0') return UTF16_REPLACEMENT_CHAR;
- if (((**pUtf8Ptr) & 0xc0) != 0x80) return UTF16_REPLACEMENT_CHAR;
-
- UTF8_SHIFT_AND_MASK(ret, **pUtf8Ptr);
- }
-
- return ret;
-}
-
-
-/**
- * out_len is an out parameter (which may not be null) containing the
- * length of the UTF-16 string (which may contain embedded \0's)
- */
-
-extern char16_t * strcpy8to16 (char16_t *utf16Str, const char*utf8Str,
- size_t *out_len)
-{
- char16_t *dest = utf16Str;
-
- while (*utf8Str != '\0') {
- uint32_t ret;
-
- ret = getUtf32FromUtf8(&utf8Str);
-
- if (ret <= 0xffff) {
- *dest++ = (char16_t) ret;
- } else if (ret <= UNICODE_UPPER_LIMIT) {
- /* Create surrogate pairs */
- /* See http://en.wikipedia.org/wiki/UTF-16/UCS-2#Method_for_code_points_in_Plane_1.2C_Plane_2 */
-
- *dest++ = 0xd800 | ((ret - 0x10000) >> 10);
- *dest++ = 0xdc00 | ((ret - 0x10000) & 0x3ff);
- } else {
- *dest++ = UTF16_REPLACEMENT_CHAR;
- }
- }
-
- *out_len = dest - utf16Str;
-
- return utf16Str;
-}
-
-/**
- * length is the number of characters in the UTF-8 string.
- * out_len is an out parameter (which may not be null) containing the
- * length of the UTF-16 string (which may contain embedded \0's)
- */
-
-extern char16_t * strcpylen8to16 (char16_t *utf16Str, const char*utf8Str,
- int length, size_t *out_len)
-{
- /* TODO: Share more of this code with the method above. Only 2 lines changed. */
-
- char16_t *dest = utf16Str;
-
- const char *end = utf8Str + length; /* This line */
- while (utf8Str < end) { /* and this line changed. */
- uint32_t ret;
-
- ret = getUtf32FromUtf8(&utf8Str);
-
- if (ret <= 0xffff) {
- *dest++ = (char16_t) ret;
- } else if (ret <= UNICODE_UPPER_LIMIT) {
- /* Create surrogate pairs */
- /* See http://en.wikipedia.org/wiki/UTF-16/UCS-2#Method_for_code_points_in_Plane_1.2C_Plane_2 */
-
- *dest++ = 0xd800 | ((ret - 0x10000) >> 10);
- *dest++ = 0xdc00 | ((ret - 0x10000) & 0x3ff);
- } else {
- *dest++ = UTF16_REPLACEMENT_CHAR;
- }
- }
-
- *out_len = dest - utf16Str;
-
- return utf16Str;
-}
diff --git a/liblog/logger.h b/liblog/logger.h
index 1f632c0..accb6e7 100644
--- a/liblog/logger.h
+++ b/liblog/logger.h
@@ -17,7 +17,6 @@
#pragma once
#include <stdatomic.h>
-#include <stdbool.h>
#include <cutils/list.h>
#include <log/log.h>
diff --git a/liblog/logprint.cpp b/liblog/logprint.cpp
index 6b5ea4c..3a54445 100644
--- a/liblog/logprint.cpp
+++ b/liblog/logprint.cpp
@@ -26,7 +26,6 @@
#ifndef __MINGW32__
#include <pwd.h>
#endif
-#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
diff --git a/liblog/pmsg_reader.cpp b/liblog/pmsg_reader.cpp
index ba27fd7..eaac97f 100644
--- a/liblog/pmsg_reader.cpp
+++ b/liblog/pmsg_reader.cpp
@@ -17,7 +17,6 @@
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
-#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
diff --git a/liblog/pmsg_writer.cpp b/liblog/pmsg_writer.cpp
index e851100..4632b32 100644
--- a/liblog/pmsg_writer.cpp
+++ b/liblog/pmsg_writer.cpp
@@ -20,7 +20,6 @@
#include <errno.h>
#include <fcntl.h>
-#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
diff --git a/liblog/stderr_write.cpp b/liblog/stderr_write.cpp
index e324a7c..e76673f 100644
--- a/liblog/stderr_write.cpp
+++ b/liblog/stderr_write.cpp
@@ -27,7 +27,6 @@
*/
#include <errno.h>
-#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
diff --git a/libmodprobe/Android.bp b/libmodprobe/Android.bp
index a2824d1..78da46c 100644
--- a/libmodprobe/Android.bp
+++ b/libmodprobe/Android.bp
@@ -3,6 +3,7 @@
cflags: [
"-Werror",
],
+ vendor_available: true,
recovery_available: true,
srcs: [
"libmodprobe.cpp",
diff --git a/libmodprobe/OWNERS b/libmodprobe/OWNERS
new file mode 100644
index 0000000..4b770b1
--- /dev/null
+++ b/libmodprobe/OWNERS
@@ -0,0 +1,2 @@
+tomcherry@google.com
+smuckle@google.com
diff --git a/libmodprobe/include/modprobe/modprobe.h b/libmodprobe/include/modprobe/modprobe.h
index 0ec766a..dcb4ffb 100644
--- a/libmodprobe/include/modprobe/modprobe.h
+++ b/libmodprobe/include/modprobe/modprobe.h
@@ -16,6 +16,7 @@
#pragma once
+#include <set>
#include <string>
#include <unordered_map>
#include <vector>
@@ -25,12 +26,21 @@
Modprobe(const std::vector<std::string>&);
bool LoadListedModules();
- bool LoadWithAliases(const std::string& module_name, bool strict);
+ bool LoadWithAliases(const std::string& module_name, bool strict,
+ const std::string& parameters = "");
+ bool Remove(const std::string& module_name);
+ std::vector<std::string> ListModules(const std::string& pattern);
+ bool GetAllDependencies(const std::string& module, std::vector<std::string>* pre_dependencies,
+ std::vector<std::string>* dependencies,
+ std::vector<std::string>* post_dependencies);
+ void EnableBlacklist(bool enable);
+ void EnableVerbose(bool enable);
private:
std::string MakeCanonical(const std::string& module_path);
- bool InsmodWithDeps(const std::string& module_name);
- bool Insmod(const std::string& path_name);
+ bool InsmodWithDeps(const std::string& module_name, const std::string& parameters);
+ bool Insmod(const std::string& path_name, const std::string& parameters);
+ bool Rmmod(const std::string& module_name);
std::vector<std::string> GetDependencies(const std::string& module);
bool ModuleExists(const std::string& module_name);
@@ -39,6 +49,7 @@
bool ParseSoftdepCallback(const std::vector<std::string>& args);
bool ParseLoadCallback(const std::vector<std::string>& args);
bool ParseOptionsCallback(const std::vector<std::string>& args);
+ bool ParseBlacklistCallback(const std::vector<std::string>& args);
void ParseCfg(const std::string& cfg, std::function<bool(const std::vector<std::string>&)> f);
std::vector<std::pair<std::string, std::string>> module_aliases_;
@@ -47,4 +58,6 @@
std::vector<std::pair<std::string, std::string>> module_post_softdep_;
std::vector<std::string> module_load_;
std::unordered_map<std::string, std::string> module_options_;
+ std::set<std::string> module_blacklist_;
+ bool blacklist_enabled = false;
};
diff --git a/libmodprobe/libmodprobe.cpp b/libmodprobe/libmodprobe.cpp
index 01cf2e3..73ae15b 100644
--- a/libmodprobe/libmodprobe.cpp
+++ b/libmodprobe/libmodprobe.cpp
@@ -194,6 +194,31 @@
return true;
}
+bool Modprobe::ParseBlacklistCallback(const std::vector<std::string>& args) {
+ auto it = args.begin();
+ const std::string& type = *it++;
+
+ if (type != "blacklist") {
+ LOG(ERROR) << "non-blacklist line encountered in modules.blacklist";
+ return false;
+ }
+
+ if (args.size() != 2) {
+ LOG(ERROR) << "lines in modules.blacklist must have exactly 2 entries, not " << args.size();
+ return false;
+ }
+
+ const std::string& module = *it++;
+
+ const std::string& canonical_name = MakeCanonical(module);
+ if (canonical_name.empty()) {
+ return false;
+ }
+ this->module_blacklist_.emplace(canonical_name);
+
+ return true;
+}
+
void Modprobe::ParseCfg(const std::string& cfg,
std::function<bool(const std::vector<std::string>&)> f) {
std::string cfg_contents;
@@ -231,6 +256,23 @@
auto options_callback = std::bind(&Modprobe::ParseOptionsCallback, this, _1);
ParseCfg(base_path + "/modules.options", options_callback);
+
+ auto blacklist_callback = std::bind(&Modprobe::ParseBlacklistCallback, this, _1);
+ ParseCfg(base_path + "/modules.blacklist", blacklist_callback);
+ }
+
+ android::base::SetMinimumLogSeverity(android::base::INFO);
+}
+
+void Modprobe::EnableBlacklist(bool enable) {
+ blacklist_enabled = enable;
+}
+
+void Modprobe::EnableVerbose(bool enable) {
+ if (enable) {
+ android::base::SetMinimumLogSeverity(android::base::VERBOSE);
+ } else {
+ android::base::SetMinimumLogSeverity(android::base::INFO);
}
}
@@ -242,7 +284,7 @@
return it->second;
}
-bool Modprobe::InsmodWithDeps(const std::string& module_name) {
+bool Modprobe::InsmodWithDeps(const std::string& module_name, const std::string& parameters) {
if (module_name.empty()) {
LOG(ERROR) << "Need valid module name, given: " << module_name;
return false;
@@ -256,11 +298,8 @@
// load module dependencies in reverse order
for (auto dep = dependencies.rbegin(); dep != dependencies.rend() - 1; ++dep) {
- const std::string& canonical_name = MakeCanonical(*dep);
- if (canonical_name.empty()) {
- return false;
- }
- if (!LoadWithAliases(canonical_name, true)) {
+ LOG(VERBOSE) << "Loading hard dep for '" << module_name << "': " << *dep;
+ if (!LoadWithAliases(*dep, true)) {
return false;
}
}
@@ -268,18 +307,20 @@
// try to load soft pre-dependencies
for (const auto& [module, softdep] : module_pre_softdep_) {
if (module_name == module) {
+ LOG(VERBOSE) << "Loading soft pre-dep for '" << module << "': " << softdep;
LoadWithAliases(softdep, false);
}
}
// load target module itself with args
- if (!Insmod(dependencies[0])) {
+ if (!Insmod(dependencies[0], parameters)) {
return false;
}
// try to load soft post-dependencies
for (const auto& [module, softdep] : module_post_softdep_) {
if (module_name == module) {
+ LOG(VERBOSE) << "Loading soft post-dep for '" << module << "': " << softdep;
LoadWithAliases(softdep, false);
}
}
@@ -287,25 +328,27 @@
return true;
}
-bool Modprobe::LoadWithAliases(const std::string& module_name, bool strict) {
- std::set<std::string> modules_to_load = {module_name};
+bool Modprobe::LoadWithAliases(const std::string& module_name, bool strict,
+ const std::string& parameters) {
+ std::set<std::string> modules_to_load = {MakeCanonical(module_name)};
bool module_loaded = false;
// use aliases to expand list of modules to load (multiple modules
// may alias themselves to the requested name)
for (const auto& [alias, aliased_module] : module_aliases_) {
if (fnmatch(alias.c_str(), module_name.c_str(), 0) != 0) continue;
+ LOG(VERBOSE) << "Found alias for '" << module_name << "': '" << aliased_module;
modules_to_load.emplace(aliased_module);
}
// attempt to load all modules aliased to this name
for (const auto& module : modules_to_load) {
if (!ModuleExists(module)) continue;
- if (InsmodWithDeps(module)) module_loaded = true;
+ if (InsmodWithDeps(module, parameters)) module_loaded = true;
}
if (strict && !module_loaded) {
- LOG(ERROR) << "LoadWithAliases did not find a module for " << module_name;
+ LOG(ERROR) << "LoadWithAliases was unable to load " << module_name;
return false;
}
return true;
@@ -319,3 +362,64 @@
}
return true;
}
+
+bool Modprobe::Remove(const std::string& module_name) {
+ auto dependencies = GetDependencies(MakeCanonical(module_name));
+ if (dependencies.empty()) {
+ LOG(ERROR) << "Empty dependencies for module " << module_name;
+ return false;
+ }
+ if (!Rmmod(dependencies[0])) {
+ return false;
+ }
+ for (auto dep = dependencies.begin() + 1; dep != dependencies.end(); ++dep) {
+ Rmmod(*dep);
+ }
+ return true;
+}
+
+std::vector<std::string> Modprobe::ListModules(const std::string& pattern) {
+ std::vector<std::string> rv;
+ for (const auto& [module, deps] : module_deps_) {
+ // Attempt to match both the canonical module name and the module filename.
+ if (!fnmatch(pattern.c_str(), module.c_str(), 0)) {
+ rv.emplace_back(module);
+ } else if (!fnmatch(pattern.c_str(), basename(deps[0].c_str()), 0)) {
+ rv.emplace_back(deps[0]);
+ }
+ }
+ return rv;
+}
+
+bool Modprobe::GetAllDependencies(const std::string& module,
+ std::vector<std::string>* pre_dependencies,
+ std::vector<std::string>* dependencies,
+ std::vector<std::string>* post_dependencies) {
+ std::string canonical_name = MakeCanonical(module);
+ if (pre_dependencies) {
+ pre_dependencies->clear();
+ for (const auto& [it_module, it_softdep] : module_pre_softdep_) {
+ if (canonical_name == it_module) {
+ pre_dependencies->emplace_back(it_softdep);
+ }
+ }
+ }
+ if (dependencies) {
+ dependencies->clear();
+ auto hard_deps = GetDependencies(canonical_name);
+ if (hard_deps.empty()) {
+ return false;
+ }
+ for (auto dep = hard_deps.rbegin(); dep != hard_deps.rend(); dep++) {
+ dependencies->emplace_back(*dep);
+ }
+ }
+ if (post_dependencies) {
+ for (const auto& [it_module, it_softdep] : module_post_softdep_) {
+ if (canonical_name == it_module) {
+ post_dependencies->emplace_back(it_softdep);
+ }
+ }
+ }
+ return true;
+}
diff --git a/libmodprobe/libmodprobe_ext.cpp b/libmodprobe/libmodprobe_ext.cpp
index 5f3a04d..2efcac2 100644
--- a/libmodprobe/libmodprobe_ext.cpp
+++ b/libmodprobe/libmodprobe_ext.cpp
@@ -22,7 +22,7 @@
#include <modprobe/modprobe.h>
-bool Modprobe::Insmod(const std::string& path_name) {
+bool Modprobe::Insmod(const std::string& path_name, const std::string& parameters) {
android::base::unique_fd fd(
TEMP_FAILURE_RETRY(open(path_name.c_str(), O_RDONLY | O_NOFOLLOW | O_CLOEXEC)));
if (fd == -1) {
@@ -35,6 +35,9 @@
if (options_iter != module_options_.end()) {
options = options_iter->second;
}
+ if (!parameters.empty()) {
+ options = options + " " + parameters;
+ }
LOG(INFO) << "Loading module " << path_name << " with args \"" << options << "\"";
int ret = syscall(__NR_finit_module, fd.get(), options.c_str(), 0);
@@ -51,17 +54,32 @@
return true;
}
+bool Modprobe::Rmmod(const std::string& module_name) {
+ int ret = syscall(__NR_delete_module, MakeCanonical(module_name).c_str(), O_NONBLOCK);
+ if (ret != 0) {
+ PLOG(ERROR) << "Failed to remove module '" << module_name << "'";
+ return false;
+ }
+ return true;
+}
+
bool Modprobe::ModuleExists(const std::string& module_name) {
struct stat fileStat;
+ if (blacklist_enabled && module_blacklist_.count(module_name)) {
+ LOG(INFO) << "module " << module_name << " is blacklisted";
+ return false;
+ }
auto deps = GetDependencies(module_name);
if (deps.empty()) {
// missing deps can happen in the case of an alias
return false;
}
if (stat(deps.front().c_str(), &fileStat)) {
+ LOG(INFO) << "module " << module_name << " does not exist";
return false;
}
if (!S_ISREG(fileStat.st_mode)) {
+ LOG(INFO) << "module " << module_name << " is not a regular file";
return false;
}
return true;
diff --git a/libmodprobe/libmodprobe_ext_test.cpp b/libmodprobe/libmodprobe_ext_test.cpp
index 0f073cb..7d817b1 100644
--- a/libmodprobe/libmodprobe_ext_test.cpp
+++ b/libmodprobe/libmodprobe_ext_test.cpp
@@ -29,7 +29,7 @@
#include "libmodprobe_test.h"
-bool Modprobe::Insmod(const std::string& path_name) {
+bool Modprobe::Insmod(const std::string& path_name, const std::string& parameters) {
auto deps = GetDependencies(MakeCanonical(path_name));
if (deps.empty()) {
return false;
@@ -47,12 +47,29 @@
if (options_iter != module_options_.end()) {
options = " " + options_iter->second;
}
+ if (!parameters.empty()) {
+ options = options + " " + parameters;
+ }
+
modules_loaded.emplace_back(path_name + options);
return true;
}
+bool Modprobe::Rmmod(const std::string& module_name) {
+ for (auto it = modules_loaded.begin(); it != modules_loaded.end(); it++) {
+ if (*it == module_name) {
+ modules_loaded.erase(it);
+ return true;
+ }
+ }
+ return false;
+}
+
bool Modprobe::ModuleExists(const std::string& module_name) {
auto deps = GetDependencies(module_name);
+ if (blacklist_enabled && module_blacklist_.count(module_name)) {
+ return false;
+ }
if (deps.empty()) {
// missing deps can happen in the case of an alias
return false;
diff --git a/libmodprobe/libmodprobe_test.cpp b/libmodprobe/libmodprobe_test.cpp
index 481658d..a711631 100644
--- a/libmodprobe/libmodprobe_test.cpp
+++ b/libmodprobe/libmodprobe_test.cpp
@@ -56,6 +56,14 @@
"/test13.ko",
};
+ std::vector<std::string> expected_after_remove = {
+ "/test14.ko", "/test15.ko", "/test1.ko",
+ "/test6.ko", "/test2.ko", "/test5.ko",
+ "/test8.ko", "/test7.ko param1=4", "/test9.ko param_x=1 param_y=2 param_z=3",
+ "/test10.ko", "/test12.ko", "/test11.ko",
+ "/test13.ko",
+ };
+
const std::string modules_dep =
"test1.ko:\n"
"test2.ko:\n"
@@ -91,6 +99,10 @@
"options test9.ko param_x=1 param_y=2 param_z=3\n"
"options test100.ko param_1=1\n";
+ const std::string modules_blacklist =
+ "blacklist test9.ko\n"
+ "blacklist test3.ko\n";
+
const std::string modules_load =
"test4.ko\n"
"test1.ko\n"
@@ -101,17 +113,20 @@
"test11.ko\n";
TemporaryDir dir;
- ASSERT_TRUE(android::base::WriteStringToFile(
- modules_alias, std::string(dir.path) + "/modules.alias", 0600, getuid(), getgid()));
+ auto dir_path = std::string(dir.path);
+ ASSERT_TRUE(android::base::WriteStringToFile(modules_alias, dir_path + "/modules.alias", 0600,
+ getuid(), getgid()));
- ASSERT_TRUE(android::base::WriteStringToFile(
- modules_dep, std::string(dir.path) + "/modules.dep", 0600, getuid(), getgid()));
- ASSERT_TRUE(android::base::WriteStringToFile(
- modules_softdep, std::string(dir.path) + "/modules.softdep", 0600, getuid(), getgid()));
- ASSERT_TRUE(android::base::WriteStringToFile(
- modules_options, std::string(dir.path) + "/modules.options", 0600, getuid(), getgid()));
- ASSERT_TRUE(android::base::WriteStringToFile(
- modules_load, std::string(dir.path) + "/modules.load", 0600, getuid(), getgid()));
+ ASSERT_TRUE(android::base::WriteStringToFile(modules_dep, dir_path + "/modules.dep", 0600,
+ getuid(), getgid()));
+ ASSERT_TRUE(android::base::WriteStringToFile(modules_softdep, dir_path + "/modules.softdep",
+ 0600, getuid(), getgid()));
+ ASSERT_TRUE(android::base::WriteStringToFile(modules_options, dir_path + "/modules.options",
+ 0600, getuid(), getgid()));
+ ASSERT_TRUE(android::base::WriteStringToFile(modules_load, dir_path + "/modules.load", 0600,
+ getuid(), getgid()));
+ ASSERT_TRUE(android::base::WriteStringToFile(modules_blacklist, dir_path + "/modules.blacklist",
+ 0600, getuid(), getgid()));
for (auto i = test_modules.begin(); i != test_modules.end(); ++i) {
*i = dir.path + *i;
@@ -131,4 +146,21 @@
}
EXPECT_TRUE(modules_loaded == expected_modules_loaded);
+
+ EXPECT_TRUE(m.Remove("test4"));
+
+ GTEST_LOG_(INFO) << "Expected modules loaded after removing test4 (in order):";
+ for (auto i = expected_after_remove.begin(); i != expected_after_remove.end(); ++i) {
+ *i = dir.path + *i;
+ GTEST_LOG_(INFO) << "\"" << *i << "\"";
+ }
+ GTEST_LOG_(INFO) << "Actual modules loaded after removing test4 (in order):";
+ for (auto i = modules_loaded.begin(); i != modules_loaded.end(); ++i) {
+ GTEST_LOG_(INFO) << "\"" << *i << "\"";
+ }
+
+ EXPECT_TRUE(modules_loaded == expected_after_remove);
+
+ m.EnableBlacklist(true);
+ EXPECT_FALSE(m.LoadWithAliases("test4", true));
}
diff --git a/libstats/include/stats_event_list.h b/libstats/include/stats_event_list.h
index 845a197..b7ada0c 100644
--- a/libstats/include/stats_event_list.h
+++ b/libstats/include/stats_event_list.h
@@ -14,8 +14,7 @@
* limitations under the License.
*/
-#ifndef ANDROID_STATS_LOG_STATS_EVENT_LIST_H
-#define ANDROID_STATS_LOG_STATS_EVENT_LIST_H
+#pragma once
#include <log/log_event_list.h>
#include <sys/uio.h>
@@ -133,7 +132,6 @@
return *this;
}
-#if defined(_USING_LIBCXX)
stats_event_list& operator<<(const std::string& value) {
int retval = android_log_write_string8_len(ctx, value.data(), value.length());
if (retval < 0) {
@@ -141,7 +139,6 @@
}
return *this;
}
-#endif
stats_event_list& operator<<(float value) {
int retval = android_log_write_float32(ctx, value);
@@ -203,7 +200,6 @@
return ret >= 0;
}
-#if defined(_USING_LIBCXX)
bool AppendString(const std::string& value) {
int retval = android_log_write_string8_len(ctx, value.data(), value.length());
if (retval < 0) {
@@ -219,7 +215,6 @@
}
return ret;
}
-#endif
bool AppendFloat(float value) {
int retval = android_log_write_float32(ctx, value);
@@ -253,4 +248,3 @@
};
#endif
-#endif // ANDROID_STATS_LOG_STATS_EVENT_LIST_H
diff --git a/libutils/Unicode.cpp b/libutils/Unicode.cpp
index 24a745a..b08e061 100644
--- a/libutils/Unicode.cpp
+++ b/libutils/Unicode.cpp
@@ -452,48 +452,6 @@
*codePoint |= 0x3F & byte;
}
-size_t utf8_to_utf32_length(const char *src, size_t src_len)
-{
- if (src == nullptr || src_len == 0) {
- return 0;
- }
- size_t ret = 0;
- const char* cur;
- const char* end;
- size_t num_to_skip;
- for (cur = src, end = src + src_len, num_to_skip = 1;
- cur < end;
- cur += num_to_skip, ret++) {
- const char first_char = *cur;
- num_to_skip = 1;
- if ((first_char & 0x80) == 0) { // ASCII
- continue;
- }
- int32_t mask;
-
- for (mask = 0x40; (first_char & mask); num_to_skip++, mask >>= 1) {
- }
- }
- return ret;
-}
-
-void utf8_to_utf32(const char* src, size_t src_len, char32_t* dst)
-{
- if (src == nullptr || src_len == 0 || dst == nullptr) {
- return;
- }
-
- const char* cur = src;
- const char* const end = src + src_len;
- char32_t* cur_utf32 = dst;
- while (cur < end) {
- size_t num_read;
- *cur_utf32++ = static_cast<char32_t>(utf32_at_internal(cur, &num_read));
- cur += num_read;
- }
- *cur_utf32 = 0;
-}
-
static inline uint32_t utf8_to_utf32_codepoint(const uint8_t *src, size_t length)
{
uint32_t unicode;
diff --git a/libutils/include/utils/Unicode.h b/libutils/include/utils/Unicode.h
index a2aaa47..fc6712d 100644
--- a/libutils/include/utils/Unicode.h
+++ b/libutils/include/utils/Unicode.h
@@ -129,18 +129,6 @@
ssize_t utf8_length(const char *src);
/**
- * Measure the length of a UTF-32 string.
- */
-size_t utf8_to_utf32_length(const char *src, size_t src_len);
-
-/**
- * Stores a UTF-32 string converted from "src" in "dst". "dst" must be large
- * enough to store the entire converted string as measured by
- * utf8_to_utf32_length plus space for a NUL terminator.
- */
-void utf8_to_utf32(const char* src, size_t src_len, char32_t* dst);
-
-/**
* Returns the UTF-16 length of UTF-8 string "src". Returns -1 in case
* it's invalid utf8. No buffer over-read occurs because of bound checks. Using overreadIsFatal you
* can ask to log a message and fail in case the invalid utf8 could have caused an override if no
diff --git a/logcat/logcat.cpp b/logcat/logcat.cpp
index 6e38d95..6507711 100644
--- a/logcat/logcat.cpp
+++ b/logcat/logcat.cpp
@@ -899,6 +899,11 @@
case 0:
// only long options
if (long_options[option_index].name == pid_str) {
+ if (pid != 0) {
+ logcat_panic(context, HELP_TRUE, "Only supports one PID argument.\n");
+ goto exit;
+ }
+
// ToDo: determine runtime PID_MAX?
if (!getSizeTArg(optarg, &pid, 1)) {
logcat_panic(context, HELP_TRUE, "%s %s out of range\n",
diff --git a/rootdir/Android.mk b/rootdir/Android.mk
index 76d6f7e..4559050 100644
--- a/rootdir/Android.mk
+++ b/rootdir/Android.mk
@@ -230,7 +230,7 @@
# A symlink can't overwrite a directory and the /system/usr/icu directory once
# existed so the required structure must be created whatever we find.
LOCAL_POST_INSTALL_CMD = mkdir -p $(TARGET_OUT)/usr && rm -rf $(TARGET_OUT)/usr/icu
-LOCAL_POST_INSTALL_CMD += && ln -sf /apex/com.android.runtime/etc/icu $(TARGET_OUT)/usr/icu
+LOCAL_POST_INSTALL_CMD += && ln -sf /apex/com.android.i18n/etc/icu $(TARGET_OUT)/usr/icu
# TODO(b/124106384): Clean up compat symlinks for ART binaries.
ART_BINARIES := \
diff --git a/rootdir/init.environ.rc.in b/rootdir/init.environ.rc.in
index 455c9a8..93b7f43 100644
--- a/rootdir/init.environ.rc.in
+++ b/rootdir/init.environ.rc.in
@@ -6,6 +6,7 @@
export ANDROID_DATA /data
export ANDROID_STORAGE /storage
export ANDROID_RUNTIME_ROOT /apex/com.android.runtime
+ export ANDROID_I18N_ROOT /apex/com.android.i18n
export ANDROID_TZDATA_ROOT /apex/com.android.tzdata
export EXTERNAL_STORAGE /sdcard
export ASEC_MOUNTPOINT /mnt/asec
diff --git a/rootdir/init.rc b/rootdir/init.rc
index d22e9a7..86d8042 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -415,6 +415,7 @@
chmod 0700 /metadata/vold
mkdir /metadata/password_slots 0771 root system
mkdir /metadata/ota 0700 root system
+ mkdir /metadata/ota/snapshots 0700 root system
mkdir /metadata/apex 0700 root system
mkdir /metadata/apex/sessions 0700 root system
diff --git a/toolbox/Android.bp b/toolbox/Android.bp
index 0cc603a..4ca5f5a 100644
--- a/toolbox/Android.bp
+++ b/toolbox/Android.bp
@@ -24,6 +24,7 @@
"toolbox.c",
"getevent.c",
"getprop.cpp",
+ "modprobe.cpp",
"setprop.cpp",
"start.cpp",
],
@@ -33,11 +34,15 @@
shared_libs: [
"libbase",
],
- static_libs: ["libpropertyinfoparser"],
+ static_libs: [
+ "libmodprobe",
+ "libpropertyinfoparser",
+ ],
symlinks: [
"getevent",
"getprop",
+ "modprobe",
"setprop",
"start",
"stop",
diff --git a/toolbox/modprobe.cpp b/toolbox/modprobe.cpp
new file mode 100644
index 0000000..1b5f54e
--- /dev/null
+++ b/toolbox/modprobe.cpp
@@ -0,0 +1,201 @@
+/*
+ * 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 <ctype.h>
+#include <getopt.h>
+#include <stdlib.h>
+#include <iostream>
+
+#include <android-base/strings.h>
+#include <modprobe/modprobe.h>
+
+enum modprobe_mode {
+ AddModulesMode,
+ RemoveModulesMode,
+ ListModulesMode,
+ ShowDependenciesMode,
+};
+
+static void print_usage(void) {
+ std::cerr << "Usage:" << std::endl;
+ std::cerr << std::endl;
+ std::cerr << " modprobe [-alrqvsDb] [-d DIR] [MODULE]+" << std::endl;
+ std::cerr << " modprobe [-alrqvsDb] [-d DIR] MODULE [symbol=value][...]" << std::endl;
+ std::cerr << std::endl;
+ std::cerr << "Options:" << std::endl;
+ std::cerr << " -b: Apply blacklist to module names too" << std::endl;
+ std::cerr << " -d: Load modules from DIR, option may be used multiple times" << std::endl;
+ std::cerr << " -D: Print dependencies for modules only, do not load";
+ std::cerr << " -h: Print this help" << std::endl;
+ std::cerr << " -l: List modules matching pattern" << std::endl;
+ std::cerr << " -r: Remove MODULE (multiple modules may be specified)" << std::endl;
+ std::cerr << " -q: Quiet" << std::endl;
+ std::cerr << " -v: Verbose" << std::endl;
+ std::cerr << std::endl;
+}
+
+#define check_mode() \
+ if (mode != AddModulesMode) { \
+ std::cerr << "Error, multiple mode flags specified" << std::endl; \
+ print_usage(); \
+ return EXIT_FAILURE; \
+ }
+
+extern "C" int modprobe_main(int argc, char** argv) {
+ std::vector<std::string> modules;
+ std::string module_parameters;
+ std::vector<std::string> mod_dirs;
+ modprobe_mode mode = AddModulesMode;
+ bool blacklist = false;
+ bool verbose = false;
+ int rv = EXIT_SUCCESS;
+
+ int opt;
+ while ((opt = getopt(argc, argv, "abd:Dhlqrv")) != -1) {
+ switch (opt) {
+ case 'a':
+ // toybox modprobe supported -a to load multiple modules, this
+ // is supported here by default, ignore flag
+ check_mode();
+ break;
+ case 'b':
+ blacklist = true;
+ break;
+ case 'd':
+ mod_dirs.emplace_back(optarg);
+ break;
+ case 'D':
+ check_mode();
+ mode = ShowDependenciesMode;
+ break;
+ case 'h':
+ print_usage();
+ return EXIT_SUCCESS;
+ case 'l':
+ check_mode();
+ mode = ListModulesMode;
+ break;
+ case 'q':
+ verbose = false;
+ break;
+ case 'r':
+ check_mode();
+ mode = RemoveModulesMode;
+ break;
+ case 'v':
+ verbose = true;
+ break;
+ default:
+ std::cerr << "Unrecognized option: " << opt << std::endl;
+ return EXIT_FAILURE;
+ }
+ }
+
+ int parameter_count = 0;
+ for (opt = optind; opt < argc; opt++) {
+ if (!strchr(argv[opt], '=')) {
+ modules.emplace_back(argv[opt]);
+ } else {
+ parameter_count++;
+ if (module_parameters.empty()) {
+ module_parameters = argv[opt];
+ } else {
+ module_parameters = module_parameters + " " + argv[opt];
+ }
+ }
+ }
+
+ if (verbose) {
+ std::cout << "mode is " << mode << std::endl;
+ std::cout << "verbose is " << verbose << std::endl;
+ std::cout << "mod_dirs is: " << android::base::Join(mod_dirs, "") << std::endl;
+ std::cout << "modules is: " << android::base::Join(modules, "") << std::endl;
+ std::cout << "module parameters is: " << android::base::Join(module_parameters, "")
+ << std::endl;
+ }
+
+ if (modules.empty()) {
+ if (mode == ListModulesMode) {
+ // emulate toybox modprobe list with no pattern (list all)
+ modules.emplace_back("*");
+ } else {
+ std::cerr << "No modules given." << std::endl;
+ print_usage();
+ return EXIT_FAILURE;
+ }
+ }
+ if (mod_dirs.empty()) {
+ std::cerr << "No module configuration directories given." << std::endl;
+ print_usage();
+ return EXIT_FAILURE;
+ }
+ if (parameter_count && modules.size() > 1) {
+ std::cerr << "Only one module may be loaded when specifying module parameters."
+ << std::endl;
+ print_usage();
+ return EXIT_FAILURE;
+ }
+
+ Modprobe m(mod_dirs);
+ m.EnableVerbose(verbose);
+ if (blacklist) {
+ m.EnableBlacklist(true);
+ }
+
+ for (const auto& module : modules) {
+ switch (mode) {
+ case AddModulesMode:
+ if (!m.LoadWithAliases(module, true, module_parameters)) {
+ std::cerr << "Failed to load module " << module;
+ rv = EXIT_FAILURE;
+ }
+ break;
+ case RemoveModulesMode:
+ if (!m.Remove(module)) {
+ std::cerr << "Failed to remove module " << module;
+ rv = EXIT_FAILURE;
+ }
+ break;
+ case ListModulesMode: {
+ std::vector<std::string> list = m.ListModules(module);
+ std::cout << android::base::Join(list, "\n") << std::endl;
+ break;
+ }
+ case ShowDependenciesMode: {
+ std::vector<std::string> pre_deps;
+ std::vector<std::string> deps;
+ std::vector<std::string> post_deps;
+ if (!m.GetAllDependencies(module, &pre_deps, &deps, &post_deps)) {
+ rv = EXIT_FAILURE;
+ break;
+ }
+ std::cout << "Dependencies for " << module << ":" << std::endl;
+ std::cout << "Soft pre-dependencies:" << std::endl;
+ std::cout << android::base::Join(pre_deps, "\n") << std::endl;
+ std::cout << "Hard dependencies:" << std::endl;
+ std::cout << android::base::Join(deps, "\n") << std::endl;
+ std::cout << "Soft post-dependencies:" << std::endl;
+ std::cout << android::base::Join(post_deps, "\n") << std::endl;
+ break;
+ }
+ default:
+ std::cerr << "Bad mode";
+ rv = EXIT_FAILURE;
+ }
+ }
+
+ return rv;
+}
diff --git a/toolbox/tools.h b/toolbox/tools.h
index 9a7ebd2..bb57e67 100644
--- a/toolbox/tools.h
+++ b/toolbox/tools.h
@@ -1,5 +1,6 @@
TOOL(getevent)
TOOL(getprop)
+TOOL(modprobe)
TOOL(setprop)
TOOL(start)
TOOL(stop)