Merge "ion-unit-tests: Add heap id verification test."
diff --git a/adb/adb.cpp b/adb/adb.cpp
index ba6df7a..1ec145b 100644
--- a/adb/adb.cpp
+++ b/adb/adb.cpp
@@ -1131,7 +1131,9 @@
if (service == "features") {
std::string error;
- atransport* t = acquire_one_transport(type, serial, transport_id, nullptr, &error);
+ atransport* t =
+ s->transport ? s->transport
+ : acquire_one_transport(type, serial, transport_id, nullptr, &error);
if (t != nullptr) {
SendOkay(reply_fd, FeatureSetToString(t->features()));
} else {
@@ -1190,7 +1192,9 @@
// These always report "unknown" rather than the actual error, for scripts.
if (service == "get-serialno") {
std::string error;
- atransport* t = acquire_one_transport(type, serial, transport_id, nullptr, &error);
+ atransport* t =
+ s->transport ? s->transport
+ : acquire_one_transport(type, serial, transport_id, nullptr, &error);
if (t) {
SendOkay(reply_fd, !t->serial.empty() ? t->serial : "unknown");
} else {
@@ -1200,7 +1204,9 @@
}
if (service == "get-devpath") {
std::string error;
- atransport* t = acquire_one_transport(type, serial, transport_id, nullptr, &error);
+ atransport* t =
+ s->transport ? s->transport
+ : acquire_one_transport(type, serial, transport_id, nullptr, &error);
if (t) {
SendOkay(reply_fd, !t->devpath.empty() ? t->devpath : "unknown");
} else {
@@ -1210,7 +1216,9 @@
}
if (service == "get-state") {
std::string error;
- atransport* t = acquire_one_transport(type, serial, transport_id, nullptr, &error);
+ atransport* t =
+ s->transport ? s->transport
+ : acquire_one_transport(type, serial, transport_id, nullptr, &error);
if (t) {
SendOkay(reply_fd, t->connection_state_name());
} else {
@@ -1234,7 +1242,9 @@
if (service == "reconnect") {
std::string response;
- atransport* t = acquire_one_transport(type, serial, transport_id, nullptr, &response, true);
+ atransport* t = s->transport ? s->transport
+ : acquire_one_transport(type, serial, transport_id, nullptr,
+ &response, true);
if (t != nullptr) {
kick_transport(t, true);
response =
@@ -1246,8 +1256,15 @@
// 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 s->transport; }, reply_fd)) {
+ auto transport_acquirer = [=](std::string* error) {
+ if (s->transport) {
+ return s->transport;
+ } else {
+ std::string error;
+ return acquire_one_transport(type, serial, transport_id, nullptr, &error);
+ }
+ };
+ if (handle_forward_request(service_str.c_str(), transport_acquirer, reply_fd)) {
return HostRequestResult::Handled;
}
diff --git a/adb/fastdeploy/deploypatchgenerator/deploy_patch_generator.cpp b/adb/fastdeploy/deploypatchgenerator/deploy_patch_generator.cpp
index 22c9243..154c9b9 100644
--- a/adb/fastdeploy/deploypatchgenerator/deploy_patch_generator.cpp
+++ b/adb/fastdeploy/deploypatchgenerator/deploy_patch_generator.cpp
@@ -46,10 +46,10 @@
void DeployPatchGenerator::APKEntryToLog(const APKEntry& entry) {
Log("Filename: %s", entry.filename().c_str());
- Log("CRC32: 0x%08llX", entry.crc32());
- Log("Data Offset: %lld", entry.dataoffset());
- Log("Compressed Size: %lld", entry.compressedsize());
- Log("Uncompressed Size: %lld", entry.uncompressedsize());
+ Log("CRC32: 0x%08" PRIX64, entry.crc32());
+ Log("Data Offset: %" PRId64, entry.dataoffset());
+ Log("Compressed Size: %" PRId64, entry.compressedsize());
+ Log("Uncompressed Size: %" PRId64, entry.uncompressedsize());
}
void DeployPatchGenerator::APKMetaDataToLog(const char* file, const APKMetaData& metadata) {
@@ -164,4 +164,4 @@
return lhs.localEntry->dataoffset() < rhs.localEntry->dataoffset();
});
return totalSize;
-}
\ No newline at end of file
+}
diff --git a/adb/sockets.cpp b/adb/sockets.cpp
index 75993b3..e78530c 100644
--- a/adb/sockets.cpp
+++ b/adb/sockets.cpp
@@ -757,6 +757,8 @@
#if ADB_HOST
service = std::string_view(s->smart_socket_data).substr(4);
+
+ // TODO: These should be handled in handle_host_request.
if (android::base::ConsumePrefix(&service, "host-serial:")) {
// serial number should follow "host:" and could be a host:port string.
if (!internal::parse_host_service(&serial, &service, service)) {
diff --git a/adb/test_device.py b/adb/test_device.py
index f95a5b3..dbd80ed 100755
--- a/adb/test_device.py
+++ b/adb/test_device.py
@@ -139,6 +139,25 @@
msg = self.device.forward_list()
self.assertEqual('', msg.strip())
+ def test_forward_old_protocol(self):
+ serialno = subprocess.check_output(self.device.adb_cmd + ['get-serialno']).strip()
+
+ msg = self.device.forward_list()
+ self.assertEqual('', msg.strip(),
+ 'Forwarding list must be empty to run this test.')
+
+ s = socket.create_connection(("localhost", 5037))
+ service = b"host-serial:%s:forward:tcp:5566;tcp:6655" % serialno
+ cmd = b"%04x%s" % (len(service), service)
+ s.sendall(cmd)
+
+ msg = self.device.forward_list()
+ self.assertTrue(re.search(r'tcp:5566.+tcp:6655', msg))
+
+ self.device.forward_remove_all()
+ msg = self.device.forward_list()
+ self.assertEqual('', msg.strip())
+
def test_forward_tcp_port_0(self):
self.assertEqual('', self.device.forward_list().strip(),
'Forwarding list must be empty to run this test.')
diff --git a/base/Android.bp b/base/Android.bp
index 357ce01..f5000c1 100644
--- a/base/Android.bp
+++ b/base/Android.bp
@@ -20,8 +20,14 @@
"-Wall",
"-Werror",
"-Wextra",
- "-D_FILE_OFFSET_BITS=64",
],
+ target: {
+ android: {
+ cflags: [
+ "-D_FILE_OFFSET_BITS=64",
+ ],
+ },
+ },
}
cc_library_headers {
diff --git a/fastboot/Android.bp b/fastboot/Android.bp
index 978eed0..546bce2 100644
--- a/fastboot/Android.bp
+++ b/fastboot/Android.bp
@@ -143,6 +143,10 @@
static_libs: [
"libhealthhalutils",
],
+
+ header_libs: [
+ "libsnapshot_headers",
+ ]
}
cc_defaults {
diff --git a/fastboot/device/flashing.cpp b/fastboot/device/flashing.cpp
index 99854c9..102ebdb 100644
--- a/fastboot/device/flashing.cpp
+++ b/fastboot/device/flashing.cpp
@@ -32,6 +32,7 @@
#include <fstab/fstab.h>
#include <liblp/builder.h>
#include <liblp/liblp.h>
+#include <libsnapshot/snapshot.h>
#include <sparse/sparse.h>
#include "fastboot_device.h"
@@ -171,6 +172,11 @@
if (!slot_suffix.empty() && GetPartitionSlotSuffix(partition_name) == slot_suffix) {
continue;
}
+ std::string group_name = GetPartitionGroupName(old_metadata->groups[partition.group_index]);
+ // Skip partitions in the COW group
+ if (group_name == android::snapshot::kCowGroupName) {
+ continue;
+ }
partitions_to_keep.emplace(partition_name);
}
diff --git a/fs_mgr/fs_mgr_dm_linear.cpp b/fs_mgr/fs_mgr_dm_linear.cpp
index ea799ce..0dcb9fe 100644
--- a/fs_mgr/fs_mgr_dm_linear.cpp
+++ b/fs_mgr/fs_mgr_dm_linear.cpp
@@ -79,22 +79,22 @@
return true;
}
-bool CreateDmTable(const IPartitionOpener& opener, const LpMetadata& metadata,
- const LpMetadataPartition& partition, const std::string& super_device,
- DmTable* table) {
+bool CreateDmTableInternal(const CreateLogicalPartitionParams& params, DmTable* table) {
+ const auto& super_device = params.block_device;
+
uint64_t sector = 0;
- for (size_t i = 0; i < partition.num_extents; i++) {
- const auto& extent = metadata.extents[partition.first_extent_index + i];
+ for (size_t i = 0; i < params.partition->num_extents; i++) {
+ const auto& extent = params.metadata->extents[params.partition->first_extent_index + i];
std::unique_ptr<DmTarget> target;
switch (extent.target_type) {
case LP_TARGET_TYPE_ZERO:
target = std::make_unique<DmTargetZero>(sector, extent.num_sectors);
break;
case LP_TARGET_TYPE_LINEAR: {
- const auto& block_device = metadata.block_devices[extent.target_source];
+ const auto& block_device = params.metadata->block_devices[extent.target_source];
std::string dev_string;
- if (!GetPhysicalPartitionDevicePath(opener, metadata, block_device, super_device,
- &dev_string)) {
+ if (!GetPhysicalPartitionDevicePath(*params.partition_opener, *params.metadata,
+ block_device, super_device, &dev_string)) {
LOG(ERROR) << "Unable to complete device-mapper table, unknown block device";
return false;
}
@@ -111,12 +111,21 @@
}
sector += extent.num_sectors;
}
- if (partition.attributes & LP_PARTITION_ATTR_READONLY) {
+ if (params.partition->attributes & LP_PARTITION_ATTR_READONLY) {
table->set_readonly(true);
}
+ if (params.force_writable) {
+ table->set_readonly(false);
+ }
return true;
}
+bool CreateDmTable(CreateLogicalPartitionParams params, DmTable* table) {
+ CreateLogicalPartitionParams::OwnedData owned_data;
+ if (!params.InitDefaults(&owned_data)) return false;
+ return CreateDmTableInternal(params, table);
+}
+
bool CreateLogicalPartitions(const std::string& block_device) {
uint32_t slot = SlotNumberForSlotSuffix(fs_mgr_get_slot_suffix());
auto metadata = ReadMetadata(block_device.c_str(), slot);
@@ -160,6 +169,11 @@
return false;
}
+ if (!partition_opener) {
+ owned->partition_opener = std::make_unique<PartitionOpener>();
+ partition_opener = owned->partition_opener.get();
+ }
+
// Read metadata if needed.
if (!metadata) {
if (!metadata_slot) {
@@ -167,7 +181,8 @@
return false;
}
auto slot = *metadata_slot;
- if (owned->metadata = ReadMetadata(block_device, slot); !owned->metadata) {
+ if (owned->metadata = ReadMetadata(*partition_opener, block_device, slot);
+ !owned->metadata) {
LOG(ERROR) << "Could not read partition table for: " << block_device;
return false;
}
@@ -195,11 +210,6 @@
return false;
}
- if (!partition_opener) {
- owned->partition_opener = std::make_unique<PartitionOpener>();
- partition_opener = owned->partition_opener.get();
- }
-
if (device_name.empty()) {
device_name = partition_name;
}
@@ -212,13 +222,9 @@
if (!params.InitDefaults(&owned_data)) return false;
DmTable table;
- if (!CreateDmTable(*params.partition_opener, *params.metadata, *params.partition,
- params.block_device, &table)) {
+ if (!CreateDmTableInternal(params, &table)) {
return false;
}
- if (params.force_writable) {
- table.set_readonly(false);
- }
DeviceMapper& dm = DeviceMapper::Instance();
if (!dm.CreateDevice(params.device_name, table, path, params.timeout_ms)) {
diff --git a/fs_mgr/include/fs_mgr_dm_linear.h b/fs_mgr/include/fs_mgr_dm_linear.h
index a912208..f3d09fb 100644
--- a/fs_mgr/include/fs_mgr_dm_linear.h
+++ b/fs_mgr/include/fs_mgr_dm_linear.h
@@ -105,9 +105,7 @@
bool DestroyLogicalPartition(const std::string& name);
// Helper for populating a DmTable for a logical partition.
-bool CreateDmTable(const IPartitionOpener& opener, const LpMetadata& metadata,
- const LpMetadataPartition& partition, const std::string& super_device,
- android::dm::DmTable* table);
+bool CreateDmTable(CreateLogicalPartitionParams params, android::dm::DmTable* table);
} // namespace fs_mgr
} // namespace android
diff --git a/fs_mgr/liblp/Android.bp b/fs_mgr/liblp/Android.bp
index f0142bb..b2572f6 100644
--- a/fs_mgr/liblp/Android.bp
+++ b/fs_mgr/liblp/Android.bp
@@ -67,6 +67,9 @@
"libfs_mgr",
"liblp",
] + liblp_lib_deps,
+ header_libs: [
+ "libstorage_literals_headers",
+ ],
stl: "libc++_static",
srcs: [
"builder_test.cpp",
diff --git a/fs_mgr/liblp/builder.cpp b/fs_mgr/liblp/builder.cpp
index c5d6a3b..ea3b58e 100644
--- a/fs_mgr/liblp/builder.cpp
+++ b/fs_mgr/liblp/builder.cpp
@@ -40,6 +40,10 @@
return true;
}
+Interval LinearExtent::AsInterval() const {
+ return Interval(device_index(), physical_sector(), end_sector());
+}
+
bool ZeroExtent::AddTo(LpMetadata* out) const {
out->extents.emplace_back(LpMetadataExtent{num_sectors_, LP_TARGET_TYPE_ZERO, 0, 0});
return true;
@@ -96,6 +100,20 @@
DCHECK(size_ == aligned_size);
}
+Partition Partition::GetBeginningExtents(uint64_t aligned_size) const {
+ Partition p(name_, group_name_, attributes_);
+ for (const auto& extent : extents_) {
+ auto le = extent->AsLinearExtent();
+ if (le) {
+ p.AddExtent(std::make_unique<LinearExtent>(*le));
+ } else {
+ p.AddExtent(std::make_unique<ZeroExtent>(extent->num_sectors()));
+ }
+ }
+ p.ShrinkTo(aligned_size);
+ return p;
+}
+
uint64_t Partition::BytesOnDisk() const {
uint64_t sectors = 0;
for (const auto& extent : extents_) {
@@ -602,6 +620,10 @@
return ret;
}
+std::unique_ptr<Extent> Interval::AsExtent() const {
+ return std::make_unique<LinearExtent>(length(), device_index, start);
+}
+
bool MetadataBuilder::GrowPartition(Partition* partition, uint64_t aligned_size,
const std::vector<Interval>& free_region_hint) {
uint64_t space_needed = aligned_size - partition->size();
@@ -1168,5 +1190,9 @@
: "";
}
+uint64_t MetadataBuilder::logical_block_size() const {
+ return geometry_.logical_block_size;
+}
+
} // namespace fs_mgr
} // namespace android
diff --git a/fs_mgr/liblp/builder_test.cpp b/fs_mgr/liblp/builder_test.cpp
index bd41f59..a67ffa7 100644
--- a/fs_mgr/liblp/builder_test.cpp
+++ b/fs_mgr/liblp/builder_test.cpp
@@ -17,11 +17,13 @@
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <liblp/builder.h>
+#include <storage_literals/storage_literals.h>
#include "liblp_test.h"
#include "utility.h"
using namespace std;
+using namespace android::storage_literals;
using namespace android::fs_mgr;
using namespace android::fs_mgr::testing;
using ::testing::_;
@@ -591,13 +593,6 @@
ASSERT_NE(builder->Export(), nullptr);
}
-constexpr unsigned long long operator"" _GiB(unsigned long long x) { // NOLINT
- return x << 30;
-}
-constexpr unsigned long long operator"" _MiB(unsigned long long x) { // NOLINT
- return x << 20;
-}
-
TEST_F(BuilderTest, RemoveAndAddFirstPartition) {
auto builder = MetadataBuilder::New(10_GiB, 65536, 2);
ASSERT_NE(nullptr, builder);
diff --git a/fs_mgr/liblp/images.cpp b/fs_mgr/liblp/images.cpp
index 58a88b5..6b842b3 100644
--- a/fs_mgr/liblp/images.cpp
+++ b/fs_mgr/liblp/images.cpp
@@ -17,6 +17,7 @@
#include "images.h"
#include <limits.h>
+#include <sys/stat.h>
#include <android-base/file.h>
@@ -27,12 +28,45 @@
namespace android {
namespace fs_mgr {
+using android::base::borrowed_fd;
using android::base::unique_fd;
#if defined(_WIN32)
static const int O_NOFOLLOW = 0;
#endif
+static bool IsEmptySuperImage(borrowed_fd fd) {
+ struct stat s;
+ if (fstat(fd.get(), &s) < 0) {
+ PERROR << __PRETTY_FUNCTION__ << " fstat failed";
+ return false;
+ }
+ if (s.st_size < LP_METADATA_GEOMETRY_SIZE) {
+ return false;
+ }
+
+ // Rewind back to the start, read the geometry struct.
+ LpMetadataGeometry geometry = {};
+ if (SeekFile64(fd.get(), 0, SEEK_SET) < 0) {
+ PERROR << __PRETTY_FUNCTION__ << " lseek failed";
+ return false;
+ }
+ if (!android::base::ReadFully(fd, &geometry, sizeof(geometry))) {
+ PERROR << __PRETTY_FUNCTION__ << " read failed";
+ return false;
+ }
+ return geometry.magic == LP_METADATA_GEOMETRY_MAGIC;
+}
+
+bool IsEmptySuperImage(const std::string& file) {
+ unique_fd fd = GetControlFileOrOpen(file, O_RDONLY | O_CLOEXEC);
+ if (fd < 0) {
+ PERROR << __PRETTY_FUNCTION__ << " open failed";
+ return false;
+ }
+ return IsEmptySuperImage(fd);
+}
+
std::unique_ptr<LpMetadata> ReadFromImageFile(int fd) {
std::unique_ptr<uint8_t[]> buffer = std::make_unique<uint8_t[]>(LP_METADATA_GEOMETRY_SIZE);
if (SeekFile64(fd, 0, SEEK_SET) < 0) {
diff --git a/fs_mgr/liblp/include/liblp/builder.h b/fs_mgr/liblp/include/liblp/builder.h
index 6f2ab75..69885fe 100644
--- a/fs_mgr/liblp/include/liblp/builder.h
+++ b/fs_mgr/liblp/include/liblp/builder.h
@@ -33,10 +33,11 @@
namespace fs_mgr {
class LinearExtent;
+struct Interval;
// By default, partitions are aligned on a 1MiB boundary.
-static const uint32_t kDefaultPartitionAlignment = 1024 * 1024;
-static const uint32_t kDefaultBlockSize = 4096;
+static constexpr uint32_t kDefaultPartitionAlignment = 1024 * 1024;
+static constexpr uint32_t kDefaultBlockSize = 4096;
// Name of the default group in a metadata.
static constexpr std::string_view kDefaultGroup = "default";
@@ -74,6 +75,8 @@
return sector >= physical_sector_ && sector < end_sector();
}
+ Interval AsInterval() const;
+
private:
uint32_t device_index_;
uint64_t physical_sector_;
@@ -127,6 +130,12 @@
const std::vector<std::unique_ptr<Extent>>& extents() const { return extents_; }
uint64_t size() const { return size_; }
+ // Return a copy of *this, but with extents that includes only the first
+ // |aligned_size| bytes. |aligned_size| should be aligned to
+ // logical_block_size() of the MetadataBuilder that this partition belongs
+ // to.
+ Partition GetBeginningExtents(uint64_t aligned_size) const;
+
private:
void ShrinkTo(uint64_t aligned_size);
void set_group_name(std::string_view group_name) { group_name_ = group_name; }
@@ -156,6 +165,8 @@
return (start == other.start) ? end < other.end : start < other.start;
}
+ std::unique_ptr<Extent> AsExtent() const;
+
// Intersect |a| with |b|.
// If no intersection, result has 0 length().
static Interval Intersect(const Interval& a, const Interval& b);
@@ -325,6 +336,8 @@
// Return the list of free regions not occupied by extents in the metadata.
std::vector<Interval> GetFreeRegions() const;
+ uint64_t logical_block_size() const;
+
private:
MetadataBuilder();
MetadataBuilder(const MetadataBuilder&) = delete;
diff --git a/fs_mgr/liblp/include/liblp/liblp.h b/fs_mgr/liblp/include/liblp/liblp.h
index 135a1b3..cd860cd 100644
--- a/fs_mgr/liblp/include/liblp/liblp.h
+++ b/fs_mgr/liblp/include/liblp/liblp.h
@@ -70,8 +70,15 @@
uint32_t slot_number);
std::unique_ptr<LpMetadata> ReadMetadata(const std::string& super_partition, uint32_t slot_number);
+// Returns whether an image is an "empty" image or not. An empty image contains
+// only metadata. Unlike a flashed block device, there are no reserved bytes or
+// backup sections, and only one slot is stored (even if multiple slots are
+// supported). It is a format specifically for storing only metadata.
+bool IsEmptySuperImage(const std::string& file);
+
// Read/Write logical partition metadata to an image file, for diagnostics or
-// flashing.
+// flashing. If no partition images are specified, the file will be in the
+// empty format.
bool WriteToImageFile(const std::string& file, const LpMetadata& metadata, uint32_t block_size,
const std::map<std::string, std::string>& images, bool sparsify);
bool WriteToImageFile(const std::string& file, const LpMetadata& metadata);
diff --git a/fs_mgr/liblp/utility.cpp b/fs_mgr/liblp/utility.cpp
index afcce8f..48c5c83 100644
--- a/fs_mgr/liblp/utility.cpp
+++ b/fs_mgr/liblp/utility.cpp
@@ -205,9 +205,9 @@
#endif
}
-base::unique_fd GetControlFileOrOpen(const char* path, int flags) {
+base::unique_fd GetControlFileOrOpen(std::string_view path, int flags) {
#if defined(__ANDROID__)
- int fd = android_get_control_file(path);
+ int fd = android_get_control_file(path.data());
if (fd >= 0) {
int newfd = TEMP_FAILURE_RETRY(dup(fd));
if (newfd >= 0) {
@@ -216,7 +216,7 @@
PERROR << "Cannot dup fd for already controlled file: " << path << ", reopening...";
}
#endif
- return base::unique_fd(open(path, flags));
+ return base::unique_fd(open(path.data(), flags));
}
bool UpdateMetadataForInPlaceSnapshot(LpMetadata* metadata, uint32_t source_slot_number,
diff --git a/fs_mgr/liblp/utility.h b/fs_mgr/liblp/utility.h
index 25ab66b..0661769 100644
--- a/fs_mgr/liblp/utility.h
+++ b/fs_mgr/liblp/utility.h
@@ -21,6 +21,9 @@
#include <stdint.h>
#include <sys/types.h>
+#include <string>
+#include <string_view>
+
#include <android-base/logging.h>
#include <android-base/unique_fd.h>
@@ -94,7 +97,7 @@
// Call BLKROSET ioctl on fd so that fd is readonly / read-writable.
bool SetBlockReadonly(int fd, bool readonly);
-::android::base::unique_fd GetControlFileOrOpen(const char* path, int flags);
+::android::base::unique_fd GetControlFileOrOpen(std::string_view path, int flags);
// For Virtual A/B updates, modify |metadata| so that it can be written to |target_slot_number|.
bool UpdateMetadataForInPlaceSnapshot(LpMetadata* metadata, uint32_t source_slot_number,
diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp
index 8df9c52..f73b189 100644
--- a/fs_mgr/libsnapshot/Android.bp
+++ b/fs_mgr/libsnapshot/Android.bp
@@ -25,10 +25,12 @@
shared_libs: [
"libbase",
"liblog",
+ "liblp",
],
static_libs: [
"libdm",
"libfs_mgr",
+ "libfstab",
"liblp",
],
whole_static_libs: [
@@ -49,10 +51,17 @@
name: "libsnapshot_sources",
srcs: [
"snapshot.cpp",
+ "partition_cow_creator.cpp",
"utility.cpp",
],
}
+cc_library_headers {
+ name: "libsnapshot_headers",
+ recovery_available: true,
+ defaults: ["libsnapshot_defaults"],
+}
+
cc_library_static {
name: "libsnapshot",
defaults: ["libsnapshot_defaults"],
@@ -77,6 +86,7 @@
defaults: ["libsnapshot_defaults"],
srcs: [
"snapshot_test.cpp",
+ "partition_cow_creator_test.cpp",
"test_helpers.cpp",
],
shared_libs: [
@@ -90,5 +100,10 @@
"libgmock",
"liblp",
"libsnapshot",
+ "libsparse",
+ "libz",
+ ],
+ header_libs: [
+ "libstorage_literals_headers",
],
}
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
index c41a951..6bc09a1 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
@@ -17,13 +17,17 @@
#include <stdint.h>
#include <chrono>
+#include <map>
#include <memory>
#include <string>
+#include <string_view>
#include <vector>
#include <android-base/unique_fd.h>
+#include <fs_mgr_dm_linear.h>
#include <libdm/dm.h>
#include <libfiemap/image_manager.h>
+#include <liblp/builder.h>
#include <liblp/liblp.h>
#ifndef FRIEND_TEST
@@ -45,6 +49,13 @@
namespace snapshot {
+struct AutoDeleteCowImage;
+struct AutoDeleteSnapshot;
+struct PartitionCowCreator;
+struct AutoDeviceList;
+
+static constexpr const std::string_view kCowGroupName = "cow";
+
enum class UpdateState : unsigned int {
// No update or merge is in progress.
None,
@@ -75,8 +86,9 @@
class SnapshotManager final {
using CreateLogicalPartitionParams = android::fs_mgr::CreateLogicalPartitionParams;
- using LpMetadata = android::fs_mgr::LpMetadata;
using IPartitionOpener = android::fs_mgr::IPartitionOpener;
+ using LpMetadata = android::fs_mgr::LpMetadata;
+ using MetadataBuilder = android::fs_mgr::MetadataBuilder;
public:
// Dependency injection for testing.
@@ -88,6 +100,7 @@
virtual std::string GetSlotSuffix() const = 0;
virtual std::string GetSuperDevice(uint32_t slot) const = 0;
virtual const IPartitionOpener& GetPartitionOpener() const = 0;
+ virtual bool IsOverlayfsSetup() const = 0;
};
~SnapshotManager();
@@ -153,6 +166,20 @@
// Other: 0
UpdateState GetUpdateState(double* progress = nullptr);
+ // Create necessary COW device / files for OTA clients. New logical partitions will be added to
+ // group "cow" in target_metadata. Regions of partitions of current_metadata will be
+ // "write-protected" and snapshotted.
+ bool CreateUpdateSnapshots(MetadataBuilder* target_metadata, const std::string& target_suffix,
+ MetadataBuilder* current_metadata, const std::string& current_suffix,
+ const std::map<std::string, uint64_t>& cow_sizes);
+
+ // Map a snapshotted partition for OTA clients to write to. Write-protected regions are
+ // determined previously in CreateSnapshots.
+ bool MapUpdateSnapshot(const CreateLogicalPartitionParams& params, std::string* snapshot_path);
+
+ // Unmap a snapshot device that's previously mapped with MapUpdateSnapshot.
+ bool UnmapUpdateSnapshot(const std::string& target_partition_name);
+
// If this returns true, first-stage mount must call
// CreateLogicalAndSnapshotPartitions rather than CreateLogicalPartitions.
bool NeedSnapshotsInFirstStageMount();
@@ -173,7 +200,12 @@
FRIEND_TEST(SnapshotTest, Merge);
FRIEND_TEST(SnapshotTest, MergeCannotRemoveCow);
FRIEND_TEST(SnapshotTest, NoMergeBeforeReboot);
+ FRIEND_TEST(SnapshotUpdateTest, SnapshotStatusFileWithoutCow);
friend class SnapshotTest;
+ friend class SnapshotUpdateTest;
+ friend struct AutoDeleteCowImage;
+ friend struct AutoDeleteSnapshot;
+ friend struct PartitionCowCreator;
using DmTargetSnapshot = android::dm::DmTargetSnapshot;
using IImageManager = android::fiemap::IImageManager;
@@ -241,9 +273,8 @@
// 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, snapshot and COW partition sizes
- // must be a multiple of the sector size (512 bytes). COW file size will be rounded up
- // to the nearest sector.
+ // All sizes are specified in bytes, and the device, snapshot, COW partition and COW file sizes
+ // must be a multiple of the sector size (512 bytes).
bool CreateSnapshot(LockedFile* lock, const std::string& name, SnapshotStatus status);
// |name| should be the base partition name (e.g. "system_a"). Create the
@@ -262,8 +293,7 @@
std::string* dev_path);
// Map a COW image that was previous created with CreateCowImage.
- bool MapCowImage(const std::string& name, const std::chrono::milliseconds& timeout_ms,
- std::string* cow_image_device);
+ bool MapCowImage(const std::string& name, const std::chrono::milliseconds& timeout_ms);
// Remove the backing copy-on-write image and snapshot states for the named snapshot. The
// caller is responsible for ensuring that the snapshot is unmapped.
@@ -344,6 +374,24 @@
bool MapPartitionWithSnapshot(LockedFile* lock, CreateLogicalPartitionParams params,
std::string* path);
+ // Map the COW devices, including the partition in super and the images.
+ // |params|:
+ // - |partition_name| should be the name of the top-level partition (e.g. system_b),
+ // not system_b-cow-img
+ // - |device_name| and |partition| is ignored
+ // - |timeout_ms| and the rest is respected
+ // Return the path in |cow_device_path| (e.g. /dev/block/dm-1) and major:minor in
+ // |cow_device_string|
+ bool MapCowDevices(LockedFile* lock, const CreateLogicalPartitionParams& params,
+ const SnapshotStatus& snapshot_status, AutoDeviceList* created_devices,
+ std::string* cow_name);
+
+ // The reverse of MapCowDevices.
+ bool UnmapCowDevices(LockedFile* lock, const std::string& name);
+
+ // The reverse of MapPartitionWithSnapshot.
+ bool UnmapPartitionWithSnapshot(LockedFile* lock, const std::string& target_partition_name);
+
std::string gsid_dir_;
std::string metadata_dir_;
std::unique_ptr<IDeviceInfo> device_;
diff --git a/fs_mgr/libsnapshot/partition_cow_creator.cpp b/fs_mgr/libsnapshot/partition_cow_creator.cpp
new file mode 100644
index 0000000..3163e61
--- /dev/null
+++ b/fs_mgr/libsnapshot/partition_cow_creator.cpp
@@ -0,0 +1,176 @@
+// 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 "partition_cow_creator.h"
+
+#include <math.h>
+
+#include <android-base/logging.h>
+
+#include "utility.h"
+
+using android::dm::kSectorSize;
+using android::fs_mgr::Extent;
+using android::fs_mgr::Interval;
+using android::fs_mgr::kDefaultBlockSize;
+using android::fs_mgr::Partition;
+
+namespace android {
+namespace snapshot {
+
+// Round |d| up to a multiple of |block_size|.
+static uint64_t RoundUp(double d, uint64_t block_size) {
+ uint64_t ret = ((uint64_t)ceil(d) + block_size - 1) / block_size * block_size;
+ CHECK(ret >= d) << "Can't round " << d << " up to a multiple of " << block_size;
+ return ret;
+}
+
+// Intersect two linear extents. If no intersection, return an extent with length 0.
+static std::unique_ptr<Extent> Intersect(Extent* target_extent, Extent* existing_extent) {
+ // Convert target_extent and existing_extent to linear extents. Zero extents
+ // doesn't matter and doesn't result in any intersection.
+ auto existing_linear_extent = existing_extent->AsLinearExtent();
+ if (!existing_linear_extent) return nullptr;
+
+ auto target_linear_extent = target_extent->AsLinearExtent();
+ if (!target_linear_extent) return nullptr;
+
+ return Interval::Intersect(target_linear_extent->AsInterval(),
+ existing_linear_extent->AsInterval())
+ .AsExtent();
+}
+
+// Check that partition |p| contains |e| fully. Both of them should
+// be from |target_metadata|.
+// Returns true as long as |e| is a subrange of any extent of |p|.
+bool PartitionCowCreator::HasExtent(Partition* p, Extent* e) {
+ for (auto& partition_extent : p->extents()) {
+ auto intersection = Intersect(partition_extent.get(), e);
+ if (intersection != nullptr && intersection->num_sectors() == e->num_sectors()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// Return the number of sectors, N, where |target_partition|[0..N] (from
+// |target_metadata|) are the sectors that should be snapshotted. N is computed
+// so that this range of sectors are used by partitions in |current_metadata|.
+//
+// The client code (update_engine) should have computed target_metadata by
+// resizing partitions of current_metadata, so only the first N sectors should
+// be snapshotted, not a range with start index != 0.
+//
+// Note that if partition A has shrunk and partition B has grown, the new
+// extents of partition B may use the empty space that was used by partition A.
+// In this case, that new extent cannot be written directly, as it may be used
+// by the running system. Hence, all extents of the new partition B must be
+// intersected with all old partitions (including old partition A and B) to get
+// the region that needs to be snapshotted.
+std::optional<uint64_t> PartitionCowCreator::GetSnapshotSize() {
+ // Compute the number of sectors that needs to be snapshotted.
+ uint64_t snapshot_sectors = 0;
+ std::vector<std::unique_ptr<Extent>> intersections;
+ for (const auto& extent : target_partition->extents()) {
+ for (auto* existing_partition :
+ ListPartitionsWithSuffix(current_metadata, current_suffix)) {
+ for (const auto& existing_extent : existing_partition->extents()) {
+ auto intersection = Intersect(extent.get(), existing_extent.get());
+ if (intersection != nullptr && intersection->num_sectors() > 0) {
+ snapshot_sectors += intersection->num_sectors();
+ intersections.emplace_back(std::move(intersection));
+ }
+ }
+ }
+ }
+ uint64_t snapshot_size = snapshot_sectors * kSectorSize;
+
+ // Sanity check that all recorded intersections are indeed within
+ // target_partition[0..snapshot_sectors].
+ Partition target_partition_snapshot = target_partition->GetBeginningExtents(snapshot_size);
+ for (const auto& intersection : intersections) {
+ if (!HasExtent(&target_partition_snapshot, intersection.get())) {
+ auto linear_intersection = intersection->AsLinearExtent();
+ LOG(ERROR) << "Extent "
+ << (linear_intersection
+ ? (std::to_string(linear_intersection->physical_sector()) + "," +
+ std::to_string(linear_intersection->end_sector()))
+ : "")
+ << " is not part of Partition " << target_partition->name() << "[0.."
+ << snapshot_size
+ << "]. The metadata wasn't constructed correctly. This should not happen.";
+ return std::nullopt;
+ }
+ }
+
+ return snapshot_size;
+}
+
+std::optional<PartitionCowCreator::Return> PartitionCowCreator::Run() {
+ static constexpr double kCowEstimateFactor = 1.05;
+
+ CHECK(current_metadata->GetBlockDevicePartitionName(0) == LP_METADATA_DEFAULT_PARTITION_NAME &&
+ target_metadata->GetBlockDevicePartitionName(0) == LP_METADATA_DEFAULT_PARTITION_NAME);
+
+ uint64_t logical_block_size = current_metadata->logical_block_size();
+ CHECK(logical_block_size != 0 && !(logical_block_size & (logical_block_size - 1)))
+ << "logical_block_size is not power of 2";
+
+ Return ret;
+ ret.snapshot_status.device_size = target_partition->size();
+
+ auto snapshot_size = GetSnapshotSize();
+ if (!snapshot_size.has_value()) return std::nullopt;
+
+ ret.snapshot_status.snapshot_size = *snapshot_size;
+
+ // TODO: always read from cow_size when the COW size is written in
+ // update package. kCowEstimateFactor is good for prototyping but
+ // we can't use that in production.
+ if (!cow_size.has_value()) {
+ cow_size =
+ RoundUp(ret.snapshot_status.snapshot_size * kCowEstimateFactor, kDefaultBlockSize);
+ }
+
+ // Compute regions that are free in both current and target metadata. These are the regions
+ // we can use for COW partition.
+ auto target_free_regions = target_metadata->GetFreeRegions();
+ auto current_free_regions = current_metadata->GetFreeRegions();
+ auto free_regions = Interval::Intersect(target_free_regions, current_free_regions);
+ uint64_t free_region_length = 0;
+ for (const auto& interval : free_regions) {
+ free_region_length += interval.length() * kSectorSize;
+ }
+
+ LOG(INFO) << "Remaining free space for COW: " << free_region_length << " bytes";
+
+ // Compute the COW partition size.
+ ret.snapshot_status.cow_partition_size = std::min(*cow_size, free_region_length);
+ // Round it down to the nearest logical block. Logical partitions must be a multiple
+ // of logical blocks.
+ ret.snapshot_status.cow_partition_size &= ~(logical_block_size - 1);
+ // Assign cow_partition_usable_regions to indicate what regions should the COW partition uses.
+ ret.cow_partition_usable_regions = std::move(free_regions);
+
+ // The rest of the COW space is allocated on ImageManager.
+ ret.snapshot_status.cow_file_size = (*cow_size) - ret.snapshot_status.cow_partition_size;
+ // Round it up to the nearest sector.
+ ret.snapshot_status.cow_file_size += kSectorSize - 1;
+ ret.snapshot_status.cow_file_size &= ~(kSectorSize - 1);
+
+ return ret;
+}
+
+} // namespace snapshot
+} // namespace android
diff --git a/fs_mgr/libsnapshot/partition_cow_creator.h b/fs_mgr/libsnapshot/partition_cow_creator.h
new file mode 100644
index 0000000..a235914
--- /dev/null
+++ b/fs_mgr/libsnapshot/partition_cow_creator.h
@@ -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.
+
+#pragma once
+
+#include <stdint.h>
+
+#include <optional>
+#include <string>
+
+#include <liblp/builder.h>
+
+#include <libsnapshot/snapshot.h>
+
+namespace android {
+namespace snapshot {
+
+// Helper class that creates COW for a partition.
+struct PartitionCowCreator {
+ using Extent = android::fs_mgr::Extent;
+ using Interval = android::fs_mgr::Interval;
+ using MetadataBuilder = android::fs_mgr::MetadataBuilder;
+ using Partition = android::fs_mgr::Partition;
+
+ // The metadata that will be written to target metadata slot.
+ MetadataBuilder* target_metadata;
+ // The suffix of the target slot.
+ std::string target_suffix;
+ // The partition in target_metadata that needs to be snapshotted.
+ Partition* target_partition;
+ // The metadata at the current slot (that would be used if the device boots
+ // normally). This is used to determine which extents are being used.
+ MetadataBuilder* current_metadata;
+ // The suffix of the current slot.
+ std::string current_suffix;
+ // The COW size given by client code.
+ std::optional<uint64_t> cow_size;
+
+ struct Return {
+ SnapshotManager::SnapshotStatus snapshot_status;
+ std::vector<Interval> cow_partition_usable_regions;
+ };
+
+ std::optional<Return> Run();
+
+ private:
+ bool HasExtent(Partition* p, Extent* e);
+ std::optional<uint64_t> GetSnapshotSize();
+};
+
+} // namespace snapshot
+} // namespace android
diff --git a/fs_mgr/libsnapshot/partition_cow_creator_test.cpp b/fs_mgr/libsnapshot/partition_cow_creator_test.cpp
new file mode 100644
index 0000000..e308943
--- /dev/null
+++ b/fs_mgr/libsnapshot/partition_cow_creator_test.cpp
@@ -0,0 +1,90 @@
+// 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 <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <liblp/builder.h>
+#include <liblp/property_fetcher.h>
+
+#include "partition_cow_creator.h"
+
+using ::android::fs_mgr::MetadataBuilder;
+using ::testing::_;
+using ::testing::AnyNumber;
+using ::testing::Return;
+
+namespace android {
+namespace snapshot {
+
+class MockPropertyFetcher : public fs_mgr::IPropertyFetcher {
+ public:
+ MOCK_METHOD2(GetProperty, std::string(const std::string&, const std::string&));
+ MOCK_METHOD2(GetBoolProperty, bool(const std::string&, bool));
+};
+
+class PartitionCowCreatorTest : ::testing::Test {
+ public:
+ void SetUp() override {
+ fs_mgr::IPropertyFetcher::OverrideForTesting(std::make_unique<MockPropertyFetcher>());
+
+ EXPECT_CALL(fetcher(), GetProperty("ro.boot.slot_suffix", _))
+ .Times(AnyNumber())
+ .WillRepeatedly(Return("_a"));
+ EXPECT_CALL(fetcher(), GetBoolProperty("ro.boot.dynamic_partitions", _))
+ .Times(AnyNumber())
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(fetcher(), GetBoolProperty("ro.boot.dynamic_partitions_retrofit", _))
+ .Times(AnyNumber())
+ .WillRepeatedly(Return(false));
+ EXPECT_CALL(fetcher(), GetBoolProperty("ro.virtual_ab.enabled", _))
+ .Times(AnyNumber())
+ .WillRepeatedly(Return(true));
+ }
+ void TearDown() override {
+ fs_mgr::IPropertyFetcher::OverrideForTesting(std::make_unique<MockPropertyFetcher>());
+ }
+ MockPropertyFetcher& fetcher() {
+ return *static_cast<MockPropertyFetcher*>(fs_mgr::IPropertyFetcher::GetInstance());
+ }
+};
+
+TEST(PartitionCowCreator, IntersectSelf) {
+ auto builder_a = MetadataBuilder::New(1024 * 1024, 1024, 2);
+ ASSERT_NE(builder_a, nullptr);
+ auto system_a = builder_a->AddPartition("system_a", LP_PARTITION_ATTR_READONLY);
+ ASSERT_NE(system_a, nullptr);
+ ASSERT_TRUE(builder_a->ResizePartition(system_a, 40 * 1024));
+
+ auto builder_b = MetadataBuilder::New(1024 * 1024, 1024, 2);
+ ASSERT_NE(builder_b, nullptr);
+ auto system_b = builder_b->AddPartition("system_b", LP_PARTITION_ATTR_READONLY);
+ ASSERT_NE(system_b, nullptr);
+ ASSERT_TRUE(builder_b->ResizePartition(system_b, 40 * 1024));
+
+ PartitionCowCreator creator{.target_metadata = builder_b.get(),
+ .target_suffix = "_b",
+ .target_partition = system_b,
+ .current_metadata = builder_a.get(),
+ .current_suffix = "_a",
+ .cow_size = 20 * 1024};
+ auto ret = creator.Run();
+ ASSERT_TRUE(ret.has_value());
+ ASSERT_EQ(40 * 1024, ret->snapshot_status.device_size);
+ ASSERT_EQ(40 * 1024, ret->snapshot_status.snapshot_size);
+ ASSERT_EQ(20 * 1024,
+ ret->snapshot_status.cow_file_size + ret->snapshot_status.cow_partition_size);
+}
+
+} // namespace snapshot
+} // namespace android
diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp
index f00129a..15d52f1 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -15,6 +15,7 @@
#include <libsnapshot/snapshot.h>
#include <dirent.h>
+#include <math.h>
#include <sys/file.h>
#include <sys/types.h>
#include <sys/unistd.h>
@@ -31,11 +32,13 @@
#include <ext4_utils/ext4_utils.h>
#include <fs_mgr.h>
#include <fs_mgr_dm_linear.h>
+#include <fs_mgr_overlayfs.h>
#include <fstab/fstab.h>
#include <libdm/dm.h>
#include <libfiemap/image_manager.h>
#include <liblp/liblp.h>
+#include "partition_cow_creator.h"
#include "utility.h"
namespace android {
@@ -53,8 +56,10 @@
using android::fs_mgr::CreateDmTable;
using android::fs_mgr::CreateLogicalPartition;
using android::fs_mgr::CreateLogicalPartitionParams;
+using android::fs_mgr::GetPartitionGroupName;
using android::fs_mgr::GetPartitionName;
using android::fs_mgr::LpMetadata;
+using android::fs_mgr::MetadataBuilder;
using android::fs_mgr::SlotNumberForSlotSuffix;
using std::chrono::duration_cast;
using namespace std::chrono_literals;
@@ -73,6 +78,7 @@
std::string GetSuperDevice(uint32_t slot) const override {
return fs_mgr_get_super_partition_name(slot);
}
+ bool IsOverlayfsSetup() const override { return fs_mgr_overlayfs_is_setup(); }
private:
android::fs_mgr::PartitionOpener opener_;
@@ -102,7 +108,7 @@
metadata_dir_ = device_->GetMetadataDir();
}
-[[maybe_unused]] static std::string GetCowName(const std::string& snapshot_name) {
+static std::string GetCowName(const std::string& snapshot_name) {
return snapshot_name + "-cow";
}
@@ -136,11 +142,27 @@
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 (state == UpdateState::Initiated) {
+ LOG(INFO) << "Update has been initiated, now canceling";
+ return RemoveAllUpdateState(file.get());
}
- return RemoveAllUpdateState(file.get());
+
+ if (state == UpdateState::Unverified) {
+ // We completed an update, but it can still be canceled if we haven't booted into it.
+ auto boot_file = GetSnapshotBootIndicatorPath();
+ std::string contents;
+ if (!android::base::ReadFileToString(boot_file, &contents)) {
+ PLOG(WARNING) << "Cannot read " << boot_file << ", proceed to canceling the update:";
+ return RemoveAllUpdateState(file.get());
+ }
+ if (device_->GetSlotSuffix() == contents) {
+ LOG(INFO) << "Canceling a previously completed update";
+ return RemoveAllUpdateState(file.get());
+ }
+ }
+ LOG(ERROR) << "Cannot cancel update after it has completed or started merging";
+ return false;
}
bool SnapshotManager::RemoveAllUpdateState(LockedFile* lock) {
@@ -189,7 +211,7 @@
CHECK(lock->lock_mode() == LOCK_EX);
// 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,
+ // is useful for locating avb footers correctly). The COW file size, however,
// can be arbitrarily larger than specified, so we can safely round it up.
if (status.device_size % kSectorSize != 0) {
LOG(ERROR) << "Snapshot " << name
@@ -201,10 +223,17 @@
<< status.snapshot_size;
return false;
}
-
- // Round the COW size up to the nearest sector.
- status.cow_file_size += kSectorSize - 1;
- status.cow_file_size &= ~(kSectorSize - 1);
+ if (status.cow_partition_size % kSectorSize != 0) {
+ LOG(ERROR) << "Snapshot " << name
+ << " cow partition size is not a multiple of the sector size: "
+ << status.cow_partition_size;
+ return false;
+ }
+ if (status.cow_file_size % kSectorSize != 0) {
+ LOG(ERROR) << "Snapshot " << name << " cow file size is not a multiple of the sector size: "
+ << status.cow_partition_size;
+ return false;
+ }
status.state = SnapshotState::Created;
status.sectors_allocated = 0;
@@ -237,26 +266,7 @@
std::string cow_image_name = GetCowImageDeviceName(name);
int cow_flags = IImageManager::CREATE_IMAGE_DEFAULT;
- if (!images_->CreateBackingImage(cow_image_name, status.cow_file_size, cow_flags)) {
- return false;
- }
-
- // when the kernel creates a persistent dm-snapshot, it requires a CoW file
- // to store the modifications. The kernel interface does not specify how
- // the CoW is used, and there is no standard associated.
- // By looking at the current implementation, the CoW file is treated as:
- // - a _NEW_ snapshot if its first 32 bits are zero, so the newly created
- // dm-snapshot device will look like a perfect copy of the origin device;
- // - an _EXISTING_ snapshot if the first 32 bits are equal to a
- // kernel-specified magic number and the CoW file metadata is set as valid,
- // so it can be used to resume the last state of a snapshot device;
- // - an _INVALID_ snapshot otherwise.
- // To avoid zero-filling the whole CoW file when a new dm-snapshot is
- // created, here we zero-fill only the first 32 bits. This is a temporary
- // workaround that will be discussed again when the kernel API gets
- // consolidated.
- ssize_t dm_snap_magic_size = 4; // 32 bit
- return images_->ZeroFillNewImage(cow_image_name, dm_snap_magic_size);
+ return images_->CreateBackingImage(cow_image_name, status.cow_file_size, cow_flags);
}
bool SnapshotManager::MapSnapshot(LockedFile* lock, const std::string& name,
@@ -346,12 +356,18 @@
}
if (linear_sectors) {
+ std::string snap_dev;
+ if (!dm.GetDeviceString(snap_name, &snap_dev)) {
+ LOG(ERROR) << "Cannot determine major/minor for: " << snap_name;
+ return false;
+ }
+
// 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>(0, snapshot_sectors, snap_dev, 0);
table.Emplace<DmTargetLinear>(snapshot_sectors, linear_sectors, base_device,
snapshot_sectors);
if (!dm.CreateDevice(name, table, dev_path, timeout_ms)) {
@@ -368,21 +384,24 @@
}
bool SnapshotManager::MapCowImage(const std::string& name,
- const std::chrono::milliseconds& timeout_ms,
- std::string* cow_dev) {
+ const std::chrono::milliseconds& timeout_ms) {
if (!EnsureImageManager()) return false;
auto cow_image_name = GetCowImageDeviceName(name);
bool ok;
+ std::string cow_dev;
if (has_local_image_manager_) {
// If we forced a local image manager, it means we don't have binder,
// which means first-stage init. We must use device-mapper.
const auto& opener = device_->GetPartitionOpener();
- ok = images_->MapImageWithDeviceMapper(opener, cow_image_name, cow_dev);
+ ok = images_->MapImageWithDeviceMapper(opener, cow_image_name, &cow_dev);
} else {
- ok = images_->MapImageDevice(cow_image_name, timeout_ms, cow_dev);
+ ok = images_->MapImageDevice(cow_image_name, timeout_ms, &cow_dev);
}
- if (!ok) {
+
+ if (ok) {
+ LOG(INFO) << "Mapped " << cow_image_name << " to " << cow_dev;
+ } else {
LOG(ERROR) << "Could not map image device: " << cow_image_name;
}
return ok;
@@ -391,22 +410,15 @@
bool SnapshotManager::UnmapSnapshot(LockedFile* lock, const std::string& name) {
CHECK(lock);
- SnapshotStatus status;
- if (!ReadSnapshotStatus(lock, name, &status)) {
- return false;
- }
-
auto& dm = DeviceMapper::Instance();
if (!dm.DeleteDeviceIfExists(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.DeleteDeviceIfExists(dm_name)) {
- LOG(ERROR) << "Could not delete inner snapshot device: " << dm_name;
+ auto snapshot_extra_device = GetSnapshotExtraDeviceName(name);
+ if (!dm.DeleteDeviceIfExists(snapshot_extra_device)) {
+ LOG(ERROR) << "Could not delete snapshot inner device: " << snapshot_extra_device;
return false;
}
@@ -423,11 +435,12 @@
CHECK(lock->lock_mode() == LOCK_EX);
if (!EnsureImageManager()) return false;
+ if (!UnmapCowDevices(lock, name)) {
+ return false;
+ }
+
auto cow_image_name = GetCowImageDeviceName(name);
if (images_->BackingImageExists(cow_image_name)) {
- if (!images_->UnmapImageIfExists(cow_image_name)) {
- return false;
- }
if (!images_->DeleteBackingImage(cow_image_name)) {
return false;
}
@@ -927,8 +940,8 @@
return false;
}
- uint64_t num_sectors = status.snapshot_size / kSectorSize;
- if (num_sectors * kSectorSize != status.snapshot_size) {
+ uint64_t snapshot_sectors = status.snapshot_size / kSectorSize;
+ if (snapshot_sectors * kSectorSize != status.snapshot_size) {
LOG(ERROR) << "Snapshot " << name
<< " size is not sector aligned: " << status.snapshot_size;
return false;
@@ -956,32 +969,30 @@
return false;
}
}
- uint64_t sectors = outer_table[0].spec.length + outer_table[1].spec.length;
- if (sectors != num_sectors) {
- LOG(ERROR) << "Outer snapshot " << name << " should have " << num_sectors
- << ", got: " << sectors;
+ if (outer_table[0].spec.length != snapshot_sectors) {
+ LOG(ERROR) << "dm-snapshot " << name << " should have " << snapshot_sectors
+ << " sectors, got: " << outer_table[0].spec.length;
+ return false;
+ }
+ uint64_t expected_device_sectors = status.device_size / kSectorSize;
+ uint64_t actual_device_sectors = outer_table[0].spec.length + outer_table[1].spec.length;
+ if (expected_device_sectors != actual_device_sectors) {
+ LOG(ERROR) << "Outer device " << name << " should have " << expected_device_sectors
+ << " sectors, got: " << actual_device_sectors;
return false;
}
}
- // Grab the partition metadata for the snapshot.
uint32_t slot = SlotNumberForSlotSuffix(device_->GetSlotSuffix());
- auto super_device = device_->GetSuperDevice(slot);
- const auto& opener = device_->GetPartitionOpener();
- auto metadata = android::fs_mgr::ReadMetadata(opener, super_device, slot);
- if (!metadata) {
- LOG(ERROR) << "Could not read super partition metadata.";
- return false;
- }
- auto partition = android::fs_mgr::FindPartition(*metadata.get(), name);
- if (!partition) {
- LOG(ERROR) << "Snapshot does not have a partition in super: " << name;
- return false;
- }
-
// Create a DmTable that is identical to the base device.
+ CreateLogicalPartitionParams base_device_params{
+ .block_device = device_->GetSuperDevice(slot),
+ .metadata_slot = slot,
+ .partition_name = name,
+ .partition_opener = &device_->GetPartitionOpener(),
+ };
DmTable table;
- if (!CreateDmTable(opener, *metadata.get(), *partition, super_device, &table)) {
+ if (!CreateDmTable(base_device_params, &table)) {
LOG(ERROR) << "Could not create a DmTable for partition: " << name;
return false;
}
@@ -1058,7 +1069,7 @@
bool ok = true;
for (const auto& name : snapshots) {
- ok &= DeleteSnapshot(lock, name);
+ ok &= (UnmapPartitionWithSnapshot(lock, name) && DeleteSnapshot(lock, name));
}
return ok;
}
@@ -1160,6 +1171,12 @@
}
for (const auto& partition : metadata->partitions) {
+ if (GetPartitionGroupName(metadata->groups[partition.group_index]) == kCowGroupName) {
+ LOG(INFO) << "Skip mapping partition " << GetPartitionName(partition) << " in group "
+ << kCowGroupName;
+ continue;
+ }
+
CreateLogicalPartitionParams params = {
.block_device = super_device,
.metadata = metadata.get(),
@@ -1201,6 +1218,12 @@
CHECK(lock);
path->clear();
+ if (params.GetPartitionName() != params.GetDeviceName()) {
+ LOG(ERROR) << "Mapping snapshot with a different name is unsupported: partition_name = "
+ << params.GetPartitionName() << ", device_name = " << params.GetDeviceName();
+ return false;
+ }
+
// Fill out fields in CreateLogicalPartitionParams so that we have more information (e.g. by
// reading super partition metadata).
CreateLogicalPartitionParams::OwnedData params_owned_data;
@@ -1276,22 +1299,20 @@
return false;
}
- // If there is a timeout specified, compute the remaining time to call Map* functions.
- // init calls CreateLogicalAndSnapshotPartitions, which has no timeout specified. Still call
- // Map* functions in this case.
auto remaining_time = GetRemainingTime(params.timeout_ms, begin);
if (remaining_time.count() < 0) return false;
- std::string cow_image_device;
- if (!MapCowImage(params.GetPartitionName(), remaining_time, &cow_image_device)) {
- LOG(ERROR) << "Could not map cow image for partition: " << params.GetPartitionName();
+ std::string cow_name;
+ CreateLogicalPartitionParams cow_params = params;
+ cow_params.timeout_ms = remaining_time;
+ if (!MapCowDevices(lock, cow_params, *live_snapshot_status, &created_devices, &cow_name)) {
return false;
}
- created_devices.EmplaceBack<AutoUnmapImage>(images_.get(),
- GetCowImageDeviceName(params.partition_name));
-
- // TODO: map cow linear device here
- std::string cow_device = cow_image_device;
+ std::string cow_device;
+ if (!dm.GetDeviceString(cow_name, &cow_device)) {
+ LOG(ERROR) << "Could not determine major/minor for: " << cow_name;
+ return false;
+ }
remaining_time = GetRemainingTime(params.timeout_ms, begin);
if (remaining_time.count() < 0) return false;
@@ -1310,6 +1331,121 @@
return true;
}
+bool SnapshotManager::UnmapPartitionWithSnapshot(LockedFile* lock,
+ const std::string& target_partition_name) {
+ CHECK(lock);
+
+ if (!UnmapSnapshot(lock, target_partition_name)) {
+ return false;
+ }
+
+ if (!UnmapCowDevices(lock, target_partition_name)) {
+ return false;
+ }
+
+ auto& dm = DeviceMapper::Instance();
+ std::string base_name = GetBaseDeviceName(target_partition_name);
+ if (!dm.DeleteDeviceIfExists(base_name)) {
+ LOG(ERROR) << "Cannot delete base device: " << base_name;
+ return false;
+ }
+
+ LOG(INFO) << "Successfully unmapped snapshot " << target_partition_name;
+
+ return true;
+}
+
+bool SnapshotManager::MapCowDevices(LockedFile* lock, const CreateLogicalPartitionParams& params,
+ const SnapshotStatus& snapshot_status,
+ AutoDeviceList* created_devices, std::string* cow_name) {
+ CHECK(lock);
+ if (!EnsureImageManager()) return false;
+ CHECK(snapshot_status.cow_partition_size + snapshot_status.cow_file_size > 0);
+ auto begin = std::chrono::steady_clock::now();
+
+ std::string partition_name = params.GetPartitionName();
+ std::string cow_image_name = GetCowImageDeviceName(partition_name);
+ *cow_name = GetCowName(partition_name);
+
+ auto& dm = DeviceMapper::Instance();
+
+ // Map COW image if necessary.
+ if (snapshot_status.cow_file_size > 0) {
+ auto remaining_time = GetRemainingTime(params.timeout_ms, begin);
+ if (remaining_time.count() < 0) return false;
+
+ if (!MapCowImage(partition_name, remaining_time)) {
+ LOG(ERROR) << "Could not map cow image for partition: " << partition_name;
+ return false;
+ }
+ created_devices->EmplaceBack<AutoUnmapImage>(images_.get(), cow_image_name);
+
+ // If no COW partition exists, just return the image alone.
+ if (snapshot_status.cow_partition_size == 0) {
+ *cow_name = std::move(cow_image_name);
+ LOG(INFO) << "Mapped COW image for " << partition_name << " at " << *cow_name;
+ return true;
+ }
+ }
+
+ auto remaining_time = GetRemainingTime(params.timeout_ms, begin);
+ if (remaining_time.count() < 0) return false;
+
+ CHECK(snapshot_status.cow_partition_size > 0);
+
+ // Create the DmTable for the COW device. It is the DmTable of the COW partition plus
+ // COW image device as the last extent.
+ CreateLogicalPartitionParams cow_partition_params = params;
+ cow_partition_params.partition = nullptr;
+ cow_partition_params.partition_name = *cow_name;
+ cow_partition_params.device_name.clear();
+ DmTable table;
+ if (!CreateDmTable(cow_partition_params, &table)) {
+ return false;
+ }
+ // If the COW image exists, append it as the last extent.
+ if (snapshot_status.cow_file_size > 0) {
+ std::string cow_image_device;
+ if (!dm.GetDeviceString(cow_image_name, &cow_image_device)) {
+ LOG(ERROR) << "Cannot determine major/minor for: " << cow_image_name;
+ return false;
+ }
+ auto cow_partition_sectors = snapshot_status.cow_partition_size / kSectorSize;
+ auto cow_image_sectors = snapshot_status.cow_file_size / kSectorSize;
+ table.Emplace<DmTargetLinear>(cow_partition_sectors, cow_image_sectors, cow_image_device,
+ 0);
+ }
+
+ // We have created the DmTable now. Map it.
+ std::string cow_path;
+ if (!dm.CreateDevice(*cow_name, table, &cow_path, remaining_time)) {
+ LOG(ERROR) << "Could not create COW device: " << *cow_name;
+ return false;
+ }
+ created_devices->EmplaceBack<AutoUnmapDevice>(&dm, *cow_name);
+ LOG(INFO) << "Mapped COW device for " << params.GetPartitionName() << " at " << cow_path;
+ return true;
+}
+
+bool SnapshotManager::UnmapCowDevices(LockedFile* lock, const std::string& name) {
+ CHECK(lock);
+ if (!EnsureImageManager()) return false;
+
+ auto& dm = DeviceMapper::Instance();
+ auto cow_name = GetCowName(name);
+ if (!dm.DeleteDeviceIfExists(cow_name)) {
+ LOG(ERROR) << "Cannot unmap " << cow_name;
+ return false;
+ }
+
+ std::string cow_image_name = GetCowImageDeviceName(name);
+ if (!images_->UnmapImageIfExists(cow_image_name)) {
+ LOG(ERROR) << "Cannot unmap image " << cow_image_name;
+ return false;
+ }
+ 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));
@@ -1577,5 +1713,200 @@
return true;
}
+bool SnapshotManager::CreateUpdateSnapshots(MetadataBuilder* target_metadata,
+ const std::string& target_suffix,
+ MetadataBuilder* current_metadata,
+ const std::string& current_suffix,
+ const std::map<std::string, uint64_t>& cow_sizes) {
+ auto lock = LockExclusive();
+ if (!lock) return false;
+
+ // Add _{target_suffix} to COW size map.
+ std::map<std::string, uint64_t> suffixed_cow_sizes;
+ for (const auto& [name, size] : cow_sizes) {
+ suffixed_cow_sizes[name + target_suffix] = size;
+ }
+
+ target_metadata->RemoveGroupAndPartitions(kCowGroupName);
+ if (!target_metadata->AddGroup(kCowGroupName, 0)) {
+ LOG(ERROR) << "Cannot add group " << kCowGroupName;
+ return false;
+ }
+
+ // TODO(b/134949511): remove this check. Right now, with overlayfs mounted, the scratch
+ // partition takes up a big chunk of space in super, causing COW images to be created on
+ // retrofit Virtual A/B devices.
+ if (device_->IsOverlayfsSetup()) {
+ LOG(ERROR) << "Cannot create update snapshots with overlayfs setup. Run `adb enable-verity`"
+ << ", reboot, then try again.";
+ return false;
+ }
+
+ // Check that all these metadata is not retrofit dynamic partitions. Snapshots on
+ // devices with retrofit dynamic partitions does not make sense.
+ // This ensures that current_metadata->GetFreeRegions() uses the same device
+ // indices as target_metadata (i.e. 0 -> "super").
+ // This is also assumed in MapCowDevices() call below.
+ CHECK(current_metadata->GetBlockDevicePartitionName(0) == LP_METADATA_DEFAULT_PARTITION_NAME &&
+ target_metadata->GetBlockDevicePartitionName(0) == LP_METADATA_DEFAULT_PARTITION_NAME);
+
+ std::map<std::string, SnapshotStatus> all_snapshot_status;
+
+ // In case of error, automatically delete devices that are created along the way.
+ // Note that "lock" is destroyed after "created_devices", so it is safe to use |lock| for
+ // these devices.
+ AutoDeviceList created_devices;
+
+ for (auto* target_partition : ListPartitionsWithSuffix(target_metadata, target_suffix)) {
+ std::optional<uint64_t> cow_size = std::nullopt;
+ auto it = suffixed_cow_sizes.find(target_partition->name());
+ if (it != suffixed_cow_sizes.end()) {
+ cow_size = it->second;
+ LOG(INFO) << "Using provided COW size " << *cow_size << " for partition "
+ << target_partition->name();
+ }
+
+ // Compute the device sizes for the partition.
+ PartitionCowCreator cow_creator{target_metadata, target_suffix, target_partition,
+ current_metadata, current_suffix, cow_size};
+ auto cow_creator_ret = cow_creator.Run();
+ if (!cow_creator_ret.has_value()) {
+ return false;
+ }
+
+ LOG(INFO) << "For partition " << target_partition->name()
+ << ", device size = " << cow_creator_ret->snapshot_status.device_size
+ << ", snapshot size = " << cow_creator_ret->snapshot_status.snapshot_size
+ << ", cow partition size = "
+ << cow_creator_ret->snapshot_status.cow_partition_size
+ << ", cow file size = " << cow_creator_ret->snapshot_status.cow_file_size;
+
+ // Delete any existing snapshot before re-creating one.
+ if (!DeleteSnapshot(lock.get(), target_partition->name())) {
+ LOG(ERROR) << "Cannot delete existing snapshot before creating a new one for partition "
+ << target_partition->name();
+ return false;
+ }
+
+ // It is possible that the whole partition uses free space in super, and snapshot / COW
+ // would not be needed. In this case, skip the partition.
+ bool needs_snapshot = cow_creator_ret->snapshot_status.snapshot_size > 0;
+ bool needs_cow = (cow_creator_ret->snapshot_status.cow_partition_size +
+ cow_creator_ret->snapshot_status.cow_file_size) > 0;
+ CHECK(needs_snapshot == needs_cow);
+
+ if (!needs_snapshot) {
+ LOG(INFO) << "Skip creating snapshot for partition " << target_partition->name()
+ << "because nothing needs to be snapshotted.";
+ continue;
+ }
+
+ // Store these device sizes to snapshot status file.
+ if (!CreateSnapshot(lock.get(), target_partition->name(),
+ cow_creator_ret->snapshot_status)) {
+ return false;
+ }
+ created_devices.EmplaceBack<AutoDeleteSnapshot>(this, lock.get(), target_partition->name());
+
+ // Create the COW partition. That is, use any remaining free space in super partition before
+ // creating the COW images.
+ if (cow_creator_ret->snapshot_status.cow_partition_size > 0) {
+ CHECK(cow_creator_ret->snapshot_status.cow_partition_size % kSectorSize == 0)
+ << "cow_partition_size == "
+ << cow_creator_ret->snapshot_status.cow_partition_size
+ << " is not a multiple of sector size " << kSectorSize;
+ auto cow_partition = target_metadata->AddPartition(GetCowName(target_partition->name()),
+ kCowGroupName, 0 /* flags */);
+ if (cow_partition == nullptr) {
+ return false;
+ }
+
+ if (!target_metadata->ResizePartition(
+ cow_partition, cow_creator_ret->snapshot_status.cow_partition_size,
+ cow_creator_ret->cow_partition_usable_regions)) {
+ LOG(ERROR) << "Cannot create COW partition on metadata with size "
+ << cow_creator_ret->snapshot_status.cow_partition_size;
+ return false;
+ }
+ // Only the in-memory target_metadata is modified; nothing to clean up if there is an
+ // error in the future.
+ }
+
+ // Create the backing COW image if necessary.
+ if (cow_creator_ret->snapshot_status.cow_file_size > 0) {
+ if (!CreateCowImage(lock.get(), target_partition->name())) {
+ return false;
+ }
+ }
+
+ all_snapshot_status[target_partition->name()] = std::move(cow_creator_ret->snapshot_status);
+
+ LOG(INFO) << "Successfully created snapshot for " << target_partition->name();
+ }
+
+ auto& dm = DeviceMapper::Instance();
+ auto exported_target_metadata = target_metadata->Export();
+ CreateLogicalPartitionParams cow_params{
+ .block_device = LP_METADATA_DEFAULT_PARTITION_NAME,
+ .metadata = exported_target_metadata.get(),
+ .timeout_ms = std::chrono::milliseconds::max(),
+ .partition_opener = &device_->GetPartitionOpener(),
+ };
+ for (auto* target_partition : ListPartitionsWithSuffix(target_metadata, target_suffix)) {
+ AutoDeviceList created_devices_for_cow;
+
+ if (!UnmapPartitionWithSnapshot(lock.get(), target_partition->name())) {
+ LOG(ERROR) << "Cannot unmap existing COW devices before re-mapping them for zero-fill: "
+ << target_partition->name();
+ return false;
+ }
+
+ auto it = all_snapshot_status.find(target_partition->name());
+ CHECK(it != all_snapshot_status.end()) << target_partition->name();
+ cow_params.partition_name = target_partition->name();
+ std::string cow_name;
+ if (!MapCowDevices(lock.get(), cow_params, it->second, &created_devices_for_cow,
+ &cow_name)) {
+ return false;
+ }
+
+ std::string cow_path;
+ if (!dm.GetDmDevicePathByName(cow_name, &cow_path)) {
+ LOG(ERROR) << "Cannot determine path for " << cow_name;
+ return false;
+ }
+
+ if (!InitializeCow(cow_path)) {
+ LOG(ERROR) << "Can't zero-fill COW device for " << target_partition->name() << ": "
+ << cow_path;
+ return false;
+ }
+ // Let destructor of created_devices_for_cow to unmap the COW devices.
+ };
+
+ created_devices.Release();
+ LOG(INFO) << "Successfully created all snapshots for target slot " << target_suffix;
+
+ return true;
+}
+
+bool SnapshotManager::MapUpdateSnapshot(const CreateLogicalPartitionParams& params,
+ std::string* snapshot_path) {
+ auto lock = LockShared();
+ if (!lock) return false;
+ if (!UnmapPartitionWithSnapshot(lock.get(), params.GetPartitionName())) {
+ LOG(ERROR) << "Cannot unmap existing snapshot before re-mapping it: "
+ << params.GetPartitionName();
+ return false;
+ }
+ return MapPartitionWithSnapshot(lock.get(), params, snapshot_path);
+}
+
+bool SnapshotManager::UnmapUpdateSnapshot(const std::string& target_partition_name) {
+ auto lock = LockShared();
+ if (!lock) return false;
+ return UnmapPartitionWithSnapshot(lock.get(), target_partition_name);
+}
+
} // namespace snapshot
} // namespace android
diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp
index 429fd8e..e9835d0 100644
--- a/fs_mgr/libsnapshot/snapshot_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_test.cpp
@@ -32,8 +32,10 @@
#include <libfiemap/image_manager.h>
#include <liblp/builder.h>
#include <liblp/mock_property_fetcher.h>
+#include <storage_literals/storage_literals.h>
#include "test_helpers.h"
+#include "utility.h"
namespace android {
namespace snapshot {
@@ -45,10 +47,12 @@
using android::fs_mgr::BlockDeviceInfo;
using android::fs_mgr::CreateLogicalPartitionParams;
using android::fs_mgr::DestroyLogicalPartition;
+using android::fs_mgr::GetPartitionGroupName;
using android::fs_mgr::GetPartitionName;
using android::fs_mgr::MetadataBuilder;
using namespace ::testing;
using namespace android::fs_mgr::testing;
+using namespace android::storage_literals;
using namespace std::chrono_literals;
using namespace std::string_literals;
@@ -58,7 +62,7 @@
TestDeviceInfo* test_device = nullptr;
std::string fake_super;
-static constexpr uint64_t kSuperSize = 16 * 1024 * 1024;
+static constexpr uint64_t kSuperSize = 16_MiB;
class SnapshotTest : public ::testing::Test {
public:
@@ -105,7 +109,7 @@
std::vector<std::string> snapshots = {"test-snapshot", "test_partition_a",
"test_partition_b"};
for (const auto& snapshot : snapshots) {
- DeleteSnapshotDevice(snapshot);
+ ASSERT_TRUE(DeleteSnapshotDevice(snapshot));
DeleteBackingImage(image_manager_, snapshot + "-cow-img");
auto status_file = sm->GetSnapshotStatusFilePath(snapshot);
@@ -211,15 +215,52 @@
return true;
}
- void DeleteSnapshotDevice(const std::string& snapshot) {
- DeleteDevice(snapshot);
- DeleteDevice(snapshot + "-inner");
- ASSERT_TRUE(image_manager_->UnmapImageIfExists(snapshot + "-cow-img"));
- }
- void DeleteDevice(const std::string& device) {
- if (dm_.GetState(device) != DmDeviceState::INVALID) {
- ASSERT_TRUE(dm_.DeleteDevice(device));
+ AssertionResult DeleteSnapshotDevice(const std::string& snapshot) {
+ AssertionResult res = AssertionSuccess();
+ if (!(res = DeleteDevice(snapshot))) return res;
+ if (!(res = DeleteDevice(snapshot + "-inner"))) return res;
+ if (!(res = DeleteDevice(snapshot + "-cow"))) return res;
+ if (!image_manager_->UnmapImageIfExists(snapshot + "-cow-img")) {
+ return AssertionFailure() << "Cannot unmap image " << snapshot << "-cow-img";
}
+ if (!(res = DeleteDevice(snapshot + "-base"))) return res;
+ return AssertionSuccess();
+ }
+
+ AssertionResult DeleteDevice(const std::string& device) {
+ if (!dm_.DeleteDeviceIfExists(device)) {
+ return AssertionFailure() << "Can't delete " << device;
+ }
+ return AssertionSuccess();
+ }
+
+ AssertionResult CreateCowImage(const std::string& name) {
+ if (!sm->CreateCowImage(lock_.get(), name)) {
+ return AssertionFailure() << "Cannot create COW image " << name;
+ }
+ std::string cow_device;
+ auto map_res = MapCowImage(name, 10s, &cow_device);
+ if (!map_res) {
+ return map_res;
+ }
+ if (!InitializeCow(cow_device)) {
+ return AssertionFailure() << "Cannot zero fill " << cow_device;
+ }
+ if (!sm->UnmapCowImage(name)) {
+ return AssertionFailure() << "Cannot unmap " << name << " after zero filling it";
+ }
+ return AssertionSuccess();
+ }
+
+ AssertionResult MapCowImage(const std::string& name,
+ const std::chrono::milliseconds& timeout_ms, std::string* path) {
+ if (!sm->MapCowImage(name, timeout_ms)) {
+ return AssertionFailure() << "Cannot map cow image " << name;
+ }
+ if (!dm_.GetDmDevicePathByName(name + "-cow-img"s, path)) {
+ return AssertionFailure() << "No path for " << name << "-cow-img";
+ }
+ return AssertionSuccess();
}
DeviceMapper& dm_;
@@ -236,7 +277,7 @@
{.device_size = kDeviceSize,
.snapshot_size = kDeviceSize,
.cow_file_size = kDeviceSize}));
- ASSERT_TRUE(sm->CreateCowImage(lock_.get(), "test-snapshot"));
+ ASSERT_TRUE(CreateCowImage("test-snapshot"));
std::vector<std::string> snapshots;
ASSERT_TRUE(sm->ListSnapshots(lock_.get(), &snapshots));
@@ -265,13 +306,13 @@
{.device_size = kDeviceSize,
.snapshot_size = kDeviceSize,
.cow_file_size = kDeviceSize}));
- ASSERT_TRUE(sm->CreateCowImage(lock_.get(), "test-snapshot"));
+ ASSERT_TRUE(CreateCowImage("test-snapshot"));
std::string base_device;
ASSERT_TRUE(CreatePartition("base-device", kDeviceSize, &base_device));
std::string cow_device;
- ASSERT_TRUE(sm->MapCowImage("test-snapshot", 10s, &cow_device));
+ ASSERT_TRUE(MapCowImage("test-snapshot", 10s, &cow_device));
std::string snap_device;
ASSERT_TRUE(sm->MapSnapshot(lock_.get(), "test-snapshot", base_device, cow_device, 10s,
@@ -288,13 +329,13 @@
{.device_size = kDeviceSize,
.snapshot_size = kSnapshotSize,
.cow_file_size = kSnapshotSize}));
- ASSERT_TRUE(sm->CreateCowImage(lock_.get(), "test-snapshot"));
+ ASSERT_TRUE(CreateCowImage("test-snapshot"));
std::string base_device;
ASSERT_TRUE(CreatePartition("base-device", kDeviceSize, &base_device));
std::string cow_device;
- ASSERT_TRUE(sm->MapCowImage("test-snapshot", 10s, &cow_device));
+ ASSERT_TRUE(MapCowImage("test-snapshot", 10s, &cow_device));
std::string snap_device;
ASSERT_TRUE(sm->MapSnapshot(lock_.get(), "test-snapshot", base_device, cow_device, 10s,
@@ -344,8 +385,8 @@
{.device_size = kDeviceSize,
.snapshot_size = kDeviceSize,
.cow_file_size = kDeviceSize}));
- ASSERT_TRUE(sm->CreateCowImage(lock_.get(), "test_partition_b"));
- ASSERT_TRUE(sm->MapCowImage("test_partition_b", 10s, &cow_device));
+ ASSERT_TRUE(CreateCowImage("test_partition_b"));
+ ASSERT_TRUE(MapCowImage("test_partition_b", 10s, &cow_device));
ASSERT_TRUE(sm->MapSnapshot(lock_.get(), "test_partition_b", base_device, cow_device, 10s,
&snap_device));
@@ -403,11 +444,11 @@
{.device_size = kDeviceSize,
.snapshot_size = kDeviceSize,
.cow_file_size = kDeviceSize}));
- ASSERT_TRUE(sm->CreateCowImage(lock_.get(), "test-snapshot"));
+ ASSERT_TRUE(CreateCowImage("test-snapshot"));
std::string base_device, cow_device, snap_device;
ASSERT_TRUE(CreatePartition("base-device", kDeviceSize, &base_device));
- ASSERT_TRUE(sm->MapCowImage("test-snapshot", 10s, &cow_device));
+ ASSERT_TRUE(MapCowImage("test-snapshot", 10s, &cow_device));
ASSERT_TRUE(sm->MapSnapshot(lock_.get(), "test-snapshot", base_device, cow_device, 10s,
&snap_device));
@@ -433,11 +474,11 @@
// Wait 1s, otherwise DeleteSnapshotDevice may fail with EBUSY.
sleep(1);
// Forcefully delete the snapshot device, so it looks like we just rebooted.
- DeleteSnapshotDevice("test-snapshot");
+ ASSERT_TRUE(DeleteSnapshotDevice("test-snapshot"));
// Map snapshot should fail now, because we're in a merge-complete state.
ASSERT_TRUE(AcquireLock());
- ASSERT_TRUE(sm->MapCowImage("test-snapshot", 10s, &cow_device));
+ ASSERT_TRUE(MapCowImage("test-snapshot", 10s, &cow_device));
ASSERT_FALSE(sm->MapSnapshot(lock_.get(), "test-snapshot", base_device, cow_device, 10s,
&snap_device));
@@ -462,7 +503,7 @@
{.device_size = kDeviceSize,
.snapshot_size = kDeviceSize,
.cow_file_size = kDeviceSize}));
- ASSERT_TRUE(sm->CreateCowImage(lock_.get(), "test_partition_b"));
+ ASSERT_TRUE(CreateCowImage("test_partition_b"));
// Simulate a reboot into the new slot.
lock_ = nullptr;
@@ -504,7 +545,7 @@
{.device_size = kDeviceSize,
.snapshot_size = kDeviceSize,
.cow_file_size = kDeviceSize}));
- ASSERT_TRUE(sm->CreateCowImage(lock_.get(), "test_partition_b"));
+ ASSERT_TRUE(CreateCowImage("test_partition_b"));
// Simulate a reboot into the new slot.
lock_ = nullptr;
@@ -552,7 +593,7 @@
{.device_size = kDeviceSize,
.snapshot_size = kDeviceSize,
.cow_file_size = kDeviceSize}));
- ASSERT_TRUE(sm->CreateCowImage(lock_.get(), "test_partition_b"));
+ ASSERT_TRUE(CreateCowImage("test_partition_b"));
// Simulate a reboot into the new slot.
lock_ = nullptr;
@@ -570,7 +611,7 @@
// Now, reflash super. Note that we haven't called ProcessUpdateState, so the
// status is still Merging.
- DeleteSnapshotDevice("test_partition_b");
+ ASSERT_TRUE(DeleteSnapshotDevice("test_partition_b"));
ASSERT_TRUE(init->image_manager()->UnmapImageIfExists("test_partition_b-cow-img"));
FormatFakeSuper();
ASSERT_TRUE(CreatePartition("test_partition_b", kDeviceSize));
@@ -583,6 +624,419 @@
ASSERT_EQ(sm->GetUpdateState(), UpdateState::None);
}
+class SnapshotUpdateTest : public SnapshotTest {
+ public:
+ void SetUp() override {
+ SnapshotTest::SetUp();
+ Cleanup();
+
+ // Cleanup() changes slot suffix, so initialize it again.
+ test_device->set_slot_suffix("_a");
+
+ ON_CALL(*GetMockedPropertyFetcher(), GetBoolProperty("ro.virtual_ab.enabled", _))
+ .WillByDefault(Return(true));
+
+ opener = std::make_unique<TestPartitionOpener>(fake_super);
+
+ // Initialize source partition metadata. sys_b is similar to system_other.
+ // Not using full name "system", "vendor", "product" because these names collide with the
+ // mapped partitions on the running device.
+ src = MetadataBuilder::New(*opener, "super", 0);
+ ASSERT_NE(nullptr, src);
+ auto partition = src->AddPartition("sys_a", 0);
+ ASSERT_NE(nullptr, partition);
+ ASSERT_TRUE(src->ResizePartition(partition, 3_MiB));
+ partition = src->AddPartition("vnd_a", 0);
+ ASSERT_NE(nullptr, partition);
+ ASSERT_TRUE(src->ResizePartition(partition, 3_MiB));
+ partition = src->AddPartition("prd_a", 0);
+ ASSERT_NE(nullptr, partition);
+ ASSERT_TRUE(src->ResizePartition(partition, 3_MiB));
+ partition = src->AddPartition("sys_b", 0);
+ ASSERT_NE(nullptr, partition);
+ ASSERT_TRUE(src->ResizePartition(partition, 1_MiB));
+ auto metadata = src->Export();
+ ASSERT_NE(nullptr, metadata);
+ ASSERT_TRUE(UpdatePartitionTable(*opener, "super", *metadata.get(), 0));
+
+ // Map source partitions. Additionally, map sys_b to simulate system_other after flashing.
+ std::string path;
+ for (const auto& name : {"sys_a", "vnd_a", "prd_a", "sys_b"}) {
+ ASSERT_TRUE(CreateLogicalPartition(
+ CreateLogicalPartitionParams{
+ .block_device = fake_super,
+ .metadata_slot = 0,
+ .partition_name = name,
+ .timeout_ms = 1s,
+ .partition_opener = opener.get(),
+ },
+ &path));
+ ASSERT_TRUE(WriteRandomData(path));
+ auto hash = GetHash(path);
+ ASSERT_TRUE(hash.has_value());
+ hashes_[name] = *hash;
+ }
+ }
+ void TearDown() override {
+ Cleanup();
+ SnapshotTest::TearDown();
+ }
+ void Cleanup() {
+ if (!image_manager_) {
+ InitializeState();
+ }
+ for (const auto& suffix : {"_a", "_b"}) {
+ test_device->set_slot_suffix(suffix);
+ EXPECT_TRUE(sm->CancelUpdate()) << suffix;
+ }
+ EXPECT_TRUE(UnmapAll());
+ }
+
+ static AssertionResult ResizePartitions(
+ MetadataBuilder* builder,
+ const std::vector<std::pair<std::string, uint64_t>>& partition_sizes) {
+ for (auto&& [name, size] : partition_sizes) {
+ auto partition = builder->FindPartition(name);
+ if (!partition) {
+ return AssertionFailure() << "Cannot find partition in metadata " << name;
+ }
+ if (!builder->ResizePartition(partition, size)) {
+ return AssertionFailure()
+ << "Cannot resize partition " << name << " to " << size << " bytes";
+ }
+ }
+ return AssertionSuccess();
+ }
+
+ AssertionResult IsPartitionUnchanged(const std::string& name) {
+ std::string path;
+ if (!dm_.GetDmDevicePathByName(name, &path)) {
+ return AssertionFailure() << "Path of " << name << " cannot be determined";
+ }
+ auto hash = GetHash(path);
+ if (!hash.has_value()) {
+ return AssertionFailure() << "Cannot read partition " << name << ": " << path;
+ }
+ if (hashes_[name] != *hash) {
+ return AssertionFailure() << "Content of " << name << " has changed after the merge";
+ }
+ return AssertionSuccess();
+ }
+
+ std::optional<uint64_t> GetSnapshotSize(const std::string& name) {
+ if (!AcquireLock()) {
+ return std::nullopt;
+ }
+ auto local_lock = std::move(lock_);
+
+ SnapshotManager::SnapshotStatus status;
+ if (!sm->ReadSnapshotStatus(local_lock.get(), name, &status)) {
+ return std::nullopt;
+ }
+ return status.snapshot_size;
+ }
+
+ AssertionResult UnmapAll() {
+ for (const auto& name : {"sys", "vnd", "prd"}) {
+ if (!dm_.DeleteDeviceIfExists(name + "_a"s)) {
+ return AssertionFailure() << "Cannot unmap " << name << "_a";
+ }
+ if (!DeleteSnapshotDevice(name + "_b"s)) {
+ return AssertionFailure() << "Cannot delete snapshot " << name << "_b";
+ }
+ }
+ return AssertionSuccess();
+ }
+
+ std::unique_ptr<TestPartitionOpener> opener;
+ std::unique_ptr<MetadataBuilder> src;
+ std::map<std::string, std::string> hashes_;
+};
+
+// Test full update flow executed by update_engine. Some partitions uses super empty space,
+// some uses images, and some uses both.
+// Also test UnmapUpdateSnapshot unmaps everything.
+// Also test first stage mount and merge after this.
+TEST_F(SnapshotUpdateTest, FullUpdateFlow) {
+ // OTA client calls CancelUpdate then BeginUpdate before doing anything.
+ ASSERT_TRUE(sm->CancelUpdate());
+ ASSERT_TRUE(sm->BeginUpdate());
+
+ // OTA client blindly unmaps all partitions that are possibly mapped.
+ for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
+ ASSERT_TRUE(sm->UnmapUpdateSnapshot(name));
+ }
+
+ // OTA client adjusts the partition sizes before giving it to CreateUpdateSnapshots.
+ auto tgt = MetadataBuilder::NewForUpdate(*opener, "super", 0, 1);
+ ASSERT_NE(nullptr, tgt);
+ // clang-format off
+ ASSERT_TRUE(ResizePartitions(tgt.get(), {
+ {"sys_b", 4_MiB}, // grows
+ {"vnd_b", 4_MiB}, // grows
+ {"prd_b", 4_MiB}, // grows
+ }));
+ // clang-format on
+
+ ASSERT_TRUE(sm->CreateUpdateSnapshots(tgt.get(), "_b", src.get(), "_a", {}));
+
+ // Test that partitions prioritize using space in super.
+ ASSERT_NE(nullptr, tgt->FindPartition("sys_b-cow"));
+ ASSERT_NE(nullptr, tgt->FindPartition("vnd_b-cow"));
+ ASSERT_EQ(nullptr, tgt->FindPartition("prd_b-cow"));
+
+ // The metadata slot 1 is now updated.
+ auto metadata = tgt->Export();
+ ASSERT_NE(nullptr, metadata);
+ ASSERT_TRUE(UpdatePartitionTable(*opener, "super", *metadata.get(), 1));
+
+ // Write some data to target partitions.
+ for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
+ std::string path;
+ ASSERT_TRUE(sm->MapUpdateSnapshot(
+ CreateLogicalPartitionParams{
+ .block_device = fake_super,
+ .metadata_slot = 1,
+ .partition_name = name,
+ .timeout_ms = 10s,
+ .partition_opener = opener.get(),
+ },
+ &path))
+ << name;
+ ASSERT_TRUE(WriteRandomData(path));
+ auto hash = GetHash(path);
+ ASSERT_TRUE(hash.has_value());
+ hashes_[name] = *hash;
+ }
+
+ // Assert that source partitions aren't affected.
+ for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) {
+ ASSERT_TRUE(IsPartitionUnchanged(name));
+ }
+
+ ASSERT_TRUE(sm->FinishedSnapshotWrites());
+
+ // Simulate shutting down the device.
+ ASSERT_TRUE(UnmapAll());
+
+ // After reboot, init does first stage mount.
+ auto rebooted = new TestDeviceInfo(fake_super);
+ rebooted->set_slot_suffix("_b");
+ auto init = SnapshotManager::NewForFirstStageMount(rebooted);
+ ASSERT_NE(init, nullptr);
+ ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount());
+ ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super"));
+
+ // Check that the target partitions have the same content.
+ for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
+ ASSERT_TRUE(IsPartitionUnchanged(name));
+ }
+
+ // Initiate the merge and wait for it to be completed.
+ ASSERT_TRUE(init->InitiateMerge());
+ ASSERT_EQ(UpdateState::MergeCompleted, init->ProcessUpdateState());
+
+ // Check that the target partitions have the same content after the merge.
+ for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
+ ASSERT_TRUE(IsPartitionUnchanged(name))
+ << "Content of " << name << " changes after the merge";
+ }
+}
+
+// Test that if new system partitions uses empty space in super, that region is not snapshotted.
+TEST_F(SnapshotUpdateTest, DirectWriteEmptySpace) {
+ auto tgt = MetadataBuilder::NewForUpdate(*opener, "super", 0, 1);
+ ASSERT_NE(nullptr, tgt);
+ // clang-format off
+ ASSERT_TRUE(ResizePartitions(tgt.get(), {
+ {"sys_b", 4_MiB}, // grows
+ // vnd_b and prd_b are unchanged
+ }));
+ // clang-format on
+ ASSERT_TRUE(sm->CreateUpdateSnapshots(tgt.get(), "_b", src.get(), "_a", {}));
+
+ ASSERT_EQ(3_MiB, GetSnapshotSize("sys_b").value_or(0));
+}
+
+// Test that if new system partitions uses space of old vendor partition, that region is
+// snapshotted.
+TEST_F(SnapshotUpdateTest, SnapshotOldPartitions) {
+ auto tgt = MetadataBuilder::NewForUpdate(*opener, "super", 0, 1);
+ ASSERT_NE(nullptr, tgt);
+ // clang-format off
+ ASSERT_TRUE(ResizePartitions(tgt.get(), {
+ {"vnd_b", 2_MiB}, // shrinks
+ {"sys_b", 4_MiB}, // grows
+ // prd_b is unchanged
+ }));
+ // clang-format on
+ ASSERT_TRUE(sm->CreateUpdateSnapshots(tgt.get(), "_b", src.get(), "_a", {}));
+
+ ASSERT_EQ(4_MiB, GetSnapshotSize("sys_b").value_or(0));
+}
+
+// Test that even if there seem to be empty space in target metadata, COW partition won't take
+// it because they are used by old partitions.
+TEST_F(SnapshotUpdateTest, CowPartitionDoNotTakeOldPartitions) {
+ auto tgt = MetadataBuilder::NewForUpdate(*opener, "super", 0, 1);
+ ASSERT_NE(nullptr, tgt);
+ // clang-format off
+ ASSERT_TRUE(ResizePartitions(tgt.get(), {
+ {"sys_b", 2_MiB}, // shrinks
+ // vnd_b and prd_b are unchanged
+ }));
+ // clang-format on
+ ASSERT_TRUE(sm->CreateUpdateSnapshots(tgt.get(), "_b", src.get(), "_a", {}));
+
+ auto metadata = tgt->Export();
+ ASSERT_NE(nullptr, metadata);
+ std::vector<std::string> written;
+ // Write random data to all COW partitions in super
+ for (auto p : metadata->partitions) {
+ if (GetPartitionGroupName(metadata->groups[p.group_index]) != kCowGroupName) {
+ continue;
+ }
+ std::string path;
+ ASSERT_TRUE(CreateLogicalPartition(
+ CreateLogicalPartitionParams{
+ .block_device = fake_super,
+ .metadata = metadata.get(),
+ .partition = &p,
+ .timeout_ms = 1s,
+ .partition_opener = opener.get(),
+ },
+ &path));
+ ASSERT_TRUE(WriteRandomData(path));
+ written.push_back(GetPartitionName(p));
+ }
+ ASSERT_FALSE(written.empty())
+ << "No COW partitions are created even if there are empty space in super partition";
+
+ // Make sure source partitions aren't affected.
+ for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) {
+ ASSERT_TRUE(IsPartitionUnchanged(name));
+ }
+}
+
+// Test that it crashes after creating snapshot status file but before creating COW image, then
+// calling CreateUpdateSnapshots again works.
+TEST_F(SnapshotUpdateTest, SnapshotStatusFileWithoutCow) {
+ // Write some trash snapshot files to simulate leftovers from previous runs.
+ {
+ ASSERT_TRUE(AcquireLock());
+ auto local_lock = std::move(lock_);
+ ASSERT_TRUE(sm->WriteSnapshotStatus(local_lock.get(), "sys_b",
+ SnapshotManager::SnapshotStatus{}));
+ ASSERT_TRUE(image_manager_->CreateBackingImage("sys_b-cow-img", 1_MiB,
+ IImageManager::CREATE_IMAGE_DEFAULT));
+ }
+
+ // Redo the update.
+ ASSERT_TRUE(sm->BeginUpdate());
+ ASSERT_TRUE(sm->UnmapUpdateSnapshot("sys_b"));
+ auto tgt = MetadataBuilder::NewForUpdate(*opener, "super", 0, 1);
+ ASSERT_NE(nullptr, tgt);
+ ASSERT_TRUE(sm->CreateUpdateSnapshots(tgt.get(), "_b", src.get(), "_a", {}));
+
+ // The metadata slot 1 is now updated.
+ auto metadata = tgt->Export();
+ ASSERT_NE(nullptr, metadata);
+ ASSERT_TRUE(UpdatePartitionTable(*opener, "super", *metadata.get(), 1));
+
+ // Check that target partitions can be mapped.
+ for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
+ std::string path;
+ EXPECT_TRUE(sm->MapUpdateSnapshot(
+ CreateLogicalPartitionParams{
+ .block_device = fake_super,
+ .metadata_slot = 1,
+ .partition_name = name,
+ .timeout_ms = 10s,
+ .partition_opener = opener.get(),
+ },
+ &path))
+ << name;
+ }
+}
+
+// Test that the old partitions are not modified.
+TEST_F(SnapshotUpdateTest, TestRollback) {
+ // Execute the update.
+ ASSERT_TRUE(sm->BeginUpdate());
+ ASSERT_TRUE(sm->UnmapUpdateSnapshot("sys_b"));
+ auto tgt = MetadataBuilder::NewForUpdate(*opener, "super", 0, 1);
+ ASSERT_NE(nullptr, tgt);
+ ASSERT_TRUE(sm->CreateUpdateSnapshots(tgt.get(), "_b", src.get(), "_a", {}));
+
+ // The metadata slot 1 is now updated.
+ auto metadata = tgt->Export();
+ ASSERT_NE(nullptr, metadata);
+ ASSERT_TRUE(UpdatePartitionTable(*opener, "super", *metadata.get(), 1));
+
+ // Write some data to target partitions.
+ for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
+ std::string path;
+ ASSERT_TRUE(sm->MapUpdateSnapshot(
+ CreateLogicalPartitionParams{
+ .block_device = fake_super,
+ .metadata_slot = 1,
+ .partition_name = name,
+ .timeout_ms = 10s,
+ .partition_opener = opener.get(),
+ },
+ &path))
+ << name;
+ ASSERT_TRUE(WriteRandomData(path));
+ auto hash = GetHash(path);
+ ASSERT_TRUE(hash.has_value());
+ hashes_[name] = *hash;
+ }
+
+ // Assert that source partitions aren't affected.
+ for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) {
+ ASSERT_TRUE(IsPartitionUnchanged(name));
+ }
+
+ ASSERT_TRUE(sm->FinishedSnapshotWrites());
+
+ // Simulate shutting down the device.
+ ASSERT_TRUE(UnmapAll());
+
+ // After reboot, init does first stage mount.
+ auto rebooted = new TestDeviceInfo(fake_super);
+ rebooted->set_slot_suffix("_b");
+ auto init = SnapshotManager::NewForFirstStageMount(rebooted);
+ ASSERT_NE(init, nullptr);
+ ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount());
+ ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super"));
+
+ // Check that the target partitions have the same content.
+ for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
+ ASSERT_TRUE(IsPartitionUnchanged(name));
+ }
+
+ // Simulate shutting down the device again.
+ ASSERT_TRUE(UnmapAll());
+ rebooted = new TestDeviceInfo(fake_super);
+ rebooted->set_slot_suffix("_a");
+ init = SnapshotManager::NewForFirstStageMount(rebooted);
+ ASSERT_NE(init, nullptr);
+ ASSERT_FALSE(init->NeedSnapshotsInFirstStageMount());
+ ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super"));
+
+ // Assert that the source partitions aren't affected.
+ for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) {
+ ASSERT_TRUE(IsPartitionUnchanged(name));
+ }
+}
+
+// Test that if an update is applied but not booted into, it can be canceled.
+TEST_F(SnapshotUpdateTest, CancelAfterApply) {
+ ASSERT_TRUE(sm->BeginUpdate());
+ ASSERT_TRUE(sm->FinishedSnapshotWrites());
+ ASSERT_TRUE(sm->CancelUpdate());
+}
+
} // namespace snapshot
} // namespace android
@@ -624,6 +1078,7 @@
}
// Clean up previous run.
+ SnapshotUpdateTest().Cleanup();
SnapshotTest().Cleanup();
// Use a separate image manager for our fake super partition.
diff --git a/fs_mgr/libsnapshot/test_helpers.cpp b/fs_mgr/libsnapshot/test_helpers.cpp
index f67dd21..e12ec77 100644
--- a/fs_mgr/libsnapshot/test_helpers.cpp
+++ b/fs_mgr/libsnapshot/test_helpers.cpp
@@ -14,11 +14,18 @@
#include "test_helpers.h"
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/unique_fd.h>
#include <gtest/gtest.h>
+#include <openssl/sha.h>
namespace android {
namespace snapshot {
+using android::base::ReadFully;
+using android::base::unique_fd;
+using android::base::WriteFully;
using android::fiemap::IImageManager;
void DeleteBackingImage(IImageManager* manager, const std::string& name) {
@@ -53,5 +60,55 @@
return PartitionOpener::GetDeviceString(partition_name);
}
+bool WriteRandomData(const std::string& path) {
+ unique_fd rand(open("/dev/urandom", O_RDONLY));
+ unique_fd fd(open(path.c_str(), O_WRONLY));
+
+ char buf[4096];
+ while (true) {
+ ssize_t n = TEMP_FAILURE_RETRY(read(rand.get(), buf, sizeof(buf)));
+ if (n <= 0) return false;
+ if (!WriteFully(fd.get(), buf, n)) {
+ if (errno == ENOSPC) {
+ return true;
+ }
+ PLOG(ERROR) << "Cannot write " << path;
+ return false;
+ }
+ }
+}
+
+std::string ToHexString(const uint8_t* buf, size_t len) {
+ char lookup[] = "0123456789abcdef";
+ std::string out(len * 2 + 1, '\0');
+ char* outp = out.data();
+ for (; len > 0; len--, buf++) {
+ *outp++ = (char)lookup[*buf >> 4];
+ *outp++ = (char)lookup[*buf & 0xf];
+ }
+ return out;
+}
+
+std::optional<std::string> GetHash(const std::string& path) {
+ unique_fd fd(open(path.c_str(), O_RDONLY));
+ char buf[4096];
+ SHA256_CTX ctx;
+ SHA256_Init(&ctx);
+ while (true) {
+ ssize_t n = TEMP_FAILURE_RETRY(read(fd.get(), buf, sizeof(buf)));
+ if (n < 0) {
+ PLOG(ERROR) << "Cannot read " << path;
+ return std::nullopt;
+ }
+ if (n == 0) {
+ break;
+ }
+ SHA256_Update(&ctx, buf, n);
+ }
+ uint8_t out[32];
+ SHA256_Final(out, &ctx);
+ return ToHexString(out, sizeof(out));
+}
+
} // namespace snapshot
} // namespace android
diff --git a/fs_mgr/libsnapshot/test_helpers.h b/fs_mgr/libsnapshot/test_helpers.h
index 9f582d9..d917e35 100644
--- a/fs_mgr/libsnapshot/test_helpers.h
+++ b/fs_mgr/libsnapshot/test_helpers.h
@@ -14,6 +14,7 @@
#pragma once
+#include <optional>
#include <string>
#include <libfiemap/image_manager.h>
@@ -51,6 +52,7 @@
const android::fs_mgr::IPartitionOpener& GetPartitionOpener() const override {
return *opener_.get();
}
+ bool IsOverlayfsSetup() const override { return false; }
void set_slot_suffix(const std::string& suffix) { slot_suffix_ = suffix; }
void set_fake_super(const std::string& path) {
@@ -65,5 +67,10 @@
// Helper for error-spam-free cleanup.
void DeleteBackingImage(android::fiemap::IImageManager* manager, const std::string& name);
+// Write some random data to the given device. Will write until reaching end of the device.
+bool WriteRandomData(const std::string& device);
+
+std::optional<std::string> GetHash(const std::string& path);
+
} // namespace snapshot
} // namespace android
diff --git a/fs_mgr/libsnapshot/utility.cpp b/fs_mgr/libsnapshot/utility.cpp
index 164b472..66629e8 100644
--- a/fs_mgr/libsnapshot/utility.cpp
+++ b/fs_mgr/libsnapshot/utility.cpp
@@ -14,9 +14,13 @@
#include "utility.h"
+#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/strings.h>
+using android::fs_mgr::MetadataBuilder;
+using android::fs_mgr::Partition;
+
namespace android {
namespace snapshot {
@@ -52,5 +56,58 @@
}
}
+std::vector<Partition*> ListPartitionsWithSuffix(MetadataBuilder* builder,
+ const std::string& suffix) {
+ std::vector<Partition*> ret;
+ for (const auto& group : builder->ListGroups()) {
+ for (auto* partition : builder->ListPartitionsInGroup(group)) {
+ if (!base::EndsWith(partition->name(), suffix)) {
+ continue;
+ }
+ ret.push_back(partition);
+ }
+ }
+ return ret;
+}
+
+AutoDeleteSnapshot::~AutoDeleteSnapshot() {
+ if (!name_.empty() && !manager_->DeleteSnapshot(lock_, name_)) {
+ LOG(ERROR) << "Failed to auto delete snapshot " << name_;
+ }
+}
+
+bool InitializeCow(const std::string& device) {
+ // When the kernel creates a persistent dm-snapshot, it requires a CoW file
+ // to store the modifications. The kernel interface does not specify how
+ // the CoW is used, and there is no standard associated.
+ // By looking at the current implementation, the CoW file is treated as:
+ // - a _NEW_ snapshot if its first 32 bits are zero, so the newly created
+ // dm-snapshot device will look like a perfect copy of the origin device;
+ // - an _EXISTING_ snapshot if the first 32 bits are equal to a
+ // kernel-specified magic number and the CoW file metadata is set as valid,
+ // so it can be used to resume the last state of a snapshot device;
+ // - an _INVALID_ snapshot otherwise.
+ // To avoid zero-filling the whole CoW file when a new dm-snapshot is
+ // created, here we zero-fill only the first 32 bits. This is a temporary
+ // workaround that will be discussed again when the kernel API gets
+ // consolidated.
+ // TODO(b/139202197): Remove this hack once the kernel API is consolidated.
+ constexpr ssize_t kDmSnapZeroFillSize = 4; // 32-bit
+
+ char zeros[kDmSnapZeroFillSize] = {0};
+ android::base::unique_fd fd(open(device.c_str(), O_WRONLY | O_BINARY));
+ if (fd < 0) {
+ PLOG(ERROR) << "Can't open COW device: " << device;
+ return false;
+ }
+
+ LOG(INFO) << "Zero-filling COW device: " << device;
+ if (!android::base::WriteFully(fd, zeros, kDmSnapZeroFillSize)) {
+ PLOG(ERROR) << "Can't zero-fill COW device for " << device;
+ return false;
+ }
+ return true;
+}
+
} // namespace snapshot
} // namespace android
diff --git a/fs_mgr/libsnapshot/utility.h b/fs_mgr/libsnapshot/utility.h
index cbab472..a700c46 100644
--- a/fs_mgr/libsnapshot/utility.h
+++ b/fs_mgr/libsnapshot/utility.h
@@ -14,11 +14,14 @@
#pragma once
+#include <functional>
#include <string>
#include <android-base/macros.h>
#include <libdm/dm.h>
#include <libfiemap/image_manager.h>
+#include <liblp/builder.h>
+#include <libsnapshot/snapshot.h>
namespace android {
namespace snapshot {
@@ -81,5 +84,27 @@
android::fiemap::IImageManager* images_ = nullptr;
};
+// Automatically deletes a snapshot. |name| should be the name of the partition, e.g. "system_a".
+// Client is responsible for maintaining the lifetime of |manager| and |lock|.
+struct AutoDeleteSnapshot : AutoDevice {
+ AutoDeleteSnapshot(SnapshotManager* manager, SnapshotManager::LockedFile* lock,
+ const std::string& name)
+ : AutoDevice(name), manager_(manager), lock_(lock) {}
+ AutoDeleteSnapshot(AutoDeleteSnapshot&& other);
+ ~AutoDeleteSnapshot();
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(AutoDeleteSnapshot);
+ SnapshotManager* manager_ = nullptr;
+ SnapshotManager::LockedFile* lock_ = nullptr;
+};
+
+// Return a list of partitions in |builder| with the name ending in |suffix|.
+std::vector<android::fs_mgr::Partition*> ListPartitionsWithSuffix(
+ android::fs_mgr::MetadataBuilder* builder, const std::string& suffix);
+
+// Initialize a device before using it as the COW device for a dm-snapshot device.
+bool InitializeCow(const std::string& device);
+
} // namespace snapshot
} // namespace android
diff --git a/fs_mgr/libstorage_literals/Android.bp b/fs_mgr/libstorage_literals/Android.bp
new file mode 100644
index 0000000..11611dd
--- /dev/null
+++ b/fs_mgr/libstorage_literals/Android.bp
@@ -0,0 +1,6 @@
+
+cc_library_headers {
+ name: "libstorage_literals_headers",
+ host_supported: true,
+ export_include_dirs: ["."],
+}
diff --git a/fs_mgr/libstorage_literals/storage_literals/storage_literals.h b/fs_mgr/libstorage_literals/storage_literals/storage_literals.h
new file mode 100644
index 0000000..ac0dfbd
--- /dev/null
+++ b/fs_mgr/libstorage_literals/storage_literals/storage_literals.h
@@ -0,0 +1,77 @@
+// 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 <stdint.h>
+#include <stdlib.h>
+
+namespace android {
+namespace storage_literals {
+
+template <size_t Power>
+struct Size {
+ static constexpr size_t power = Power;
+ explicit constexpr Size(uint64_t count) : value_(count) {}
+
+ constexpr uint64_t bytes() const { return value_ << power; }
+ constexpr uint64_t count() const { return value_; }
+ constexpr operator uint64_t() const { return bytes(); }
+
+ private:
+ uint64_t value_;
+};
+
+using B = Size<0>;
+using KiB = Size<10>;
+using MiB = Size<20>;
+using GiB = Size<30>;
+
+constexpr B operator""_B(unsigned long long v) { // NOLINT
+ return B{v};
+}
+
+constexpr KiB operator""_KiB(unsigned long long v) { // NOLINT
+ return KiB{v};
+}
+
+constexpr MiB operator""_MiB(unsigned long long v) { // NOLINT
+ return MiB{v};
+}
+
+constexpr GiB operator""_GiB(unsigned long long v) { // NOLINT
+ return GiB{v};
+}
+
+template <typename Dest, typename Src>
+constexpr Dest size_cast(Src src) {
+ if (Src::power < Dest::power) {
+ return Dest(src.count() >> (Dest::power - Src::power));
+ }
+ if (Src::power > Dest::power) {
+ return Dest(src.count() << (Src::power - Dest::power));
+ }
+ return Dest(src.count());
+}
+
+static_assert(1_B == 1);
+static_assert(1_KiB == 1 << 10);
+static_assert(1_MiB == 1 << 20);
+static_assert(1_GiB == 1 << 30);
+static_assert(size_cast<KiB>(1_B).count() == 0);
+static_assert(size_cast<KiB>(1024_B).count() == 1);
+static_assert(size_cast<KiB>(1_MiB).count() == 1024);
+
+} // namespace storage_literals
+} // namespace android
diff --git a/init/Android.bp b/init/Android.bp
index 52cd1ca..9714014 100644
--- a/init/Android.bp
+++ b/init/Android.bp
@@ -129,6 +129,7 @@
"persistent_properties.cpp",
"persistent_properties.proto",
"property_service.cpp",
+ "property_service.proto",
"property_type.cpp",
"reboot.cpp",
"reboot_utils.cpp",
@@ -223,6 +224,7 @@
srcs: [
"devices_test.cpp",
+ "firmware_handler_test.cpp",
"init_test.cpp",
"keychords_test.cpp",
"persistent_properties_test.cpp",
diff --git a/init/Android.mk b/init/Android.mk
index d7258a7..54163fa 100644
--- a/init/Android.mk
+++ b/init/Android.mk
@@ -119,6 +119,7 @@
LOCAL_SANITIZE := signed-integer-overflow
# First stage init is weird: it may start without stdout/stderr, and no /proc.
LOCAL_NOSANITIZE := hwaddress
+LOCAL_INJECT_BSSL_HASH := true
include $(BUILD_EXECUTABLE)
endif
diff --git a/init/README.md b/init/README.md
index 2de76a9..423c6d1 100644
--- a/init/README.md
+++ b/init/README.md
@@ -263,6 +263,12 @@
> Scheduling priority of the service process. This value has to be in range
-20 to 19. Default priority is 0. Priority is set via setpriority().
+`reboot_on_failure <target>`
+> If this process cannot be started or if the process terminates with an exit code other than
+ CLD_EXITED or an status other than '0', reboot the system with the target specified in
+ _target_. _target_ takes the same format as the parameter to sys.powerctl. This is particularly
+ intended to be used with the `exec_start` builtin for any must-have checks during boot.
+
`restart_period <seconds>`
> If a non-oneshot service exits, it will be restarted at its start time plus
this period. It defaults to 5s to rate limit crashing services.
diff --git a/init/README.ueventd.md b/init/README.ueventd.md
index c592c37..053ebf8 100644
--- a/init/README.ueventd.md
+++ b/init/README.ueventd.md
@@ -82,7 +82,7 @@
## Firmware loading
----------------
-Ueventd automatically serves firmware requests by searching through a list of firmware directories
+Ueventd by default serves firmware requests by searching through a list of firmware directories
for a file matching the uevent `FIRMWARE`. It then forks a process to serve this firmware to the
kernel.
@@ -100,6 +100,26 @@
Ueventd will wait until after `post-fs` in init, to keep retrying before believing the firmwares are
not present.
+The exact firmware file to be served can be customized by running an external program by a
+`external_firmware_handler` line in a ueventd.rc file. This line takes the format of
+
+ external_firmware_handler <devpath> <user name to run as> <path to external program>
+For example
+
+ external_firmware_handler /devices/leds/red/firmware/coeffs.bin system /vendor/bin/led_coeffs.bin
+Will launch `/vendor/bin/led_coeffs.bin` as the system user instead of serving the default firmware
+for `/devices/leds/red/firmware/coeffs.bin`.
+
+Ueventd will provide the uevent `DEVPATH` and `FIRMWARE` to this external program on the environment
+via environment variables with the same names. Ueventd will use the string written to stdout as the
+new name of the firmware to load. It will still look for the new firmware in the list of firmware
+directories stated above. It will also reject file names with `..` in them, to prevent leaving these
+directories. If stdout cannot be read, or the program returns with any exit code other than
+`EXIT_SUCCESS`, or the program crashes, the default firmware from the uevent will be loaded.
+
+Ueventd will additionally log all messages sent to stderr from the external program to the serial
+console after the external program has exited.
+
## Coldboot
--------
Ueventd must create devices in `/dev` for all devices that have already sent their uevents before
@@ -110,3 +130,9 @@
For boot time purposes, this is done in parallel across a set of child processes. `ueventd.cpp` in
this directory contains documentation on how the parallelization is done.
+
+There is an option to parallelize the restorecon function during cold boot as well. This should only
+be done for devices that do not use genfscon, which is the recommended method for labeling sysfs
+nodes. To enable this option, use the below line in a ueventd.rc script:
+
+ parallel_restorecon enabled
diff --git a/init/builtins.cpp b/init/builtins.cpp
index d2c73cb..42211b2 100644
--- a/init/builtins.cpp
+++ b/init/builtins.cpp
@@ -80,6 +80,7 @@
using namespace std::literals::string_literals;
using android::base::Basename;
+using android::base::StartsWith;
using android::base::StringPrintf;
using android::base::unique_fd;
using android::fs_mgr::Fstab;
@@ -701,6 +702,15 @@
}
static Result<void> do_setprop(const BuiltinArguments& args) {
+ if (StartsWith(args[1], "ctl.")) {
+ return Error()
+ << "Cannot set ctl. properties from init; call the Service functions directly";
+ }
+ if (args[1] == kRestoreconProperty) {
+ return Error() << "Cannot set '" << kRestoreconProperty
+ << "' from init; use the restorecon builtin directly";
+ }
+
property_set(args[1], args[2]);
return {};
}
@@ -1016,7 +1026,20 @@
}
static Result<void> do_load_persist_props(const BuiltinArguments& args) {
- load_persist_props();
+ // Devices with FDE have load_persist_props called twice; the first time when the temporary
+ // /data partition is mounted and then again once /data is truly mounted. We do not want to
+ // read persistent properties from the temporary /data partition or mark persistent properties
+ // as having been loaded during the first call, so we return in that case.
+ std::string crypto_state = android::base::GetProperty("ro.crypto.state", "");
+ std::string crypto_type = android::base::GetProperty("ro.crypto.type", "");
+ if (crypto_state == "encrypted" && crypto_type == "block") {
+ static size_t num_calls = 0;
+ if (++num_calls == 1) return {};
+ }
+
+ SendLoadPersistentPropertiesMessage();
+
+ start_waiting_for_property("ro.persistent_properties.ready", "true");
return {};
}
@@ -1081,20 +1104,6 @@
return {};
}
-static Result<void> do_exec_reboot_on_failure(const BuiltinArguments& args) {
- auto reboot_reason = args[1];
- auto reboot = [reboot_reason](const std::string& message) {
- property_set(LAST_REBOOT_REASON_PROPERTY, reboot_reason);
- sync();
- LOG(FATAL) << message << ": rebooting into bootloader, reason: " << reboot_reason;
- };
-
- std::vector<std::string> remaining_args(args.begin() + 1, args.end());
- remaining_args[0] = args[0];
-
- return ExecWithFunctionOnFailure(remaining_args, reboot);
-}
-
static Result<void> ExecVdcRebootOnFailure(const std::string& vdc_arg) {
auto reboot_reason = vdc_arg + "_failed";
@@ -1202,7 +1211,6 @@
{"enable", {1, 1, {false, do_enable}}},
{"exec", {1, kMax, {false, do_exec}}},
{"exec_background", {1, kMax, {false, do_exec_background}}},
- {"exec_reboot_on_failure", {2, kMax, {false, do_exec_reboot_on_failure}}},
{"exec_start", {1, 1, {false, do_exec_start}}},
{"export", {2, 2, {false, do_export}}},
{"hostname", {1, 1, {true, do_hostname}}},
diff --git a/init/firmware_handler.cpp b/init/firmware_handler.cpp
index c067f6f..1dce2d5 100644
--- a/init/firmware_handler.cpp
+++ b/init/firmware_handler.cpp
@@ -17,6 +17,10 @@
#include "firmware_handler.h"
#include <fcntl.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
#include <sys/sendfile.h>
#include <sys/wait.h>
#include <unistd.h>
@@ -26,25 +30,29 @@
#include <android-base/chrono_utils.h>
#include <android-base/file.h>
#include <android-base/logging.h>
+#include <android-base/strings.h>
#include <android-base/unique_fd.h>
+using android::base::ReadFdToString;
+using android::base::Socketpair;
+using android::base::Split;
using android::base::Timer;
+using android::base::Trim;
using android::base::unique_fd;
using android::base::WriteFully;
namespace android {
namespace init {
-static void LoadFirmware(const Uevent& uevent, const std::string& root, int fw_fd, size_t fw_size,
- int loading_fd, int data_fd) {
+static void LoadFirmware(const std::string& firmware, const std::string& root, int fw_fd,
+ size_t fw_size, int loading_fd, int data_fd) {
// Start transfer.
WriteFully(loading_fd, "1", 1);
// Copy the firmware.
int rc = sendfile(data_fd, fw_fd, nullptr, fw_size);
if (rc == -1) {
- PLOG(ERROR) << "firmware: sendfile failed { '" << root << "', '" << uevent.firmware
- << "' }";
+ PLOG(ERROR) << "firmware: sendfile failed { '" << root << "', '" << firmware << "' }";
}
// Tell the firmware whether to abort or commit.
@@ -56,36 +64,151 @@
return access("/dev/.booting", F_OK) == 0;
}
-FirmwareHandler::FirmwareHandler(std::vector<std::string> firmware_directories)
- : firmware_directories_(std::move(firmware_directories)) {}
+FirmwareHandler::FirmwareHandler(std::vector<std::string> firmware_directories,
+ std::vector<ExternalFirmwareHandler> external_firmware_handlers)
+ : firmware_directories_(std::move(firmware_directories)),
+ external_firmware_handlers_(std::move(external_firmware_handlers)) {}
-void FirmwareHandler::ProcessFirmwareEvent(const Uevent& uevent) {
- int booting = IsBooting();
+Result<std::string> FirmwareHandler::RunExternalHandler(const std::string& handler, uid_t uid,
+ const Uevent& uevent) const {
+ unique_fd child_stdout;
+ unique_fd parent_stdout;
+ if (!Socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, &child_stdout, &parent_stdout)) {
+ return ErrnoError() << "Socketpair() for stdout failed";
+ }
+ unique_fd child_stderr;
+ unique_fd parent_stderr;
+ if (!Socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, &child_stderr, &parent_stderr)) {
+ return ErrnoError() << "Socketpair() for stderr failed";
+ }
+
+ signal(SIGCHLD, SIG_DFL);
+
+ auto pid = fork();
+ if (pid < 0) {
+ return ErrnoError() << "fork() failed";
+ }
+
+ if (pid == 0) {
+ setenv("FIRMWARE", uevent.firmware.c_str(), 1);
+ setenv("DEVPATH", uevent.path.c_str(), 1);
+ parent_stdout.reset();
+ parent_stderr.reset();
+ close(STDOUT_FILENO);
+ close(STDERR_FILENO);
+ dup2(child_stdout.get(), STDOUT_FILENO);
+ dup2(child_stderr.get(), STDERR_FILENO);
+
+ auto args = Split(handler, " ");
+ std::vector<char*> c_args;
+ for (auto& arg : args) {
+ c_args.emplace_back(arg.data());
+ }
+ c_args.emplace_back(nullptr);
+
+ if (setuid(uid) != 0) {
+ fprintf(stderr, "setuid() failed: %s", strerror(errno));
+ _exit(EXIT_FAILURE);
+ }
+
+ execv(c_args[0], c_args.data());
+ fprintf(stderr, "exec() failed: %s", strerror(errno));
+ _exit(EXIT_FAILURE);
+ }
+
+ child_stdout.reset();
+ child_stderr.reset();
+
+ int status;
+ pid_t waited_pid = TEMP_FAILURE_RETRY(waitpid(pid, &status, 0));
+ if (waited_pid == -1) {
+ return ErrnoError() << "waitpid() failed";
+ }
+
+ std::string stdout_content;
+ if (!ReadFdToString(parent_stdout.get(), &stdout_content)) {
+ return ErrnoError() << "ReadFdToString() for stdout failed";
+ }
+
+ std::string stderr_content;
+ if (ReadFdToString(parent_stderr.get(), &stderr_content)) {
+ auto messages = Split(stderr_content, "\n");
+ for (const auto& message : messages) {
+ if (!message.empty()) {
+ LOG(ERROR) << "External Firmware Handler: " << message;
+ }
+ }
+ } else {
+ LOG(ERROR) << "ReadFdToString() for stderr failed";
+ }
+
+ if (WIFEXITED(status)) {
+ if (WEXITSTATUS(status) == EXIT_SUCCESS) {
+ return Trim(stdout_content);
+ } else {
+ return Error() << "exited with status " << WEXITSTATUS(status);
+ }
+ } else if (WIFSIGNALED(status)) {
+ return Error() << "killed by signal " << WTERMSIG(status);
+ }
+
+ return Error() << "unexpected exit status " << status;
+}
+
+std::string FirmwareHandler::GetFirmwarePath(const Uevent& uevent) const {
+ for (const auto& external_handler : external_firmware_handlers_) {
+ if (external_handler.devpath == uevent.path) {
+ LOG(INFO) << "Launching external firmware handler '" << external_handler.handler_path
+ << "' for devpath: '" << uevent.path << "' firmware: '" << uevent.firmware
+ << "'";
+
+ auto result =
+ RunExternalHandler(external_handler.handler_path, external_handler.uid, uevent);
+ if (!result) {
+ LOG(ERROR) << "Using default firmware; External firmware handler failed: "
+ << result.error();
+ return uevent.firmware;
+ }
+ if (result->find("..") != std::string::npos) {
+ LOG(ERROR) << "Using default firmware; External firmware handler provided an "
+ "invalid path, '"
+ << *result << "'";
+ return uevent.firmware;
+ }
+ LOG(INFO) << "Loading firmware '" << *result << "' in place of '" << uevent.firmware
+ << "'";
+ return *result;
+ }
+ }
LOG(INFO) << "firmware: loading '" << uevent.firmware << "' for '" << uevent.path << "'";
+ return uevent.firmware;
+}
- std::string root = "/sys" + uevent.path;
+void FirmwareHandler::ProcessFirmwareEvent(const std::string& root,
+ const std::string& firmware) const {
std::string loading = root + "/loading";
std::string data = root + "/data";
unique_fd loading_fd(open(loading.c_str(), O_WRONLY | O_CLOEXEC));
if (loading_fd == -1) {
- PLOG(ERROR) << "couldn't open firmware loading fd for " << uevent.firmware;
+ PLOG(ERROR) << "couldn't open firmware loading fd for " << firmware;
return;
}
unique_fd data_fd(open(data.c_str(), O_WRONLY | O_CLOEXEC));
if (data_fd == -1) {
- PLOG(ERROR) << "couldn't open firmware data fd for " << uevent.firmware;
+ PLOG(ERROR) << "couldn't open firmware data fd for " << firmware;
return;
}
std::vector<std::string> attempted_paths_and_errors;
+ int booting = IsBooting();
try_loading_again:
attempted_paths_and_errors.clear();
for (const auto& firmware_directory : firmware_directories_) {
- std::string file = firmware_directory + uevent.firmware;
+ std::string file = firmware_directory + firmware;
unique_fd fw_fd(open(file.c_str(), O_RDONLY | O_CLOEXEC));
if (fw_fd == -1) {
attempted_paths_and_errors.emplace_back("firmware: attempted " + file +
@@ -98,7 +221,7 @@
", fstat failed: " + strerror(errno));
continue;
}
- LoadFirmware(uevent, root, fw_fd, sb.st_size, loading_fd, data_fd);
+ LoadFirmware(firmware, root, fw_fd, sb.st_size, loading_fd, data_fd);
return;
}
@@ -110,7 +233,7 @@
goto try_loading_again;
}
- LOG(ERROR) << "firmware: could not find firmware for " << uevent.firmware;
+ LOG(ERROR) << "firmware: could not find firmware for " << firmware;
for (const auto& message : attempted_paths_and_errors) {
LOG(ERROR) << message;
}
@@ -129,7 +252,8 @@
}
if (pid == 0) {
Timer t;
- ProcessFirmwareEvent(uevent);
+ auto firmware = GetFirmwarePath(uevent);
+ ProcessFirmwareEvent("/sys" + uevent.path, firmware);
LOG(INFO) << "loading " << uevent.path << " took " << t;
_exit(EXIT_SUCCESS);
}
diff --git a/init/firmware_handler.h b/init/firmware_handler.h
index 3996096..b4138f1 100644
--- a/init/firmware_handler.h
+++ b/init/firmware_handler.h
@@ -14,32 +14,48 @@
* limitations under the License.
*/
-#ifndef _INIT_FIRMWARE_HANDLER_H
-#define _INIT_FIRMWARE_HANDLER_H
+#pragma once
+
+#include <pwd.h>
#include <string>
#include <vector>
+#include "result.h"
#include "uevent.h"
#include "uevent_handler.h"
namespace android {
namespace init {
+struct ExternalFirmwareHandler {
+ ExternalFirmwareHandler(std::string devpath, uid_t uid, std::string handler_path)
+ : devpath(std::move(devpath)), uid(uid), handler_path(std::move(handler_path)) {}
+ std::string devpath;
+ uid_t uid;
+ std::string handler_path;
+};
+
class FirmwareHandler : public UeventHandler {
public:
- explicit FirmwareHandler(std::vector<std::string> firmware_directories);
+ FirmwareHandler(std::vector<std::string> firmware_directories,
+ std::vector<ExternalFirmwareHandler> external_firmware_handlers);
virtual ~FirmwareHandler() = default;
void HandleUevent(const Uevent& uevent) override;
private:
- void ProcessFirmwareEvent(const Uevent& uevent);
+ friend void FirmwareTestWithExternalHandler(const std::string& test_name,
+ bool expect_new_firmware);
+
+ Result<std::string> RunExternalHandler(const std::string& handler, uid_t uid,
+ const Uevent& uevent) const;
+ std::string GetFirmwarePath(const Uevent& uevent) const;
+ void ProcessFirmwareEvent(const std::string& root, const std::string& firmware) const;
std::vector<std::string> firmware_directories_;
+ std::vector<ExternalFirmwareHandler> external_firmware_handlers_;
};
} // namespace init
} // namespace android
-
-#endif
diff --git a/init/firmware_handler_test.cpp b/init/firmware_handler_test.cpp
new file mode 100644
index 0000000..7bb603c
--- /dev/null
+++ b/init/firmware_handler_test.cpp
@@ -0,0 +1,125 @@
+/*
+ * 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 "firmware_handler.h"
+
+#include <stdlib.h>
+#include <iostream>
+
+#include <android-base/file.h>
+#include <gtest/gtest.h>
+
+#include "uevent.h"
+
+using android::base::GetExecutablePath;
+using namespace std::literals;
+
+namespace android {
+namespace init {
+
+void FirmwareTestWithExternalHandler(const std::string& test_name, bool expect_new_firmware) {
+ auto test_path = GetExecutablePath() + " firmware " + test_name;
+ auto external_firmware_handler = ExternalFirmwareHandler(
+ "/devices/led/firmware/test_firmware001.bin", getuid(), test_path);
+
+ auto firmware_handler = FirmwareHandler({"/test"}, {external_firmware_handler});
+
+ auto uevent = Uevent{
+ .path = "/devices/led/firmware/test_firmware001.bin",
+ .firmware = "test_firmware001.bin",
+ };
+
+ if (expect_new_firmware) {
+ EXPECT_EQ("other_firmware001.bin", firmware_handler.GetFirmwarePath(uevent));
+ } else {
+ EXPECT_EQ("test_firmware001.bin", firmware_handler.GetFirmwarePath(uevent));
+ }
+
+ // Always test the base case that the handler isn't invoked if the devpath doesn't match.
+ auto uevent_different_path = Uevent{
+ .path = "/devices/led/not/mine",
+ .firmware = "test_firmware001.bin",
+ };
+ EXPECT_EQ("test_firmware001.bin", firmware_handler.GetFirmwarePath(uevent_different_path));
+}
+
+TEST(firmware_handler, HandleChange) {
+ FirmwareTestWithExternalHandler("HandleChange", true);
+}
+
+int HandleChange(int argc, char** argv) {
+ // Assert that the environment is set up correctly.
+ if (getenv("DEVPATH") != "/devices/led/firmware/test_firmware001.bin"s) {
+ std::cerr << "$DEVPATH not set correctly" << std::endl;
+ return EXIT_FAILURE;
+ }
+ if (getenv("FIRMWARE") != "test_firmware001.bin"s) {
+ std::cerr << "$FIRMWARE not set correctly" << std::endl;
+ return EXIT_FAILURE;
+ }
+ std::cout << "other_firmware001.bin" << std::endl;
+ return 0;
+}
+
+TEST(firmware_handler, HandleAbort) {
+ FirmwareTestWithExternalHandler("HandleAbort", false);
+}
+
+int HandleAbort(int argc, char** argv) {
+ abort();
+ return 0;
+}
+
+TEST(firmware_handler, HandleFailure) {
+ FirmwareTestWithExternalHandler("HandleFailure", false);
+}
+
+int HandleFailure(int argc, char** argv) {
+ std::cerr << "Failed" << std::endl;
+ return EXIT_FAILURE;
+}
+
+TEST(firmware_handler, HandleBadPath) {
+ FirmwareTestWithExternalHandler("HandleBadPath", false);
+}
+
+int HandleBadPath(int argc, char** argv) {
+ std::cout << "../firmware.bin";
+ return 0;
+}
+
+} // namespace init
+} // namespace android
+
+// init_test.cpp contains the main entry point for all init tests.
+int FirmwareTestChildMain(int argc, char** argv) {
+ if (argc < 3) {
+ return 1;
+ }
+
+#define RunTest(testname) \
+ if (argv[2] == std::string(#testname)) { \
+ return android::init::testname(argc, argv); \
+ }
+
+ RunTest(HandleChange);
+ RunTest(HandleAbort);
+ RunTest(HandleFailure);
+ RunTest(HandleBadPath);
+
+#undef RunTest
+ return 1;
+}
diff --git a/init/host_init_stubs.h b/init/host_init_stubs.h
index f9a08a5..e3068b2 100644
--- a/init/host_init_stubs.h
+++ b/init/host_init_stubs.h
@@ -34,6 +34,11 @@
namespace android {
namespace init {
+// init.h
+inline void EnterShutdown(const std::string&) {
+ abort();
+}
+
// property_service.h
inline bool CanReadProperty(const std::string&, const std::string&) {
return true;
diff --git a/init/init.cpp b/init/init.cpp
index 1f74ab6..53b065f 100644
--- a/init/init.cpp
+++ b/init/init.cpp
@@ -28,6 +28,9 @@
#include <sys/types.h>
#include <unistd.h>
+#define _REALLY_INCLUDE_SYS__SYSTEM_PROPERTIES_H_
+#include <sys/_system_properties.h>
+
#include <functional>
#include <map>
#include <memory>
@@ -60,6 +63,7 @@
#include "mount_handler.h"
#include "mount_namespace.h"
#include "property_service.h"
+#include "proto_utils.h"
#include "reboot.h"
#include "reboot_utils.h"
#include "security.h"
@@ -68,6 +72,7 @@
#include "service.h"
#include "service_parser.h"
#include "sigchld_handler.h"
+#include "system/core/init/property_service.pb.h"
#include "util.h"
using namespace std::chrono_literals;
@@ -89,6 +94,7 @@
static char qemu[32];
static int signal_fd = -1;
+static int property_fd = -1;
static std::unique_ptr<Timer> waiting_for_prop(nullptr);
static std::string wait_prop_name;
@@ -174,6 +180,16 @@
waiting_for_prop.reset();
}
+void EnterShutdown(const std::string& command) {
+ // We can't call HandlePowerctlMessage() directly in this function,
+ // because it modifies the contents of the action queue, which can cause the action queue
+ // to get into a bad state if this function is called from a command being executed by the
+ // action queue. Instead we set this flag and ensure that shutdown happens before the next
+ // command is run in the main init loop.
+ shutdown_command = command;
+ do_shutdown = true;
+}
+
void property_changed(const std::string& name, const std::string& value) {
// If the property is sys.powerctl, we bypass the event queue and immediately handle it.
// This is to ensure that init will always and immediately shutdown/reboot, regardless of
@@ -182,16 +198,7 @@
// In non-thermal-shutdown case, 'shutdown' trigger will be fired to let device specific
// commands to be executed.
if (name == "sys.powerctl") {
- // Despite the above comment, we can't call HandlePowerctlMessage() in this function,
- // because it modifies the contents of the action queue, which can cause the action queue
- // to get into a bad state if this function is called from a command being executed by the
- // action queue. Instead we set this flag and ensure that shutdown happens before the next
- // command is run in the main init loop.
- // TODO: once property service is removed from init, this will never happen from a builtin,
- // but rather from a callback from the property service socket, in which case this hack can
- // go away.
- shutdown_command = value;
- do_shutdown = true;
+ EnterShutdown(value);
}
if (property_triggers_enabled) ActionManager::GetInstance().QueuePropertyChange(name, value);
@@ -614,6 +621,60 @@
selinux_start_time_ns));
}
+void SendLoadPersistentPropertiesMessage() {
+ auto init_message = InitMessage{};
+ init_message.set_load_persistent_properties(true);
+ if (auto result = SendMessage(property_fd, init_message); !result) {
+ LOG(ERROR) << "Failed to send load persistent properties message: " << result.error();
+ }
+}
+
+void SendStopSendingMessagesMessage() {
+ auto init_message = InitMessage{};
+ init_message.set_stop_sending_messages(true);
+ if (auto result = SendMessage(property_fd, init_message); !result) {
+ LOG(ERROR) << "Failed to send load persistent properties message: " << result.error();
+ }
+}
+
+static void HandlePropertyFd() {
+ auto message = ReadMessage(property_fd);
+ if (!message) {
+ LOG(ERROR) << "Could not read message from property service: " << message.error();
+ return;
+ }
+
+ auto property_message = PropertyMessage{};
+ if (!property_message.ParseFromString(*message)) {
+ LOG(ERROR) << "Could not parse message from property service";
+ return;
+ }
+
+ switch (property_message.msg_case()) {
+ case PropertyMessage::kControlMessage: {
+ auto& control_message = property_message.control_message();
+ bool success = HandleControlMessage(control_message.msg(), control_message.name(),
+ control_message.pid());
+
+ uint32_t response = success ? PROP_SUCCESS : PROP_ERROR_HANDLE_CONTROL_MESSAGE;
+ if (control_message.has_fd()) {
+ int fd = control_message.fd();
+ TEMP_FAILURE_RETRY(send(fd, &response, sizeof(response), 0));
+ close(fd);
+ }
+ break;
+ }
+ case PropertyMessage::kChangedMessage: {
+ auto& changed_message = property_message.changed_message();
+ property_changed(changed_message.name(), changed_message.value());
+ break;
+ }
+ default:
+ LOG(ERROR) << "Unknown message type from property service: "
+ << property_message.msg_case();
+ }
+}
+
int SecondStageMain(int argc, char** argv) {
if (REBOOT_BOOTLOADER_ON_PANIC) {
InstallRebootSignalHandlers();
@@ -685,7 +746,12 @@
UmountDebugRamdisk();
fs_mgr_vendor_overlay_mount_all();
export_oem_lock_status();
- StartPropertyService(&epoll);
+
+ StartPropertyService(&property_fd);
+ if (auto result = epoll.RegisterHandler(property_fd, HandlePropertyFd); !result) {
+ LOG(FATAL) << "Could not register epoll handler for property fd: " << result.error();
+ }
+
MountHandler mount_handler(&epoll);
set_usb_controller();
diff --git a/init/init.h b/init/init.h
index cfc28f1..61fb110 100644
--- a/init/init.h
+++ b/init/init.h
@@ -31,9 +31,7 @@
Parser CreateParser(ActionManager& action_manager, ServiceList& service_list);
Parser CreateServiceOnlyParser(ServiceList& service_list);
-bool HandleControlMessage(const std::string& msg, const std::string& arg, pid_t pid);
-
-void property_changed(const std::string& name, const std::string& value);
+void EnterShutdown(const std::string& command);
bool start_waiting_for_property(const char *name, const char *value);
@@ -41,6 +39,9 @@
void ResetWaitForProp();
+void SendLoadPersistentPropertiesMessage();
+void SendStopSendingMessagesMessage();
+
int SecondStageMain(int argc, char** argv);
} // namespace init
diff --git a/init/init_test.cpp b/init/init_test.cpp
index 0411214..315d584 100644
--- a/init/init_test.cpp
+++ b/init/init_test.cpp
@@ -221,3 +221,19 @@
} // namespace init
} // namespace android
+
+int SubcontextTestChildMain(int, char**);
+int FirmwareTestChildMain(int, char**);
+
+int main(int argc, char** argv) {
+ if (argc > 1 && !strcmp(argv[1], "subcontext")) {
+ return SubcontextTestChildMain(argc, argv);
+ }
+
+ if (argc > 1 && !strcmp(argv[1], "firmware")) {
+ return FirmwareTestChildMain(argc, argv);
+ }
+
+ testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/init/interface_utils.cpp b/init/interface_utils.cpp
index a54860f..ddbacd7 100644
--- a/init/interface_utils.cpp
+++ b/init/interface_utils.cpp
@@ -77,6 +77,12 @@
const InterfaceInheritanceHierarchyMap& hierarchy) {
std::set<FQName> interface_fqnames;
for (const std::string& instance : instances) {
+ // There is insufficient build-time information on AIDL interfaces to check them here
+ // TODO(b/139307527): Rework how services store interfaces to avoid excess string parsing
+ if (base::Split(instance, "/")[0] == "aidl") {
+ continue;
+ }
+
FqInstance fqinstance;
if (!fqinstance.setTo(instance)) {
return Error() << "Unable to parse interface instance '" << instance << "'";
diff --git a/init/property_service.cpp b/init/property_service.cpp
index 3408ff3..c18decc 100644
--- a/init/property_service.cpp
+++ b/init/property_service.cpp
@@ -42,6 +42,7 @@
#include <map>
#include <memory>
#include <mutex>
+#include <optional>
#include <queue>
#include <thread>
#include <vector>
@@ -63,8 +64,10 @@
#include "init.h"
#include "persistent_properties.h"
#include "property_type.h"
+#include "proto_utils.h"
#include "selinux.h"
#include "subcontext.h"
+#include "system/core/init/property_service.pb.h"
#include "util.h"
using namespace std::literals;
@@ -76,6 +79,7 @@
using android::base::StringPrintf;
using android::base::Timer;
using android::base::Trim;
+using android::base::unique_fd;
using android::base::WriteStringToFile;
using android::properties::BuildTrie;
using android::properties::ParsePropertyInfoFile;
@@ -85,18 +89,13 @@
namespace android {
namespace init {
-static constexpr const char kRestoreconProperty[] = "selinux.restorecon_recursive";
-
static bool persistent_properties_loaded = false;
static int property_set_fd = -1;
+static int init_socket = -1;
static PropertyInfoAreaFile property_info_area;
-uint32_t InitPropertySet(const std::string& name, const std::string& value);
-
-uint32_t (*property_set)(const std::string& name, const std::string& value) = InitPropertySet;
-
void CreateSerializedPropertyInfo();
struct PropertyAuditData {
@@ -164,6 +163,17 @@
return has_access;
}
+static void SendPropertyChanged(const std::string& name, const std::string& value) {
+ auto property_msg = PropertyMessage{};
+ auto* changed_message = property_msg.mutable_changed_message();
+ changed_message->set_name(name);
+ changed_message->set_value(value);
+
+ if (auto result = SendMessage(init_socket, property_msg); !result) {
+ LOG(ERROR) << "Failed to send property changed message: " << result.error();
+ }
+}
+
static uint32_t PropertySet(const std::string& name, const std::string& value, std::string* error) {
size_t valuelen = value.size();
@@ -199,7 +209,11 @@
if (persistent_properties_loaded && StartsWith(name, "persist.")) {
WritePersistentProperty(name, value);
}
- property_changed(name, value);
+ // If init hasn't started its main loop, then it won't be handling property changed messages
+ // anyway, so there's no need to try to send them.
+ if (init_socket != -1) {
+ SendPropertyChanged(name, value);
+ }
return PROP_SUCCESS;
}
@@ -239,35 +253,10 @@
bool thread_started_ = false;
};
-uint32_t InitPropertySet(const std::string& name, const std::string& value) {
- if (StartsWith(name, "ctl.")) {
- LOG(ERROR) << "InitPropertySet: Do not set ctl. properties from init; call the Service "
- "functions directly";
- return PROP_ERROR_INVALID_NAME;
- }
- if (name == kRestoreconProperty) {
- LOG(ERROR) << "InitPropertySet: Do not set '" << kRestoreconProperty
- << "' from init; use the restorecon builtin directly";
- return PROP_ERROR_INVALID_NAME;
- }
-
- uint32_t result = 0;
- ucred cr = {.pid = 1, .uid = 0, .gid = 0};
- std::string error;
- result = HandlePropertySet(name, value, kInitContext.c_str(), cr, &error);
- if (result != PROP_SUCCESS) {
- LOG(ERROR) << "Init cannot set '" << name << "' to '" << value << "': " << error;
- }
-
- return result;
-}
-
class SocketConnection {
public:
SocketConnection(int socket, const ucred& cred) : socket_(socket), cred_(cred) {}
- ~SocketConnection() { close(socket_); }
-
bool RecvUint32(uint32_t* value, uint32_t* timeout_ms) {
return RecvFully(value, sizeof(*value), timeout_ms);
}
@@ -304,6 +293,9 @@
}
bool SendUint32(uint32_t value) {
+ if (!socket_.ok()) {
+ return true;
+ }
int result = TEMP_FAILURE_RETRY(send(socket_, &value, sizeof(value), 0));
return result == sizeof(value);
}
@@ -318,7 +310,7 @@
return true;
}
- int socket() { return socket_; }
+ [[nodiscard]] int Release() { return socket_.release(); }
const ucred& cred() { return cred_; }
@@ -389,12 +381,46 @@
return bytes_left == 0;
}
- int socket_;
+ unique_fd socket_;
ucred cred_;
DISALLOW_IMPLICIT_CONSTRUCTORS(SocketConnection);
};
+static uint32_t SendControlMessage(const std::string& msg, const std::string& name, pid_t pid,
+ SocketConnection* socket, std::string* error) {
+ if (init_socket == -1) {
+ *error = "Received control message after shutdown, ignoring";
+ return PROP_ERROR_HANDLE_CONTROL_MESSAGE;
+ }
+
+ auto property_msg = PropertyMessage{};
+ auto* control_message = property_msg.mutable_control_message();
+ control_message->set_msg(msg);
+ control_message->set_name(name);
+ control_message->set_pid(pid);
+
+ // We must release the fd before sending it to init, otherwise there will be a race with init.
+ // If init calls close() before Release(), then fdsan will see the wrong tag and abort().
+ int fd = -1;
+ if (socket != nullptr) {
+ fd = socket->Release();
+ control_message->set_fd(fd);
+ }
+
+ if (auto result = SendMessage(init_socket, property_msg); !result) {
+ // We've already released the fd above, so if we fail to send the message to init, we need
+ // to manually free it here.
+ if (fd != -1) {
+ close(fd);
+ }
+ *error = "Failed to send control message: " + result.error().message();
+ return PROP_ERROR_HANDLE_CONTROL_MESSAGE;
+ }
+
+ return PROP_SUCCESS;
+}
+
bool CheckControlPropertyPerms(const std::string& name, const std::string& value,
const std::string& source_context, const ucred& cr) {
// We check the legacy method first but these properties are dontaudit, so we only log an audit
@@ -462,15 +488,14 @@
// This returns one of the enum of PROP_SUCCESS or PROP_ERROR*.
uint32_t HandlePropertySet(const std::string& name, const std::string& value,
- const std::string& source_context, const ucred& cr, std::string* error) {
+ const std::string& source_context, const ucred& cr,
+ SocketConnection* socket, std::string* error) {
if (auto ret = CheckPermissions(name, value, source_context, cr, error); ret != PROP_SUCCESS) {
return ret;
}
if (StartsWith(name, "ctl.")) {
- return HandleControlMessage(name.c_str() + 4, value, cr.pid)
- ? PROP_SUCCESS
- : PROP_ERROR_HANDLE_CONTROL_MESSAGE;
+ return SendControlMessage(name.c_str() + 4, value, cr.pid, socket, error);
}
// sys.powerctl is a special property that is used to make the device reboot. We want to log
@@ -501,6 +526,20 @@
return PropertySet(name, value, error);
}
+uint32_t InitPropertySet(const std::string& name, const std::string& value) {
+ uint32_t result = 0;
+ ucred cr = {.pid = 1, .uid = 0, .gid = 0};
+ std::string error;
+ result = HandlePropertySet(name, value, kInitContext.c_str(), cr, nullptr, &error);
+ if (result != PROP_SUCCESS) {
+ LOG(ERROR) << "Init cannot set '" << name << "' to '" << value << "': " << error;
+ }
+
+ return result;
+}
+
+uint32_t (*property_set)(const std::string& name, const std::string& value) = InitPropertySet;
+
static void handle_property_set_fd() {
static constexpr uint32_t kDefaultSocketTimeout = 2000; /* ms */
@@ -549,7 +588,8 @@
const auto& cr = socket.cred();
std::string error;
- uint32_t result = HandlePropertySet(prop_name, prop_value, source_context, cr, &error);
+ uint32_t result =
+ HandlePropertySet(prop_name, prop_value, source_context, cr, nullptr, &error);
if (result != PROP_SUCCESS) {
LOG(ERROR) << "Unable to set property '" << prop_name << "' from uid:" << cr.uid
<< " gid:" << cr.gid << " pid:" << cr.pid << ": " << error;
@@ -577,7 +617,7 @@
const auto& cr = socket.cred();
std::string error;
- uint32_t result = HandlePropertySet(name, value, source_context, cr, &error);
+ uint32_t result = HandlePropertySet(name, value, source_context, cr, &socket, &error);
if (result != PROP_SUCCESS) {
LOG(ERROR) << "Unable to set property '" << name << "' from uid:" << cr.uid
<< " gid:" << cr.gid << " pid:" << cr.pid << ": " << error;
@@ -741,33 +781,6 @@
}
}
-/* When booting an encrypted system, /data is not mounted when the
- * property service is started, so any properties stored there are
- * not loaded. Vold triggers init to load these properties once it
- * has mounted /data.
- */
-void load_persist_props(void) {
- // Devices with FDE have load_persist_props called twice; the first time when the temporary
- // /data partition is mounted and then again once /data is truly mounted. We do not want to
- // read persistent properties from the temporary /data partition or mark persistent properties
- // as having been loaded during the first call, so we return in that case.
- std::string crypto_state = android::base::GetProperty("ro.crypto.state", "");
- std::string crypto_type = android::base::GetProperty("ro.crypto.type", "");
- if (crypto_state == "encrypted" && crypto_type == "block") {
- static size_t num_calls = 0;
- if (++num_calls == 1) return;
- }
-
- load_override_properties();
- /* Read persistent properties after all default values have been loaded. */
- auto persistent_properties = LoadPersistentProperties();
- for (const auto& persistent_property_record : persistent_properties.properties()) {
- property_set(persistent_property_record.name(), persistent_property_record.value());
- }
- persistent_properties_loaded = true;
- property_set("ro.persistent_properties.ready", "true");
-}
-
// If the ro.product.[brand|device|manufacturer|model|name] properties have not been explicitly
// set, derive them from ro.product.${partition}.* properties
static void property_initialize_ro_product_props() {
@@ -985,21 +998,92 @@
selinux_android_restorecon(kPropertyInfosPath, 0);
}
-void StartPropertyService(Epoll* epoll) {
+static void HandleInitSocket() {
+ auto message = ReadMessage(init_socket);
+ if (!message) {
+ LOG(ERROR) << "Could not read message from init_dedicated_recv_socket: " << message.error();
+ return;
+ }
+
+ auto init_message = InitMessage{};
+ if (!init_message.ParseFromString(*message)) {
+ LOG(ERROR) << "Could not parse message from init";
+ return;
+ }
+
+ switch (init_message.msg_case()) {
+ case InitMessage::kLoadPersistentProperties: {
+ load_override_properties();
+ // Read persistent properties after all default values have been loaded.
+ auto persistent_properties = LoadPersistentProperties();
+ for (const auto& persistent_property_record : persistent_properties.properties()) {
+ InitPropertySet(persistent_property_record.name(),
+ persistent_property_record.value());
+ }
+ InitPropertySet("ro.persistent_properties.ready", "true");
+ persistent_properties_loaded = true;
+ break;
+ }
+ case InitMessage::kStopSendingMessages: {
+ init_socket = -1;
+ break;
+ }
+ default:
+ LOG(ERROR) << "Unknown message type from init: " << init_message.msg_case();
+ }
+}
+
+static void PropertyServiceThread() {
+ Epoll epoll;
+ if (auto result = epoll.Open(); !result) {
+ LOG(FATAL) << result.error();
+ }
+
+ if (auto result = epoll.RegisterHandler(property_set_fd, handle_property_set_fd); !result) {
+ LOG(FATAL) << result.error();
+ }
+
+ if (auto result = epoll.RegisterHandler(init_socket, HandleInitSocket); !result) {
+ LOG(FATAL) << result.error();
+ }
+
+ while (true) {
+ auto pending_functions = epoll.Wait(std::nullopt);
+ if (!pending_functions) {
+ LOG(ERROR) << pending_functions.error();
+ } else {
+ for (const auto& function : *pending_functions) {
+ (*function)();
+ }
+ }
+ }
+}
+
+void StartPropertyService(int* epoll_socket) {
property_set("ro.property_service.version", "2");
+ int sockets[2];
+ if (socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0, sockets) != 0) {
+ PLOG(FATAL) << "Failed to socketpair() between property_service and init";
+ }
+ *epoll_socket = sockets[0];
+ init_socket = sockets[1];
+
if (auto result = CreateSocket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
false, 0666, 0, 0, {})) {
property_set_fd = *result;
} else {
- PLOG(FATAL) << "start_property_service socket creation failed: " << result.error();
+ LOG(FATAL) << "start_property_service socket creation failed: " << result.error();
}
listen(property_set_fd, 8);
- if (auto result = epoll->RegisterHandler(property_set_fd, handle_property_set_fd); !result) {
- PLOG(FATAL) << result.error();
- }
+ std::thread{PropertyServiceThread}.detach();
+
+ property_set = [](const std::string& key, const std::string& value) -> uint32_t {
+ android::base::SetProperty(key, value);
+ return 0;
+ };
}
} // namespace init
diff --git a/init/property_service.h b/init/property_service.h
index 7f9f844..8f7d8d9 100644
--- a/init/property_service.h
+++ b/init/property_service.h
@@ -25,17 +25,15 @@
namespace android {
namespace init {
+static constexpr const char kRestoreconProperty[] = "selinux.restorecon_recursive";
+
bool CanReadProperty(const std::string& source_context, const std::string& name);
extern uint32_t (*property_set)(const std::string& name, const std::string& value);
-uint32_t HandlePropertySet(const std::string& name, const std::string& value,
- const std::string& source_context, const ucred& cr, std::string* error);
-
void property_init();
void property_load_boot_defaults(bool load_debug_prop);
-void load_persist_props();
-void StartPropertyService(Epoll* epoll);
+void StartPropertyService(int* epoll_socket);
} // namespace init
} // namespace android
diff --git a/init/property_service.proto b/init/property_service.proto
new file mode 100644
index 0000000..ea454d4
--- /dev/null
+++ b/init/property_service.proto
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+option optimize_for = LITE_RUNTIME;
+
+message PropertyMessage {
+ message ControlMessage {
+ optional string msg = 1;
+ optional string name = 2;
+ optional int32 pid = 3;
+ optional int32 fd = 4;
+ }
+
+ message ChangedMessage {
+ optional string name = 1;
+ optional string value = 2;
+ }
+
+ oneof msg {
+ ControlMessage control_message = 1;
+ ChangedMessage changed_message = 2;
+ };
+}
+
+message InitMessage {
+ oneof msg {
+ bool load_persistent_properties = 1;
+ bool stop_sending_messages = 2;
+ };
+}
diff --git a/init/proto_utils.h b/init/proto_utils.h
new file mode 100644
index 0000000..93a7d57
--- /dev/null
+++ b/init/proto_utils.h
@@ -0,0 +1,62 @@
+/*
+ * 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 <sys/socket.h>
+#include <unistd.h>
+
+#include <string>
+
+#include "result.h"
+
+namespace android {
+namespace init {
+
+constexpr size_t kBufferSize = 4096;
+
+inline Result<std::string> ReadMessage(int socket) {
+ char buffer[kBufferSize] = {};
+ auto result = TEMP_FAILURE_RETRY(recv(socket, buffer, sizeof(buffer), 0));
+ if (result == 0) {
+ return Error();
+ } else if (result < 0) {
+ return ErrnoError();
+ }
+ return std::string(buffer, result);
+}
+
+template <typename T>
+Result<void> SendMessage(int socket, const T& message) {
+ std::string message_string;
+ if (!message.SerializeToString(&message_string)) {
+ return Error() << "Unable to serialize message";
+ }
+
+ if (message_string.size() > kBufferSize) {
+ return Error() << "Serialized message too long to send";
+ }
+
+ if (auto result =
+ TEMP_FAILURE_RETRY(send(socket, message_string.c_str(), message_string.size(), 0));
+ result != static_cast<long>(message_string.size())) {
+ return ErrnoError() << "send() failed to send message contents";
+ }
+ return {};
+}
+
+} // namespace init
+} // namespace android
diff --git a/init/reboot.cpp b/init/reboot.cpp
index b0b5b54..786a084 100644
--- a/init/reboot.cpp
+++ b/init/reboot.cpp
@@ -730,6 +730,12 @@
s->UnSetExec();
}
+ // We no longer process messages about properties changing coming from property service, so we
+ // need to tell property service to stop sending us these messages, otherwise it'll fill the
+ // buffers and block indefinitely, causing future property sets, including those that init makes
+ // during shutdown in Service::NotifyStateChange() to also block indefinitely.
+ SendStopSendingMessagesMessage();
+
return true;
}
diff --git a/init/service.cpp b/init/service.cpp
index 7a20966..793a2b2 100644
--- a/init/service.cpp
+++ b/init/service.cpp
@@ -29,6 +29,7 @@
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/properties.h>
+#include <android-base/scopeguard.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#include <cutils/sockets.h>
@@ -41,6 +42,7 @@
#if defined(__ANDROID__)
#include <ApexProperties.sysprop.h>
+#include "init.h"
#include "mount_namespace.h"
#include "property_service.h"
#else
@@ -50,6 +52,7 @@
using android::base::boot_clock;
using android::base::GetProperty;
using android::base::Join;
+using android::base::make_scope_guard;
using android::base::StartsWith;
using android::base::StringPrintf;
using android::base::WriteStringToFile;
@@ -250,6 +253,11 @@
f(siginfo);
}
+ if ((siginfo.si_code != CLD_EXITED || siginfo.si_status != 0) && on_failure_reboot_target_) {
+ LOG(ERROR) << "Service with 'reboot_on_failure' option failed, shutting down system.";
+ EnterShutdown(*on_failure_reboot_target_);
+ }
+
if (flags_ & SVC_EXEC) UnSetExec();
if (flags_ & SVC_TEMPORARY) return;
@@ -325,6 +333,12 @@
Result<void> Service::ExecStart() {
+ auto reboot_on_failure = make_scope_guard([this] {
+ if (on_failure_reboot_target_) {
+ EnterShutdown(*on_failure_reboot_target_);
+ }
+ });
+
if (is_updatable() && !ServiceList::GetInstance().IsServicesUpdated()) {
// Don't delay the service for ExecStart() as the semantic is that
// the caller might depend on the side effect of the execution.
@@ -345,10 +359,17 @@
<< " gid " << proc_attr_.gid << "+" << proc_attr_.supp_gids.size() << " context "
<< (!seclabel_.empty() ? seclabel_ : "default") << ") started; waiting...";
+ reboot_on_failure.Disable();
return {};
}
Result<void> Service::Start() {
+ auto reboot_on_failure = make_scope_guard([this] {
+ if (on_failure_reboot_target_) {
+ EnterShutdown(*on_failure_reboot_target_);
+ }
+ });
+
if (is_updatable() && !ServiceList::GetInstance().IsServicesUpdated()) {
ServiceList::GetInstance().DelayService(*this);
return Error() << "Cannot start an updatable service '" << name_
@@ -371,6 +392,7 @@
flags_ |= SVC_RESTART;
}
// It is not an error to try to start a service that is already running.
+ reboot_on_failure.Disable();
return {};
}
@@ -532,6 +554,7 @@
}
NotifyStateChange("running");
+ reboot_on_failure.Disable();
return {};
}
diff --git a/init/service.h b/init/service.h
index ccefc8e..788f792 100644
--- a/init/service.h
+++ b/init/service.h
@@ -196,6 +196,8 @@
bool post_data_ = false;
bool running_at_post_data_reset_ = false;
+
+ std::optional<std::string> on_failure_reboot_target_;
};
} // namespace init
diff --git a/init/service_parser.cpp b/init/service_parser.cpp
index dd552fb..4322dc7 100644
--- a/init/service_parser.cpp
+++ b/init/service_parser.cpp
@@ -145,17 +145,21 @@
const std::string& interface_name = args[1];
const std::string& instance_name = args[2];
- FQName fq_name;
- if (!FQName::parse(interface_name, &fq_name)) {
- return Error() << "Invalid fully-qualified name for interface '" << interface_name << "'";
- }
+ // AIDL services don't use fully qualified names and instead just use "interface aidl <name>"
+ if (interface_name != "aidl") {
+ FQName fq_name;
+ if (!FQName::parse(interface_name, &fq_name)) {
+ return Error() << "Invalid fully-qualified name for interface '" << interface_name
+ << "'";
+ }
- if (!fq_name.isFullyQualified()) {
- return Error() << "Interface name not fully-qualified '" << interface_name << "'";
- }
+ if (!fq_name.isFullyQualified()) {
+ return Error() << "Interface name not fully-qualified '" << interface_name << "'";
+ }
- if (fq_name.isValidValueName()) {
- return Error() << "Interface name must not be a value name '" << interface_name << "'";
+ if (fq_name.isValidValueName()) {
+ return Error() << "Interface name must not be a value name '" << interface_name << "'";
+ }
}
const std::string fullname = interface_name + "/" + instance_name;
@@ -306,6 +310,18 @@
return {};
}
+Result<void> ServiceParser::ParseRebootOnFailure(std::vector<std::string>&& args) {
+ if (service_->on_failure_reboot_target_) {
+ return Error() << "Only one reboot_on_failure command may be specified";
+ }
+ if (!StartsWith(args[1], "shutdown") && !StartsWith(args[1], "reboot")) {
+ return Error()
+ << "reboot_on_failure commands must begin with either 'shutdown' or 'reboot'";
+ }
+ service_->on_failure_reboot_target_ = std::move(args[1]);
+ return {};
+}
+
Result<void> ServiceParser::ParseRestartPeriod(std::vector<std::string>&& args) {
int period;
if (!ParseInt(args[1], &period, 5)) {
@@ -467,49 +483,41 @@
constexpr std::size_t kMax = std::numeric_limits<std::size_t>::max();
// clang-format off
static const KeywordMap<ServiceParser::OptionParser> parser_map = {
- {"capabilities",
- {0, kMax, &ServiceParser::ParseCapabilities}},
- {"class", {1, kMax, &ServiceParser::ParseClass}},
- {"console", {0, 1, &ServiceParser::ParseConsole}},
- {"critical", {0, 0, &ServiceParser::ParseCritical}},
- {"disabled", {0, 0, &ServiceParser::ParseDisabled}},
- {"enter_namespace",
- {2, 2, &ServiceParser::ParseEnterNamespace}},
- {"file", {2, 2, &ServiceParser::ParseFile}},
- {"group", {1, NR_SVC_SUPP_GIDS + 1, &ServiceParser::ParseGroup}},
- {"interface", {2, 2, &ServiceParser::ParseInterface}},
- {"ioprio", {2, 2, &ServiceParser::ParseIoprio}},
- {"keycodes", {1, kMax, &ServiceParser::ParseKeycodes}},
- {"memcg.limit_in_bytes",
- {1, 1, &ServiceParser::ParseMemcgLimitInBytes}},
- {"memcg.limit_percent",
- {1, 1, &ServiceParser::ParseMemcgLimitPercent}},
- {"memcg.limit_property",
- {1, 1, &ServiceParser::ParseMemcgLimitProperty}},
+ {"capabilities", {0, kMax, &ServiceParser::ParseCapabilities}},
+ {"class", {1, kMax, &ServiceParser::ParseClass}},
+ {"console", {0, 1, &ServiceParser::ParseConsole}},
+ {"critical", {0, 0, &ServiceParser::ParseCritical}},
+ {"disabled", {0, 0, &ServiceParser::ParseDisabled}},
+ {"enter_namespace", {2, 2, &ServiceParser::ParseEnterNamespace}},
+ {"file", {2, 2, &ServiceParser::ParseFile}},
+ {"group", {1, NR_SVC_SUPP_GIDS + 1, &ServiceParser::ParseGroup}},
+ {"interface", {2, 2, &ServiceParser::ParseInterface}},
+ {"ioprio", {2, 2, &ServiceParser::ParseIoprio}},
+ {"keycodes", {1, kMax, &ServiceParser::ParseKeycodes}},
+ {"memcg.limit_in_bytes", {1, 1, &ServiceParser::ParseMemcgLimitInBytes}},
+ {"memcg.limit_percent", {1, 1, &ServiceParser::ParseMemcgLimitPercent}},
+ {"memcg.limit_property", {1, 1, &ServiceParser::ParseMemcgLimitProperty}},
{"memcg.soft_limit_in_bytes",
- {1, 1, &ServiceParser::ParseMemcgSoftLimitInBytes}},
- {"memcg.swappiness",
- {1, 1, &ServiceParser::ParseMemcgSwappiness}},
- {"namespace", {1, 2, &ServiceParser::ParseNamespace}},
- {"oneshot", {0, 0, &ServiceParser::ParseOneshot}},
- {"onrestart", {1, kMax, &ServiceParser::ParseOnrestart}},
- {"oom_score_adjust",
- {1, 1, &ServiceParser::ParseOomScoreAdjust}},
- {"override", {0, 0, &ServiceParser::ParseOverride}},
- {"priority", {1, 1, &ServiceParser::ParsePriority}},
- {"restart_period",
- {1, 1, &ServiceParser::ParseRestartPeriod}},
- {"rlimit", {3, 3, &ServiceParser::ParseProcessRlimit}},
- {"seclabel", {1, 1, &ServiceParser::ParseSeclabel}},
- {"setenv", {2, 2, &ServiceParser::ParseSetenv}},
- {"shutdown", {1, 1, &ServiceParser::ParseShutdown}},
- {"sigstop", {0, 0, &ServiceParser::ParseSigstop}},
- {"socket", {3, 6, &ServiceParser::ParseSocket}},
- {"timeout_period",
- {1, 1, &ServiceParser::ParseTimeoutPeriod}},
- {"updatable", {0, 0, &ServiceParser::ParseUpdatable}},
- {"user", {1, 1, &ServiceParser::ParseUser}},
- {"writepid", {1, kMax, &ServiceParser::ParseWritepid}},
+ {1, 1, &ServiceParser::ParseMemcgSoftLimitInBytes}},
+ {"memcg.swappiness", {1, 1, &ServiceParser::ParseMemcgSwappiness}},
+ {"namespace", {1, 2, &ServiceParser::ParseNamespace}},
+ {"oneshot", {0, 0, &ServiceParser::ParseOneshot}},
+ {"onrestart", {1, kMax, &ServiceParser::ParseOnrestart}},
+ {"oom_score_adjust", {1, 1, &ServiceParser::ParseOomScoreAdjust}},
+ {"override", {0, 0, &ServiceParser::ParseOverride}},
+ {"priority", {1, 1, &ServiceParser::ParsePriority}},
+ {"reboot_on_failure", {1, 1, &ServiceParser::ParseRebootOnFailure}},
+ {"restart_period", {1, 1, &ServiceParser::ParseRestartPeriod}},
+ {"rlimit", {3, 3, &ServiceParser::ParseProcessRlimit}},
+ {"seclabel", {1, 1, &ServiceParser::ParseSeclabel}},
+ {"setenv", {2, 2, &ServiceParser::ParseSetenv}},
+ {"shutdown", {1, 1, &ServiceParser::ParseShutdown}},
+ {"sigstop", {0, 0, &ServiceParser::ParseSigstop}},
+ {"socket", {3, 6, &ServiceParser::ParseSocket}},
+ {"timeout_period", {1, 1, &ServiceParser::ParseTimeoutPeriod}},
+ {"updatable", {0, 0, &ServiceParser::ParseUpdatable}},
+ {"user", {1, 1, &ServiceParser::ParseUser}},
+ {"writepid", {1, kMax, &ServiceParser::ParseWritepid}},
};
// clang-format on
return parser_map;
diff --git a/init/service_parser.h b/init/service_parser.h
index 4729874..ace4d70 100644
--- a/init/service_parser.h
+++ b/init/service_parser.h
@@ -68,6 +68,7 @@
Result<void> ParseMemcgSwappiness(std::vector<std::string>&& args);
Result<void> ParseNamespace(std::vector<std::string>&& args);
Result<void> ParseProcessRlimit(std::vector<std::string>&& args);
+ Result<void> ParseRebootOnFailure(std::vector<std::string>&& args);
Result<void> ParseRestartPeriod(std::vector<std::string>&& args);
Result<void> ParseSeclabel(std::vector<std::string>&& args);
Result<void> ParseSetenv(std::vector<std::string>&& args);
diff --git a/init/subcontext.cpp b/init/subcontext.cpp
index 00f91d8..ec93b58 100644
--- a/init/subcontext.cpp
+++ b/init/subcontext.cpp
@@ -18,16 +18,17 @@
#include <fcntl.h>
#include <poll.h>
-#include <sys/socket.h>
#include <unistd.h>
#include <android-base/file.h>
#include <android-base/logging.h>
+#include <android-base/properties.h>
#include <android-base/strings.h>
#include <selinux/android.h>
#include "action.h"
#include "builtins.h"
+#include "proto_utils.h"
#include "util.h"
#if defined(__ANDROID__)
@@ -59,45 +60,6 @@
namespace {
-constexpr size_t kBufferSize = 4096;
-
-Result<std::string> ReadMessage(int socket) {
- char buffer[kBufferSize] = {};
- auto result = TEMP_FAILURE_RETRY(recv(socket, buffer, sizeof(buffer), 0));
- if (result == 0) {
- return Error();
- } else if (result < 0) {
- return ErrnoError();
- }
- return std::string(buffer, result);
-}
-
-template <typename T>
-Result<void> SendMessage(int socket, const T& message) {
- std::string message_string;
- if (!message.SerializeToString(&message_string)) {
- return Error() << "Unable to serialize message";
- }
-
- if (message_string.size() > kBufferSize) {
- return Error() << "Serialized message too long to send";
- }
-
- if (auto result =
- TEMP_FAILURE_RETRY(send(socket, message_string.c_str(), message_string.size(), 0));
- result != static_cast<long>(message_string.size())) {
- return ErrnoError() << "send() failed to send message contents";
- }
- return {};
-}
-
-std::vector<std::pair<std::string, std::string>> properties_to_set;
-
-uint32_t SubcontextPropertySet(const std::string& name, const std::string& value) {
- properties_to_set.emplace_back(name, value);
- return 0;
-}
-
class SubcontextProcess {
public:
SubcontextProcess(const BuiltinFunctionMap* function_map, std::string context, int init_fd)
@@ -131,14 +93,6 @@
result = RunBuiltinFunction(map_result->function, args, context_);
}
- for (const auto& [name, value] : properties_to_set) {
- auto property = reply->add_properties_to_set();
- property->set_name(name);
- property->set_value(value);
- }
-
- properties_to_set.clear();
-
if (result) {
reply->set_success(true);
} else {
@@ -224,7 +178,10 @@
SelabelInitialize();
- property_set = SubcontextPropertySet;
+ property_set = [](const std::string& key, const std::string& value) -> uint32_t {
+ android::base::SetProperty(key, value);
+ return 0;
+ };
auto subcontext_process = SubcontextProcess(function_map, context, init_fd);
subcontext_process.MainLoop();
@@ -311,15 +268,6 @@
return subcontext_reply.error();
}
- for (const auto& property : subcontext_reply->properties_to_set()) {
- ucred cr = {.pid = pid_, .uid = 0, .gid = 0};
- std::string error;
- if (HandlePropertySet(property.name(), property.value(), context_, cr, &error) != 0) {
- LOG(ERROR) << "Subcontext init could not set '" << property.name() << "' to '"
- << property.value() << "': " << error;
- }
- }
-
if (subcontext_reply->reply_case() == SubcontextReply::kFailure) {
auto& failure = subcontext_reply->failure();
return ResultError(failure.error_string(), failure.error_errno());
diff --git a/init/subcontext.proto b/init/subcontext.proto
index c31f4fb..e68115e 100644
--- a/init/subcontext.proto
+++ b/init/subcontext.proto
@@ -38,10 +38,4 @@
Failure failure = 2;
ExpandArgsReply expand_args_reply = 3;
}
-
- message PropertyToSet {
- optional string name = 1;
- optional string value = 2;
- }
- repeated PropertyToSet properties_to_set = 4;
}
\ No newline at end of file
diff --git a/init/subcontext_test.cpp b/init/subcontext_test.cpp
index dcbff82..7565eb6 100644
--- a/init/subcontext_test.cpp
+++ b/init/subcontext_test.cpp
@@ -224,12 +224,8 @@
} // namespace init
} // namespace android
-int main(int argc, char** argv) {
- if (argc > 1 && !strcmp(basename(argv[1]), "subcontext")) {
- auto test_function_map = android::init::BuildTestFunctionMap();
- return android::init::SubcontextMain(argc, argv, &test_function_map);
- }
-
- testing::InitGoogleTest(&argc, argv);
- return RUN_ALL_TESTS();
+// init_test.cpp contains the main entry point for all init tests.
+int SubcontextTestChildMain(int argc, char** argv) {
+ auto test_function_map = android::init::BuildTestFunctionMap();
+ return android::init::SubcontextMain(argc, argv, &test_function_map);
}
diff --git a/init/ueventd.cpp b/init/ueventd.cpp
index cffc1b9..59f91ee 100644
--- a/init/ueventd.cpp
+++ b/init/ueventd.cpp
@@ -113,10 +113,12 @@
class ColdBoot {
public:
ColdBoot(UeventListener& uevent_listener,
- std::vector<std::unique_ptr<UeventHandler>>& uevent_handlers)
+ std::vector<std::unique_ptr<UeventHandler>>& uevent_handlers,
+ bool enable_parallel_restorecon)
: uevent_listener_(uevent_listener),
uevent_handlers_(uevent_handlers),
- num_handler_subprocesses_(std::thread::hardware_concurrency() ?: 4) {}
+ num_handler_subprocesses_(std::thread::hardware_concurrency() ?: 4),
+ enable_parallel_restorecon_(enable_parallel_restorecon) {}
void Run();
@@ -132,6 +134,8 @@
std::vector<std::unique_ptr<UeventHandler>>& uevent_handlers_;
unsigned int num_handler_subprocesses_;
+ bool enable_parallel_restorecon_;
+
std::vector<Uevent> uevent_queue_;
std::set<pid_t> subprocess_pids_;
@@ -155,7 +159,6 @@
selinux_android_restorecon(dir.c_str(), SELINUX_ANDROID_RESTORECON_RECURSE);
}
- _exit(EXIT_SUCCESS);
}
void ColdBoot::GenerateRestoreCon(const std::string& directory) {
@@ -195,7 +198,10 @@
if (pid == 0) {
UeventHandlerMain(i, num_handler_subprocesses_);
- RestoreConHandler(i, num_handler_subprocesses_);
+ if (enable_parallel_restorecon_) {
+ RestoreConHandler(i, num_handler_subprocesses_);
+ }
+ _exit(EXIT_SUCCESS);
}
subprocess_pids_.emplace(pid);
@@ -240,14 +246,20 @@
RegenerateUevents();
- selinux_android_restorecon("/sys", 0);
- selinux_android_restorecon("/sys/devices", 0);
- GenerateRestoreCon("/sys");
- // takes long time for /sys/devices, parallelize it
- GenerateRestoreCon("/sys/devices");
+ if (enable_parallel_restorecon_) {
+ selinux_android_restorecon("/sys", 0);
+ selinux_android_restorecon("/sys/devices", 0);
+ GenerateRestoreCon("/sys");
+ // takes long time for /sys/devices, parallelize it
+ GenerateRestoreCon("/sys/devices");
+ }
ForkSubProcesses();
+ if (!enable_parallel_restorecon_) {
+ selinux_android_restorecon("/sys", SELINUX_ANDROID_RESTORECON_RECURSE);
+ }
+
WaitForSubProcesses();
android::base::SetProperty(kColdBootDoneProp, "true");
@@ -284,7 +296,8 @@
std::move(ueventd_configuration.sysfs_permissions),
std::move(ueventd_configuration.subsystems), android::fs_mgr::GetBootDevices(), true));
uevent_handlers.emplace_back(std::make_unique<FirmwareHandler>(
- std::move(ueventd_configuration.firmware_directories)));
+ std::move(ueventd_configuration.firmware_directories),
+ std::move(ueventd_configuration.external_firmware_handlers)));
if (ueventd_configuration.enable_modalias_handling) {
std::vector<std::string> base_paths = {"/odm/lib/modules", "/vendor/lib/modules"};
@@ -293,7 +306,8 @@
UeventListener uevent_listener(ueventd_configuration.uevent_socket_rcvbuf_size);
if (!android::base::GetBoolProperty(kColdBootDoneProp, false)) {
- ColdBoot cold_boot(uevent_listener, uevent_handlers);
+ ColdBoot cold_boot(uevent_listener, uevent_handlers,
+ ueventd_configuration.enable_parallel_restorecon);
cold_boot.Run();
}
diff --git a/init/ueventd_parser.cpp b/init/ueventd_parser.cpp
index 8ee0cce..a74b247 100644
--- a/init/ueventd_parser.cpp
+++ b/init/ueventd_parser.cpp
@@ -88,18 +88,42 @@
return {};
}
-Result<void> ParseModaliasHandlingLine(std::vector<std::string>&& args,
- bool* enable_modalias_handling) {
+Result<void> ParseExternalFirmwareHandlerLine(
+ std::vector<std::string>&& args,
+ std::vector<ExternalFirmwareHandler>* external_firmware_handlers) {
+ if (args.size() != 4) {
+ return Error() << "external_firmware_handler lines must have exactly 3 parameters";
+ }
+
+ if (std::find_if(external_firmware_handlers->begin(), external_firmware_handlers->end(),
+ [&args](const auto& other) { return other.devpath == args[2]; }) !=
+ external_firmware_handlers->end()) {
+ return Error() << "found a previous external_firmware_handler with the same devpath, '"
+ << args[2] << "'";
+ }
+
+ passwd* pwd = getpwnam(args[2].c_str());
+ if (!pwd) {
+ return ErrnoError() << "invalid handler uid'" << args[2] << "'";
+ }
+
+ ExternalFirmwareHandler handler(std::move(args[1]), pwd->pw_uid, std::move(args[3]));
+ external_firmware_handlers->emplace_back(std::move(handler));
+
+ return {};
+}
+
+Result<void> ParseEnabledDisabledLine(std::vector<std::string>&& args, bool* feature) {
if (args.size() != 2) {
- return Error() << "modalias_handling lines take exactly one parameter";
+ return Error() << args[0] << " lines take exactly one parameter";
}
if (args[1] == "enabled") {
- *enable_modalias_handling = true;
+ *feature = true;
} else if (args[1] == "disabled") {
- *enable_modalias_handling = false;
+ *feature = false;
} else {
- return Error() << "modalias_handling takes either 'enabled' or 'disabled' as a parameter";
+ return Error() << args[0] << " takes either 'enabled' or 'disabled' as a parameter";
}
return {};
@@ -212,12 +236,18 @@
parser.AddSingleLineParser("firmware_directories",
std::bind(ParseFirmwareDirectoriesLine, _1,
&ueventd_configuration.firmware_directories));
+ parser.AddSingleLineParser("external_firmware_handler",
+ std::bind(ParseExternalFirmwareHandlerLine, _1,
+ &ueventd_configuration.external_firmware_handlers));
parser.AddSingleLineParser("modalias_handling",
- std::bind(ParseModaliasHandlingLine, _1,
+ std::bind(ParseEnabledDisabledLine, _1,
&ueventd_configuration.enable_modalias_handling));
parser.AddSingleLineParser("uevent_socket_rcvbuf_size",
std::bind(ParseUeventSocketRcvbufSizeLine, _1,
&ueventd_configuration.uevent_socket_rcvbuf_size));
+ parser.AddSingleLineParser("parallel_restorecon",
+ std::bind(ParseEnabledDisabledLine, _1,
+ &ueventd_configuration.enable_parallel_restorecon));
for (const auto& config : configs) {
parser.ParseConfig(config);
diff --git a/init/ueventd_parser.h b/init/ueventd_parser.h
index d476dec..eaafa5a 100644
--- a/init/ueventd_parser.h
+++ b/init/ueventd_parser.h
@@ -14,13 +14,13 @@
* limitations under the License.
*/
-#ifndef _INIT_UEVENTD_PARSER_H
-#define _INIT_UEVENTD_PARSER_H
+#pragma once
#include <string>
#include <vector>
#include "devices.h"
+#include "firmware_handler.h"
namespace android {
namespace init {
@@ -30,13 +30,13 @@
std::vector<SysfsPermissions> sysfs_permissions;
std::vector<Permissions> dev_permissions;
std::vector<std::string> firmware_directories;
+ std::vector<ExternalFirmwareHandler> external_firmware_handlers;
bool enable_modalias_handling = false;
size_t uevent_socket_rcvbuf_size = 0;
+ bool enable_parallel_restorecon = false;
};
UeventdConfiguration ParseConfig(const std::vector<std::string>& configs);
} // namespace init
} // namespace android
-
-#endif
diff --git a/init/ueventd_parser_test.cpp b/init/ueventd_parser_test.cpp
index 9c1cedf..172ba0b 100644
--- a/init/ueventd_parser_test.cpp
+++ b/init/ueventd_parser_test.cpp
@@ -20,6 +20,8 @@
#include <gtest/gtest.h>
#include <private/android_filesystem_config.h>
+#include "firmware_handler.h"
+
namespace android {
namespace init {
@@ -93,7 +95,7 @@
{"test_devname2", Subsystem::DEVNAME_UEVENT_DEVNAME, "/dev"},
{"test_devpath_dirname", Subsystem::DEVNAME_UEVENT_DEVPATH, "/dev/graphics"}};
- TestUeventdFile(ueventd_file, {subsystems, {}, {}, {}});
+ TestUeventdFile(ueventd_file, {subsystems, {}, {}, {}, {}});
}
TEST(ueventd_parser, Permissions) {
@@ -119,7 +121,7 @@
{"/sys/devices/virtual/*/input", "poll_delay", 0660, AID_ROOT, AID_INPUT},
};
- TestUeventdFile(ueventd_file, {{}, sysfs_permissions, permissions, {}});
+ TestUeventdFile(ueventd_file, {{}, sysfs_permissions, permissions, {}, {}});
}
TEST(ueventd_parser, FirmwareDirectories) {
@@ -135,7 +137,52 @@
"/more",
};
- TestUeventdFile(ueventd_file, {{}, {}, {}, firmware_directories});
+ TestUeventdFile(ueventd_file, {{}, {}, {}, firmware_directories, {}});
+}
+
+TEST(ueventd_parser, ExternalFirmwareHandlers) {
+ auto ueventd_file = R"(
+external_firmware_handler devpath root handler_path
+external_firmware_handler /devices/path/firmware/something001.bin system /vendor/bin/firmware_handler.sh
+external_firmware_handler /devices/path/firmware/something001.bin radio "/vendor/bin/firmware_handler.sh --has --arguments"
+)";
+
+ auto external_firmware_handlers = std::vector<ExternalFirmwareHandler>{
+ {
+ "devpath",
+ AID_ROOT,
+ "handler_path",
+ },
+ {
+ "/devices/path/firmware/something001.bin",
+ AID_SYSTEM,
+ "/vendor/bin/firmware_handler.sh",
+ },
+ {
+ "/devices/path/firmware/something001.bin",
+ AID_RADIO,
+ "/vendor/bin/firmware_handler.sh --has --arguments",
+ },
+ };
+
+ TestUeventdFile(ueventd_file, {{}, {}, {}, {}, external_firmware_handlers});
+}
+
+TEST(ueventd_parser, ExternalFirmwareHandlersDuplicate) {
+ auto ueventd_file = R"(
+external_firmware_handler devpath root handler_path
+external_firmware_handler devpath root handler_path2
+)";
+
+ auto external_firmware_handlers = std::vector<ExternalFirmwareHandler>{
+ {
+ "devpath",
+ AID_ROOT,
+ "handler_path",
+ },
+ };
+
+ TestUeventdFile(ueventd_file, {{}, {}, {}, {}, external_firmware_handlers});
}
TEST(ueventd_parser, UeventSocketRcvbufSize) {
@@ -144,7 +191,25 @@
uevent_socket_rcvbuf_size 8M
)";
- TestUeventdFile(ueventd_file, {{}, {}, {}, {}, false, 8 * 1024 * 1024});
+ TestUeventdFile(ueventd_file, {{}, {}, {}, {}, {}, false, 8 * 1024 * 1024});
+}
+
+TEST(ueventd_parser, EnabledDisabledLines) {
+ auto ueventd_file = R"(
+modalias_handling enabled
+parallel_restorecon enabled
+modalias_handling disabled
+)";
+
+ TestUeventdFile(ueventd_file, {{}, {}, {}, {}, {}, false, 0, true});
+
+ auto ueventd_file2 = R"(
+parallel_restorecon enabled
+modalias_handling enabled
+parallel_restorecon disabled
+)";
+
+ TestUeventdFile(ueventd_file2, {{}, {}, {}, {}, {}, true, 0, false});
}
TEST(ueventd_parser, AllTogether) {
@@ -178,7 +243,11 @@
/sys/devices/virtual/*/input poll_delay 0660 root input
firmware_directories /more
+external_firmware_handler /devices/path/firmware/firmware001.bin root /vendor/bin/touch.sh
+
uevent_socket_rcvbuf_size 6M
+modalias_handling enabled
+parallel_restorecon enabled
#ending comment
)";
@@ -208,10 +277,15 @@
"/more",
};
+ auto external_firmware_handlers = std::vector<ExternalFirmwareHandler>{
+ {"/devices/path/firmware/firmware001.bin", AID_ROOT, "/vendor/bin/touch.sh"},
+ };
+
size_t uevent_socket_rcvbuf_size = 6 * 1024 * 1024;
- TestUeventdFile(ueventd_file, {subsystems, sysfs_permissions, permissions, firmware_directories,
- false, uevent_socket_rcvbuf_size});
+ TestUeventdFile(ueventd_file,
+ {subsystems, sysfs_permissions, permissions, firmware_directories,
+ external_firmware_handlers, true, uevent_socket_rcvbuf_size, true});
}
// All of these lines are ill-formed, so test that there is 0 output.
@@ -230,6 +304,18 @@
subsystem #no name
+modalias_handling
+modalias_handling enabled enabled
+modalias_handling blah
+
+parallel_restorecon
+parallel_restorecon enabled enabled
+parallel_restorecon blah
+
+external_firmware_handler
+external_firmware_handler blah blah
+external_firmware_handler blah blah blah blah
+
)";
TestUeventdFile(ueventd_file, {});
diff --git a/libbacktrace/include/backtrace/BacktraceMap.h b/libbacktrace/include/backtrace/BacktraceMap.h
index f8d5058..e000a00 100644
--- a/libbacktrace/include/backtrace/BacktraceMap.h
+++ b/libbacktrace/include/backtrace/BacktraceMap.h
@@ -79,7 +79,7 @@
index_++;
return *this;
}
- iterator& operator++(int increment) {
+ const iterator operator++(int increment) {
index_ += increment;
return *this;
}
@@ -87,7 +87,7 @@
index_--;
return *this;
}
- iterator& operator--(int decrement) {
+ const iterator operator--(int decrement) {
index_ -= decrement;
return *this;
}
diff --git a/libcutils/fs_config.cpp b/libcutils/fs_config.cpp
index b29638c..d0d83de 100644
--- a/libcutils/fs_config.cpp
+++ b/libcutils/fs_config.cpp
@@ -84,7 +84,7 @@
{ 00755, AID_ROOT, AID_ROOT, 0, "system/etc/ppp" },
{ 00755, AID_ROOT, AID_SHELL, 0, "system/vendor" },
{ 00751, AID_ROOT, AID_SHELL, 0, "system/xbin" },
- { 00755, AID_ROOT, AID_SHELL, 0, "system/apex/*/bin" },
+ { 00751, AID_ROOT, AID_SHELL, 0, "system/apex/*/bin" },
{ 00751, AID_ROOT, AID_SHELL, 0, "vendor/bin" },
{ 00755, AID_ROOT, AID_SHELL, 0, "vendor" },
{ 00755, AID_ROOT, AID_ROOT, 0, 0 },
diff --git a/logcat/logcat.cpp b/logcat/logcat.cpp
index e2711af..4dcb338 100644
--- a/logcat/logcat.cpp
+++ b/logcat/logcat.cpp
@@ -32,6 +32,7 @@
#include <stdlib.h>
#include <string.h>
#include <sys/cdefs.h>
+#include <sys/ioctl.h>
#include <sys/resource.h>
#include <sys/socket.h>
#include <sys/stat.h>
@@ -158,8 +159,22 @@
enum helpType showHelp, const char* fmt, ...)
__printflike(3, 4);
-static int openLogFile(const char* pathname) {
- return open(pathname, O_WRONLY | O_APPEND | O_CREAT, S_IRUSR | S_IWUSR);
+#ifndef F2FS_IOC_SET_PIN_FILE
+#define F2FS_IOCTL_MAGIC 0xf5
+#define F2FS_IOC_SET_PIN_FILE _IOW(F2FS_IOCTL_MAGIC, 13, __u32)
+#endif
+
+static int openLogFile(const char* pathname, size_t sizeKB) {
+ int fd = open(pathname, O_WRONLY | O_APPEND | O_CREAT, S_IRUSR | S_IWUSR);
+ if (fd < 0) {
+ return fd;
+ }
+
+ // no need to check errors
+ __u32 set = 1;
+ ioctl(fd, F2FS_IOC_SET_PIN_FILE, &set);
+ fallocate(fd, FALLOC_FL_KEEP_SIZE, 0, (sizeKB << 10));
+ return fd;
}
static void close_output(android_logcat_context_internal* context) {
@@ -192,6 +207,7 @@
if (context->fds[1] == context->output_fd) {
context->fds[1] = -1;
}
+ posix_fadvise(context->output_fd, 0, 0, POSIX_FADV_DONTNEED);
close(context->output_fd);
}
context->output_fd = -1;
@@ -276,7 +292,7 @@
}
}
- context->output_fd = openLogFile(context->outputFileName);
+ context->output_fd = openLogFile(context->outputFileName, context->logRotateSizeKBytes);
if (context->output_fd < 0) {
logcat_panic(context, HELP_FALSE, "couldn't open output file");
@@ -398,7 +414,7 @@
close_output(context);
- context->output_fd = openLogFile(context->outputFileName);
+ context->output_fd = openLogFile(context->outputFileName, context->logRotateSizeKBytes);
if (context->output_fd < 0) {
logcat_panic(context, HELP_FALSE, "couldn't open output file");
diff --git a/logcat/logcatd.rc b/logcat/logcatd.rc
index 26c9de3..e986184 100644
--- a/logcat/logcatd.rc
+++ b/logcat/logcatd.rc
@@ -52,7 +52,7 @@
stop logcatd
# logcatd service
-service logcatd /system/bin/logcatd -L -b ${logd.logpersistd.buffer:-all} -v threadtime -v usec -v printable -D -f /data/misc/logd/logcat -r ${logd.logpersistd.rotate_kbytes:-1024} -n ${logd.logpersistd.size:-256} --id=${ro.build.id}
+service logcatd /system/bin/logcatd -L -b ${logd.logpersistd.buffer:-all} -v threadtime -v usec -v printable -D -f /data/misc/logd/logcat -r ${logd.logpersistd.rotate_kbytes:-2048} -n ${logd.logpersistd.size:-256} --id=${ro.build.id}
class late_start
disabled
# logd for write to /data/misc/logd, log group for read from log daemon
diff --git a/rootdir/init.rc b/rootdir/init.rc
index 0d3d4db..8808846 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -52,6 +52,36 @@
# the libraries are available to the processes started after this statement.
exec_start apexd-bootstrap
+ # These must already exist by the time boringssl_self_test32 / boringssl_self_test64 run.
+ mkdir /dev/boringssl 0755 root root
+ mkdir /dev/boringssl/selftest 0755 root root
+
+# Run boringssl self test for each ABI so that later processes can skip it. http://b/139348610
+on early-init && property:ro.product.cpu.abilist32=*
+ exec_start boringssl_self_test32
+on early-init && property:ro.product.cpu.abilist64=*
+ exec_start boringssl_self_test64
+on property:apexd.status=ready && property:ro.product.cpu.abilist32=*
+ exec_start boringssl_self_test_apex32
+on property:apexd.status=ready && property:ro.product.cpu.abilist64=*
+ exec_start boringssl_self_test_apex64
+
+service boringssl_self_test32 /system/bin/boringssl_self_test32
+ setenv BORINGSSL_SELF_TEST_CREATE_FLAG true # Any nonempty value counts as true
+ reboot_on_failure reboot,bootloader,boringssl-self-check-failed
+
+service boringssl_self_test64 /system/bin/boringssl_self_test64
+ setenv BORINGSSL_SELF_TEST_CREATE_FLAG true # Any nonempty value counts as true
+ reboot_on_failure reboot,bootloader,boringssl-self-check-failed
+
+service boringssl_self_test_apex32 /apex/com.android.conscrypt/bin/boringssl_self_test32
+ setenv BORINGSSL_SELF_TEST_CREATE_FLAG true # Any nonempty value counts as true
+ reboot_on_failure reboot,bootloader,boringssl-self-check-failed
+
+service boringssl_self_test_apex64 /apex/com.android.conscrypt/bin/boringssl_self_test64
+ setenv BORINGSSL_SELF_TEST_CREATE_FLAG true # Any nonempty value counts as true
+ reboot_on_failure reboot,bootloader,boringssl-self-check-failed
+
on init
sysclktz 0
@@ -127,10 +157,6 @@
mkdir /mnt/expand 0771 system system
mkdir /mnt/appfuse 0711 root root
- # These must already exist by the time boringssl_self_test32 / boringssl_self_test64 run.
- mkdir /dev/boringssl 0755 root root
- mkdir /dev/boringssl/selftest 0755 root root
-
# Storage views to support runtime permissions
mkdir /mnt/runtime 0700 root root
mkdir /mnt/runtime/default 0755 root root
@@ -315,16 +341,6 @@
start hwservicemanager
start vndservicemanager
-# Run boringssl self test for each ABI so that later processes can skip it. http://b/139348610
-on init && property:ro.product.cpu.abilist32=*:
- exec_reboot_on_failure boringssl-self-check-failed /system/bin/boringssl_self_test32
-on init && property:ro.product.cpu.abilist64=*
- exec_reboot_on_failure boringssl-self-check-failed /system/bin/boringssl_self_test64
-on property:apexd.status=ready && property:ro.product.cpu.abilist64=*
- exec_reboot_on_failure boringssl-self-check-failed /apex/com.android.conscrypt/bin/boringssl_self_test64
-on property:apexd.status=ready && property:ro.product.cpu.abilist32=*
- exec_reboot_on_failure boringssl-self-check-failed /apex/com.android.conscrypt/bin/boringssl_self_test32
-
# Healthd can trigger a full boot from charger mode by signaling this
# property when the power button is held.
on property:sys.boot_from_charger_mode=1
@@ -445,7 +461,6 @@
mark_post_data
# Start checkpoint before we touch data
- start vold
exec - system system -- /system/bin/vdc checkpoint prepareCheckpoint
# We chown/chmod /data again so because mount is run as root + defaults
diff --git a/rootdir/update_and_install_ld_config.mk b/rootdir/update_and_install_ld_config.mk
index dbe60e5..9d131dc 100644
--- a/rootdir/update_and_install_ld_config.mk
+++ b/rootdir/update_and_install_ld_config.mk
@@ -70,19 +70,30 @@
# /system image.
llndk_libraries_moved_to_apex_list:=$(LLNDK_MOVED_TO_APEX_LIBRARIES)
+# Returns the unique installed basenames of a module, or module.so if there are
+# none. The guess is to handle cases like libc, where the module itself is
+# marked uninstallable but a symlink is installed with the name libc.so.
# $(1): list of libraries
-# $(2): output file to write the list of libraries to
-define write-libs-to-file
-$(2): PRIVATE_LIBRARIES := $(1)
-$(2):
- echo -n > $$@ && $$(foreach lib,$$(PRIVATE_LIBRARIES),echo $$(lib).so >> $$@;)
+# $(2): suffix to to add to each library (not used for guess)
+define module-installed-files-or-guess
+$(foreach lib,$(1),$(or $(strip $(sort $(notdir $(call module-installed-files,$(lib)$(2))))),$(lib).so))
endef
-$(eval $(call write-libs-to-file,$(llndk_libraries_list),$(llndk_libraries_file)))
-$(eval $(call write-libs-to-file,$(vndksp_libraries_list),$(vndksp_libraries_file)))
-$(eval $(call write-libs-to-file,$(VNDK_CORE_LIBRARIES),$(vndkcore_libraries_file)))
-$(eval $(call write-libs-to-file,$(VNDK_PRIVATE_LIBRARIES),$(vndkprivate_libraries_file)))
+
+# $(1): list of libraries
+# $(2): suffix to add to each library
+# $(3): output file to write the list of libraries to
+define write-libs-to-file
+$(3): PRIVATE_LIBRARIES := $(1)
+$(3): PRIVATE_SUFFIX := $(2)
+$(3):
+ echo -n > $$@ && $$(foreach so,$$(call module-installed-files-or-guess,$$(PRIVATE_LIBRARIES),$$(PRIVATE_SUFFIX)),echo $$(so) >> $$@;)
+endef
+$(eval $(call write-libs-to-file,$(llndk_libraries_list),,$(llndk_libraries_file)))
+$(eval $(call write-libs-to-file,$(vndksp_libraries_list),.vendor,$(vndksp_libraries_file)))
+$(eval $(call write-libs-to-file,$(VNDK_CORE_LIBRARIES),.vendor,$(vndkcore_libraries_file)))
+$(eval $(call write-libs-to-file,$(VNDK_PRIVATE_LIBRARIES),.vendor,$(vndkprivate_libraries_file)))
ifeq ($(my_vndk_use_core_variant),true)
-$(eval $(call write-libs-to-file,$(VNDK_USING_CORE_VARIANT_LIBRARIES),$(vndk_using_core_variant_libraries_file)))
+$(eval $(call write-libs-to-file,$(VNDK_USING_CORE_VARIANT_LIBRARIES),,$(vndk_using_core_variant_libraries_file)))
endif
endif # ifneq ($(lib_list_from_prebuilts),true)
diff --git a/trusty/keymaster/4.0/TrustyKeymaster4Device.cpp b/trusty/keymaster/4.0/TrustyKeymaster4Device.cpp
index b5fc6bf..ec2ba12 100644
--- a/trusty/keymaster/4.0/TrustyKeymaster4Device.cpp
+++ b/trusty/keymaster/4.0/TrustyKeymaster4Device.cpp
@@ -17,6 +17,7 @@
#define LOG_TAG "android.hardware.keymaster@4.0-impl.trusty"
+#include <android/hardware/keymaster/3.0/IKeymasterDevice.h>
#include <authorization_set.h>
#include <cutils/log.h>
#include <keymaster/android_keymaster_messages.h>
@@ -46,6 +47,9 @@
using ::keymaster::UpdateOperationResponse;
using ::keymaster::ng::Tag;
+typedef ::android::hardware::keymaster::V3_0::Tag Tag3;
+using ::android::hardware::keymaster::V4_0::Constants;
+
namespace keymaster {
namespace V4_0 {
namespace {
@@ -79,6 +83,45 @@
return keymaster_tag_get_type(tag);
}
+/*
+ * injectAuthToken translates a KM4 authToken into a legacy AUTH_TOKEN tag
+ *
+ * Currently, system/keymaster's reference implementation only accepts this
+ * method for passing an auth token, so until that changes we need to
+ * translate to the old format.
+ */
+inline hidl_vec<KeyParameter> injectAuthToken(const hidl_vec<KeyParameter>& keyParamsBase,
+ const HardwareAuthToken& authToken) {
+ std::vector<KeyParameter> keyParams(keyParamsBase);
+ const size_t mac_len = static_cast<size_t>(Constants::AUTH_TOKEN_MAC_LENGTH);
+ /*
+ * mac.size() == 0 indicates no token provided, so we should not copy.
+ * mac.size() != mac_len means it is incompatible with the old
+ * hw_auth_token_t structure. This is forbidden by spec, but to be safe
+ * we only copy if mac.size() == mac_len, e.g. there is an authToken
+ * with a hw_auth_token_t compatible MAC.
+ */
+ if (authToken.mac.size() == mac_len) {
+ KeyParameter p;
+ p.tag = static_cast<Tag>(Tag3::AUTH_TOKEN);
+ p.blob.resize(sizeof(hw_auth_token_t));
+
+ hw_auth_token_t* auth_token = reinterpret_cast<hw_auth_token_t*>(p.blob.data());
+ auth_token->version = 0;
+ auth_token->challenge = authToken.challenge;
+ auth_token->user_id = authToken.userId;
+ auth_token->authenticator_id = authToken.authenticatorId;
+ auth_token->authenticator_type =
+ htobe32(static_cast<uint32_t>(authToken.authenticatorType));
+ auth_token->timestamp = htobe64(authToken.timestamp);
+ static_assert(mac_len == sizeof(auth_token->hmac));
+ memcpy(auth_token->hmac, authToken.mac.data(), mac_len);
+ keyParams.push_back(p);
+ }
+
+ return hidl_vec<KeyParameter>(std::move(keyParams));
+}
+
class KmParamSet : public keymaster_key_param_set_t {
public:
KmParamSet(const hidl_vec<KeyParameter>& keyParams) {
@@ -472,11 +515,11 @@
Return<void> TrustyKeymaster4Device::begin(KeyPurpose purpose, const hidl_vec<uint8_t>& key,
const hidl_vec<KeyParameter>& inParams,
const HardwareAuthToken& authToken, begin_cb _hidl_cb) {
- (void)authToken;
+ hidl_vec<KeyParameter> extendedParams = injectAuthToken(inParams, authToken);
BeginOperationRequest request;
request.purpose = legacy_enum_conversion(purpose);
request.SetKeyMaterial(key.data(), key.size());
- request.additional_params.Reinitialize(KmParamSet(inParams));
+ request.additional_params.Reinitialize(KmParamSet(extendedParams));
BeginOperationResponse response;
impl_->BeginOperation(request, &response);
@@ -496,16 +539,16 @@
const HardwareAuthToken& authToken,
const VerificationToken& verificationToken,
update_cb _hidl_cb) {
- (void)authToken;
(void)verificationToken;
UpdateOperationRequest request;
UpdateOperationResponse response;
hidl_vec<KeyParameter> resultParams;
hidl_vec<uint8_t> resultBlob;
+ hidl_vec<KeyParameter> extendedParams = injectAuthToken(inParams, authToken);
uint32_t resultConsumed = 0;
request.op_handle = operationHandle;
- request.additional_params.Reinitialize(KmParamSet(inParams));
+ request.additional_params.Reinitialize(KmParamSet(extendedParams));
size_t inp_size = input.size();
size_t ser_size = request.SerializedSize();
@@ -537,13 +580,13 @@
const HardwareAuthToken& authToken,
const VerificationToken& verificationToken,
finish_cb _hidl_cb) {
- (void)authToken;
(void)verificationToken;
FinishOperationRequest request;
+ hidl_vec<KeyParameter> extendedParams = injectAuthToken(inParams, authToken);
request.op_handle = operationHandle;
request.input.Reinitialize(input.data(), input.size());
request.signature.Reinitialize(signature.data(), signature.size());
- request.additional_params.Reinitialize(KmParamSet(inParams));
+ request.additional_params.Reinitialize(KmParamSet(extendedParams));
FinishOperationResponse response;
impl_->FinishOperation(request, &response);
diff --git a/trusty/keymaster/4.0/android.hardware.keymaster@4.0-service.trusty.xml b/trusty/keymaster/4.0/android.hardware.keymaster@4.0-service.trusty.xml
new file mode 100644
index 0000000..aa30707
--- /dev/null
+++ b/trusty/keymaster/4.0/android.hardware.keymaster@4.0-service.trusty.xml
@@ -0,0 +1,11 @@
+<manifest version="1.0" type="device">
+ <hal format="hidl">
+ <name>android.hardware.keymaster</name>
+ <transport>hwbinder</transport>
+ <version>4.0</version>
+ <interface>
+ <name>IKeymasterDevice</name>
+ <instance>default</instance>
+ </interface>
+ </hal>
+</manifest>
diff --git a/trusty/keymaster/Android.bp b/trusty/keymaster/Android.bp
index 3a9beaf..f32a69e 100644
--- a/trusty/keymaster/Android.bp
+++ b/trusty/keymaster/Android.bp
@@ -136,4 +136,6 @@
"libkeymaster4",
"android.hardware.keymaster@4.0"
],
+
+ vintf_fragments: ["4.0/android.hardware.keymaster@4.0-service.trusty.xml"],
}