1 // Copyright 2018 The Fuchsia Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include <assert.h>
6 #include <getopt.h>
7 #include <inttypes.h>
8 #include <libgen.h>
9 #include <limits.h>
10 #include <sys/stat.h>
11
12 #include <fbl/algorithm.h>
13 #include <fbl/auto_call.h>
14
15 #include "fs-host/common.h"
16
17 #define MIN_ARGS 3
18
19 // Options struct.
20 struct {
21 const char* name;
22 Option option;
23 const char* argument;
24 const char* default_value;
25 const char* help;
26 } OPTS[] = {
27 {"depfile", Option::kDepfile, "", nullptr,
28 "Produce a depfile"},
29 {"readonly", Option::kReadonly, "", nullptr,
30 "Mount filesystem read-only"},
31 {"offset", Option::kOffset, "[bytes]", "0",
32 "Byte offset at which minfs partition starts"},
33 {"length", Option::kLength, "[bytes]", "Remaining Length",
34 "Length in bytes of minfs partition"},
35 {"compress", Option::kCompress, "", nullptr,
36 "Compress files before adding them to blobfs"},
37 {"help", Option::kHelp, "", nullptr,
38 "Display this message"},
39 };
40
41 // Commands struct.
42 struct {
43 const char* name;
44 Command command;
45 uint32_t flags;
46 ArgType arg_type;
47 const char* help;
48 } CMDS[] = {
49 {"create", Command::kMkfs, O_RDWR | O_CREAT, ArgType::kOptional,
50 "Initialize filesystem."},
51 {"mkfs", Command::kMkfs, O_RDWR | O_CREAT, ArgType::kOptional,
52 "Initialize filesystem."},
53 {"check", Command::kFsck, O_RDONLY, ArgType::kNone,
54 "Check filesystem integrity."},
55 {"fsck", Command::kFsck, O_RDONLY, ArgType::kNone,
56 "Check filesystem integrity."},
57 {"add", Command::kAdd, O_RDWR, ArgType::kMany,
58 "Add files to an fs image (additional arguments required)."},
59 {"cp", Command::kCp, O_RDWR, ArgType::kTwo,
60 "Copy to/from fs."},
61 {"mkdir", Command::kMkdir, O_RDWR, ArgType::kOne,
62 "Create directory."},
63 {"ls", Command::kLs, O_RDONLY, ArgType::kOne,
64 "List contents of directory."},
65 {"manifest", Command::kManifest, O_RDWR, ArgType::kOne,
66 "Add files to fs as specified in manifest (deprecated)."},
67 };
68
69 // Arguments struct.
70 struct {
71 const char* name;
72 Argument argument;
73 } ARGS[] = {
74 {"--manifest", Argument::kManifest},
75 {"--blob", Argument::kBlob},
76 };
77
ProcessAndRun(int argc,char ** argv)78 zx_status_t FsCreator::ProcessAndRun(int argc, char** argv) {
79 zx_status_t status;
80 if ((status = ProcessArgs(argc, argv)) != ZX_OK) {
81 return status;
82 }
83
84 return RunCommand();
85 }
86
Usage()87 zx_status_t FsCreator::Usage() {
88 fprintf(stderr,
89 "usage: %s [ <option>* ] <file-or-device>[@<size>] <command> [ <arg>* ]\n\n",
90 GetToolName());
91
92 // Display all valid pre-command options.
93 bool first = true;
94 for (unsigned n = 0; n < fbl::count_of(OPTS); n++) {
95 if (IsOptionValid(OPTS[n].option)) {
96 fprintf(stderr, "%-8s -%c|--%-8s ", first ? "options:" : "",
97 OPTS[n].name[0], OPTS[n].name);
98
99 fprintf(stderr, "%-8s", OPTS[n].argument);
100
101 fprintf(stderr, "\t%s\n", OPTS[n].help);
102 if (OPTS[n].default_value != nullptr) {
103 fprintf(stderr, "%33s(Default = %s)\n", "", OPTS[n].default_value);
104 }
105 first = false;
106 }
107 }
108 fprintf(stderr, "\n");
109
110 // Display all valid commands.
111 first = true;
112 for (unsigned n = 0; n < fbl::count_of(CMDS); n++) {
113 if (IsCommandValid(CMDS[n].command)) {
114 fprintf(stderr, "%9s %-10s %s\n", first ? "commands:" : "",
115 CMDS[n].name, CMDS[n].help);
116 first = false;
117 }
118 }
119 fprintf(stderr, "\n");
120
121 // Display all valid '--' arguments.
122 fprintf(stderr, "arguments (valid for create, one or more required for add):\n");
123 for (unsigned n = 0; n < fbl::count_of(ARGS); n++) {
124 if (IsArgumentValid(ARGS[n].argument)) {
125 fprintf(stderr, "\t%-10s <path>\n", ARGS[n].name);
126 }
127 }
128
129 return ZX_ERR_INVALID_ARGS;
130 }
131
ProcessManifest(char * manifest_path)132 zx_status_t FsCreator::ProcessManifest(char* manifest_path) {
133 fbl::unique_fd manifest_fd(open(manifest_path, O_RDONLY, 0644));
134 if (!manifest_fd) {
135 fprintf(stderr, "error: cannot open '%s'\n", manifest_path);
136 return ZX_ERR_IO;
137 }
138
139 char dir_path[PATH_MAX];
140 strncpy(dir_path, dirname(manifest_path), PATH_MAX);
141 FILE* manifest = fdopen(manifest_fd.release(), "r");
142 while (true) {
143 // Keep processing lines in the manifest until we have reached EOF.
144 zx_status_t status = ProcessManifestLine(manifest, dir_path);
145 if (status == ZX_ERR_OUT_OF_RANGE) {
146 fclose(manifest);
147 return ZX_OK;
148 } else if (status != ZX_OK) {
149 fclose(manifest);
150 return status;
151 }
152 }
153 }
154
ParseManifestLine(FILE * manifest,const char * dir_path,char * src,char * dst)155 zx_status_t FsCreator::ParseManifestLine(FILE* manifest, const char* dir_path, char* src, char* dst) {
156 size_t size = 0;
157 char* line = nullptr;
158
159 // Always free the line on exiting this method.
160 auto cleanup = fbl::MakeAutoCall([&line]() { if (line) free(line); });
161
162 // Retrieve the next line from the manifest.
163 ssize_t r = getline(&line, &size, manifest);
164 if (r < 0) {
165 return ZX_ERR_OUT_OF_RANGE;
166 }
167
168 // Exit early if line is commented out
169 if (line[0] == '#') {
170 return ZX_OK;
171 }
172
173 char* equals = strchr(line, '=');
174 char* src_start = line;
175
176 if (equals != nullptr) {
177 // If we found an '=', there is a destination in this line.
178 // (Note that destinations are allowed but not required for blobfs.)
179 if (strchr(equals + 1, '=') != nullptr) {
180 fprintf(stderr, "Too many '=' in input\n");
181 return ZX_ERR_INVALID_ARGS;
182 }
183
184 src_start = equals + 1;
185 equals[0] = '\0';
186
187 strncat(dst, line, PATH_MAX - strlen(dst));
188 }
189
190 // If the source is not an absolute path, append the manifest's local directory.
191 if (src_start[0] != '/') {
192 strncpy(src, dir_path, PATH_MAX);
193 strncat(src, "/", PATH_MAX - strlen(src));
194 }
195
196 strncat(src, src_start, PATH_MAX - strlen(src));
197
198 // Set the source path to terminate if it currently ends in a new line.
199 char* new_line = strchr(src, '\n');
200 if (new_line != nullptr) {
201 *new_line = '\0';
202 }
203
204 return ZX_OK;
205 }
206
ProcessArgs(int argc,char ** argv)207 zx_status_t FsCreator::ProcessArgs(int argc, char** argv) {
208 if (argc < MIN_ARGS) {
209 fprintf(stderr, "Not enough args\n");
210 return Usage();
211 }
212
213 bool depfile_needed = false;
214
215 // Read options.
216 while (true) {
217 // Set up options struct for pre-device option processing.
218 unsigned index = 0;
219 struct option opts[fbl::count_of(OPTS) + 1];
220 for (unsigned n = 0; n < fbl::count_of(OPTS); n++) {
221 if (IsOptionValid(OPTS[n].option)) {
222 opts[index].name = OPTS[n].name;
223 opts[index].has_arg = strlen(OPTS[n].argument) ? required_argument : no_argument;
224 opts[index].flag = nullptr;
225 opts[index].val = OPTS[n].name[0];
226 index++;
227 }
228 }
229
230 opts[index] = {nullptr, 0, nullptr, 0};
231
232 int opt_index;
233 int c = getopt_long(argc, argv, "+dro:l:ch", opts, &opt_index);
234 if (c < 0) {
235 break;
236 }
237
238 switch (c) {
239 case 'd':
240 depfile_needed = true;
241 break;
242 case 'r':
243 read_only_ = true;
244 break;
245 case 'o':
246 offset_ = atoll(optarg);
247 break;
248 case 'l':
249 length_ = atoll(optarg);
250 break;
251 case 'c':
252 compress_ = true;
253 break;
254 case 'h':
255 default:
256 return Usage();
257 }
258
259 }
260
261 argc -= optind;
262 argv += optind;
263
264 if (argc < 2) {
265 fprintf(stderr, "Not enough arguments\n");
266 return Usage();
267 }
268
269 // Read device name.
270 char* device = argv[0];
271 argc--;
272 argv++;
273
274 // Read command name.
275 char* command = argv[0];
276 argc--;
277 argv++;
278
279 uint32_t open_flags = 0;
280 ArgType arg_type = ArgType::kNone;
281
282 // Validate command.
283 for (unsigned i = 0; i < sizeof(CMDS) / sizeof(CMDS[0]); i++) {
284 if (!strcmp(command, CMDS[i].name)) {
285 if (!IsCommandValid(CMDS[i].command)) {
286 fprintf(stderr, "Invalid command %s\n", command);
287 return Usage();
288 }
289
290 command_ = CMDS[i].command;
291 open_flags = read_only_ ? O_RDONLY : CMDS[i].flags;
292 arg_type = CMDS[i].arg_type;
293 }
294 }
295
296 if (command_ == Command::kNone) {
297 fprintf(stderr, "Unknown command: %s\n", argv[0]);
298 return Usage();
299 }
300
301 // Parse the size argument (if any) from the device string.
302 size_t requested_size = 0;
303 if (ParseSize(device, &requested_size) != ZX_OK) {
304 return Usage();
305 }
306
307 // Open the target device. Do this before we continue processing arguments, in case we are
308 // copying directories from a minfs image and need to pre-process them.
309 fd_.reset(open(device, open_flags, 0644));
310 if (!fd_) {
311 fprintf(stderr, "error: cannot open '%s'\n", device);
312 return ZX_ERR_IO;
313 }
314
315 struct stat stats;
316 if (fstat(fd_.get(), &stats) < 0) {
317 fprintf(stderr, "Failed to stat device %s\n", device);
318 return ZX_ERR_IO;
319 }
320
321 // Unless we are creating an image, the length_ has already been decided.
322 if (command_ != Command::kMkfs) {
323 if (length_) {
324 if (offset_ + length_ > stats.st_size) {
325 fprintf(stderr, "Must specify offset + length <= file size\n");
326 return ZX_ERR_INVALID_ARGS;
327 }
328 } else {
329 length_ = stats.st_size - offset_;
330 }
331 }
332
333 // Verify that we've received a valid number of arguments for the given command.
334 bool valid = true;
335 switch (arg_type) {
336 case ArgType::kNone:
337 valid = argc == 0;
338 break;
339 case ArgType::kOne:
340 valid = argc == 1;
341 break;
342 case ArgType::kTwo:
343 valid = argc == 2;
344 break;
345 case ArgType::kMany:
346 valid = argc;
347 break;
348 case ArgType::kOptional:
349 break;
350 default:
351 return ZX_ERR_INTERNAL;
352 }
353
354 if (!valid) {
355 fprintf(stderr, "Invalid arguments\n");
356 return Usage();
357 }
358
359 // Process remaining arguments.
360 while (argc > 0) {
361 // Default to 2 arguments processed for manifest. If ProcessCustom is called, processed
362 // will be populated with the actual number of arguments used.
363 uint8_t processed = 2;
364 if (!strcmp(argv[0], "--manifest")) {
365 if (argc < 2) {
366 return ZX_ERR_INVALID_ARGS;
367 }
368
369 zx_status_t status;
370 if ((status = ProcessManifest(argv[1])) != ZX_OK) {
371 return status;
372 }
373 } else if (ProcessCustom(argc, argv, &processed) != ZX_OK) {
374 return Usage();
375 }
376
377 argc -= processed;
378 argv += processed;
379 }
380
381 // Resize the file if we need to.
382 zx_status_t status;
383 if ((status = ResizeFile(requested_size, stats)) != ZX_OK) {
384 return status;
385 }
386
387 if (depfile_needed) {
388 size_t len = strlen(device);
389 assert(len+2 < PATH_MAX);
390 char buf[PATH_MAX] = {0};
391 memcpy(&buf[0], device, strlen(device));
392 buf[len++] = '.';
393 buf[len++] = 'd';
394
395 depfile_.reset(open(buf, O_CREAT|O_TRUNC|O_WRONLY, 0644));
396 if (!depfile_) {
397 fprintf(stderr, "error: cannot open '%s'\n", buf);
398 return ZX_ERR_IO;
399 }
400
401 // update the buf to be suitable to pass to AppendDepfile.
402 buf[len-2] = ':';
403 buf[len-1] = 0;
404
405 status = AppendDepfile(&buf[0]);
406 }
407
408 return status;
409 }
410
AppendDepfile(const char * str)411 zx_status_t FsCreator::AppendDepfile(const char* str) {
412 if (!depfile_) {
413 return ZX_OK;
414 }
415
416 size_t len = strlen(str);
417 assert(len < PATH_MAX);
418 char buf[PATH_MAX] = {0};
419 memcpy(&buf[0], str, len);
420 buf[len++] = ' ';
421
422 std::lock_guard<std::mutex> lock(depfile_lock_);
423
424 // this code makes assumptions about the size of atomic writes on target
425 // platforms which currently hold true, but are not part of e.g. POSIX.
426 if (write(depfile_.get(), buf, len) != len) {
427 fprintf(stderr, "error: depfile append error\n");
428 return ZX_ERR_IO;
429 }
430 return ZX_OK;
431 }
432
RunCommand()433 zx_status_t FsCreator::RunCommand() {
434 if (!fd_) {
435 fprintf(stderr, "Failed to open fd before running command\n");
436 return ZX_ERR_INTERNAL;
437 }
438
439 switch (command_) {
440 case Command::kMkfs:
441 return Mkfs();
442 case Command::kFsck:
443 return Fsck();
444 case Command::kAdd:
445 case Command::kCp:
446 case Command::kManifest:
447 case Command::kMkdir:
448 return Add();
449 case Command::kLs:
450 return Ls();
451 default:
452 fprintf(stderr, "Error: Command not defined\n");
453 return ZX_ERR_INTERNAL;
454 }
455 }
456
ParseSize(char * device,size_t * out)457 zx_status_t FsCreator::ParseSize(char* device, size_t* out) {
458 char* sizestr = nullptr;
459 if ((sizestr = strchr(device, '@')) != nullptr) {
460 if (command_ != Command::kMkfs) {
461 fprintf(stderr, "Cannot specify size for this command\n");
462 return ZX_ERR_INVALID_ARGS;
463 }
464 // Create a file with an explicitly requested size
465 *sizestr++ = 0;
466 char* end;
467 size_t size = strtoull(sizestr, &end, 10);
468 if (end == sizestr) {
469 fprintf(stderr, "%s: bad size: %s\n", GetToolName(), sizestr);
470 return ZX_ERR_INVALID_ARGS;
471 }
472 switch (end[0]) {
473 case 'M':
474 case 'm':
475 size *= (1024 * 1024);
476 end++;
477 break;
478 case 'G':
479 case 'g':
480 size *= (1024 * 1024 * 1024);
481 end++;
482 break;
483 }
484 if (end[0] || size == 0) {
485 fprintf(stderr, "%s: bad size: %s\n", GetToolName(), sizestr);
486 return ZX_ERR_INVALID_ARGS;
487 }
488
489 if (length_ && offset_ + length_ > size) {
490 fprintf(stderr, "Must specify size > offset + length\n");
491 return ZX_ERR_INVALID_ARGS;
492 }
493 *out = size;
494 }
495
496 return ZX_OK;
497 }
498
ResizeFile(off_t requested_size,struct stat stats)499 zx_status_t FsCreator::ResizeFile(off_t requested_size, struct stat stats) {
500 if (command_ != Command::kMkfs) {
501 // This method is only valid on creation of the fs image.
502 return ZX_OK;
503 }
504
505 // Calculate the total required size for the fs image, given all files that have been processed
506 // up to this point.
507 off_t required_size;
508 zx_status_t status = CalculateRequiredSize(&required_size);
509 if (status != ZX_OK) {
510 return status;
511 }
512
513 bool is_block = S_ISBLK(stats.st_mode);
514
515 if (requested_size) {
516 if (requested_size < required_size) {
517 // If the size requested by @ is smaller than the size required, return an error.
518 fprintf(stderr, "Must specify size larger than required size %" PRIu64 "\n",
519 required_size);
520 return ZX_ERR_INVALID_ARGS;
521 } else if (is_block) {
522 // Do not allow re-sizing for block devices.
523 fprintf(stderr, "%s: @size argument is not supported for block device targets\n",
524 GetToolName());
525 return ZX_ERR_INVALID_ARGS;
526 }
527 }
528
529 if (command_ == Command::kMkfs && !is_block &&
530 (stats.st_size != required_size || requested_size)) {
531 // Only truncate the file size under the following conditions:
532 // 1. We are creating the fs store for the first time.
533 // 2. We are not operating on a block device.
534 // 3a. The current file size is different than the size required for the specified files, OR
535 // 3b. The user has requested a particular size using the @ argument.
536 off_t truncate_size = requested_size ? requested_size : required_size;
537
538 if (length_ && (offset_ + length_) > truncate_size) {
539 // If an offset+length were specified and they are smaller than the minimum required,
540 // return an error.
541 fprintf(stderr, "Length %" PRIu64 " too small for required size %" PRIu64 "\n", length_,
542 truncate_size);
543 return ZX_ERR_INVALID_ARGS;
544 }
545
546 if (ftruncate(fd_.get(), truncate_size)) {
547 fprintf(stderr, "error: cannot truncate device\n");
548 return ZX_ERR_IO;
549 }
550
551 if (!length_) {
552 length_ = truncate_size - offset_;
553 }
554 } else if (!length_) {
555 // If not otherwise specified, update length to be equal to the size of the image.
556 length_ = stats.st_size - offset_;
557 }
558
559 return ZX_OK;
560 }
561