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