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/sparse-reader.h>
10 #include <fvm-host/container.h>
11 
12 #define DEFAULT_SLICE_SIZE (8lu * (1 << 20))
13 
usage(void)14 int usage(void) {
15     fprintf(stderr, "usage: fvm [ output_path ] [ command ] [ <flags>* ] [ <input_paths>* ]\n");
16     fprintf(stderr, "fvm performs host-side FVM and sparse file creation\n");
17     fprintf(stderr, "Commands:\n");
18     fprintf(stderr, " create : Creates an FVM partition\n");
19     fprintf(stderr, " add : Adds a Minfs or Blobfs partition to an FVM (input path is"
20                     " required)\n");
21     fprintf(stderr, " extend : Extends an FVM container to the specified size (length is"
22                     " required)\n");
23     fprintf(stderr, " sparse : Creates a sparse file. One or more input paths are required.\n");
24     fprintf(stderr, " pave : Creates an FVM container from a sparse file.\n");
25     fprintf(stderr, " verify : Report basic information about sparse/fvm files and run fsck on"
26                     " contained partitions.\n");
27     fprintf(stderr, " size : Prints the minimum size required in order to pave a sparse file."
28                     " If the --disk flag is provided, instead checks that the paved sparse file"
29                     " will fit within a disk of this size. On success, no information is"
30                     " outputted\n");
31     fprintf(stderr, " decompress : Decompresses a compressed sparse file. --sparse input path is"
32                     " required.\n");
33     fprintf(stderr, "Flags (neither or both of offset/length must be specified):\n");
34     fprintf(stderr, " --slice [bytes] - specify slice size (default: %zu)\n", DEFAULT_SLICE_SIZE);
35     fprintf(stderr, " --offset [bytes] - offset at which container begins (fvm only)\n");
36     fprintf(stderr, " --length [bytes] - length of container within file (fvm only)\n");
37     fprintf(stderr, " --compress - specify that file should be compressed (sparse only)\n");
38     fprintf(stderr, " --disk [bytes] - Size of target disk (valid for size command only)\n");
39     fprintf(stderr, "Input options:\n");
40     fprintf(stderr, " --blob [path] - Add path as blob type (must be blobfs)\n");
41     fprintf(stderr, " --data [path] - Add path as encrypted data type (must be minfs)\n");
42     fprintf(stderr, " --data-unsafe [path] - Add path as unencrypted data type (must be minfs)\n");
43     fprintf(stderr, " --system [path] - Add path as system type (must be minfs)\n");
44     fprintf(stderr, " --default [path] - Add generic path\n");
45     fprintf(stderr, " --sparse [path] - Path to compressed sparse file\n");
46     exit(-1);
47 }
48 
add_partitions(Container * container,int argc,char ** argv)49 int add_partitions(Container* container, int argc, char** argv) {
50     for (unsigned i = 0; i < argc; i += 2) {
51         if (argc - i < 2 || argv[i][0] != '-' || argv[i][1] != '-') {
52             usage();
53         }
54 
55         char* partition_type = argv[i] + 2;
56         char* partition_path = argv[i + 1];
57         if ((container->AddPartition(partition_path, partition_type)) != ZX_OK) {
58             fprintf(stderr, "Failed to add partition\n");
59             return -1;
60         }
61     }
62 
63     return 0;
64 }
65 
get_disk_size(const char * path,size_t offset)66 size_t get_disk_size(const char* path, size_t offset) {
67     fbl::unique_fd fd(open(path, O_RDONLY, 0644));
68 
69     if (fd) {
70         struct stat s;
71         if (fstat(fd.get(), &s) < 0) {
72             fprintf(stderr, "Failed to stat %s\n", path);
73             exit(-1);
74         }
75 
76         return s.st_size - offset;
77     }
78 
79     return 0;
80 }
81 
parse_size(const char * size_str,size_t * out)82 int parse_size(const char* size_str, size_t* out) {
83     char* end;
84     size_t size = strtoull(size_str, &end, 10);
85 
86     switch (end[0]) {
87     case 'K':
88     case 'k':
89         size *= 1024;
90         end++;
91         break;
92     case 'M':
93     case 'm':
94         size *= (1024 * 1024);
95         end++;
96         break;
97     case 'G':
98     case 'g':
99         size *= (1024 * 1024 * 1024);
100         end++;
101         break;
102     }
103 
104     if (end[0] || size == 0) {
105         fprintf(stderr, "Bad size: %s\n", size_str);
106         return -1;
107     }
108 
109     *out = size;
110     return 0;
111 }
112 
main(int argc,char ** argv)113 int main(int argc, char** argv) {
114     if (argc < 3) {
115         usage();
116     }
117 
118     unsigned i = 1;
119     const char* path = argv[i++];    // Output path
120     const char* command = argv[i++]; // Command
121 
122     size_t length = 0;
123     size_t offset = 0;
124     size_t slice_size = DEFAULT_SLICE_SIZE;
125     size_t disk_size = 0;
126     bool should_unlink = true;
127     uint32_t flags = 0;
128     while (i < argc) {
129         if (!strcmp(argv[i], "--slice") && i + 1 < argc) {
130             if (parse_size(argv[++i], &slice_size) < 0) {
131                 return -1;
132             }
133             if (!slice_size ||
134                 slice_size % blobfs::kBlobfsBlockSize ||
135                 slice_size % minfs::kMinfsBlockSize) {
136                 fprintf(stderr, "Invalid slice size - must be a multiple of %u and %u\n",
137                         blobfs::kBlobfsBlockSize, minfs::kMinfsBlockSize);
138                 return -1;
139             }
140         } else if (!strcmp(argv[i], "--offset") && i + 1 < argc) {
141             should_unlink = false;
142             if (parse_size(argv[++i], &offset) < 0) {
143                 return -1;
144             }
145         } else if (!strcmp(argv[i], "--length") && i + 1 < argc) {
146             if (parse_size(argv[++i], &length) < 0) {
147                 return -1;
148             }
149         } else if (!strcmp(argv[i], "--compress")) {
150             if (!strcmp(argv[++i], "lz4")) {
151                 flags |= fvm::kSparseFlagLz4;
152             } else {
153                 fprintf(stderr, "Invalid compression type\n");
154                 return -1;
155             }
156         } else if (!strcmp(argv[i], "--disk")) {
157             if (parse_size(argv[++i], &disk_size) < 0) {
158                 return -1;
159             }
160         } else {
161             break;
162         }
163 
164         ++i;
165     }
166 
167     if (!strcmp(command, "create") && should_unlink) {
168         unlink(path);
169     }
170 
171     // If length was not specified, use remainder of file after offset
172     if (length == 0) {
173         length = get_disk_size(path, offset);
174     }
175 
176     if (!strcmp(command, "create")) {
177         // If length was specified, an offset was not, we were asked to create a
178         // file, and the file does not exist, truncate it to the given length.
179         if (length != 0 && offset == 0) {
180             fbl::unique_fd fd(open(path, O_CREAT | O_EXCL | O_WRONLY, 0644));
181 
182             if (fd) {
183                 ftruncate(fd.get(), length);
184             }
185         }
186 
187         fbl::unique_ptr<FvmContainer> fvmContainer;
188         if (FvmContainer::Create(path, slice_size, offset, length, &fvmContainer) != ZX_OK) {
189             return -1;
190         }
191 
192         if (add_partitions(fvmContainer.get(), argc - i, argv + i) < 0) {
193             return -1;
194         }
195 
196         if (fvmContainer->Commit() != ZX_OK) {
197             return -1;
198         }
199     } else if (!strcmp(command, "add")) {
200         fbl::unique_ptr<FvmContainer> fvmContainer(new FvmContainer(path, slice_size, offset,
201                                                                     length));
202 
203         if (add_partitions(fvmContainer.get(), argc - i, argv + i) < 0) {
204             return -1;
205         }
206 
207         if (fvmContainer->Commit() != ZX_OK) {
208             return -1;
209         }
210     } else if (!strcmp(command, "extend")) {
211         if (length == 0 || offset > 0) {
212             usage();
213         }
214 
215         size_t disk_size = get_disk_size(path, 0);
216 
217         if (length <= disk_size) {
218             fprintf(stderr, "Cannot extend to a value %zu less than current size %zu\n", length,
219                     disk_size);
220             usage();
221         }
222 
223         fbl::unique_ptr<FvmContainer> fvmContainer(new FvmContainer(path, slice_size, offset,
224                                                                     disk_size));
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         SparseContainer compressedContainer(input_path, slice_size, flags);
271         if (compressedContainer.Decompress(path) != ZX_OK) {
272             return -1;
273         }
274 
275         SparseContainer sparseContainer(path, slice_size, flags);
276         if (sparseContainer.Verify() != ZX_OK) {
277             return -1;
278         }
279     } else if (!strcmp(command, "size")) {
280         SparseContainer sparseContainer(path, slice_size, flags);
281 
282         if (disk_size == 0) {
283             printf("%" PRIu64 "\n", sparseContainer.CalculateDiskSize());
284         } else if (sparseContainer.CheckDiskSize(disk_size) != ZX_OK) {
285             fprintf(stderr, "Sparse container will not fit in target disk size\n");
286             return -1;
287         }
288     } else if (!strcmp(command, "pave")) {
289         char* input_type = argv[i];
290         char* input_path = argv[i + 1];
291 
292         if (strcmp(input_type, "--sparse")) {
293             fprintf(stderr, "pave command only accepts --sparse input option\n");
294             usage();
295             return -1;
296         }
297 
298         SparseContainer sparseData(input_path, slice_size, flags);
299         if (sparseData.Pave(path, offset, length) != ZX_OK) {
300             return -1;
301         }
302     } else {
303         usage();
304     }
305 
306     return 0;
307 }
308