diff options
author | Yifan Hong <elsk@google.com> | 2019-03-15 16:06:50 -0700 |
---|---|---|
committer | Yifan Hong <elsk@google.com> | 2019-03-20 16:28:06 -0700 |
commit | 7ea1a93cbf30eaaadeec0616a0008902909ad4a2 (patch) | |
tree | 9a5a299abb4364dfdea86afebd2ba90eb5fdf5c1 /partition_tools | |
parent | b84fb57317c99f56e77dd21d80d230a3d5aefe9b (diff) | |
download | extras-7ea1a93cbf30eaaadeec0616a0008902909ad4a2.tar.gz |
lpdump: Add --json option
This option outputs in JSON format. In addition, it emits information
like usage data on mounted file systems, and block size of super block
devices.
Test: `lpdump --json` on host and device
Bug: 126233777
Change-Id: Iad68eb3932f317637e07cd692cdebe71e52db769
Diffstat (limited to 'partition_tools')
-rw-r--r-- | partition_tools/Android.bp | 11 | ||||
-rw-r--r-- | partition_tools/dynamic_partitions_device_info.proto | 58 | ||||
-rw-r--r-- | partition_tools/lpdump.cc | 177 |
3 files changed, 240 insertions, 6 deletions
diff --git a/partition_tools/Android.bp b/partition_tools/Android.bp index 2b9e37fd..b68feba3 100644 --- a/partition_tools/Android.bp +++ b/partition_tools/Android.bp @@ -35,10 +35,18 @@ cc_library_shared { "libbase", "liblog", "liblp", + "libprotobuf-cpp-full", + ], + static_libs: [ + "libjsonpbparse", ], srcs: [ "lpdump.cc", + "dynamic_partitions_device_info.proto", ], + proto: { + type: "full", + }, target: { android: { shared_libs: [ @@ -58,6 +66,9 @@ cc_binary { "liblog", "liblp", ], + static_libs: [ + "libjsonpbparse", + ], target: { android: { srcs: [ diff --git a/partition_tools/dynamic_partitions_device_info.proto b/partition_tools/dynamic_partitions_device_info.proto new file mode 100644 index 00000000..e53b40e2 --- /dev/null +++ b/partition_tools/dynamic_partitions_device_info.proto @@ -0,0 +1,58 @@ +/* + * 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 = "proto3"; + +package android; + +// Keep in sync with proto files on EDI backend. Otherwise, new fields will +// go ignored. + +// Next: 6 +message DynamicPartitionsDeviceInfoProto { + bool enabled = 1; + bool retrofit = 2; + + // Next: 7 + message Partition { + string name = 1; + string group_name = 2 [json_name = "group_name"]; + bool is_dynamic = 3 [json_name = "is_dynamic"]; + /** Total size of all extents on super partition */ + uint64 size = 4; + /** Total size of the filesystem. */ + uint64 fs_size = 5 [json_name = "fs_size"]; + /** Used space of the filesystem. */ + uint64 fs_used = 6 [json_name = "fs_used"]; + } + repeated Partition partitions = 3; + + // Next: 3 + message Group { + string name = 1; + uint64 maximum_size = 2 [json_name = "maximum_size"]; + } + repeated Group groups = 4; + + // Next: 6 + message BlockDevice { + string name = 1; + uint64 size = 2; + uint64 block_size = 3 [json_name = "block_size"]; + uint64 alignment = 4; + uint64 alignment_offset = 5 [json_name = "alignment_offset"]; + } + repeated BlockDevice block_devices = 5 [json_name = "block_devices"]; +} diff --git a/partition_tools/lpdump.cc b/partition_tools/lpdump.cc index e3c6d393..1b607f8a 100644 --- a/partition_tools/lpdump.cc +++ b/partition_tools/lpdump.cc @@ -16,23 +16,31 @@ #include <getopt.h> #include <inttypes.h> +#include <sys/mount.h> #include <sys/stat.h> +#include <sys/statvfs.h> #include <sys/types.h> #include <sysexits.h> #include <unistd.h> #include <iostream> +#include <optional> +#include <regex> #include <string> #include <vector> -#include <android-base/strings.h> #include <android-base/parseint.h> +#include <android-base/properties.h> +#include <android-base/strings.h> #ifdef __ANDROID__ #include <cutils/android_get_control_file.h> #include <fs_mgr.h> #endif +#include <jsonpb/jsonpb.h> +#include <liblp/builder.h> #include <liblp/liblp.h> +#include "dynamic_partitions_device_info.pb.h" using namespace android; using namespace android::fs_mgr; @@ -43,10 +51,11 @@ static int usage(int /* argc */, char* argv[], std::ostream& cerr) { "Usage:\n" " " << argv[0] - << " [-s <SLOT#>|--slot=<SLOT#>] [FILE|DEVICE]\n" + << " [-s <SLOT#>|--slot=<SLOT#>] [-j|--json] [FILE|DEVICE]\n" "\n" "Options:\n" - " -s, --slot=N Slot number or suffix.\n"; + " -s, --slot=N Slot number or suffix.\n" + " -j, --json Print in JSON format.\n"; return EX_USAGE; } @@ -94,6 +103,150 @@ static std::string GetSuperPartionName() { return super_partition + GetSlotSuffix(); } +static std::string RemoveSuffix(const std::string& s, const std::string& suffix) { + if (base::EndsWith(s, suffix)) { + return s.substr(0, s.length() - suffix.length()); + } + return s; +} + +// Merge proto with information from metadata. +static bool MergeMetadata(const LpMetadata* metadata, + DynamicPartitionsDeviceInfoProto* proto) { + if (!metadata) return false; + auto builder = MetadataBuilder::New(*metadata); + if (!builder) return false; + + std::string slot_suffix = GetSlotSuffix(); + + for (const auto& group_name : builder->ListGroups()) { + auto group = builder->FindGroup(group_name); + if (!group) continue; + if (!base::EndsWith(group_name, slot_suffix)) continue; + auto group_proto = proto->add_groups(); + group_proto->set_name(RemoveSuffix(group_name, slot_suffix)); + group_proto->set_maximum_size(group->maximum_size()); + + for (auto partition : builder->ListPartitionsInGroup(group_name)) { + auto partition_name = partition->name(); + if (!base::EndsWith(partition_name, slot_suffix)) continue; + auto partition_proto = proto->add_partitions(); + partition_proto->set_name(RemoveSuffix(partition_name, slot_suffix)); + partition_proto->set_group_name(RemoveSuffix(group_name, slot_suffix)); + partition_proto->set_size(partition->size()); + partition_proto->set_is_dynamic(true); + } + } + + for (const auto& block_device : metadata->block_devices) { + std::string name = GetBlockDevicePartitionName(block_device); + BlockDeviceInfo info; + if (!builder->GetBlockDeviceInfo(name, &info)) { + continue; + } + auto block_device_proto = proto->add_block_devices(); + block_device_proto->set_name(RemoveSuffix(name, slot_suffix)); + block_device_proto->set_size(info.size); + block_device_proto->set_block_size(info.logical_block_size); + block_device_proto->set_alignment(info.alignment); + block_device_proto->set_alignment_offset(info.alignment_offset); + } + return true; +} + +#ifdef __ANDROID__ +static DynamicPartitionsDeviceInfoProto::Partition* FindPartition( + DynamicPartitionsDeviceInfoProto* proto, const std::string& partition) { + for (DynamicPartitionsDeviceInfoProto::Partition& p : *proto->mutable_partitions()) { + if (p.name() == partition) { + return &p; + } + } + return nullptr; +} + +static std::optional<std::string> GetReadonlyPartitionName(const android::fs_mgr::FstabEntry& entry) { + // Only report readonly partitions. + if ((entry.flags & MS_RDONLY) == 0) return std::nullopt; + std::regex regex("/([a-zA-Z_]*)$"); + std::smatch match; + if (!std::regex_match(entry.mount_point, match, regex)) return std::nullopt; + // On system-as-root devices, fstab lists / for system partition. + std::string partition = match[1]; + return partition.empty() ? "system" : partition; +} + +static bool MergeFsUsage(DynamicPartitionsDeviceInfoProto* proto, + std::ostream& cerr) { + using namespace std::string_literals; + Fstab fstab; + if (!ReadDefaultFstab(&fstab)) { + cerr << "Cannot read fstab\n"; + return false; + } + + for (const auto& entry : fstab) { + auto partition = GetReadonlyPartitionName(entry); + if (!partition) { + continue; + } + + // system is mounted to "/"; + const char* mount_point = (entry.mount_point == "/system") + ? "/" : entry.mount_point.c_str(); + + struct statvfs vst; + if (statvfs(mount_point, &vst) == -1) { + continue; + } + + auto partition_proto = FindPartition(proto, *partition); + if (partition_proto == nullptr) { + partition_proto = proto->add_partitions(); + partition_proto->set_name(*partition); + partition_proto->set_is_dynamic(false); + } + partition_proto->set_fs_size((uint64_t)vst.f_blocks * vst.f_frsize); + if (vst.f_bavail <= vst.f_blocks) { + partition_proto->set_fs_used((uint64_t)(vst.f_blocks - vst.f_bavail) * vst.f_frsize); + } + } + return true; +} +#endif + +// Print output in JSON format. +// If successful, this function must write a valid JSON string to "cout" and return 0. +static int PrintJson(const LpMetadata* metadata, std::ostream& cout, + std::ostream& cerr) { + DynamicPartitionsDeviceInfoProto proto; + + if (base::GetBoolProperty("ro.boot.dynamic_partitions", false)) { + proto.set_enabled(true); + } + + if (base::GetBoolProperty("ro.boot.dynamic_partitions_retrofit", false)) { + proto.set_retrofit(true); + } + + if (!MergeMetadata(metadata, &proto)) { + cerr << "Warning: Failed to read metadata.\n"; + } +#ifdef __ANDROID__ + if (!MergeFsUsage(&proto, cerr)) { + cerr << "Warning: Failed to read filesystem size and usage.\n"; + } +#endif + + auto error_or_json = jsonpb::MessageToJsonString(proto); + if (!error_or_json.ok()) { + cerr << error_or_json.error() << "\n"; + return EX_SOFTWARE; + } + cout << *error_or_json; + return EX_OK; +} + class FileOrBlockDeviceOpener final : public PartitionOpener { public: android::base::unique_fd Open(const std::string& path, int flags) const override { @@ -112,11 +265,14 @@ public: }; int LpdumpMain(int argc, char* argv[], std::ostream& cout, std::ostream& cerr) { + // clang-format off struct option options[] = { { "slot", required_argument, nullptr, 's' }, { "help", no_argument, nullptr, 'h' }, + { "json", no_argument, nullptr, 'j' }, { nullptr, 0, nullptr, 0 }, }; + // clang-format on // Allow this function to be invoked by lpdumpd multiple times. optind = 1; @@ -124,7 +280,8 @@ int LpdumpMain(int argc, char* argv[], std::ostream& cout, std::ostream& cerr) { int rv; int index; uint32_t slot = 0; - while ((rv = getopt_long_only(argc, argv, "s:h", options, &index)) != -1) { + bool json = false; + while ((rv = getopt_long_only(argc, argv, "s:jh", options, &index)) != -1) { switch (rv) { case 'h': return usage(argc, argv, cerr); @@ -133,14 +290,16 @@ int LpdumpMain(int argc, char* argv[], std::ostream& cout, std::ostream& cerr) { slot = SlotNumberForSlotSuffix(optarg); } break; + case 'j': + json = true; + break; } } std::unique_ptr<LpMetadata> pt; if (optind < argc) { - const char* file = argv[optind++]; - FileOrBlockDeviceOpener opener; + const char* file = argv[optind++]; pt = ReadMetadata(opener, file, slot); if (!pt && !IsBlockDevice(file)) { pt = ReadFromImageFile(file); @@ -153,6 +312,12 @@ int LpdumpMain(int argc, char* argv[], std::ostream& cout, std::ostream& cerr) { return usage(argc, argv, cerr); #endif } + + // --json option doesn't require metadata to be present. + if (json) { + return PrintJson(pt.get(), cout, cerr); + } + if (!pt) { cerr << "Failed to read metadata.\n"; return EX_NOINPUT; |