1 /*
2  * Copyright 2014-2017 Frank Hunleth
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "functions.h"
18 #include "util.h"
19 #include "disk_crypto.h"
20 #include "fatfs.h"
21 #include "mbr.h"
22 #include "gpt.h"
23 #include "fwfile.h"
24 #include "block_cache.h"
25 #include "uboot_env.h"
26 #include "sparse_file.h"
27 #include "progress.h"
28 #include "pad_to_block_writer.h"
29 
30 #include <assert.h>
31 #include <errno.h>
32 #include <inttypes.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <unistd.h>
36 #include <sys/types.h>
37 #include <sys/stat.h>
38 #include <fcntl.h>
39 
40 #include "monocypher.h"
41 
42 #define DECLARE_FUN(FUN) \
43     static int FUN ## _validate(struct fun_context *fctx); \
44     static int FUN ## _compute_progress(struct fun_context *fctx); \
45     static int FUN ## _run(struct fun_context *fctx)
46 
47 DECLARE_FUN(raw_write);
48 DECLARE_FUN(raw_memset);
49 DECLARE_FUN(fat_attrib);
50 DECLARE_FUN(fat_mkfs);
51 DECLARE_FUN(fat_write);
52 DECLARE_FUN(fat_mv);
53 DECLARE_FUN(fat_rm);
54 DECLARE_FUN(fat_cp);
55 DECLARE_FUN(fat_mkdir);
56 DECLARE_FUN(fat_setlabel);
57 DECLARE_FUN(fat_touch);
58 DECLARE_FUN(gpt_write);
59 DECLARE_FUN(mbr_write);
60 DECLARE_FUN(trim);
61 DECLARE_FUN(uboot_clearenv);
62 DECLARE_FUN(uboot_setenv);
63 DECLARE_FUN(uboot_unsetenv);
64 DECLARE_FUN(uboot_recover);
65 DECLARE_FUN(error);
66 DECLARE_FUN(info);
67 DECLARE_FUN(path_write);
68 DECLARE_FUN(pipe_write);
69 DECLARE_FUN(execute);
70 
71 struct fun_info {
72     const char *name;
73     int (*validate)(struct fun_context *fctx);
74     int (*compute_progress)(struct fun_context *fctx);
75     int (*run)(struct fun_context *fctx);
76 };
77 
78 #define FUN_INFO(FUN) {#FUN, FUN ## _validate, FUN ## _compute_progress, FUN ## _run}
79 #define FUN_BANG_INFO(FUN) {#FUN "!", FUN ## _validate, FUN ## _compute_progress, FUN ## _run}
80 static struct fun_info fun_table[] = {
81     FUN_INFO(raw_write),
82     FUN_INFO(raw_memset),
83     FUN_INFO(fat_attrib),
84     FUN_INFO(fat_mkfs),
85     FUN_INFO(fat_write),
86     FUN_INFO(fat_mv),
87     FUN_BANG_INFO(fat_mv),
88     FUN_INFO(fat_rm),
89     FUN_BANG_INFO(fat_rm),
90     FUN_INFO(fat_cp),
91     FUN_INFO(fat_mkdir),
92     FUN_INFO(fat_setlabel),
93     FUN_INFO(fat_touch),
94     FUN_INFO(gpt_write),
95     FUN_INFO(mbr_write),
96     FUN_INFO(trim),
97     FUN_INFO(uboot_clearenv),
98     FUN_INFO(uboot_setenv),
99     FUN_INFO(uboot_unsetenv),
100     FUN_INFO(uboot_recover),
101     FUN_INFO(error),
102     FUN_INFO(info),
103     FUN_INFO(path_write),
104     FUN_INFO(pipe_write),
105     FUN_INFO(execute),
106 };
107 
lookup(int argc,const char ** argv)108 static struct fun_info *lookup(int argc, const char **argv)
109 {
110     if (argc < 1) {
111         set_last_error("Not enough parameters");
112         return 0;
113     }
114 
115     size_t i;
116     for (i = 0; i < NUM_ELEMENTS(fun_table); i++) {
117         if (strcmp(argv[0], fun_table[i].name) == 0) {
118             return &fun_table[i];
119         }
120     }
121 
122     set_last_error("Unknown function");
123     return 0;
124 }
125 
126 /**
127  * @brief Validate the parameters passed to the function
128  *
129  * This is called when creating the firmware file.
130  *
131  * @param fctx the function context
132  * @return 0 if ok
133  */
fun_validate(struct fun_context * fctx)134 int fun_validate(struct fun_context *fctx)
135 {
136     struct fun_info *fun = lookup(fctx->argc, fctx->argv);
137     if (!fun)
138         return -1;
139 
140     return fun->validate(fctx);
141 }
142 
143 /**
144  * @brief Compute the total progress units expected
145  *
146  * This is called before running.
147  *
148  * @param fctx the function context
149  * @return 0 if ok
150  */
fun_compute_progress(struct fun_context * fctx)151 int fun_compute_progress(struct fun_context *fctx)
152 {
153     struct fun_info *fun = lookup(fctx->argc, fctx->argv);
154     if (!fun)
155         return -1;
156 
157     return fun->compute_progress(fctx);
158 }
159 
160 /**
161  * @brief Run a function
162  *
163  * This is called when applying the firmware.
164  *
165  * @param fctx the function context
166  * @return 0 if ok
167  */
fun_run(struct fun_context * fctx)168 int fun_run(struct fun_context *fctx)
169 {
170     struct fun_info *fun = lookup(fctx->argc, fctx->argv);
171     if (!fun)
172         return -1;
173 
174     return fun->run(fctx);
175 }
176 
177 
178 /**
179  * @brief Run all of the functions in a funlist
180  * @param fctx the context to use (argc and argv will be updated in it)
181  * @param funlist the list
182  * @param fun the function to execute (either fun_run or fun_compute_progress)
183  * @return 0 if ok
184  */
fun_apply_funlist(struct fun_context * fctx,cfg_opt_t * funlist,int (* fun)(struct fun_context * fctx))185 int fun_apply_funlist(struct fun_context *fctx, cfg_opt_t *funlist, int (*fun)(struct fun_context *fctx))
186 {
187     int ix = 0;
188     char *aritystr;
189     while ((aritystr = cfg_opt_getnstr(funlist, ix++)) != NULL) {
190         fctx->argc = strtoul(aritystr, NULL, 0);
191         if (fctx->argc <= 0 || fctx->argc > FUN_MAX_ARGS) {
192             set_last_error("Unexpected argc value in funlist");
193             return -1;
194         }
195         int i;
196         for (i = 0; i < fctx->argc; i++) {
197             fctx->argv[i] = cfg_opt_getnstr(funlist, ix++);
198             if (fctx->argv[i] == NULL) {
199                 set_last_error("Unexpected error with funlist");
200                 return -1;
201             }
202         }
203         // Clear out the rest of the argv entries to avoid confusion when debugging.
204         for (; i < FUN_MAX_ARGS; i++)
205             fctx->argv[i] = 0;
206 
207         if (fun(fctx) < 0)
208             return -1;
209     }
210     return 0;
211 }
212 
213 /**
214  * Helper function that is paired with process_resource() to compute
215  * progress.
216  *
217  * Set count_holes to true if holes in sparse files are manually written
218  * to the destination as zeros. E.g., holes can't be optimized out by the
219  * OS.
220  */
process_resource_compute_progress(struct fun_context * fctx,bool count_holes)221 static int process_resource_compute_progress(struct fun_context *fctx, bool count_holes)
222 {
223     assert(fctx->type == FUN_CONTEXT_FILE);
224     assert(fctx->on_event);
225 
226     struct sparse_file_map sfm;
227     sparse_file_init(&sfm);
228     OK_OR_RETURN(sparse_file_get_map_from_config(fctx->cfg, fctx->on_event->title, &sfm));
229     off_t expected_length = count_holes ? sparse_file_size(&sfm) : sparse_file_data_size(&sfm);
230     sparse_file_free(&sfm);
231 
232     // Count each byte as a progress unit
233     fctx->progress->total_units += expected_length;
234 
235     return 0;
236 }
237 /**
238  * This is a helper function for reading a resource out of a file and doing
239  * something with it. (Like write it somewhere)
240  *
241  * It handles the following:
242  *   1. Finds the resource
243  *   2. Verifies that the resource contents pass the checksums
244  *   3. Handles sparse resources
245  *   4. Checks nit-picky issues and returns errors when detected
246  *
247  * NOTE: count_holes must match the value passed to process_resource_compute_progress.
248  */
process_resource(struct fun_context * fctx,bool count_holes,int (* pwrite_callback)(void * cookie,const void * buf,size_t count,off_t offset),int (* final_hole_callback)(void * cookie,off_t hole_size,off_t file_size),void * cookie)249 static int process_resource(struct fun_context *fctx,
250                             bool count_holes,
251                             int (*pwrite_callback)(void *cookie, const void *buf, size_t count, off_t offset),
252                             int (*final_hole_callback)(void *cookie, off_t hole_size, off_t file_size),
253                             void *cookie)
254 {
255     assert(fctx->type == FUN_CONTEXT_FILE);
256     assert(fctx->on_event);
257 
258     int rc = 0;
259     struct sparse_file_map sfm;
260     sparse_file_init(&sfm);
261 
262     cfg_t *resource = cfg_gettsec(fctx->cfg, "file-resource", fctx->on_event->title);
263     if (!resource)
264         ERR_CLEANUP_MSG("%s can't find file-resource '%s'", fctx->argv[0], fctx->on_event->title);
265 
266     char *expected_hash = cfg_getstr(resource, "blake2b-256");
267     if (!expected_hash || strlen(expected_hash) != FWUP_BLAKE2b_256_LEN * 2)
268         ERR_CLEANUP_MSG("invalid blake2b hash for '%s'", fctx->on_event->title);
269 
270     OK_OR_CLEANUP(sparse_file_get_map_from_resource(resource, &sfm));
271     off_t expected_data_length = sparse_file_data_size(&sfm);
272 
273     off_t total_data_read = 0;
274 
275     crypto_blake2b_ctx hash_state;
276     crypto_blake2b_general_init(&hash_state, FWUP_BLAKE2b_256_LEN, NULL, 0);
277 
278     off_t last_offset = 0;
279     for (;;) {
280         off_t offset;
281         size_t len;
282         const void *buffer;
283 
284         OK_OR_CLEANUP(fctx->read(fctx, &buffer, &len, &offset));
285 
286         // Check if done.
287         if (len == 0)
288             break;
289 
290         crypto_blake2b_update(&hash_state, (const uint8_t*) buffer, len);
291 
292         OK_OR_CLEANUP(pwrite_callback(cookie, buffer, len, offset));
293 
294         total_data_read += len;
295         if (!count_holes) {
296             // If not counting holes for progress reporting, then report
297             // that we wrote exactly what was read.
298             progress_report(fctx->progress, len);
299         } else {
300             // If counting holes for progress reporting, then report
301             // everything since the last time.
302             off_t next_offset_to_write = offset + len;
303             progress_report(fctx->progress, next_offset_to_write - last_offset);
304             last_offset = next_offset_to_write;
305         }
306     }
307 
308     // Handle a final hole in a sparse file
309     off_t ending_hole = sparse_ending_hole_size(&sfm);
310     if (ending_hole > 0) {
311         OK_OR_CLEANUP(final_hole_callback(cookie, ending_hole, sparse_file_size(&sfm)));
312 
313         if (count_holes)
314             progress_report(fctx->progress, ending_hole);
315     }
316 
317     if (total_data_read != expected_data_length) {
318         if (total_data_read == 0)
319             ERR_CLEANUP_MSG("%s didn't write anything and was likely called twice in an on-resource for '%s'. Try a \"cp\" function.", fctx->argv[0], fctx->on_event->title);
320         else
321             ERR_CLEANUP_MSG("%s wrote %" PRId64" bytes for '%s', but should have written %" PRId64, fctx->argv[0], total_data_read, fctx->on_event->title, expected_data_length);
322     }
323 
324     // Verify hash
325     unsigned char hash[32];
326     crypto_blake2b_final(&hash_state, hash);
327     char hash_str[sizeof(hash) * 2 + 1];
328     bytes_to_hex(hash, hash_str, sizeof(hash));
329     if (memcmp(hash_str, expected_hash, sizeof(hash_str)) != 0)
330         ERR_CLEANUP_MSG("%s detected blake2b mismatch on '%s'", fctx->argv[0], fctx->on_event->title);
331 
332 cleanup:
333     sparse_file_free(&sfm);
334     return rc;
335 }
336 
337 struct raw_write_options {
338     const char *cipher;
339     const char *secret;
340 };
parse_raw_write_options(const struct fun_context * fctx,struct raw_write_options * options)341 static int parse_raw_write_options(const struct fun_context *fctx, struct raw_write_options *options)
342 {
343     memset(options, 0, sizeof(*options));
344 
345     for (int i = 2; i < fctx->argc; i++) {
346         const char *key;
347         const char *value;
348 
349         key = fctx->argv[i];
350         value = strchr(key, '=');
351         if (!value)
352             ERR_RETURN("Expecting '=' for optional raw_write parameter");
353 
354         value++;
355 
356         if (strncmp(key, "cipher=", 7) == 0)
357             options->cipher = value;
358         else if (strncmp(key, "secret=", 7) == 0)
359             options->secret = value;
360         else
361             ERR_RETURN("Unexpected parameter to raw_write: %s", key);
362     }
363     return 0;
364 }
raw_write_validate(struct fun_context * fctx)365 int raw_write_validate(struct fun_context *fctx)
366 {
367     if (fctx->type != FUN_CONTEXT_FILE)
368         ERR_RETURN("raw_write only usable in on-resource");
369 
370     if (fctx->argc < 2)
371         ERR_RETURN("raw_write requires a block offset");
372 
373     CHECK_ARG_UINT64(fctx->argv[1], "raw_write requires a non-negative integer block offset");
374 
375     // Check the options
376     struct raw_write_options options;
377     OK_OR_RETURN(parse_raw_write_options(fctx, &options));
378 
379     // If there's a cipher, then there must be a secret
380     if ((options.cipher && !options.secret) ||
381         (options.secret && !options.cipher))
382         ERR_RETURN("raw_write requires both a cipher and secret if one is supplied");
383 
384     return 0;
385 }
raw_write_compute_progress(struct fun_context * fctx)386 int raw_write_compute_progress(struct fun_context *fctx)
387 {
388     return process_resource_compute_progress(fctx, false);
389 }
390 struct raw_write_cookie {
391     off_t dest_offset;
392     struct pad_to_block_writer ptbw;
393 };
raw_write_pwrite_callback(void * cookie,const void * buf,size_t count,off_t offset)394 static int raw_write_pwrite_callback(void *cookie, const void *buf, size_t count, off_t offset)
395 {
396     struct raw_write_cookie *rwc = (struct raw_write_cookie *) cookie;
397     return ptbw_pwrite(&rwc->ptbw, buf, count, rwc->dest_offset + offset);
398 }
raw_write_final_hole_callback(void * cookie,off_t hole_size,off_t file_size)399 static int raw_write_final_hole_callback(void *cookie, off_t hole_size, off_t file_size)
400 {
401     struct raw_write_cookie *rwc = (struct raw_write_cookie *) cookie;
402 
403     // If this is a regular file, seeking is insufficient in making the file
404     // the right length, so write a block of zeros to the end.
405     uint8_t zeros[FWUP_BLOCK_SIZE];
406     memset(zeros, 0, sizeof(zeros));
407     off_t to_write = sizeof(zeros);
408     if (hole_size < to_write)
409         to_write = hole_size;
410     off_t offset = file_size - to_write;
411     return ptbw_pwrite(&rwc->ptbw, zeros, to_write, rwc->dest_offset + offset);
412 }
raw_write_run(struct fun_context * fctx)413 int raw_write_run(struct fun_context *fctx)
414 {
415     int rc = 0;
416 
417     // Raw write runs all writes through pad_to_block_writer to guarantee
418     // block size writes to the caching code no matter how the input resources
419     // get decompressed.
420 
421     struct raw_write_cookie rwc;
422     rwc.dest_offset = strtoull(fctx->argv[1], NULL, 0) * FWUP_BLOCK_SIZE;
423 
424     struct raw_write_options options;
425     OK_OR_RETURN(parse_raw_write_options(fctx, &options));
426 
427     struct disk_crypto dc_info;
428     struct disk_crypto *dc = NULL;
429     if (options.cipher) {
430         if (disk_crypto_init(&dc_info, options.cipher, options.secret, rwc.dest_offset) < 0)
431             return -1;
432         dc = &dc_info;
433     }
434 
435     ptbw_init(&rwc.ptbw, fctx->output, dc);
436 
437     OK_OR_CLEANUP(process_resource(fctx,
438                                   false,
439                                   raw_write_pwrite_callback,
440                                   raw_write_final_hole_callback,
441                                   &rwc));
442 
443     rc = ptbw_flush(&rwc.ptbw);
444 
445 cleanup:
446     if (dc)
447         disk_crypto_free(dc);
448 
449     return rc;
450 }
451 
raw_memset_validate(struct fun_context * fctx)452 int raw_memset_validate(struct fun_context *fctx)
453 {
454     if (fctx->argc != 4)
455         ERR_RETURN("raw_memset requires a block offset, count, and value");
456 
457     CHECK_ARG_UINT64(fctx->argv[1], "raw_memset requires a non-negative integer block offset");
458     CHECK_ARG_UINT64_RANGE(fctx->argv[2], 1, INT32_MAX / FWUP_BLOCK_SIZE, "raw_memset requires a positive integer block count");
459 
460     int value = strtol(fctx->argv[3], NULL, 0);
461     if (value < 0 || value > 255)
462         ERR_RETURN("raw_memset requires value to be between 0 and 255");
463 
464     return 0;
465 }
raw_memset_compute_progress(struct fun_context * fctx)466 int raw_memset_compute_progress(struct fun_context *fctx)
467 {
468     int count = strtol(fctx->argv[2], NULL, 0);
469 
470     // Count each byte as a progress unit
471     fctx->progress->total_units += count * FWUP_BLOCK_SIZE;
472 
473     return 0;
474 }
raw_memset_run(struct fun_context * fctx)475 int raw_memset_run(struct fun_context *fctx)
476 {
477     const size_t block_size = FWUP_BLOCK_SIZE;
478 
479     off_t dest_offset = strtoull(fctx->argv[1], NULL, 0) * FWUP_BLOCK_SIZE;
480     off_t count = strtoull(fctx->argv[2], NULL, 0) * FWUP_BLOCK_SIZE;
481     int value = strtol(fctx->argv[3], NULL, 0);
482     char buffer[block_size];
483     memset(buffer, value, sizeof(buffer));
484 
485     off_t len_written = 0;
486     off_t offset;
487     for (offset = 0; offset < count; offset += block_size) {
488         OK_OR_RETURN_MSG(block_cache_pwrite(fctx->output, buffer, block_size, dest_offset + offset, true),
489                          "raw_memset couldn't write %d bytes to offset %" PRId64, block_size, dest_offset + offset);
490 
491         len_written += block_size;
492         progress_report(fctx->progress, block_size);
493     }
494 
495     return 0;
496 }
497 
fat_mkfs_validate(struct fun_context * fctx)498 int fat_mkfs_validate(struct fun_context *fctx)
499 {
500     if (fctx->argc != 3)
501         ERR_RETURN("fat_mkfs requires a block offset and block count");
502 
503     CHECK_ARG_UINT64(fctx->argv[1], "fat_mkfs requires a non-negative integer block offset");
504     CHECK_ARG_UINT64(fctx->argv[2], "fat_mkfs requires a non-negative integer block count");
505 
506     return 0;
507 }
fat_mkfs_compute_progress(struct fun_context * fctx)508 int fat_mkfs_compute_progress(struct fun_context *fctx)
509 {
510     fctx->progress->total_units += FWUP_BLOCK_SIZE; // Arbitarily count as 1 block
511     return 0;
512 }
fat_mkfs_run(struct fun_context * fctx)513 int fat_mkfs_run(struct fun_context *fctx)
514 {
515     off_t block_offset = strtoull(fctx->argv[1], NULL, 0);
516     size_t block_count = strtoul(fctx->argv[2], NULL, 0);
517 
518     if (fatfs_mkfs(fctx->output, block_offset, block_count) < 0)
519         return -1;
520 
521     progress_report(fctx->progress, FWUP_BLOCK_SIZE);
522     return 0;
523 }
524 
fat_attrib_validate(struct fun_context * fctx)525 int fat_attrib_validate(struct fun_context *fctx)
526 {
527     if (fctx->argc != 4)
528         ERR_RETURN("fat_attrib requires a block offset, filename, and attributes (SHR)");
529 
530     CHECK_ARG_UINT64(fctx->argv[1], "fat_mkfs requires a non-negative integer block offset");
531 
532     const char *attrib = fctx->argv[3];
533     while (*attrib) {
534         switch (*attrib) {
535         case 'S':
536         case 's':
537         case 'H':
538         case 'h':
539         case 'R':
540         case 'r':
541             break;
542 
543         default:
544             ERR_RETURN("fat_attrib only supports R, H, and S attributes");
545         }
546         attrib++;
547     }
548     return 0;
549 }
fat_attrib_compute_progress(struct fun_context * fctx)550 int fat_attrib_compute_progress(struct fun_context *fctx)
551 {
552     fctx->progress->total_units += FWUP_BLOCK_SIZE; // Arbitarily count as 1 block
553     return 0;
554 }
fat_attrib_run(struct fun_context * fctx)555 int fat_attrib_run(struct fun_context *fctx)
556 {
557     if (fatfs_attrib(fctx->output, strtoull(fctx->argv[1], NULL, 0), fctx->argv[2], fctx->argv[3]) < 0)
558         return 1;
559 
560     progress_report(fctx->progress, FWUP_BLOCK_SIZE);
561     return 0;
562 }
563 
fat_write_validate(struct fun_context * fctx)564 int fat_write_validate(struct fun_context *fctx)
565 {
566     if (fctx->type != FUN_CONTEXT_FILE)
567         ERR_RETURN("fat_write only usable in on-resource");
568 
569     if (fctx->argc != 3)
570         ERR_RETURN("fat_write requires a block offset and destination filename");
571 
572     CHECK_ARG_UINT64(fctx->argv[1], "fat_write requires a non-negative integer block offset");
573 
574     return 0;
575 }
fat_write_compute_progress(struct fun_context * fctx)576 int fat_write_compute_progress(struct fun_context *fctx)
577 {
578     return process_resource_compute_progress(fctx, true);
579 }
580 struct fat_write_cookie {
581     struct fun_context *fctx;
582     off_t block_offset;
583 };
fat_write_pwrite_callback(void * cookie,const void * buf,size_t count,off_t offset)584 static int fat_write_pwrite_callback(void *cookie, const void *buf, size_t count, off_t offset)
585 {
586     struct fat_write_cookie *fwc = (struct fat_write_cookie *) cookie;
587     struct fun_context *fctx = fwc->fctx;
588 
589     return fatfs_pwrite(fctx->output, fwc->block_offset, fctx->argv[2], (int) offset, buf, count);
590 }
fat_write_final_hole_callback(void * cookie,off_t hole_size,off_t file_size)591 static int fat_write_final_hole_callback(void *cookie, off_t hole_size, off_t file_size)
592 {
593     (void) hole_size;
594 
595     struct fat_write_cookie *fwc = (struct fat_write_cookie *) cookie;
596     struct fun_context *fctx = fwc->fctx;
597 
598     // If the file ends in a hole, fatfs_pwrite can be used to grow it.
599     return fatfs_pwrite(fctx->output, fwc->block_offset, fctx->argv[2], (int) file_size, NULL, 0);
600 }
fat_write_run(struct fun_context * fctx)601 int fat_write_run(struct fun_context *fctx)
602 {
603     struct fat_write_cookie fwc;
604     fwc.fctx = fctx;
605     fwc.block_offset = strtoull(fctx->argv[1], NULL, 0);
606 
607     // Enforce truncation semantics if the file exists
608     OK_OR_RETURN(fatfs_truncate(fctx->output, fwc.block_offset, fctx->argv[2]));
609 
610     return process_resource(fctx,
611                             true,
612                             fat_write_pwrite_callback,
613                             fat_write_final_hole_callback,
614                             &fwc);
615 }
616 
fat_mv_validate(struct fun_context * fctx)617 int fat_mv_validate(struct fun_context *fctx)
618 {
619     if (fctx->argc != 4)
620         ERR_RETURN("fat_mv requires a block offset, old filename, new filename");
621 
622     CHECK_ARG_UINT64(fctx->argv[1], "fat_mv requires a non-negative integer block offset");
623     return 0;
624 }
fat_mv_compute_progress(struct fun_context * fctx)625 int fat_mv_compute_progress(struct fun_context *fctx)
626 {
627     fctx->progress->total_units += FWUP_BLOCK_SIZE; // Arbitarily count as 1 block
628     return 0;
629 }
fat_mv_run(struct fun_context * fctx)630 int fat_mv_run(struct fun_context *fctx)
631 {
632     off_t block_offset = strtoull(fctx->argv[1], NULL, 0);
633 
634     bool force = (fctx->argv[0][6] == '!');
635     OK_OR_RETURN(fatfs_mv(fctx->output, block_offset, fctx->argv[0], fctx->argv[2], fctx->argv[3], force));
636 
637     progress_report(fctx->progress, FWUP_BLOCK_SIZE);
638     return 0;
639 }
640 
fat_rm_validate(struct fun_context * fctx)641 int fat_rm_validate(struct fun_context *fctx)
642 {
643     if (fctx->argc != 3)
644         ERR_RETURN("fat_rm requires a block offset and filename");
645 
646     CHECK_ARG_UINT64(fctx->argv[1], "fat_rm requires a non-negative integer block offset");
647 
648     return 0;
649 }
fat_rm_compute_progress(struct fun_context * fctx)650 int fat_rm_compute_progress(struct fun_context *fctx)
651 {
652     fctx->progress->total_units += FWUP_BLOCK_SIZE; // Arbitarily count as 1 block
653     return 0;
654 }
fat_rm_run(struct fun_context * fctx)655 int fat_rm_run(struct fun_context *fctx)
656 {
657     off_t block_offset = strtoull(fctx->argv[1], NULL, 0);
658 
659     bool file_must_exist = (fctx->argv[0][6] == '!');
660     OK_OR_RETURN(fatfs_rm(fctx->output, block_offset, fctx->argv[0], fctx->argv[2], file_must_exist));
661 
662     progress_report(fctx->progress, FWUP_BLOCK_SIZE);
663     return 0;
664 }
665 
fat_cp_validate(struct fun_context * fctx)666 int fat_cp_validate(struct fun_context *fctx)
667 {
668     if (fctx->argc != 4)
669         ERR_RETURN("fat_cp requires a block offset, from filename, and to filename");
670 
671     CHECK_ARG_UINT64(fctx->argv[1], "fat_cp requires a non-negative integer block offset");
672 
673     return 0;
674 }
fat_cp_compute_progress(struct fun_context * fctx)675 int fat_cp_compute_progress(struct fun_context *fctx)
676 {
677     fctx->progress->total_units += FWUP_BLOCK_SIZE; // Arbitarily count as 1 block
678     return 0;
679 }
fat_cp_run(struct fun_context * fctx)680 int fat_cp_run(struct fun_context *fctx)
681 {
682     off_t block_offset = strtoull(fctx->argv[1], NULL, 0);
683 
684     OK_OR_RETURN(fatfs_cp(fctx->output, block_offset, fctx->argv[2], fctx->argv[3]));
685 
686     progress_report(fctx->progress, FWUP_BLOCK_SIZE);
687     return 0;
688 }
689 
fat_mkdir_validate(struct fun_context * fctx)690 int fat_mkdir_validate(struct fun_context *fctx)
691 {
692     if (fctx->argc != 3)
693         ERR_RETURN("fat_mkdir requires a block offset and directory name");
694 
695     CHECK_ARG_UINT64(fctx->argv[1], "fat_mkdir requires a non-negative integer block offset");
696 
697     return 0;
698 }
fat_mkdir_compute_progress(struct fun_context * fctx)699 int fat_mkdir_compute_progress(struct fun_context *fctx)
700 {
701     fctx->progress->total_units += FWUP_BLOCK_SIZE; // Arbitarily count as 1 block
702     return 0;
703 }
fat_mkdir_run(struct fun_context * fctx)704 int fat_mkdir_run(struct fun_context *fctx)
705 {
706     off_t block_offset = strtoull(fctx->argv[1], NULL, 0);
707 
708     OK_OR_RETURN(fatfs_mkdir(fctx->output, block_offset, fctx->argv[2]));
709 
710     progress_report(fctx->progress, FWUP_BLOCK_SIZE);
711     return 0;
712 }
713 
fat_setlabel_validate(struct fun_context * fctx)714 int fat_setlabel_validate(struct fun_context *fctx)
715 {
716     if (fctx->argc != 3)
717         ERR_RETURN("fat_setlabel requires a block offset and name");
718 
719     CHECK_ARG_UINT64(fctx->argv[1], "fat_setlabel requires a non-negative integer block offset");
720 
721     return 0;
722 }
fat_setlabel_compute_progress(struct fun_context * fctx)723 int fat_setlabel_compute_progress(struct fun_context *fctx)
724 {
725     fctx->progress->total_units += FWUP_BLOCK_SIZE; // Arbitarily count as 1 block
726     return 0;
727 }
fat_setlabel_run(struct fun_context * fctx)728 int fat_setlabel_run(struct fun_context *fctx)
729 {
730     off_t block_offset = strtoull(fctx->argv[1], NULL, 0);
731 
732     OK_OR_RETURN(fatfs_setlabel(fctx->output, block_offset, fctx->argv[2]));
733 
734     progress_report(fctx->progress, FWUP_BLOCK_SIZE);
735     return 0;
736 }
737 
fat_touch_validate(struct fun_context * fctx)738 int fat_touch_validate(struct fun_context *fctx)
739 {
740     if (fctx->argc != 3)
741         ERR_RETURN("fat_touch requires a block offset and filename");
742 
743     CHECK_ARG_UINT64(fctx->argv[1], "fat_touch requires a non-negative integer block offset");
744 
745     return 0;
746 }
fat_touch_compute_progress(struct fun_context * fctx)747 int fat_touch_compute_progress(struct fun_context *fctx)
748 {
749     fctx->progress->total_units += FWUP_BLOCK_SIZE; // Arbitarily count as 1 block
750     return 0;
751 }
fat_touch_run(struct fun_context * fctx)752 int fat_touch_run(struct fun_context *fctx)
753 {
754     off_t block_offset = strtoull(fctx->argv[1], NULL, 0);
755 
756     OK_OR_RETURN(fatfs_touch(fctx->output, block_offset, fctx->argv[2]));
757 
758     progress_report(fctx->progress, FWUP_BLOCK_SIZE);
759     return 0;
760 }
761 
762 #define GPT_UNITS (GPT_SIZE * 2) // primary and secondary GPT
763 
gpt_write_validate(struct fun_context * fctx)764 int gpt_write_validate(struct fun_context *fctx)
765 {
766     if (fctx->argc != 2)
767         ERR_RETURN("gpt_write requires a gpt");
768 
769     const char *gpt_name = fctx->argv[1];
770     cfg_t *gptsec = cfg_gettsec(fctx->cfg, "gpt", gpt_name);
771 
772     if (!gptsec)
773         ERR_RETURN("gpt_write can't find gpt reference");
774 
775     return 0;
776 }
gpt_write_compute_progress(struct fun_context * fctx)777 int gpt_write_compute_progress(struct fun_context *fctx)
778 {
779     fctx->progress->total_units += GPT_UNITS;
780     return 0;
781 }
gpt_write_run(struct fun_context * fctx)782 int gpt_write_run(struct fun_context *fctx)
783 {
784     const char *gpt_name = fctx->argv[1];
785     cfg_t *gptsec = cfg_gettsec(fctx->cfg, "gpt", gpt_name);
786     int rc = 0;
787 
788     uint8_t *mbr_and_primary_gpt = malloc(FWUP_BLOCK_SIZE + GPT_SIZE);
789     uint8_t *secondary_gpt = malloc(GPT_SIZE);
790 
791     off_t secondary_gpt_offset;
792     OK_OR_CLEANUP(gpt_create_cfg(gptsec, fctx->output->num_blocks, mbr_and_primary_gpt, secondary_gpt, &secondary_gpt_offset));
793 
794     OK_OR_CLEANUP_MSG(block_cache_pwrite(fctx->output, mbr_and_primary_gpt, FWUP_BLOCK_SIZE + GPT_SIZE, 0, false),
795                      "unexpected error writing protective mbr and primary gpt: %s", strerror(errno));
796     OK_OR_CLEANUP_MSG(block_cache_pwrite(fctx->output, secondary_gpt, GPT_SIZE, secondary_gpt_offset, false),
797                      "unexpected error writing secondary gpt: %s", strerror(errno));
798 
799     progress_report(fctx->progress, GPT_UNITS);
800 
801  cleanup:
802     free(mbr_and_primary_gpt);
803     free(secondary_gpt);
804     return rc;
805 }
806 
mbr_write_validate(struct fun_context * fctx)807 int mbr_write_validate(struct fun_context *fctx)
808 {
809     if (fctx->argc != 2)
810         ERR_RETURN("mbr_write requires an mbr");
811 
812     const char *mbr_name = fctx->argv[1];
813     cfg_t *mbrsec = cfg_gettsec(fctx->cfg, "mbr", mbr_name);
814 
815     if (!mbrsec)
816         ERR_RETURN("mbr_write can't find mbr reference");
817 
818     return 0;
819 }
mbr_write_compute_progress(struct fun_context * fctx)820 int mbr_write_compute_progress(struct fun_context *fctx)
821 {
822     fctx->progress->total_units += FWUP_BLOCK_SIZE;
823     return 0;
824 }
mbr_write_run(struct fun_context * fctx)825 int mbr_write_run(struct fun_context *fctx)
826 {
827     const char *mbr_name = fctx->argv[1];
828     cfg_t *mbrsec = cfg_gettsec(fctx->cfg, "mbr", mbr_name);
829 
830     uint8_t buffer[FWUP_BLOCK_SIZE];
831     OK_OR_RETURN(mbr_create_cfg(mbrsec, fctx->output->num_blocks, buffer));
832 
833     OK_OR_RETURN_MSG(block_cache_pwrite(fctx->output, buffer, FWUP_BLOCK_SIZE, 0, false),
834                      "unexpected error writing mbr: %s", strerror(errno));
835 
836     progress_report(fctx->progress, FWUP_BLOCK_SIZE);
837     return 0;
838 }
839 
trim_validate(struct fun_context * fctx)840 int trim_validate(struct fun_context *fctx)
841 {
842     if (fctx->argc != 3)
843         ERR_RETURN("trim requires a block offset and count");
844 
845     CHECK_ARG_UINT64(fctx->argv[1], "trim requires a non-negative integer block offset");
846     CHECK_ARG_UINT64_RANGE(fctx->argv[2], 1, INT64_MAX / FWUP_BLOCK_SIZE, "trim requires a block count >1");
847 
848     return 0;
849 }
trim_compute_progress(struct fun_context * fctx)850 int trim_compute_progress(struct fun_context *fctx)
851 {
852     off_t block_count = strtoull(fctx->argv[2], NULL, 0);
853 
854     // Use a heuristic for counting trim progress units -> 1 per 128KB
855     fctx->progress->total_units += block_count / 256;
856 
857     return 0;
858 }
trim_run(struct fun_context * fctx)859 int trim_run(struct fun_context *fctx)
860 {
861     off_t block_offset = strtoull(fctx->argv[1], NULL, 0);
862     off_t block_count = strtoull(fctx->argv[2], NULL, 0);
863 
864     off_t offset = block_offset * FWUP_BLOCK_SIZE;
865     off_t count = block_count * FWUP_BLOCK_SIZE;
866 
867     OK_OR_RETURN(block_cache_trim(fctx->output, offset, count, true));
868 
869     progress_report(fctx->progress, block_count / 256);
870     return 0;
871 }
872 
873 
uboot_recover_validate(struct fun_context * fctx)874 int uboot_recover_validate(struct fun_context *fctx)
875 {
876     if (fctx->argc != 2)
877         ERR_RETURN("uboot_recover requires a uboot-environment reference");
878 
879     const char *uboot_env_name = fctx->argv[1];
880     cfg_t *ubootsec = cfg_gettsec(fctx->cfg, "uboot-environment", uboot_env_name);
881 
882     if (!ubootsec)
883         ERR_RETURN("uboot_recover can't find uboot-environment reference");
884 
885     return 0;
886 }
uboot_recover_compute_progress(struct fun_context * fctx)887 int uboot_recover_compute_progress(struct fun_context *fctx)
888 {
889     fctx->progress->total_units += FWUP_BLOCK_SIZE; // Arbitarily count as 1 block
890     return 0;
891 }
uboot_recover_run(struct fun_context * fctx)892 int uboot_recover_run(struct fun_context *fctx)
893 {
894     int rc = 0;
895     const char *uboot_env_name = fctx->argv[1];
896     cfg_t *ubootsec = cfg_gettsec(fctx->cfg, "uboot-environment", uboot_env_name);
897     struct uboot_env env;
898     struct uboot_env clean_env;
899 
900     if (uboot_env_create_cfg(ubootsec, &env) < 0 ||
901         uboot_env_create_cfg(ubootsec, &clean_env) < 0)
902         return -1;
903 
904     if (uboot_env_read(fctx->output, &env) < 0) {
905         // Corrupt, so make a clean environment and write it.
906         OK_OR_CLEANUP(uboot_env_write(fctx->output, &clean_env));
907     }
908 
909     progress_report(fctx->progress, FWUP_BLOCK_SIZE);
910 
911 cleanup:
912     uboot_env_free(&env);
913     uboot_env_free(&clean_env);
914     return rc;
915 }
916 
uboot_clearenv_validate(struct fun_context * fctx)917 int uboot_clearenv_validate(struct fun_context *fctx)
918 {
919     if (fctx->argc != 2)
920         ERR_RETURN("uboot_clearenv requires a uboot-environment reference");
921 
922     const char *uboot_env_name = fctx->argv[1];
923     cfg_t *ubootsec = cfg_gettsec(fctx->cfg, "uboot-environment", uboot_env_name);
924 
925     if (!ubootsec)
926         ERR_RETURN("uboot_clearenv can't find uboot-environment reference");
927 
928     return 0;
929 }
uboot_clearenv_compute_progress(struct fun_context * fctx)930 int uboot_clearenv_compute_progress(struct fun_context *fctx)
931 {
932     fctx->progress->total_units += FWUP_BLOCK_SIZE; // Arbitarily count as 1 block
933     return 0;
934 }
uboot_clearenv_run(struct fun_context * fctx)935 int uboot_clearenv_run(struct fun_context *fctx)
936 {
937     int rc = 0;
938     const char *uboot_env_name = fctx->argv[1];
939     cfg_t *ubootsec = cfg_gettsec(fctx->cfg, "uboot-environment", uboot_env_name);
940     struct uboot_env env;
941 
942     if (uboot_env_create_cfg(ubootsec, &env) < 0)
943         return -1;
944 
945     OK_OR_CLEANUP(uboot_env_write(fctx->output, &env));
946 
947     progress_report(fctx->progress, FWUP_BLOCK_SIZE);
948 
949 cleanup:
950     uboot_env_free(&env);
951     return rc;
952 }
953 
uboot_setenv_validate(struct fun_context * fctx)954 int uboot_setenv_validate(struct fun_context *fctx)
955 {
956     if (fctx->argc != 4)
957         ERR_RETURN("uboot_setenv requires a uboot-environment reference, variable name and value");
958 
959     const char *uboot_env_name = fctx->argv[1];
960     cfg_t *ubootsec = cfg_gettsec(fctx->cfg, "uboot-environment", uboot_env_name);
961 
962     if (!ubootsec)
963         ERR_RETURN("uboot_setenv can't find uboot-environment reference");
964 
965     return 0;
966 }
uboot_setenv_compute_progress(struct fun_context * fctx)967 int uboot_setenv_compute_progress(struct fun_context *fctx)
968 {
969     fctx->progress->total_units += FWUP_BLOCK_SIZE; // Arbitarily count as 1 block
970     return 0;
971 }
uboot_setenv_run(struct fun_context * fctx)972 int uboot_setenv_run(struct fun_context *fctx)
973 {
974     int rc = 0;
975     const char *uboot_env_name = fctx->argv[1];
976     cfg_t *ubootsec = cfg_gettsec(fctx->cfg, "uboot-environment", uboot_env_name);
977     struct uboot_env env;
978 
979     if (uboot_env_create_cfg(ubootsec, &env) < 0)
980         return -1;
981 
982     OK_OR_CLEANUP(uboot_env_read(fctx->output, &env));
983 
984     OK_OR_CLEANUP(uboot_env_setenv(&env, fctx->argv[2], fctx->argv[3]));
985 
986     OK_OR_CLEANUP(uboot_env_write(fctx->output, &env));
987 
988     progress_report(fctx->progress, FWUP_BLOCK_SIZE);
989 
990 cleanup:
991     uboot_env_free(&env);
992     return rc;
993 }
994 
uboot_unsetenv_validate(struct fun_context * fctx)995 int uboot_unsetenv_validate(struct fun_context *fctx)
996 {
997     if (fctx->argc != 3)
998         ERR_RETURN("uboot_unsetenv requires a uboot-environment reference and a variable name");
999 
1000     const char *uboot_env_name = fctx->argv[1];
1001     cfg_t *ubootsec = cfg_gettsec(fctx->cfg, "uboot-environment", uboot_env_name);
1002 
1003     if (!ubootsec)
1004         ERR_RETURN("uboot_unsetenv can't find uboot-environment reference");
1005 
1006     return 0;
1007 }
uboot_unsetenv_compute_progress(struct fun_context * fctx)1008 int uboot_unsetenv_compute_progress(struct fun_context *fctx)
1009 {
1010     fctx->progress->total_units += FWUP_BLOCK_SIZE; // Arbitarily count as 1 block
1011     return 0;
1012 }
uboot_unsetenv_run(struct fun_context * fctx)1013 int uboot_unsetenv_run(struct fun_context *fctx)
1014 {
1015     int rc = 0;
1016     const char *uboot_env_name = fctx->argv[1];
1017     cfg_t *ubootsec = cfg_gettsec(fctx->cfg, "uboot-environment", uboot_env_name);
1018     struct uboot_env env;
1019 
1020     OK_OR_RETURN(uboot_env_create_cfg(ubootsec, &env));
1021 
1022     OK_OR_CLEANUP(uboot_env_read(fctx->output, &env));
1023 
1024     OK_OR_CLEANUP(uboot_env_unsetenv(&env, fctx->argv[2]));
1025 
1026     OK_OR_CLEANUP(uboot_env_write(fctx->output, &env));
1027 
1028     progress_report(fctx->progress, FWUP_BLOCK_SIZE);
1029 
1030 cleanup:
1031     uboot_env_free(&env);
1032     return rc;
1033 }
1034 
error_validate(struct fun_context * fctx)1035 int error_validate(struct fun_context *fctx)
1036 {
1037     if (fctx->argc != 2)
1038         ERR_RETURN("error() requires a message parameter");
1039 
1040     return 0;
1041 }
error_compute_progress(struct fun_context * fctx)1042 int error_compute_progress(struct fun_context *fctx)
1043 {
1044     (void) fctx; // UNUSED
1045     return 0;
1046 }
error_run(struct fun_context * fctx)1047 int error_run(struct fun_context *fctx)
1048 {
1049     ERR_RETURN("%s", fctx->argv[1]);
1050 }
1051 
info_validate(struct fun_context * fctx)1052 int info_validate(struct fun_context *fctx)
1053 {
1054     if (fctx->argc != 2)
1055         ERR_RETURN("info() requires a message parameter");
1056 
1057     return 0;
1058 }
info_compute_progress(struct fun_context * fctx)1059 int info_compute_progress(struct fun_context *fctx)
1060 {
1061     (void) fctx; // UNUSED
1062     return 0;
1063 }
info_run(struct fun_context * fctx)1064 int info_run(struct fun_context *fctx)
1065 {
1066     fwup_warnx("%s", fctx->argv[1]);
1067     return 0;
1068 }
1069 
check_unsafe(struct fun_context * fctx)1070 static int check_unsafe(struct fun_context *fctx)
1071 {
1072     if (!fwup_unsafe)
1073         ERR_RETURN("%s requires --unsafe", fctx->argv[0]);
1074     return 0;
1075 }
1076 
path_write_validate(struct fun_context * fctx)1077 int path_write_validate(struct fun_context *fctx)
1078 {
1079     if (fctx->type != FUN_CONTEXT_FILE)
1080         ERR_RETURN("path_write only usable in on-resource");
1081 
1082     if (fctx->argc != 2)
1083         ERR_RETURN("path_write requires a file path");
1084 
1085     return 0;
1086 }
path_write_compute_progress(struct fun_context * fctx)1087 int path_write_compute_progress(struct fun_context *fctx)
1088 {
1089     return process_resource_compute_progress(fctx, false);
1090 }
1091 struct path_write_cookie {
1092     const char *output_filename;
1093     FILE *fp;
1094 };
path_write_pwrite_callback(void * cookie,const void * buf,size_t count,off_t offset)1095 static int path_write_pwrite_callback(void *cookie, const void *buf, size_t count, off_t offset)
1096 {
1097     struct path_write_cookie *fwc = (struct path_write_cookie *) cookie;
1098 
1099     OK_OR_RETURN_MSG(fseek(fwc->fp, (long) offset, SEEK_SET), "seek to offset %ld failed on '%s'", (long) offset, fwc->output_filename);
1100 
1101     size_t written = fwrite(buf, 1, count, fwc->fp);
1102     if (written != count)
1103         ERR_RETURN("path_write failed to write '%s'", fwc->output_filename);
1104     return 0;
1105 }
path_write_final_hole_callback(void * cookie,off_t hole_size,off_t file_size)1106 static int path_write_final_hole_callback(void *cookie, off_t hole_size, off_t file_size)
1107 {
1108     (void) hole_size;
1109 
1110     // Write a zero at the last offset to force the file to expand to the proper size
1111     char zero = 0;
1112     return path_write_pwrite_callback(cookie, &zero, 1, file_size - 1);
1113 }
path_write_run(struct fun_context * fctx)1114 int path_write_run(struct fun_context *fctx)
1115 {
1116     OK_OR_RETURN(check_unsafe(fctx));
1117 
1118     struct path_write_cookie pwc;
1119     pwc.output_filename = fctx->argv[1];
1120     pwc.fp = fopen(pwc.output_filename, "wb");
1121     if (pwc.fp == NULL)
1122         ERR_RETURN("path_write can't open '%s'", pwc.output_filename);
1123 
1124     int rc = 0;
1125     OK_OR_CLEANUP(process_resource(fctx,
1126                             false,
1127                             path_write_pwrite_callback,
1128                             path_write_final_hole_callback,
1129                             &pwc));
1130 
1131 cleanup:
1132     fclose(pwc.fp);
1133     return rc;
1134 }
1135 
pipe_write_validate(struct fun_context * fctx)1136 int pipe_write_validate(struct fun_context *fctx)
1137 {
1138     if (fctx->type != FUN_CONTEXT_FILE)
1139         ERR_RETURN("pipe_write only usable in on-resource");
1140 
1141     if (fctx->argc != 2)
1142         ERR_RETURN("pipe_write requires a command to execute");
1143 
1144     return 0;
1145 }
pipe_write_compute_progress(struct fun_context * fctx)1146 int pipe_write_compute_progress(struct fun_context *fctx)
1147 {
1148     return process_resource_compute_progress(fctx, true);
1149 }
1150 struct pipe_write_cookie {
1151     const char *pipe_command;
1152     off_t last_offset;
1153     FILE *fp;
1154 };
pipe_write_pwrite_callback(void * cookie,const void * buf,size_t count,off_t offset)1155 static int pipe_write_pwrite_callback(void *cookie, const void *buf, size_t count, off_t offset)
1156 {
1157     struct pipe_write_cookie *pwc = (struct pipe_write_cookie *) cookie;
1158 
1159     if (pwc->last_offset != offset) {
1160         // Fill in the gap with zeros
1161         char zeros[FWUP_BLOCK_SIZE];
1162         memset(zeros, 0, sizeof(zeros));
1163 
1164         while (pwc->last_offset < offset) {
1165             size_t to_write = offset - pwc->last_offset;
1166             if (to_write > sizeof(zeros))
1167                 to_write = sizeof(zeros);
1168             size_t written = fwrite(zeros, 1, to_write, pwc->fp);
1169             if (written != count)
1170                 ERR_RETURN("pipe_write failed to write '%s'", pwc->pipe_command);
1171             pwc->last_offset += to_write;
1172         }
1173     }
1174     size_t written = fwrite(buf, 1, count, pwc->fp);
1175     if (written != count)
1176         ERR_RETURN("pipe_write failed to write '%s'", pwc->pipe_command);
1177     pwc->last_offset += count;
1178     return 0;
1179 }
pipe_write_final_hole_callback(void * cookie,off_t hole_size,off_t file_size)1180 static int pipe_write_final_hole_callback(void *cookie, off_t hole_size, off_t file_size)
1181 {
1182     (void) hole_size;
1183 
1184     // Write a zero at the last offset to force the file to expand to the proper size
1185     char zero = 0;
1186     return pipe_write_pwrite_callback(cookie, &zero, 1, file_size - 1);
1187 }
pipe_write_run(struct fun_context * fctx)1188 int pipe_write_run(struct fun_context *fctx)
1189 {
1190     OK_OR_RETURN(check_unsafe(fctx));
1191 
1192     struct pipe_write_cookie pwc;
1193     pwc.pipe_command = fctx->argv[1];
1194     pwc.last_offset = 0;
1195 #if defined(_WIN32) || defined(__CYGWIN__)
1196     pwc.fp = popen(pwc.pipe_command, "wb");
1197 #else
1198     pwc.fp = popen(pwc.pipe_command, "w");
1199 #endif
1200     if (!pwc.fp)
1201         ERR_RETURN("pipe_write can't run '%s'", pwc.pipe_command);
1202 
1203     int rc = 0;
1204     OK_OR_CLEANUP(process_resource(fctx,
1205                             true,
1206                             pipe_write_pwrite_callback,
1207                             pipe_write_final_hole_callback,
1208                             &pwc));
1209 
1210 cleanup:
1211     if (pclose(pwc.fp) != 0)
1212         ERR_RETURN("command '%s' returned an error to pipe_write", pwc.pipe_command);
1213 
1214     return rc;
1215 }
1216 
execute_validate(struct fun_context * fctx)1217 int execute_validate(struct fun_context *fctx)
1218 {
1219     if (fctx->argc != 2)
1220         ERR_RETURN("execute requires a command to execute");
1221 
1222     return 0;
1223 }
execute_compute_progress(struct fun_context * fctx)1224 int execute_compute_progress(struct fun_context *fctx)
1225 {
1226     fctx->progress->total_units += FWUP_BLOCK_SIZE; // Arbitarily count as 1 block
1227     return 0;
1228 }
execute_run(struct fun_context * fctx)1229 int execute_run(struct fun_context *fctx)
1230 {
1231     assert(fctx->on_event);
1232     OK_OR_RETURN(check_unsafe(fctx));
1233 
1234     char const *cmd_name = fctx->argv[1];
1235     int status = system(cmd_name);
1236     if (status < 0)
1237         ERR_RETURN("execute couldn't run '%s'", cmd_name);
1238     if (status != 0)
1239         ERR_RETURN("'%s' failed with exit status %d", cmd_name, status);
1240 
1241     progress_report(fctx->progress, FWUP_BLOCK_SIZE);
1242     return 0;
1243 }
1244