blob: e6c4e5305be77dd21972d98f0efaa7ee693ec089 [file] [log] [blame]
David Anderson41241232018-06-13 16:50:11 -07001/*
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 <stdio.h>
David Anderson192e9102022-01-28 14:13:56 -080020#ifndef WIN32
David Anderson41241232018-06-13 16:50:11 -070021#include <sysexits.h>
David Anderson192e9102022-01-28 14:13:56 -080022#endif
David Anderson41241232018-06-13 16:50:11 -070023
Inseob Kim74414a92021-04-01 13:14:47 +090024#include <algorithm>
David Anderson41241232018-06-13 16:50:11 -070025#include <memory>
26
27#include <android-base/parseint.h>
Inseob Kim74414a92021-04-01 13:14:47 +090028#include <android-base/result.h>
David Anderson41241232018-06-13 16:50:11 -070029#include <android-base/strings.h>
30#include <liblp/builder.h>
David Anderson0640c672018-07-12 13:10:57 -070031#include <liblp/liblp.h>
David Anderson41241232018-06-13 16:50:11 -070032
33using namespace android;
34using namespace android::fs_mgr;
35
Inseob Kim74414a92021-04-01 13:14:47 +090036using android::base::Error;
37using android::base::Result;
38
David Anderson192e9102022-01-28 14:13:56 -080039#ifdef WIN32
40static constexpr int EX_OK = 0;
41static constexpr int EX_USAGE = 1;
42static constexpr int EX_SOFTWARE = 2;
43static constexpr int EX_CANTCREAT = 3;
44#endif
45
David Anderson41241232018-06-13 16:50:11 -070046/* Prints program usage to |where|. */
47static int usage(int /* argc */, char* argv[]) {
48 fprintf(stderr,
49 "%s - command-line tool for creating Android Logical Partition images.\n"
50 "\n"
51 "Usage:\n"
52 " %s [options]\n"
53 "\n"
54 "Required options:\n"
Inseob Kim74414a92021-04-01 13:14:47 +090055 " -d,--device-size=[SIZE|auto] Size of the block device for logical partitions.\n"
56 " Can be set to auto to automatically calculate the\n"
57 " minimum size, the sum of partition sizes plus\n"
58 " metadata-size times the number of partitions.\n"
David Anderson41241232018-06-13 16:50:11 -070059 " -m,--metadata-size=SIZE Maximum size to reserve for partition metadata.\n"
60 " -s,--metadata-slots=COUNT Number of slots to store metadata copies.\n"
61 " -p,--partition=DATA Add a partition given the data, see below.\n"
62 " -o,--output=FILE Output file.\n"
63 "\n"
David Anderson24a436a2018-07-17 14:53:29 -070064 "Optional:\n"
David Andersonaca58a42018-07-17 15:54:44 -070065 " -b,--block-size=SIZE Physical block size, defaults to 4096.\n"
David Anderson24a436a2018-07-17 14:53:29 -070066 " -a,--alignment=N Optimal partition alignment in bytes.\n"
67 " -O,--alignment-offset=N Alignment offset in bytes to device parent.\n"
68 " -S,--sparse Output a sparse image for fastboot.\n"
David Andersoncf0d80a2018-07-17 18:29:29 -070069 " -i,--image=PARTITION=FILE If building a sparse image for fastboot, include\n"
70 " the given file (or sparse file) as initial data for\n"
71 " the named partition.\n"
David Anderson97ec14b2018-10-02 18:50:12 -070072 " -g,--group=GROUP:SIZE Define a named partition group with the given\n"
73 " maximum size.\n"
David Andersonc6c59202018-10-23 17:52:57 -070074 " -D,--device=DATA Add a block device that the super partition\n"
75 " spans over. If specified, then -d/--device-size\n"
76 " and alignments must not be specified. The format\n"
77 " for DATA is listed below.\n"
78 " -n,--super-name=NAME Specify the name of the block device that will\n"
79 " house the super partition.\n"
David Anderson1c5907a2018-11-07 18:27:14 -080080 " -x,--auto-slot-suffixing Mark the block device and partition names needing\n"
81 " slot suffixes before being used.\n"
David Andersonad8243f2019-05-24 12:19:22 -070082 " -F,--force-full-image Force a full image to be written even if no\n"
83 " partition images were specified. Normally, this\n"
84 " would produce a minimal super_empty.img which\n"
85 " cannot be flashed; force-full-image will produce\n"
86 " a flashable image.\n"
David Anderson7b14a8d2019-12-13 15:34:54 -080087 " --virtual-ab Add the VIRTUAL_AB_DEVICE flag to the metadata\n"
88 " header. Note that the resulting super.img will\n"
89 " require a liblp capable of parsing a v1.2 header.\n"
David Anderson24a436a2018-07-17 14:53:29 -070090 "\n"
91 "Partition data format:\n"
David Andersona4b73b42018-10-04 08:38:09 -070092 " <name>:<attributes>:<size>[:group]\n"
David Andersonc6c59202018-10-23 17:52:57 -070093 " Attrs must be 'none' or 'readonly'.\n"
94 "\n"
95 "Device data format:\n"
96 " <partition_name>:<size>[:<alignment>:<alignment_offset>]\n"
97 " The partition name is the basename of the /dev/block/by-name/ path of the\n"
98 " block device. The size is the device size in bytes. The alignment and\n"
99 " alignment offset parameters are the same as -a/--alignment and \n"
100 " -O/--alignment-offset.\n",
David Anderson41241232018-06-13 16:50:11 -0700101 argv[0], argv[0]);
102 return EX_USAGE;
103}
104
David Anderson07ee83b2019-12-13 15:31:10 -0800105enum class Option : int {
David Anderson7b14a8d2019-12-13 15:34:54 -0800106 // Long-only options.
107 kVirtualAB = 1,
108
David Anderson07ee83b2019-12-13 15:31:10 -0800109 // Short character codes.
110 kDeviceSize = 'd',
111 kMetadataSize = 'm',
112 kMetadataSlots = 's',
113 kPartition = 'p',
114 kOutput = 'o',
115 kHelp = 'h',
116 kAlignmentOffset = 'O',
117 kAlignment = 'a',
118 kSparse = 'S',
119 kBlockSize = 'b',
120 kImage = 'i',
121 kGroup = 'g',
122 kDevice = 'D',
123 kSuperName = 'n',
124 kAutoSlotSuffixing = 'x',
125 kForceFullImage = 'F',
126};
127
Inseob Kim74414a92021-04-01 13:14:47 +0900128struct PartitionInfo {
129 std::string name;
130 uint64_t size;
131 uint32_t attribute_flags;
132 std::string group_name;
133
134 static Result<PartitionInfo> Parse(const char* arg) {
135 std::vector<std::string> parts = android::base::Split(arg, ":");
136 if (parts.size() > 4) {
137 return Error() << "Partition info has invalid formatting.";
138 }
139
140 std::string name = parts[0];
141 if (name.empty()) {
142 return Error() << "Partition must have a valid name.";
143 }
144
145 uint64_t size;
146 if (!android::base::ParseUint(parts[2].c_str(), &size)) {
147 return Error() << "Partition must have a valid size.";
148 }
149
150 uint32_t attribute_flags = 0;
151 std::string attributes = parts[1];
152 if (attributes == "readonly") {
153 attribute_flags |= LP_PARTITION_ATTR_READONLY;
154 } else if (attributes != "none") {
155 return Error() << "Attribute not recognized: " << attributes;
156 }
157
158 std::string group_name = "default";
159 if (parts.size() >= 4) {
160 group_name = parts[3];
161 }
162
163 return PartitionInfo{name, size, attribute_flags, group_name};
164 }
165};
166
167static uint64_t CalculateBlockDeviceSize(uint32_t alignment, uint32_t metadata_size,
168 const std::vector<PartitionInfo>& partitions) {
169 uint64_t ret = std::max(alignment, LP_PARTITION_RESERVED_BYTES +
170 (LP_METADATA_GEOMETRY_SIZE + metadata_size) * 2) +
171 partitions.size() * alignment;
172 for (const auto& partition_info : partitions) {
173 ret += partition_info.size;
174 }
175 return ret;
176}
177
David Anderson41241232018-06-13 16:50:11 -0700178int main(int argc, char* argv[]) {
179 struct option options[] = {
David Anderson07ee83b2019-12-13 15:31:10 -0800180 { "device-size", required_argument, nullptr, (int)Option::kDeviceSize },
181 { "metadata-size", required_argument, nullptr, (int)Option::kMetadataSize },
182 { "metadata-slots", required_argument, nullptr, (int)Option::kMetadataSlots },
183 { "partition", required_argument, nullptr, (int)Option::kPartition },
184 { "output", required_argument, nullptr, (int)Option::kOutput },
185 { "help", no_argument, nullptr, (int)Option::kOutput },
186 { "alignment-offset", required_argument, nullptr, (int)Option::kAlignmentOffset },
187 { "alignment", required_argument, nullptr, (int)Option::kAlignment },
188 { "sparse", no_argument, nullptr, (int)Option::kSparse },
189 { "block-size", required_argument, nullptr, (int)Option::kBlockSize },
190 { "image", required_argument, nullptr, (int)Option::kImage },
191 { "group", required_argument, nullptr, (int)Option::kGroup },
192 { "device", required_argument, nullptr, (int)Option::kDevice },
193 { "super-name", required_argument, nullptr, (int)Option::kSuperName },
194 { "auto-slot-suffixing", no_argument, nullptr, (int)Option::kAutoSlotSuffixing },
195 { "force-full-image", no_argument, nullptr, (int)Option::kForceFullImage },
David Anderson7b14a8d2019-12-13 15:34:54 -0800196 { "virtual-ab", no_argument, nullptr, (int)Option::kVirtualAB },
David Anderson41241232018-06-13 16:50:11 -0700197 { nullptr, 0, nullptr, 0 },
198 };
199
200 uint64_t blockdevice_size = 0;
201 uint32_t metadata_size = 0;
202 uint32_t metadata_slots = 0;
David Anderson63b1dd62018-07-09 14:31:20 -0700203 uint32_t alignment_offset = 0;
204 uint32_t alignment = kDefaultPartitionAlignment;
David Andersonaca58a42018-07-17 15:54:44 -0700205 uint32_t block_size = 4096;
David Andersonc6c59202018-10-23 17:52:57 -0700206 std::string super_name = "super";
David Anderson41241232018-06-13 16:50:11 -0700207 std::string output_path;
Inseob Kim74414a92021-04-01 13:14:47 +0900208 std::vector<PartitionInfo> partitions;
David Anderson97ec14b2018-10-02 18:50:12 -0700209 std::vector<std::string> groups;
David Andersonc6c59202018-10-23 17:52:57 -0700210 std::vector<BlockDeviceInfo> block_devices;
David Andersoncf0d80a2018-07-17 18:29:29 -0700211 std::map<std::string, std::string> images;
David Anderson3aa94b22018-07-13 17:38:52 -0700212 bool output_sparse = false;
David Andersonc6c59202018-10-23 17:52:57 -0700213 bool has_implied_super = false;
David Anderson1c5907a2018-11-07 18:27:14 -0800214 bool auto_slot_suffixing = false;
David Andersonad8243f2019-05-24 12:19:22 -0700215 bool force_full_image = false;
David Anderson7b14a8d2019-12-13 15:34:54 -0800216 bool virtual_ab = false;
Inseob Kim74414a92021-04-01 13:14:47 +0900217 bool auto_blockdevice_size = false;
David Anderson41241232018-06-13 16:50:11 -0700218
219 int rv;
220 int index;
David Andersonad8243f2019-05-24 12:19:22 -0700221 while ((rv = getopt_long_only(argc, argv, "d:m:s:p:o:h:FSx", options, &index)) != -1) {
David Anderson07ee83b2019-12-13 15:31:10 -0800222 switch ((Option)rv) {
223 case Option::kHelp:
David Anderson41241232018-06-13 16:50:11 -0700224 return usage(argc, argv);
David Anderson07ee83b2019-12-13 15:31:10 -0800225 case Option::kDeviceSize:
Inseob Kim74414a92021-04-01 13:14:47 +0900226 if (strcmp(optarg, "auto") == 0) {
227 auto_blockdevice_size = true;
228 } else if (!android::base::ParseUint(optarg, &blockdevice_size) ||
229 !blockdevice_size) {
David Anderson41241232018-06-13 16:50:11 -0700230 fprintf(stderr, "Invalid argument to --device-size.\n");
231 return EX_USAGE;
232 }
David Andersonc6c59202018-10-23 17:52:57 -0700233 has_implied_super = true;
David Anderson41241232018-06-13 16:50:11 -0700234 break;
David Anderson07ee83b2019-12-13 15:31:10 -0800235 case Option::kMetadataSize:
David Anderson41241232018-06-13 16:50:11 -0700236 if (!android::base::ParseUint(optarg, &metadata_size)) {
237 fprintf(stderr, "Invalid argument to --metadata-size.\n");
238 return EX_USAGE;
239 }
240 break;
David Anderson07ee83b2019-12-13 15:31:10 -0800241 case Option::kMetadataSlots:
David Anderson41241232018-06-13 16:50:11 -0700242 if (!android::base::ParseUint(optarg, &metadata_slots)) {
243 fprintf(stderr, "Invalid argument to --metadata-slots.\n");
244 return EX_USAGE;
245 }
246 break;
David Anderson07ee83b2019-12-13 15:31:10 -0800247 case Option::kPartition:
Inseob Kim74414a92021-04-01 13:14:47 +0900248 if (auto res = PartitionInfo::Parse(optarg); !res.ok()) {
249 fprintf(stderr, "%s\n", res.error().message().c_str());
250 return EX_USAGE;
251 } else {
252 partitions.push_back(std::move(*res));
253 }
David Anderson41241232018-06-13 16:50:11 -0700254 break;
David Anderson07ee83b2019-12-13 15:31:10 -0800255 case Option::kGroup:
David Anderson97ec14b2018-10-02 18:50:12 -0700256 groups.push_back(optarg);
257 break;
David Anderson07ee83b2019-12-13 15:31:10 -0800258 case Option::kOutput:
David Anderson41241232018-06-13 16:50:11 -0700259 output_path = optarg;
260 break;
David Anderson07ee83b2019-12-13 15:31:10 -0800261 case Option::kAlignmentOffset:
David Anderson63b1dd62018-07-09 14:31:20 -0700262 if (!android::base::ParseUint(optarg, &alignment_offset)) {
263 fprintf(stderr, "Invalid argument to --alignment-offset.\n");
264 return EX_USAGE;
265 }
David Andersonc6c59202018-10-23 17:52:57 -0700266 has_implied_super = true;
David Anderson63b1dd62018-07-09 14:31:20 -0700267 break;
David Anderson07ee83b2019-12-13 15:31:10 -0800268 case Option::kAlignment:
David Anderson63b1dd62018-07-09 14:31:20 -0700269 if (!android::base::ParseUint(optarg, &alignment)) {
270 fprintf(stderr, "Invalid argument to --alignment.\n");
271 return EX_USAGE;
272 }
David Andersonc6c59202018-10-23 17:52:57 -0700273 has_implied_super = true;
David Anderson63b1dd62018-07-09 14:31:20 -0700274 break;
David Anderson07ee83b2019-12-13 15:31:10 -0800275 case Option::kSparse:
David Anderson3aa94b22018-07-13 17:38:52 -0700276 output_sparse = true;
277 break;
David Anderson07ee83b2019-12-13 15:31:10 -0800278 case Option::kBlockSize:
David Andersonaca58a42018-07-17 15:54:44 -0700279 if (!android::base::ParseUint(optarg, &block_size) || !block_size) {
280 fprintf(stderr, "Invalid argument to --block-size.\n");
281 return EX_USAGE;
282 }
283 break;
David Anderson07ee83b2019-12-13 15:31:10 -0800284 case Option::kImage:
David Andersoncf0d80a2018-07-17 18:29:29 -0700285 {
286 char* separator = strchr(optarg, '=');
287 if (!separator || separator == optarg || !strlen(separator + 1)) {
288 fprintf(stderr, "Expected PARTITION=FILE.\n");
289 return EX_USAGE;
290 }
291 *separator = '\0';
292
293 std::string partition_name(optarg);
294 std::string file(separator + 1);
295 images[partition_name] = file;
296 break;
297 }
David Anderson07ee83b2019-12-13 15:31:10 -0800298 case Option::kSuperName:
David Andersonc6c59202018-10-23 17:52:57 -0700299 super_name = optarg;
300 break;
David Anderson07ee83b2019-12-13 15:31:10 -0800301 case Option::kDevice:
David Andersonc6c59202018-10-23 17:52:57 -0700302 {
303 std::vector<std::string> parts = android::base::Split(optarg, ":");
304 if (parts.size() < 2) {
305 fprintf(stderr, "Block device info has invalid formatting.\n");
306 return EX_USAGE;
307 }
308
309 BlockDeviceInfo info;
310 info.partition_name = parts[0];
311 if (!android::base::ParseUint(parts[1].c_str(), &info.size) || !info.size) {
312 fprintf(stderr, "Block device must have a valid size.\n");
313 return EX_USAGE;
314 }
315 info.alignment = kDefaultPartitionAlignment;
316 if (parts.size() >= 3 &&
317 !android::base::ParseUint(parts[2].c_str(), &info.alignment)) {
318 fprintf(stderr, "Block device must have a valid alignment.\n");
319 return EX_USAGE;
320 }
321 if (parts.size() >= 4 &&
322 !android::base::ParseUint(parts[3].c_str(), &info.alignment_offset)) {
323 fprintf(stderr, "Block device must have a valid alignment offset.\n");
324 return EX_USAGE;
325 }
326 block_devices.emplace_back(info);
327 break;
328 }
David Anderson07ee83b2019-12-13 15:31:10 -0800329 case Option::kAutoSlotSuffixing:
David Anderson1c5907a2018-11-07 18:27:14 -0800330 auto_slot_suffixing = true;
331 break;
David Anderson07ee83b2019-12-13 15:31:10 -0800332 case Option::kForceFullImage:
David Andersonad8243f2019-05-24 12:19:22 -0700333 force_full_image = true;
334 break;
David Anderson7b14a8d2019-12-13 15:34:54 -0800335 case Option::kVirtualAB:
336 virtual_ab = true;
337 break;
David Anderson41241232018-06-13 16:50:11 -0700338 default:
339 break;
340 }
341 }
342
343 // Check for empty arguments so we can print a more helpful message rather
344 // than error on each individual missing argument.
345 if (optind == 1) {
346 return usage(argc, argv);
347 }
348
Inseob Kim74414a92021-04-01 13:14:47 +0900349 if (auto_blockdevice_size) {
350 blockdevice_size = CalculateBlockDeviceSize(alignment, metadata_size, partitions);
351 }
352
David Andersonc6c59202018-10-23 17:52:57 -0700353 // Must specify a block device via the old method (--device-size etc) or
354 // via --device, but not both.
355 if ((has_implied_super && (!block_devices.empty() || !blockdevice_size)) ||
356 (!has_implied_super && block_devices.empty()) ||
357 (block_devices.empty() && !blockdevice_size)) {
358 fprintf(stderr, "Must specify --device OR --device-size.\n");
David Anderson41241232018-06-13 16:50:11 -0700359 return EX_USAGE;
360 }
361 if (!metadata_size) {
362 fprintf(stderr, "--metadata-size must be more than 0 bytes.\n");
363 return EX_USAGE;
364 }
365 if (!metadata_slots) {
366 fprintf(stderr, "--metadata-slots must be more than 0.\n");
367 return EX_USAGE;
368 }
369 if (output_path.empty()) {
370 fprintf(stderr, "--output must specify a valid path.\n");
371 return EX_USAGE;
372 }
373 if (partitions.empty()) {
374 fprintf(stderr, "Partition table must have at least one entry.\n");
375 return EX_USAGE;
376 }
377
David Anderson28915822018-08-02 09:44:50 -0700378 // Note that we take the block_size to mean both the logical block size and
379 // the block size for libsparse.
David Andersonc6c59202018-10-23 17:52:57 -0700380 if (has_implied_super) {
381 block_devices.emplace_back(super_name, blockdevice_size, alignment, alignment_offset, block_size);
382 } else {
383 // Apply the block size to each device.
384 for (auto& block_device : block_devices) {
385 block_device.logical_block_size = block_size;
386 }
387 }
David Anderson63b1dd62018-07-09 14:31:20 -0700388
David Anderson41241232018-06-13 16:50:11 -0700389 std::unique_ptr<MetadataBuilder> builder =
David Andersonc6c59202018-10-23 17:52:57 -0700390 MetadataBuilder::New(block_devices, super_name, metadata_size, metadata_slots);
391 if (!builder) {
392 fprintf(stderr, "Invalid metadata parameters.\n");
393 return EX_USAGE;
394 }
David Anderson41241232018-06-13 16:50:11 -0700395
David Anderson1c5907a2018-11-07 18:27:14 -0800396 if (auto_slot_suffixing) {
397 builder->SetAutoSlotSuffixing();
398 }
David Anderson7b14a8d2019-12-13 15:34:54 -0800399 if (virtual_ab) {
400 builder->SetVirtualABDeviceFlag();
401 }
David Anderson1c5907a2018-11-07 18:27:14 -0800402
David Anderson97ec14b2018-10-02 18:50:12 -0700403 for (const auto& group_info : groups) {
404 std::vector<std::string> parts = android::base::Split(group_info, ":");
405 if (parts.size() != 2) {
David Anderson41241232018-06-13 16:50:11 -0700406 fprintf(stderr, "Partition info has invalid formatting.\n");
407 return EX_USAGE;
408 }
409
410 std::string name = parts[0];
David Anderson97ec14b2018-10-02 18:50:12 -0700411 if (name.empty()) {
412 fprintf(stderr, "Partition group must have a valid name.\n");
413 return EX_USAGE;
414 }
415
416 uint64_t size;
417 if (!android::base::ParseUint(parts[1].c_str(), &size)) {
418 fprintf(stderr, "Partition group must have a valid maximum size.\n");
419 return EX_USAGE;
420 }
421
422 if (!builder->AddGroup(name, size)) {
423 fprintf(stderr, "Group name %s already exists.\n", name.c_str());
424 return EX_SOFTWARE;
425 }
426 }
427
428 for (const auto& partition_info : partitions) {
Inseob Kim74414a92021-04-01 13:14:47 +0900429 Partition* partition = builder->AddPartition(partition_info.name, partition_info.group_name,
430 partition_info.attribute_flags);
David Anderson97ec14b2018-10-02 18:50:12 -0700431 if (!partition) {
Inseob Kim74414a92021-04-01 13:14:47 +0900432 fprintf(stderr, "Could not add partition: %s\n", partition_info.name.c_str());
David Anderson97ec14b2018-10-02 18:50:12 -0700433 return EX_SOFTWARE;
434 }
Inseob Kim74414a92021-04-01 13:14:47 +0900435 if (!builder->ResizePartition(partition, partition_info.size)) {
David Anderson41241232018-06-13 16:50:11 -0700436 fprintf(stderr, "Not enough space on device for partition %s with size %" PRIu64 "\n",
Inseob Kim74414a92021-04-01 13:14:47 +0900437 partition_info.name.c_str(), partition_info.size);
David Anderson41241232018-06-13 16:50:11 -0700438 return EX_SOFTWARE;
439 }
440 }
441
442 std::unique_ptr<LpMetadata> metadata = builder->Export();
David Andersonad8243f2019-05-24 12:19:22 -0700443 if (!images.empty() || force_full_image) {
Cody Caldwell977afeb2019-11-20 20:36:12 +0000444 if (block_devices.size() == 1) {
Yifan Hong456e9a72019-04-02 16:19:06 -0700445 if (!WriteToImageFile(output_path.c_str(), *metadata.get(), block_size, images,
446 output_sparse)) {
David Anderson47058fb2018-11-07 20:24:17 -0800447 return EX_CANTCREAT;
448 }
449 } else {
Yifan Hong456e9a72019-04-02 16:19:06 -0700450 if (!WriteSplitImageFiles(output_path, *metadata.get(), block_size, images,
451 output_sparse)) {
David Anderson47058fb2018-11-07 20:24:17 -0800452 return EX_CANTCREAT;
453 }
David Anderson3aa94b22018-07-13 17:38:52 -0700454 }
455 } else if (!WriteToImageFile(output_path.c_str(), *metadata.get())) {
David Anderson41241232018-06-13 16:50:11 -0700456 return EX_CANTCREAT;
457 }
David Anderson41241232018-06-13 16:50:11 -0700458 return EX_OK;
459}