1// Copyright 2017 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 <unistd.h>
6
7#include <fbl/alloc_checker.h>
8#include <fbl/unique_ptr.h>
9#include <fvm/fvm-lz4.h>
10
11#include "fvm/container.h"
12
13#define DEFAULT_SLICE_SIZE (8lu * (1 << 20))
14
15int usage(void) {
16    fprintf(stderr, "usage: fvm [ output_path ] [ command ] [ <flags>* ] [ <input_paths>* ]\n");
17    fprintf(stderr, "fvm performs host-side FVM and sparse file creation\n");
18    fprintf(stderr, "Commands:\n");
19    fprintf(stderr, " create : Creates an FVM partition\n");
20    fprintf(stderr, " add : Adds a Minfs or Blobfs partition to an FVM (input path is"
21                    " required)\n");
22    fprintf(stderr, " extend : Extends an FVM container to the specified size (length is"
23                        " required)\n");
24    fprintf(stderr, " sparse : Creates a sparse file. One or more input paths are required.\n");
25    fprintf(stderr, " verify : Report basic information about sparse/fvm files and run fsck on"
26                        " contained partitions\n");
27    fprintf(stderr, " decompress : Decompresses a compressed sparse file. --sparse input path is"
28                    " required.\n");
29    fprintf(stderr, "Flags (neither or both of offset/length must be specified):\n");
30    fprintf(stderr, " --slice [bytes] - specify slice size (default: %zu)\n", DEFAULT_SLICE_SIZE);
31    fprintf(stderr, " --offset [bytes] - offset at which container begins (fvm only)\n");
32    fprintf(stderr, " --length [bytes] - length of container within file (fvm only)\n");
33    fprintf(stderr, " --compress - specify that file should be compressed (sparse only)\n");
34    fprintf(stderr, " --zxcrypt - specify that data be placed in zxcrypt volume (sparse only)\n");
35    fprintf(stderr, "Input options:\n");
36    fprintf(stderr, " --blob [path] - Add path as blob type (must be blobfs)\n");
37    fprintf(stderr, " --data [path] - Add path as data type (must be minfs)\n");
38    fprintf(stderr, " --system [path] - Add path as system type (must be minfs)\n");
39    fprintf(stderr, " --default [path] - Add generic path\n");
40    fprintf(stderr, " --sparse [path] - Path to compressed sparse file\n");
41    exit(-1);
42}
43
44int add_partitions(Container* container, int argc, char** argv) {
45    for (unsigned i = 0; i < argc; i += 2) {
46        if (argc - i < 2 || argv[i][0] != '-' || argv[i][1] != '-') {
47            usage();
48        }
49
50        char* partition_type = argv[i] + 2;
51        char* partition_path = argv[i + 1];
52        if ((container->AddPartition(partition_path, partition_type)) != ZX_OK) {
53            fprintf(stderr, "Failed to add partition\n");
54            return -1;
55        }
56    }
57
58    return 0;
59}
60
61size_t get_disk_size(const char* path, size_t offset) {
62    fbl::unique_fd fd(open(path, O_RDONLY, 0644));
63
64    if (fd) {
65        struct stat s;
66        if (fstat(fd.get(), &s) < 0) {
67            fprintf(stderr, "Failed to stat %s\n", path);
68            exit(-1);
69        }
70
71        return s.st_size - offset;
72    }
73
74    return 0;
75}
76
77int parse_size(const char* size_str, size_t* out) {
78    char* end;
79    size_t size = strtoull(size_str, &end, 10);
80
81    switch (end[0]) {
82    case 'K':
83    case 'k':
84        size *= 1024;
85        end++;
86        break;
87    case 'M':
88    case 'm':
89        size *= (1024 * 1024);
90        end++;
91        break;
92    case 'G':
93    case 'g':
94        size *= (1024 * 1024 * 1024);
95        end++;
96        break;
97    }
98
99    if (end[0] || size == 0) {
100        fprintf(stderr, "Bad size: %s\n", size_str);
101        return -1;
102    }
103
104    *out = size;
105    return 0;
106}
107
108int main(int argc, char** argv) {
109    if (argc < 3) {
110        usage();
111    }
112
113    unsigned i = 1;
114    const char* path = argv[i++]; // Output path
115    const char* command = argv[i++]; // Command
116
117    size_t length = 0;
118    size_t offset = 0;
119    size_t slice_size = DEFAULT_SLICE_SIZE;
120    bool should_unlink = true;
121    uint32_t flags = 0;
122    while (i < argc) {
123        if (!strcmp(argv[i], "--slice") && i + 1 < argc) {
124            if (parse_size(argv[++i], &slice_size) < 0) {
125                return -1;
126            }
127            if (!slice_size ||
128                slice_size % blobfs::kBlobfsBlockSize ||
129                slice_size % minfs::kMinfsBlockSize) {
130                fprintf(stderr, "Invalid slice size - must be a multiple of %u and %u\n",
131                        blobfs::kBlobfsBlockSize, minfs::kMinfsBlockSize);
132                return -1;
133            }
134        } else if (!strcmp(argv[i], "--offset") && i + 1 < argc) {
135            should_unlink = false;
136            if (parse_size(argv[++i], &offset) < 0) {
137                return -1;
138            }
139        } else if (!strcmp(argv[i], "--length") && i + 1 < argc) {
140            if (parse_size(argv[++i], &length) < 0) {
141                return -1;
142            }
143        } else if (!strcmp(argv[i], "--compress")) {
144            if (!strcmp(argv[++i], "lz4")) {
145                flags |= fvm::kSparseFlagLz4;
146            } else {
147                fprintf(stderr, "Invalid compression type\n");
148                return -1;
149            }
150        } else if (!strcmp(argv[i], "--zxcrypt")) {
151            flags |= fvm::kSparseFlagZxcrypt;
152        } else {
153            break;
154        }
155
156        ++i;
157    }
158
159    if (!strcmp(command, "create") && should_unlink) {
160        unlink(path);
161    }
162
163    // If length was not specified, use remainder of file after offset
164    if (length == 0) {
165        length = get_disk_size(path, offset);
166    }
167
168    if (!strcmp(command, "create")) {
169        // If length was specified, an offset was not, we were asked to create a
170        // file, and the file does not exist, truncate it to the given length.
171        if (length != 0 && offset == 0) {
172            fbl::unique_fd fd(open(path, O_CREAT|O_EXCL|O_WRONLY, 0644));
173
174            if (fd) {
175                ftruncate(fd.get(), length);
176            }
177        }
178
179        fbl::unique_ptr<FvmContainer> fvmContainer;
180        if (FvmContainer::Create(path, slice_size, offset, length, &fvmContainer) != ZX_OK) {
181            return -1;
182        }
183
184        if (add_partitions(fvmContainer.get(), argc - i, argv + i) < 0) {
185            return -1;
186        }
187
188        if (fvmContainer->Commit() != ZX_OK) {
189            return -1;
190        }
191    } else if (!strcmp(command, "add")) {
192        fbl::AllocChecker ac;
193        fbl::unique_ptr<FvmContainer> fvmContainer(new (&ac) FvmContainer(path, slice_size, offset,
194                                                                          length));
195        if (!ac.check()) {
196            return ZX_ERR_NO_MEMORY;
197        }
198
199        if (add_partitions(fvmContainer.get(), argc - i, argv + i) < 0) {
200            return -1;
201        }
202
203        if (fvmContainer->Commit() != ZX_OK) {
204            return -1;
205        }
206    } else if (!strcmp(command, "extend")) {
207        if (length == 0 || offset > 0) {
208            usage();
209        }
210
211        size_t disk_size = get_disk_size(path, 0);
212
213        if (length <= disk_size) {
214            fprintf(stderr, "Cannot extend to a value %zu less than current size %zu\n", length,
215                    disk_size);
216            usage();
217        }
218
219        fbl::AllocChecker ac;
220        fbl::unique_ptr<FvmContainer> fvmContainer(new (&ac) FvmContainer(path, slice_size, offset,
221                                                                          disk_size));
222        if (!ac.check()) {
223            return ZX_ERR_NO_MEMORY;
224        }
225
226        if (fvmContainer->Extend(length) != ZX_OK) {
227            return -1;
228        }
229    } else if (!strcmp(command, "sparse")) {
230        if (offset) {
231            fprintf(stderr, "Invalid sparse flags\n");
232            return -1;
233        }
234
235        fbl::unique_ptr<SparseContainer> sparseContainer;
236        if (SparseContainer::Create(path, slice_size, flags, &sparseContainer) != ZX_OK) {
237            return -1;
238        }
239
240        if (add_partitions(sparseContainer.get(), argc - i, argv + i) < 0) {
241            return -1;
242        }
243
244        if (sparseContainer->Commit() != ZX_OK) {
245            return -1;
246        }
247    } else if (!strcmp(command, "verify")) {
248        fbl::unique_ptr<Container> containerData;
249        if (Container::Create(path, offset, length, flags, &containerData) != ZX_OK) {
250            return -1;
251        }
252
253        if (containerData->Verify() != ZX_OK) {
254            return -1;
255        }
256    } else if (!strcmp(command, "decompress")) {
257        if (argc - i != 2) {
258            usage();
259            return -1;
260        }
261
262        char* input_type = argv[i];
263        char* input_path = argv[i + 1];
264
265        if (strcmp(input_type, "--sparse")) {
266            usage();
267            return -1;
268        }
269
270        if (fvm::decompress_sparse(input_path, path) != ZX_OK) {
271            return -1;
272        }
273
274        fbl::unique_ptr<SparseContainer> sparseData(new SparseContainer(path, slice_size, flags));
275        if (sparseData->Verify() != ZX_OK) {
276            return -1;
277        }
278    } else {
279        usage();
280    }
281
282    return 0;
283}
284