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