xref: /aosp_15_r20/system/extras/partition_tools/lpdump.cc (revision 288bf5226967eb3dac5cce6c939ccc2a7f2b4fe5)
1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <getopt.h>
18 #include <inttypes.h>
19 #include <sys/mount.h>
20 #include <sys/stat.h>
21 #include <sys/statvfs.h>
22 #include <sys/types.h>
23 #include <sysexits.h>
24 #include <unistd.h>
25 
26 #include <algorithm>
27 #include <iostream>
28 #include <optional>
29 #include <regex>
30 #include <string>
31 #include <vector>
32 
33 #include <android-base/parseint.h>
34 #include <android-base/properties.h>
35 #include <android-base/strings.h>
36 #ifdef __ANDROID__
37 #include <cutils/android_get_control_file.h>
38 #include <fs_mgr.h>
39 #include <libsnapshot/snapshot.h>
40 #endif
41 #include <jsonpb/jsonpb.h>
42 #include <liblp/builder.h>
43 #include <liblp/liblp.h>
44 
45 #include "dynamic_partitions_device_info.pb.h"
46 using namespace android;
47 using namespace android::fs_mgr;
48 
usage(int,char * argv[],std::ostream & cerr)49 static int usage(int /* argc */, char* argv[], std::ostream& cerr) {
50     cerr << argv[0]
51          << " - command-line tool for dumping Android Logical Partition images.\n"
52             "\n"
53             "Usage:\n"
54             "  "
55          << argv[0]
56          << " [-s <SLOT#>|--slot=<SLOT#>] [-j|--json] [FILE|DEVICE]\n"
57             "\n"
58             "Options:\n"
59             "  -s, --slot=N     Slot number or suffix.\n"
60             "  -j, --json       Print in JSON format.\n"
61             "  -d, --dump-metadata-size\n"
62             "                   Print the space reserved for metadata to stdout\n"
63             "                   in bytes.\n"
64             "  -a, --all        Dump all slots (not available in JSON mode).\n";
65     return EX_USAGE;
66 }
67 
BuildFlagString(const std::vector<std::string> & strings)68 static std::string BuildFlagString(const std::vector<std::string>& strings) {
69     return strings.empty() ? "none" : android::base::Join(strings, ",");
70 }
71 
BuildHeaderFlagString(uint32_t flags)72 static std::string BuildHeaderFlagString(uint32_t flags) {
73     std::vector<std::string> strings;
74 
75     if (flags & LP_HEADER_FLAG_VIRTUAL_AB_DEVICE) {
76         strings.emplace_back("virtual_ab_device");
77         flags &= ~LP_HEADER_FLAG_VIRTUAL_AB_DEVICE;
78     }
79 
80     for (uint32_t i = 0; i < sizeof(flags) * 8; i++) {
81         if (!(flags & (1U << i))) {
82             continue;
83         }
84         strings.emplace_back("unknown_flag_bit_" + std::to_string(i));
85     }
86     return BuildFlagString(strings);
87 }
88 
BuildAttributeString(uint32_t attrs)89 static std::string BuildAttributeString(uint32_t attrs) {
90     std::vector<std::string> strings;
91     if (attrs & LP_PARTITION_ATTR_READONLY) strings.emplace_back("readonly");
92     if (attrs & LP_PARTITION_ATTR_SLOT_SUFFIXED) strings.emplace_back("slot-suffixed");
93     if (attrs & LP_PARTITION_ATTR_UPDATED) strings.emplace_back("updated");
94     if (attrs & LP_PARTITION_ATTR_DISABLED) strings.emplace_back("disabled");
95     return BuildFlagString(strings);
96 }
97 
BuildGroupFlagString(uint32_t flags)98 static std::string BuildGroupFlagString(uint32_t flags) {
99     std::vector<std::string> strings;
100     if (flags & LP_GROUP_SLOT_SUFFIXED) strings.emplace_back("slot-suffixed");
101     return BuildFlagString(strings);
102 }
103 
BuildBlockDeviceFlagString(uint32_t flags)104 static std::string BuildBlockDeviceFlagString(uint32_t flags) {
105     std::vector<std::string> strings;
106     if (flags & LP_BLOCK_DEVICE_SLOT_SUFFIXED) strings.emplace_back("slot-suffixed");
107     return BuildFlagString(strings);
108 }
109 
110 // Reimplementation of fs_mgr_get_slot_suffix() without reading
111 // kernel commandline.
GetSlotSuffix()112 static std::string GetSlotSuffix() {
113     return base::GetProperty("ro.boot.slot_suffix", "");
114 }
115 
116 // Reimplementation of fs_mgr_get_super_partition_name() without reading
117 // kernel commandline. Always return the super partition at current slot.
GetSuperPartitionName(const std::optional<uint32_t> & slot={})118 static std::string GetSuperPartitionName(const std::optional<uint32_t>& slot = {}) {
119     std::string super_partition = base::GetProperty("ro.boot.super_partition", "");
120     if (super_partition.empty()) {
121         return LP_METADATA_DEFAULT_PARTITION_NAME;
122     }
123     if (slot.has_value()) {
124         return super_partition + SlotSuffixForSlotNumber(slot.value());
125     }
126     return super_partition + GetSlotSuffix();
127 }
128 
RemoveSuffix(const std::string & s,const std::string & suffix)129 static std::string RemoveSuffix(const std::string& s, const std::string& suffix) {
130     if (base::EndsWith(s, suffix)) {
131         return s.substr(0, s.length() - suffix.length());
132     }
133     return s;
134 }
135 
136 // Merge proto with information from metadata.
MergeMetadata(const LpMetadata * metadata,DynamicPartitionsDeviceInfoProto * proto)137 static bool MergeMetadata(const LpMetadata* metadata,
138                           DynamicPartitionsDeviceInfoProto* proto) {
139     if (!metadata) return false;
140     auto builder = MetadataBuilder::New(*metadata);
141     if (!builder) return false;
142 
143     std::string slot_suffix = GetSlotSuffix();
144 
145     for (const auto& group_name : builder->ListGroups()) {
146         auto group = builder->FindGroup(group_name);
147         if (!group) continue;
148         if (!base::EndsWith(group_name, slot_suffix)) continue;
149         auto group_proto = proto->add_groups();
150         group_proto->set_name(RemoveSuffix(group_name, slot_suffix));
151         group_proto->set_maximum_size(group->maximum_size());
152 
153         for (auto partition : builder->ListPartitionsInGroup(group_name)) {
154             auto partition_name = partition->name();
155             if (!base::EndsWith(partition_name, slot_suffix)) continue;
156             auto partition_proto = proto->add_partitions();
157             partition_proto->set_name(RemoveSuffix(partition_name, slot_suffix));
158             partition_proto->set_group_name(RemoveSuffix(group_name, slot_suffix));
159             partition_proto->set_size(partition->size());
160             partition_proto->set_is_dynamic(true);
161         }
162     }
163 
164     for (const auto& block_device : metadata->block_devices) {
165         std::string name = GetBlockDevicePartitionName(block_device);
166         BlockDeviceInfo info;
167         if (!builder->GetBlockDeviceInfo(name, &info)) {
168             continue;
169         }
170         auto block_device_proto = proto->add_block_devices();
171         block_device_proto->set_name(RemoveSuffix(name, slot_suffix));
172         block_device_proto->set_size(info.size);
173         block_device_proto->set_block_size(info.logical_block_size);
174         block_device_proto->set_alignment(info.alignment);
175         block_device_proto->set_alignment_offset(info.alignment_offset);
176     }
177 
178     auto super_device_proto = proto->mutable_super_device();
179     super_device_proto->set_name(GetSuperPartitionName());
180     super_device_proto->set_used_size(builder->UsedSpace());
181     super_device_proto->set_total_size(GetTotalSuperPartitionSize(*metadata));
182 
183     return true;
184 }
185 
186 #ifdef __ANDROID__
FindPartition(DynamicPartitionsDeviceInfoProto * proto,const std::string & partition)187 static DynamicPartitionsDeviceInfoProto::Partition* FindPartition(
188         DynamicPartitionsDeviceInfoProto* proto, const std::string& partition) {
189     for (DynamicPartitionsDeviceInfoProto::Partition& p : *proto->mutable_partitions()) {
190         if (p.name() == partition) {
191             return &p;
192         }
193     }
194     return nullptr;
195 }
196 
GetReadonlyPartitionName(const android::fs_mgr::FstabEntry & entry)197 static std::optional<std::string> GetReadonlyPartitionName(const android::fs_mgr::FstabEntry& entry) {
198     // Only report readonly partitions.
199     if ((entry.flags & MS_RDONLY) == 0) return std::nullopt;
200     std::regex regex("/([a-zA-Z_]*)$");
201     std::smatch match;
202     if (!std::regex_match(entry.mount_point, match, regex)) return std::nullopt;
203     // On system-as-root devices, fstab lists / for system partition.
204     std::string partition = match[1];
205     return partition.empty() ? "system" : partition;
206 }
207 
MergeFsUsage(DynamicPartitionsDeviceInfoProto * proto,std::ostream & cerr)208 static bool MergeFsUsage(DynamicPartitionsDeviceInfoProto* proto,
209                          std::ostream& cerr) {
210     using namespace std::string_literals;
211     Fstab fstab;
212     if (!ReadDefaultFstab(&fstab)) {
213         cerr << "Cannot read fstab\n";
214         return false;
215     }
216 
217     for (const auto& entry : fstab) {
218         auto partition = GetReadonlyPartitionName(entry);
219         if (!partition) {
220             continue;
221         }
222 
223         // system is mounted to "/";
224         const char* mount_point = (entry.mount_point == "/system")
225             ? "/" : entry.mount_point.c_str();
226 
227         struct statvfs vst;
228         if (statvfs(mount_point, &vst) == -1) {
229             continue;
230         }
231 
232         auto partition_proto = FindPartition(proto, *partition);
233         if (partition_proto == nullptr) {
234             partition_proto = proto->add_partitions();
235             partition_proto->set_name(*partition);
236             partition_proto->set_is_dynamic(false);
237         }
238         partition_proto->set_fs_size((uint64_t)vst.f_blocks * vst.f_frsize);
239 
240         if (!entry.fs_type.empty()) {
241             partition_proto->set_fs_type(entry.fs_type);
242         } else {
243             partition_proto->set_fs_type("UNKNOWN");
244         }
245 
246         if (vst.f_bavail <= vst.f_blocks) {
247             partition_proto->set_fs_used((uint64_t)(vst.f_blocks - vst.f_bavail) * vst.f_frsize);
248         }
249     }
250     return true;
251 }
DumpSnapshotState(std::ostream & output)252 static void DumpSnapshotState(std::ostream& output) {
253     if (android::base::GetBoolProperty("ro.virtual_ab.enabled", false)) {
254         if (auto sm = android::snapshot::SnapshotManager::New()) {
255             output << "---------------\n";
256             output << "Snapshot state:\n";
257             output << "---------------\n";
258             sm->Dump(output);
259         }
260     }
261 }
262 #endif
263 
264 // Print output in JSON format.
265 // If successful, this function must write a valid JSON string to "cout" and return 0.
PrintJson(const LpMetadata * metadata,std::ostream & cout,std::ostream & cerr)266 static int PrintJson(const LpMetadata* metadata, std::ostream& cout,
267                      std::ostream& cerr) {
268     DynamicPartitionsDeviceInfoProto proto;
269 
270     if (base::GetBoolProperty("ro.boot.dynamic_partitions", false)) {
271         proto.set_enabled(true);
272     }
273 
274     if (base::GetBoolProperty("ro.boot.dynamic_partitions_retrofit", false)) {
275         proto.set_retrofit(true);
276     }
277 
278     if (!MergeMetadata(metadata, &proto)) {
279         cerr << "Warning: Failed to read metadata.\n";
280     }
281 #ifdef __ANDROID__
282     if (!MergeFsUsage(&proto, cerr)) {
283         cerr << "Warning: Failed to read filesystem size and usage.\n";
284     }
285 #endif
286 
287     auto error_or_json = jsonpb::MessageToJsonString(proto);
288     if (!error_or_json.ok()) {
289         cerr << error_or_json.error() << "\n";
290         return EX_SOFTWARE;
291     }
292     cout << *error_or_json;
293     return EX_OK;
294 }
295 
DumpMetadataSize(const LpMetadata & metadata,std::ostream & cout)296 static int DumpMetadataSize(const LpMetadata& metadata, std::ostream& cout) {
297     auto super_device = GetMetadataSuperBlockDevice(metadata);
298     uint64_t metadata_size = super_device->first_logical_sector * LP_SECTOR_SIZE;
299     cout << metadata_size << std::endl;
300     return EX_OK;
301 }
302 
303 class FileOrBlockDeviceOpener final : public PartitionOpener {
304 public:
Open(const std::string & path,int flags) const305     android::base::unique_fd Open(const std::string& path, int flags) const override {
306         // Try a local file first.
307         android::base::unique_fd fd;
308 
309 #ifdef __ANDROID__
310         fd.reset(android_get_control_file(path.c_str()));
311         if (fd >= 0) return fd;
312 #endif
313         fd.reset(open(path.c_str(), flags));
314         if (fd >= 0) return fd;
315 
316         return PartitionOpener::Open(path, flags);
317     }
318 };
319 
320 std::optional<std::tuple<std::string, uint64_t>>
ParseLinearExtentData(const LpMetadata & pt,const LpMetadataExtent & extent)321 ParseLinearExtentData(const LpMetadata& pt, const LpMetadataExtent& extent) {
322     if (extent.target_type != LP_TARGET_TYPE_LINEAR) {
323         return std::nullopt;
324     }
325     const auto& block_device = pt.block_devices[extent.target_source];
326     std::string device_name = GetBlockDevicePartitionName(block_device);
327     return std::make_tuple(std::move(device_name), extent.target_data);
328 }
329 
PrintMetadata(const LpMetadata & pt,std::ostream & cout)330 static void PrintMetadata(const LpMetadata& pt, std::ostream& cout) {
331     cout << "Metadata version: " << pt.header.major_version << "." << pt.header.minor_version
332          << "\n";
333     cout << "Metadata size: " << (pt.header.header_size + pt.header.tables_size) << " bytes\n";
334     cout << "Metadata max size: " << pt.geometry.metadata_max_size << " bytes\n";
335     cout << "Metadata slot count: " << pt.geometry.metadata_slot_count << "\n";
336     cout << "Header flags: " << BuildHeaderFlagString(pt.header.flags) << "\n";
337     cout << "Partition table:\n";
338     cout << "------------------------\n";
339 
340     std::vector<std::tuple<std::string, const LpMetadataExtent*>> extents;
341 
342     for (const auto& partition : pt.partitions) {
343         std::string name = GetPartitionName(partition);
344         std::string group_name = GetPartitionGroupName(pt.groups[partition.group_index]);
345         cout << "  Name: " << name << "\n";
346         cout << "  Group: " << group_name << "\n";
347         cout << "  Attributes: " << BuildAttributeString(partition.attributes) << "\n";
348         cout << "  Extents:\n";
349         uint64_t first_sector = 0;
350         for (size_t i = 0; i < partition.num_extents; i++) {
351             const LpMetadataExtent& extent = pt.extents[partition.first_extent_index + i];
352             cout << "    " << first_sector << " .. " << (first_sector + extent.num_sectors - 1)
353                  << " ";
354             first_sector += extent.num_sectors;
355             if (extent.target_type == LP_TARGET_TYPE_LINEAR) {
356                 const auto& block_device = pt.block_devices[extent.target_source];
357                 std::string device_name = GetBlockDevicePartitionName(block_device);
358                 cout << "linear " << device_name.c_str() << " " << extent.target_data;
359             } else if (extent.target_type == LP_TARGET_TYPE_ZERO) {
360                 cout << "zero";
361             }
362             extents.push_back(std::make_tuple(name, &extent));
363             cout << "\n";
364         }
365         cout << "------------------------\n";
366     }
367 
368     std::sort(extents.begin(), extents.end(), [&](const auto& x, const auto& y) {
369         auto x_data = ParseLinearExtentData(pt, *std::get<1>(x));
370         auto y_data = ParseLinearExtentData(pt, *std::get<1>(y));
371         return x_data < y_data;
372     });
373 
374     cout << "Super partition layout:\n";
375     cout << "------------------------\n";
376     for (auto&& [name, extent] : extents) {
377         auto data = ParseLinearExtentData(pt, *extent);
378         if (!data) continue;
379         auto&& [block_device, offset] = *data;
380         cout << block_device << ": " << offset << " .. " << (offset + extent->num_sectors)
381              << ": " << name << " (" << extent->num_sectors << " sectors)\n";
382     }
383     cout << "------------------------\n";
384 
385     cout << "Block device table:\n";
386     cout << "------------------------\n";
387     for (const auto& block_device : pt.block_devices) {
388         std::string partition_name = GetBlockDevicePartitionName(block_device);
389         cout << "  Partition name: " << partition_name << "\n";
390         cout << "  First sector: " << block_device.first_logical_sector << "\n";
391         cout << "  Size: " << block_device.size << " bytes\n";
392         cout << "  Flags: " << BuildBlockDeviceFlagString(block_device.flags) << "\n";
393         cout << "------------------------\n";
394     }
395 
396     cout << "Group table:\n";
397     cout << "------------------------\n";
398     for (const auto& group : pt.groups) {
399         std::string group_name = GetPartitionGroupName(group);
400         cout << "  Name: " << group_name << "\n";
401         cout << "  Maximum size: " << group.maximum_size << " bytes\n";
402         cout << "  Flags: " << BuildGroupFlagString(group.flags) << "\n";
403         cout << "------------------------\n";
404     }
405 }
406 
ReadDeviceOrFile(const std::string & path,uint32_t slot)407 static std::unique_ptr<LpMetadata> ReadDeviceOrFile(const std::string& path, uint32_t slot) {
408     if (IsEmptySuperImage(path)) {
409         return ReadFromImageFile(path);
410     }
411     return ReadMetadata(path, slot);
412 }
413 
LpdumpMain(int argc,char * argv[],std::ostream & cout,std::ostream & cerr)414 int LpdumpMain(int argc, char* argv[], std::ostream& cout, std::ostream& cerr) {
415     // clang-format off
416     struct option options[] = {
417         { "all", no_argument, nullptr, 'a' },
418         { "slot", required_argument, nullptr, 's' },
419         { "help", no_argument, nullptr, 'h' },
420         { "json", no_argument, nullptr, 'j' },
421         { "dump-metadata-size", no_argument, nullptr, 'd' },
422         { "is-super-empty", no_argument, nullptr, 'e' },
423         { nullptr, 0, nullptr, 0 },
424     };
425     // clang-format on
426 
427     // Allow this function to be invoked by lpdumpd multiple times.
428     optind = 1;
429 
430     int rv;
431     int index;
432     bool json = false;
433     bool dump_metadata_size = false;
434     bool dump_all = false;
435     std::optional<uint32_t> slot;
436     while ((rv = getopt_long_only(argc, argv, "s:jhde", options, &index)) != -1) {
437         switch (rv) {
438             case 'a':
439                 dump_all = true;
440                 break;
441             case 'h':
442                 usage(argc, argv, cout);
443                 return EX_OK;
444             case 's': {
445                 uint32_t slot_arg;
446                 if (android::base::ParseUint(optarg, &slot_arg)) {
447                     slot = slot_arg;
448                 } else {
449                     slot = SlotNumberForSlotSuffix(optarg);
450                 }
451                 break;
452             }
453             case 'e':
454                 // This is ignored, we now derive whether it's empty automatically.
455                 break;
456             case 'd':
457                 dump_metadata_size = true;
458                 break;
459             case 'j':
460                 json = true;
461                 break;
462             case '?':
463             case ':':
464                 return usage(argc, argv, cerr);
465         }
466     }
467 
468     if (dump_all) {
469         if (slot.has_value()) {
470             cerr << "Cannot specify both --all and --slot.\n";
471             return usage(argc, argv, cerr);
472         }
473         if (json) {
474             cerr << "Cannot specify both --all and --json.\n";
475             return usage(argc, argv, cerr);
476         }
477 
478         // When dumping everything always start from the first slot.
479         slot = 0;
480     }
481 
482 #ifdef __ANDROID__
483     // Use the current slot as a default for A/B devices.
484     auto current_slot_suffix = GetSlotSuffix();
485     if (!slot.has_value() && !current_slot_suffix.empty()) {
486         slot = SlotNumberForSlotSuffix(current_slot_suffix);
487     }
488 #endif
489 
490     // If we still haven't determined a slot yet, use the first one.
491     if (!slot.has_value()) {
492         slot = 0;
493     }
494 
495     // Determine the path to the super partition (or image). If an explicit
496     // path is given, we use it for everything. Otherwise, we will infer it
497     // at the time we need to read metadata.
498     std::string super_path;
499     bool override_super_name = (optind < argc);
500     if (override_super_name) {
501         super_path = argv[optind++];
502     } else {
503 #ifdef __ANDROID__
504         super_path = GetSuperPartitionName(slot);
505 #else
506         cerr << "Must specify a super partition image.\n";
507         return usage(argc, argv, cerr);
508 #endif
509     }
510 
511     auto pt = ReadDeviceOrFile(super_path, slot.value());
512 
513     // --json option doesn't require metadata to be present.
514     if (json) {
515         return PrintJson(pt.get(), cout, cerr);
516     }
517 
518     if (!pt) {
519         cerr << "Failed to read metadata.\n";
520         return EX_NOINPUT;
521     }
522 
523     if (dump_metadata_size) {
524         return DumpMetadataSize(*pt.get(), cout);
525     }
526 
527     // When running on the device, we can check the slot count. Otherwise we
528     // use the # of metadata slots. (There is an extra slot we don't want to
529     // dump because it is currently unused.)
530 #ifdef __ANDROID__
531     uint32_t num_slots = current_slot_suffix.empty() ? 1 : 2;
532     if (dump_all && num_slots > 1) {
533         cout << "Current slot: " << current_slot_suffix << "\n";
534     }
535 #else
536     uint32_t num_slots = pt->geometry.metadata_slot_count;
537 #endif
538     // Empty images only have one slot.
539     if (IsEmptySuperImage(super_path)) {
540         num_slots = 1;
541     }
542 
543     if (num_slots > 1) {
544         cout << "Slot " << slot.value() << ":\n";
545     }
546     PrintMetadata(*pt.get(), cout);
547 
548     if (dump_all) {
549         for (uint32_t i = 1; i < num_slots; i++) {
550             if (!override_super_name) {
551                 super_path = GetSuperPartitionName(i);
552             }
553 
554             pt = ReadDeviceOrFile(super_path, i);
555             if (!pt) {
556                 continue;
557             }
558 
559             cout << "\nSlot " << i << ":\n";
560             PrintMetadata(*pt.get(), cout);
561         }
562     }
563 #ifdef __ANDROID__
564     DumpSnapshotState(cout);
565 #endif
566 
567     return EX_OK;
568 }
569 
LpdumpMain(int argc,char * argv[])570 int LpdumpMain(int argc, char* argv[]) {
571     return LpdumpMain(argc, argv, std::cout, std::cerr);
572 }
573