lxlfw: support embedding blobs
[project/firmware-utils.git] / src / lxlfw.c
1 // SPDX-License-Identifier: GPL-2.0-or-later OR MIT
2 /*
3 * Luxul's firmware container format
4 *
5 * Copyright 2020 Legrand AV Inc.
6 */
7
8 #define _GNU_SOURCE
9
10 #include <byteswap.h>
11 #include <endian.h>
12 #include <errno.h>
13 #include <libgen.h>
14 #include <stddef.h>
15 #include <stdint.h>
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <string.h>
19 #include <unistd.h>
20
21 #if __BYTE_ORDER == __BIG_ENDIAN
22 #define cpu_to_le32(x) bswap_32(x)
23 #define cpu_to_le16(x) bswap_16(x)
24 #define le32_to_cpu(x) bswap_32(x)
25 #define le16_to_cpu(x) bswap_16(x)
26 #elif __BYTE_ORDER == __LITTLE_ENDIAN
27 #define cpu_to_le32(x) (x)
28 #define cpu_to_le16(x) (x)
29 #define le32_to_cpu(x) (x)
30 #define le16_to_cpu(x) (x)
31 #endif
32
33 #define min(a, b) \
34 ({ \
35 __typeof__ (a) _a = (a); \
36 __typeof__ (b) _b = (b); \
37 _a < _b ? _a : _b; \
38 })
39
40 #define max(a, b) \
41 ({ \
42 __typeof__ (a) _a = (a); \
43 __typeof__ (b) _b = (b); \
44 _a > _b ? _a : _b; \
45 })
46
47 #define MAX_SUPPORTED_VERSION 3
48
49 #define LXL_FLAGS_VENDOR_LUXUL 0x00000001
50
51 struct lxl_hdr {
52 char magic[4]; /* "LXL#" */
53 uint32_t version;
54 uint32_t hdr_len;
55 uint8_t v0_end[0];
56 /* Version: 1+ */
57 uint32_t flags;
58 char board[16];
59 uint8_t v1_end[0];
60 /* Version: 2+ */
61 uint8_t release[4];
62 uint8_t v2_end[0];
63 /* Version: 3+ */
64 uint32_t blobs_offset;
65 uint32_t blobs_len;
66 uint8_t v3_end[0];
67 } __attribute__((packed));
68
69 struct lxl_blob {
70 char magic[2]; /* "D#" */
71 uint16_t type;
72 uint32_t len;
73 uint8_t data[0];
74 } __attribute__((packed));
75
76 /**************************************************
77 * Helpers
78 **************************************************/
79
80 static uint32_t lxlfw_hdr_len(uint32_t version)
81 {
82 switch (version) {
83 case 0:
84 return offsetof(struct lxl_hdr, v0_end);
85 case 1:
86 return offsetof(struct lxl_hdr, v1_end);
87 case 2:
88 return offsetof(struct lxl_hdr, v2_end);
89 case 3:
90 return offsetof(struct lxl_hdr, v3_end);
91 default:
92 fprintf(stderr, "Unsupported version %d\n", version);
93 return 0;
94 }
95 }
96
97 /**
98 * lxlfw_open - open Luxul firmware file and validate it
99 *
100 * @pathname: Luxul firmware file
101 * @hdr: struct to read to
102 */
103 static FILE *lxlfw_open(const char *pathname, struct lxl_hdr *hdr)
104 {
105 size_t v0_len = lxlfw_hdr_len(0);
106 size_t min_hdr_len;
107 uint32_t version;
108 uint32_t hdr_len;
109 size_t bytes;
110 FILE *lxl;
111
112 lxl = fopen(pathname, "r");
113 if (!lxl) {
114 fprintf(stderr, "Could not open \"%s\" file\n", pathname);
115 goto err_out;
116 }
117
118 bytes = fread(hdr, 1, v0_len, lxl);
119 if (bytes != v0_len) {
120 fprintf(stderr, "Input file too small to use Luxul format\n");
121 goto err_close;
122 }
123
124 if (memcmp(hdr->magic, "LXL#", 4)) {
125 fprintf(stderr, "File <file> does not use Luxul's format\n");
126 goto err_close;
127 }
128
129 version = le32_to_cpu(hdr->version);
130
131 min_hdr_len = lxlfw_hdr_len(min(version, MAX_SUPPORTED_VERSION));
132
133 bytes = fread(((uint8_t *)hdr) + v0_len, 1, min_hdr_len - v0_len, lxl);
134 if (bytes != min_hdr_len - v0_len) {
135 fprintf(stderr, "Input file too small for header version %d\n", version);
136 goto err_close;
137 }
138
139 hdr_len = le32_to_cpu(hdr->hdr_len);
140
141 if (hdr_len < min_hdr_len) {
142 fprintf(stderr, "Header length mismatch: 0x%x (expected: 0x%zx)\n", hdr_len, min_hdr_len);
143 goto err_close;
144 }
145
146 if (version >= 3 && hdr->blobs_offset && hdr->blobs_len) {
147 uint32_t blobs_end = le32_to_cpu(hdr->blobs_offset) + le32_to_cpu(hdr->blobs_len);
148
149 if (blobs_end > hdr_len) {
150 fprintf(stderr, "Blobs section ends beyond header end: 0x%x (max: 0x%x)\n", blobs_end, hdr_len);
151 goto err_close;
152 }
153 }
154
155 return lxl;
156
157 err_close:
158 fclose(lxl);
159 err_out:
160 return NULL;
161 }
162
163 /**
164 * lxlfw_copy_data - read data from one stream and write to another
165 *
166 * @from: input stream
167 * @to: output stream
168 * @size: amount of bytes to copy (0 to copy all data)
169 */
170 static ssize_t lxlfw_copy_data(FILE *from, FILE *to, size_t size)
171 {
172 int copy_all = size == 0;
173 char buf[512];
174 size_t ret = 0;
175
176 while (copy_all || size) {
177 size_t to_read = copy_all ? sizeof(buf) : min(size, sizeof(buf));
178 size_t bytes;
179
180 bytes = fread(buf, 1, to_read, from);
181 if (bytes == 0 && copy_all) {
182 break;
183 } else if (bytes <= 0) {
184 fprintf(stderr, "Failed to read data\n");
185 return -EIO;
186 }
187
188 if (fwrite(buf, 1, bytes, to) != bytes) {
189 fprintf(stderr, "Failed to write data\n");
190 return -EIO;
191 }
192
193 if (!copy_all)
194 size -= bytes;
195 ret += bytes;
196 }
197
198 return ret;
199 }
200
201 /**
202 * lxlfw_write_blob - read data from external file and write blob to stream
203 *
204 * @lxl: stream to write to
205 * @type: blob type
206 * @pathname: external file pathname to read blob data from
207 */
208 #if 0
209 static ssize_t lxlfw_write_blob(FILE *lxl, uint16_t type, const char *pathname)
210 {
211 struct lxl_blob blob = {
212 .magic = { 'D', '#' },
213 .type = cpu_to_le16(type),
214 };
215 char buf[512];
216 size_t blob_data_len;
217 size_t bytes;
218 FILE *data;
219
220 data = fopen(pathname, "r");
221 if (!data) {
222 fprintf(stderr, "Could not open input file %s\n", pathname);
223 return -EIO;
224 }
225
226 blob_data_len = 0;
227 fseek(lxl, sizeof(blob), SEEK_CUR);
228 while ((bytes = fread(buf, 1, sizeof(buf), data)) > 0) {
229 if (fwrite(buf, 1, bytes, lxl) != bytes) {
230 fprintf(stderr, "Could not copy %zu bytes from input file\n", bytes);
231 fclose(data);
232 return -EIO;
233 }
234 blob_data_len += bytes;
235 }
236
237 fclose(data);
238
239 blob.len = cpu_to_le32(blob_data_len);
240
241 fseek(lxl, -(blob_data_len + sizeof(blob)), SEEK_CUR);
242 bytes = fwrite(&blob, 1, sizeof(blob), lxl);
243 if (bytes != sizeof(blob)) {
244 fprintf(stderr, "Could not write Luxul's header\n");
245 return -EIO;
246 }
247
248 fseek(lxl, blob_data_len, SEEK_CUR);
249
250 return blob_data_len + sizeof(blob);
251 }
252 #endif
253
254 /**************************************************
255 * Info
256 **************************************************/
257
258 static int lxlfw_info(int argc, char **argv) {
259 struct lxl_hdr hdr;
260 uint32_t version;
261 char board[17];
262 int err = 0;
263 FILE *lxl;
264 int flags;
265
266 if (argc < 3) {
267 fprintf(stderr, "Missing <file> argument\n");
268 err = -EINVAL;
269 goto out;
270 }
271
272 lxl = lxlfw_open(argv[2], &hdr);
273 if (!lxl) {
274 fprintf(stderr, "Could not open \"%s\" Luxul firmware\n", argv[2]);
275 err = -ENOENT;
276 goto out;
277 }
278
279 version = le32_to_cpu(hdr.version);
280
281 printf("Format version:\t%d\n", version);
282 printf("Header length:\t%d\n", le32_to_cpu(hdr.hdr_len));
283 if (version >= 1) {
284 printf("Flags:\t\t");
285 flags = le32_to_cpu(hdr.flags);
286 if (flags & LXL_FLAGS_VENDOR_LUXUL)
287 printf("VENDOR_LUXUL ");
288 printf("\n");
289 memcpy(board, hdr.board, sizeof(hdr.board));
290 board[16] = '\0';
291 printf("Board:\t\t%s\n", board);
292 }
293 if (version >= 2) {
294 printf("Release:\t");
295 if (hdr.release[0] || hdr.release[1] || hdr.release[2] || hdr.release[3]) {
296 printf("%hu.%hu.%hu", hdr.release[0], hdr.release[1], hdr.release[2]);
297 if (hdr.release[3])
298 printf(".%hu", hdr.release[3]);
299 }
300 printf("\n");
301 }
302 if (version >= 3) {
303 printf("Blobs offset:\t%d\n", le32_to_cpu(hdr.blobs_offset));
304 printf("Blobs length:\t%d\n", le32_to_cpu(hdr.blobs_len));
305 }
306
307 if (version >= 3 && hdr.blobs_offset) {
308 size_t offset;
309
310 fseek(lxl, le32_to_cpu(hdr.blobs_offset), SEEK_SET);
311 for (offset = 0; offset < le32_to_cpu(hdr.blobs_len); ) {
312 struct lxl_blob blob;
313 size_t bytes;
314 size_t len;
315
316 bytes = fread(&blob, 1, sizeof(blob), lxl);
317 if (bytes != sizeof(blob)) {
318 fprintf(stderr, "Failed to read blob section\n");
319 err = -ENXIO;
320 goto err_close;
321 }
322
323 len = le32_to_cpu(blob.len);
324
325 printf("\n");
326 printf("Blob\n");
327 printf("Magic:\t\t%s\n", blob.magic);
328 printf("Type:\t\t0x%04x\n", le16_to_cpu(blob.type));
329 printf("Length:\t\t%zu\n", len);
330
331 offset += sizeof(blob) + len;
332 fseek(lxl, len, SEEK_CUR);
333 }
334
335 if (offset != le32_to_cpu(hdr.blobs_len)) {
336 printf("\n");
337 fprintf(stderr, "Blobs size (0x%zx) doesn't match declared length (0x%x)\n", offset, le32_to_cpu(hdr.blobs_len));
338 }
339 }
340
341 err_close:
342 fclose(lxl);
343 out:
344 return err;
345 }
346
347 /**************************************************
348 * Blobs
349 **************************************************/
350
351 /**
352 * lxlfw_blob_save - save blob data to external file
353 *
354 * @lxl: Luxul firmware FILE with position seeked to blob data
355 * @len: blob data length
356 * @path: external file pathname to write
357 */
358 #if 0
359 static int lxlfw_blob_save(FILE *lxl, size_t len, const char *path) {
360 char buf[256];
361 size_t bytes;
362 FILE *out;
363 int err = 0;
364
365 out = fopen(path, "w+");
366 if (!out) {
367 fprintf(stderr, "Could not open \"%s\" file\n", path);
368 err = -EIO;
369 goto err_out;
370 }
371
372 while (len && (bytes = fread(buf, 1, min(len, sizeof(buf)), lxl)) > 0) {
373 if (fwrite(buf, 1, bytes, out) != bytes) {
374 fprintf(stderr, "Could not copy %zu bytes from input file\n", bytes);
375 err = -EIO;
376 goto err_close_out;
377 }
378 len -= bytes;
379 }
380
381 if (len) {
382 fprintf(stderr, "Could not copy all signature\n");
383 err = -EIO;
384 goto err_close_out;
385 }
386
387 err_close_out:
388 fclose(out);
389 err_out:
390 return err;
391 }
392 #endif
393
394 static int lxlfw_blobs(int argc, char **argv) {
395 struct lxl_hdr hdr;
396 uint32_t version;
397 size_t offset;
398 size_t bytes;
399 int err = 0;
400 FILE *lxl;
401 int c;
402
403 if (argc < 3) {
404 fprintf(stderr, "Missing <file> argument\n");
405 err = -EINVAL;
406 goto out;
407 }
408
409 optind = 3;
410 while ((c = getopt(argc, argv, "")) != -1) {
411 }
412
413 if (1) {
414 fprintf(stderr, "Missing info on blobs to extract\n");
415 err = -EINVAL;
416 goto out;
417 }
418
419 lxl = lxlfw_open(argv[2], &hdr);
420 if (!lxl) {
421 fprintf(stderr, "Failed to open \"%s\" Luxul firmware\n", argv[2]);
422 err = -ENOENT;
423 goto out;
424 }
425
426 version = le32_to_cpu(hdr.version);
427
428 if (version < 3 || !hdr.blobs_offset) {
429 fprintf(stderr, "File <file> doesn't contain any blobs\n");
430 err = -ENOENT;
431 goto err_close;
432 }
433
434 fseek(lxl, le32_to_cpu(hdr.blobs_offset), SEEK_SET);
435 for (offset = 0; offset < le32_to_cpu(hdr.blobs_len); ) {
436 struct lxl_blob blob;
437 size_t len;
438
439 bytes = fread(&blob, 1, sizeof(blob), lxl);
440 if (bytes != sizeof(blob)) {
441 fprintf(stderr, "Failed to read blob section\n");
442 err = -ENXIO;
443 goto err_close;
444 }
445 offset += bytes;
446
447 if (memcmp(blob.magic, "D#", 2)) {
448 fprintf(stderr, "Failed to parse blob section\n");
449 err = -ENXIO;
450 goto err_close;
451 }
452
453 len = le32_to_cpu(blob.len);
454
455 if (0) {
456 /* TODO */
457 } else {
458 fseek(lxl, len, SEEK_CUR);
459 }
460 if (err) {
461 fprintf(stderr, "Failed to save blob section\n");
462 goto err_close;
463 }
464 offset += len;
465 }
466
467 err_close:
468 fclose(lxl);
469 out:
470 return err;
471 }
472
473 /**************************************************
474 * Create
475 **************************************************/
476
477 static int lxlfw_create(int argc, char **argv) {
478 struct lxl_hdr hdr = {
479 .magic = { 'L', 'X', 'L', '#' },
480 };
481 char *in_path = NULL;
482 uint32_t version = 0;
483 uint32_t hdr_raw_len; /* Header length without blobs */
484 uint32_t hdr_len; /* Header length with blobs */
485 uint32_t blobs_len;
486 ssize_t bytes;
487 int err = 0;
488 FILE *lxl;
489 FILE *in;
490 int c;
491
492 if (argc < 3) {
493 fprintf(stderr, "Missing <file> argument\n");
494 err = -EINVAL;
495 goto out;
496 }
497
498 optind = 3;
499 while ((c = getopt(argc, argv, "i:lb:r:")) != -1) {
500 switch (c) {
501 case 'i':
502 in_path = optarg;
503 break;
504 case 'l':
505 hdr.flags |= cpu_to_le32(LXL_FLAGS_VENDOR_LUXUL);
506 version = max(version, 1);
507 break;
508 case 'b':
509 memcpy(hdr.board, optarg, strlen(optarg) > 16 ? 16 : strlen(optarg));
510 version = max(version, 1);
511 break;
512 case 'r':
513 if (sscanf(optarg, "%hhu.%hhu.%hhu.%hhu", &hdr.release[0], &hdr.release[1], &hdr.release[2], &hdr.release[3]) < 1) {
514 fprintf(stderr, "Failed to parse release number \"%s\"\n", optarg);
515 err = -EINVAL;
516 goto out;
517 }
518 version = max(version, 2);
519 break;
520 }
521 }
522
523 hdr_raw_len = lxlfw_hdr_len(version);
524 hdr_len = hdr_raw_len;
525
526 if (!in_path) {
527 fprintf(stderr, "Missing input file argument\n");
528 err = -EINVAL;
529 goto out;
530 }
531
532 in = fopen(in_path, "r");
533 if (!in) {
534 fprintf(stderr, "Could not open input file %s\n", in_path);
535 err = -EIO;
536 goto out;
537 }
538
539 lxl = fopen(argv[2], "w+");
540 if (!lxl) {
541 fprintf(stderr, "Could not open \"%s\" file\n", argv[2]);
542 err = -EIO;
543 goto err_close_in;
544 }
545
546 /* Write blobs */
547
548 blobs_len = 0;
549
550 fseek(lxl, hdr_raw_len, SEEK_SET);
551 /* TODO */
552
553 if (blobs_len) {
554 hdr.blobs_offset = cpu_to_le32(hdr_raw_len);
555 hdr.blobs_len = cpu_to_le32(blobs_len);
556 hdr_len += blobs_len;
557 }
558
559 /* Write header */
560
561 hdr.version = cpu_to_le32(version);
562 hdr.hdr_len = cpu_to_le32(hdr_len);
563
564 fseek(lxl, 0, SEEK_SET);
565 bytes = fwrite(&hdr, 1, hdr_raw_len, lxl);
566 if (bytes != hdr_raw_len) {
567 fprintf(stderr, "Could not write Luxul's header\n");
568 err = -EIO;
569 goto err_close_lxl;
570 }
571
572 /* Write input data */
573
574 fseek(lxl, 0, SEEK_END);
575 bytes = lxlfw_copy_data(in, lxl, 0);
576 if (bytes < 0) {
577 fprintf(stderr, "Could not copy %zu bytes from input file\n", bytes);
578 err = -EIO;
579 goto err_close_lxl;
580 }
581
582 err_close_lxl:
583 fclose(lxl);
584 err_close_in:
585 fclose(in);
586 out:
587 return err;
588 }
589
590 /**************************************************
591 * Insert
592 **************************************************/
593
594 static int lxlfw_insert(int argc, char **argv) {
595 struct lxl_hdr hdr = { };
596 char *tmp_path = NULL;
597 uint32_t version = 0;
598 uint32_t hdr_raw_len; /* Header length without blobs */
599 uint32_t hdr_len; /* Header length with blobs */
600 uint32_t blobs_len;
601 ssize_t bytes;
602 char *path;
603 FILE *lxl;
604 FILE *tmp;
605 int fd;
606 int c;
607 int err = 0;
608
609 if (argc < 3) {
610 fprintf(stderr, "Missing <file> argument\n");
611 err = -EINVAL;
612 goto out;
613 }
614
615 optind = 3;
616 while ((c = getopt(argc, argv, "")) != -1) {
617 }
618
619 if (1) {
620 fprintf(stderr, "Missing info on blobs to insert\n");
621 err = -EINVAL;
622 goto out;
623 }
624
625 lxl = lxlfw_open(argv[2], &hdr);
626 if (!lxl) {
627 fprintf(stderr, "Failed to open \"%s\" Luxul firmware\n", argv[2]);
628 err = -ENOENT;
629 goto out;
630 }
631
632 version = le32_to_cpu(hdr.version);
633 if (version > MAX_SUPPORTED_VERSION) {
634 fprintf(stderr, "Unsupported <file> version %d\n", version);
635 err = -EIO;
636 goto err_close_lxl;
637 }
638
639 version = max(version, 3);
640
641 hdr_raw_len = lxlfw_hdr_len(version);
642 hdr_len = hdr_raw_len;
643
644 /* Temporary file */
645
646 path = strdup(argv[2]);
647 if (!path) {
648 err = -ENOMEM;
649 goto err_close_lxl;
650 }
651 asprintf(&tmp_path, "%s/lxlfwXXXXXX", dirname(path));
652 free(path);
653 if (!tmp_path) {
654 err = -ENOMEM;
655 goto err_close_lxl;
656 }
657
658 fd = mkstemp(tmp_path);
659 if (fd < 0) {
660 err = -errno;
661 fprintf(stderr, "Failed to open temporary file\n");
662 goto err_free_path;
663 }
664 tmp = fdopen(fd, "w+");
665
666 /* Blobs */
667
668 fseek(tmp, hdr_raw_len, SEEK_SET);
669 blobs_len = 0;
670
671 /* Copy old blobs */
672
673 if (hdr.blobs_offset) {
674 size_t offset;
675
676 fseek(lxl, le32_to_cpu(hdr.blobs_offset), SEEK_SET);
677 for (offset = 0; offset < le32_to_cpu(hdr.blobs_len); ) {
678 struct lxl_blob blob;
679 size_t len;
680
681 bytes = fread(&blob, 1, sizeof(blob), lxl);
682 if (bytes != sizeof(blob)) {
683 fprintf(stderr, "Failed to read blob section\n");
684 err = -ENXIO;
685 goto err_close_tmp;
686 }
687
688 len = le32_to_cpu(blob.len);
689
690 /* Don't copy blobs that have to be replaced */
691 if (0) {
692 /* TODO */
693 } else {
694 fseek(lxl, -sizeof(blob), SEEK_CUR);
695 bytes = lxlfw_copy_data(lxl, tmp, sizeof(blob) + len);
696 if (bytes != sizeof(blob) + len) {
697 fprintf(stderr, "Failed to copy original blob\n");
698 err = -EIO;
699 goto err_close_tmp;
700 }
701 blobs_len += sizeof(blob) + len;
702 }
703
704 offset += sizeof(blob) + len;
705 }
706 }
707
708 /* Write new blobs */
709
710 /* TODO */
711
712 hdr.blobs_offset = cpu_to_le32(hdr_raw_len);
713 hdr.blobs_len = cpu_to_le32(blobs_len);
714 hdr_len += blobs_len;
715
716 /* Write header */
717
718 hdr.version = cpu_to_le32(version);
719 hdr.hdr_len = cpu_to_le32(hdr_len);
720
721 fseek(tmp, 0, SEEK_SET);
722 bytes = fwrite(&hdr, 1, hdr_raw_len, tmp);
723 if (bytes != hdr_raw_len) {
724 fprintf(stderr, "Could not write Luxul's header\n");
725 err = -EIO;
726 goto err_close_tmp;
727 }
728
729 /* Write original data */
730
731 fseek(tmp, 0, SEEK_END);
732 bytes = lxlfw_copy_data(lxl, tmp, 0);
733 if (bytes < 0) {
734 fprintf(stderr, "Failed to copy original file\n");
735 err = -EIO;
736 goto err_close_tmp;
737 }
738
739 fclose(tmp);
740
741 fclose(lxl);
742
743 /* Replace original file */
744
745 if (rename(tmp_path, argv[2])) {
746 err = -errno;
747 fprintf(stderr, "Failed to rename %s: %d\n", tmp_path, err);
748 unlink(tmp_path);
749 goto out;
750 }
751
752 return 0;
753
754 err_close_tmp:
755 fclose(tmp);
756 err_free_path:
757 free(tmp_path);
758 err_close_lxl:
759 fclose(lxl);
760 out:
761 return err;
762 }
763
764 /**************************************************
765 * Start
766 **************************************************/
767
768 static void usage() {
769 printf("Usage:\n");
770 printf("\n");
771 printf("Get info about Luxul firmware:\n");
772 printf("\tlxlfw info <file>\n");
773 printf("\n");
774 printf("Extract blobs from Luxul firmware:\n");
775 printf("\tlxlfw blobs <file> [options]\n");
776 printf("\n");
777 printf("Create new Luxul firmware:\n");
778 printf("\tlxlfw create <file> [options]\n");
779 printf("\t-i file\t\t\t\tinput file for Luxul's firmware container\n");
780 printf("\t-l\t\t\t\tmark firmware as created by Luxul company (DON'T USE)\n");
781 printf("\t-b board\t\t\tboard (device) name\n");
782 printf("\t-r release\t\t\trelease number (e.g. 5.1.0, 7.1.0.2)\n");
783 printf("\n");
784 printf("Insert blob to Luxul firmware:\n");
785 printf("\tlxlfw insert <file> [options]\n");
786 }
787
788 int main(int argc, char **argv) {
789 if (argc > 1) {
790 if (!strcmp(argv[1], "info"))
791 return lxlfw_info(argc, argv);
792 else if (!strcmp(argv[1], "blobs"))
793 return lxlfw_blobs(argc, argv);
794 else if (!strcmp(argv[1], "create"))
795 return lxlfw_create(argc, argv);
796 else if (!strcmp(argv[1], "insert"))
797 return lxlfw_insert(argc, argv);
798 }
799
800 usage();
801 return 0;
802 }