otrx: extract shared code opening & parsing TRX format
[project/firmware-utils.git] / src / otrx.c
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * otrx
4 *
5 * Copyright (C) 2015-2017 Rafał Miłecki <zajec5@gmail.com>
6 */
7
8 #include <byteswap.h>
9 #include <endian.h>
10 #include <errno.h>
11 #include <stdint.h>
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <string.h>
15 #include <sys/stat.h>
16 #include <unistd.h>
17
18 #if !defined(__BYTE_ORDER)
19 #error "Unknown byte order"
20 #endif
21
22 #if __BYTE_ORDER == __BIG_ENDIAN
23 #define cpu_to_le32(x) bswap_32(x)
24 #define le32_to_cpu(x) bswap_32(x)
25 #elif __BYTE_ORDER == __LITTLE_ENDIAN
26 #define cpu_to_le32(x) (x)
27 #define le32_to_cpu(x) (x)
28 #else
29 #error "Unsupported endianness"
30 #endif
31
32 #define TRX_MAGIC 0x30524448
33 #define TRX_FLAGS_OFFSET 12
34 #define TRX_MAX_PARTS 3
35
36 struct trx_header {
37 uint32_t magic;
38 uint32_t length;
39 uint32_t crc32;
40 uint16_t flags;
41 uint16_t version;
42 uint32_t offset[3];
43 };
44
45 struct otrx_ctx {
46 FILE *fp;
47 struct trx_header hdr;
48 };
49
50 char *trx_path;
51 size_t trx_offset = 0;
52 char *partition[TRX_MAX_PARTS] = {};
53
54 static inline size_t otrx_min(size_t x, size_t y) {
55 return x < y ? x : y;
56 }
57
58 /**************************************************
59 * CRC32
60 **************************************************/
61
62 static const uint32_t crc32_tbl[] = {
63 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba,
64 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
65 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
66 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
67 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de,
68 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
69 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec,
70 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
71 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
72 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
73 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940,
74 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
75 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116,
76 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
77 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
78 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
79 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a,
80 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
81 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818,
82 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
83 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
84 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
85 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c,
86 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
87 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2,
88 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
89 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
90 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
91 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086,
92 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
93 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4,
94 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
95 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
96 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
97 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
98 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
99 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe,
100 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
101 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
102 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
103 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252,
104 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
105 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60,
106 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
107 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
108 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
109 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04,
110 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
111 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a,
112 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
113 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
114 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
115 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e,
116 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
117 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c,
118 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
119 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
120 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
121 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0,
122 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
123 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6,
124 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
125 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
126 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d,
127 };
128
129 uint32_t otrx_crc32(uint32_t crc, uint8_t *buf, size_t len) {
130 while (len) {
131 crc = crc32_tbl[(crc ^ *buf) & 0xff] ^ (crc >> 8);
132 buf++;
133 len--;
134 }
135
136 return crc;
137 }
138
139 /**************************************************
140 * Helpers
141 **************************************************/
142
143 static FILE *otrx_open(const char *pathname, const char *mode) {
144 if (strcmp(pathname, "-"))
145 return fopen(pathname, mode);
146
147 if (isatty(fileno(stdin))) {
148 fprintf(stderr, "Reading from TTY stdin is unsupported\n");
149 return NULL;
150 }
151
152 return stdin;
153 }
154
155 static int otrx_skip(FILE *fp, size_t length)
156 {
157 if (fseek(fp, length, SEEK_CUR)) {
158 uint8_t buf[1024];
159 size_t bytes;
160
161 do {
162 bytes = fread(buf, 1, otrx_min(sizeof(buf), length), fp);
163 if (bytes <= 0)
164 return -EIO;
165 length -= bytes;
166 } while (length);
167 }
168
169 return 0;
170 }
171
172 static void otrx_close(FILE *fp) {
173 if (fp != stdin)
174 fclose(fp);
175 }
176
177 static int otrx_open_parse(const char *pathname, const char *mode,
178 struct otrx_ctx *otrx)
179 {
180 size_t length;
181 size_t bytes;
182 int err;
183
184 otrx->fp = otrx_open(pathname, mode);
185 if (!otrx->fp) {
186 fprintf(stderr, "Couldn't open %s\n", pathname);
187 err = -EACCES;
188 goto err_out;
189 }
190
191 if (trx_offset && otrx_skip(otrx->fp, trx_offset)) {
192 fprintf(stderr, "Couldn't skip first %zd B\n", trx_offset);
193 err = -EIO;
194 goto err_close;
195 }
196
197 bytes = fread(&otrx->hdr, 1, sizeof(otrx->hdr), otrx->fp);
198 if (bytes != sizeof(otrx->hdr)) {
199 fprintf(stderr, "Couldn't read %s header\n", pathname);
200 err = -EIO;
201 goto err_close;
202 }
203
204 if (le32_to_cpu(otrx->hdr.magic) != TRX_MAGIC) {
205 fprintf(stderr, "Invalid TRX magic: 0x%08x\n", le32_to_cpu(otrx->hdr.magic));
206 err = -EINVAL;
207 goto err_close;
208 }
209
210 length = le32_to_cpu(otrx->hdr.length);
211 if (length < sizeof(otrx->hdr)) {
212 fprintf(stderr, "Length read from TRX too low (%zu B)\n", length);
213 err = -EINVAL;
214 goto err_close;
215 }
216
217 return 0;
218
219 err_close:
220 otrx_close(otrx->fp);
221 err_out:
222 return err;
223 }
224
225 /**************************************************
226 * Check
227 **************************************************/
228
229 static void otrx_check_parse_options(int argc, char **argv) {
230 int c;
231
232 while ((c = getopt(argc, argv, "o:")) != -1) {
233 switch (c) {
234 case 'o':
235 trx_offset = atoi(optarg);
236 break;
237 }
238 }
239 }
240
241 static int otrx_check(int argc, char **argv) {
242 struct otrx_ctx otrx = { };
243 size_t bytes, length;
244 uint8_t buf[1024];
245 uint32_t crc32;
246 int err = 0;
247
248 if (argc < 3) {
249 fprintf(stderr, "No TRX file passed\n");
250 err = -EINVAL;
251 goto out;
252 }
253 trx_path = argv[2];
254
255 optind = 3;
256 otrx_check_parse_options(argc, argv);
257
258 err = otrx_open_parse(trx_path, "r", &otrx);
259 if (err) {
260 fprintf(stderr, "Couldn't open & parse %s: %d\n", trx_path, err);
261 err = -EACCES;
262 goto out;
263 }
264
265 crc32 = 0xffffffff;
266 crc32 = otrx_crc32(crc32, (uint8_t *)&otrx.hdr + TRX_FLAGS_OFFSET, sizeof(otrx.hdr) - TRX_FLAGS_OFFSET);
267 length = le32_to_cpu(otrx.hdr.length) - sizeof(otrx.hdr);
268 while ((bytes = fread(buf, 1, otrx_min(sizeof(buf), length), otrx.fp)) > 0) {
269 crc32 = otrx_crc32(crc32, buf, bytes);
270 length -= bytes;
271 }
272
273 if (length) {
274 fprintf(stderr, "Couldn't read last %zd B of data from %s\n", length, trx_path);
275 err = -EIO;
276 goto err_close;
277 }
278
279 if (crc32 != le32_to_cpu(otrx.hdr.crc32)) {
280 fprintf(stderr, "Invalid data crc32: 0x%08x instead of 0x%08x\n", crc32, le32_to_cpu(otrx.hdr.crc32));
281 err = -EINVAL;
282 goto err_close;
283 }
284
285 printf("Found a valid TRX version %d\n", le32_to_cpu(otrx.hdr.version));
286
287 err_close:
288 otrx_close(otrx.fp);
289 out:
290 return err;
291 }
292
293 /**************************************************
294 * Create
295 **************************************************/
296
297 static ssize_t otrx_create_append_file(FILE *trx, const char *in_path) {
298 FILE *in;
299 size_t bytes;
300 ssize_t length = 0;
301 uint8_t buf[1024];
302
303 in = fopen(in_path, "r");
304 if (!in) {
305 fprintf(stderr, "Couldn't open %s\n", in_path);
306 return -EACCES;
307 }
308
309 while ((bytes = fread(buf, 1, sizeof(buf), in)) > 0) {
310 if (fwrite(buf, 1, bytes, trx) != bytes) {
311 fprintf(stderr, "Couldn't write %zu B to %s\n", bytes, trx_path);
312 length = -EIO;
313 break;
314 }
315 length += bytes;
316 }
317
318 fclose(in);
319
320 return length;
321 }
322
323 static ssize_t otrx_create_append_zeros(FILE *trx, size_t length) {
324 uint8_t *buf;
325
326 buf = malloc(length);
327 if (!buf)
328 return -ENOMEM;
329 memset(buf, 0, length);
330
331 if (fwrite(buf, 1, length, trx) != length) {
332 fprintf(stderr, "Couldn't write %zu B to %s\n", length, trx_path);
333 free(buf);
334 return -EIO;
335 }
336
337 free(buf);
338
339 return length;
340 }
341
342 static ssize_t otrx_create_align(FILE *trx, size_t curr_offset, size_t alignment) {
343 if (curr_offset & (alignment - 1)) {
344 size_t length = alignment - (curr_offset % alignment);
345 return otrx_create_append_zeros(trx, length);
346 }
347
348 return 0;
349 }
350
351 static int otrx_create_write_hdr(FILE *trx, struct trx_header *hdr) {
352 size_t bytes, length;
353 uint8_t buf[1024];
354 uint32_t crc32;
355
356 hdr->version = 1;
357
358 fseek(trx, 0, SEEK_SET);
359 bytes = fwrite(hdr, 1, sizeof(struct trx_header), trx);
360 if (bytes != sizeof(struct trx_header)) {
361 fprintf(stderr, "Couldn't write TRX header to %s\n", trx_path);
362 return -EIO;
363 }
364
365 length = le32_to_cpu(hdr->length);
366
367 crc32 = 0xffffffff;
368 fseek(trx, TRX_FLAGS_OFFSET, SEEK_SET);
369 length -= TRX_FLAGS_OFFSET;
370 while ((bytes = fread(buf, 1, otrx_min(sizeof(buf), length), trx)) > 0) {
371 crc32 = otrx_crc32(crc32, buf, bytes);
372 length -= bytes;
373 }
374 hdr->crc32 = cpu_to_le32(crc32);
375
376 fseek(trx, 0, SEEK_SET);
377 bytes = fwrite(hdr, 1, sizeof(struct trx_header), trx);
378 if (bytes != sizeof(struct trx_header)) {
379 fprintf(stderr, "Couldn't write TRX header to %s\n", trx_path);
380 return -EIO;
381 }
382
383 return 0;
384 }
385
386 static int otrx_create(int argc, char **argv) {
387 FILE *trx;
388 struct trx_header hdr = {};
389 ssize_t sbytes;
390 size_t curr_idx = 0;
391 size_t curr_offset = sizeof(hdr);
392 char *e;
393 uint32_t magic;
394 int c;
395 int err = 0;
396
397 hdr.magic = cpu_to_le32(TRX_MAGIC);
398
399 if (argc < 3) {
400 fprintf(stderr, "No TRX file passed\n");
401 err = -EINVAL;
402 goto out;
403 }
404 trx_path = argv[2];
405
406 trx = fopen(trx_path, "w+");
407 if (!trx) {
408 fprintf(stderr, "Couldn't open %s\n", trx_path);
409 err = -EACCES;
410 goto out;
411 }
412 fseek(trx, curr_offset, SEEK_SET);
413
414 optind = 3;
415 while ((c = getopt(argc, argv, "f:A:a:b:M:")) != -1) {
416 switch (c) {
417 case 'f':
418 if (curr_idx >= TRX_MAX_PARTS) {
419 err = -ENOSPC;
420 fprintf(stderr, "Reached TRX partitions limit, no place for %s\n", optarg);
421 goto err_close;
422 }
423
424 sbytes = otrx_create_append_file(trx, optarg);
425 if (sbytes < 0) {
426 fprintf(stderr, "Failed to append file %s\n", optarg);
427 } else {
428 hdr.offset[curr_idx++] = curr_offset;
429 curr_offset += sbytes;
430 }
431
432 sbytes = otrx_create_align(trx, curr_offset, 4);
433 if (sbytes < 0)
434 fprintf(stderr, "Failed to append zeros\n");
435 else
436 curr_offset += sbytes;
437
438 break;
439 case 'A':
440 sbytes = otrx_create_append_file(trx, optarg);
441 if (sbytes < 0) {
442 fprintf(stderr, "Failed to append file %s\n", optarg);
443 } else {
444 curr_offset += sbytes;
445 }
446
447 sbytes = otrx_create_align(trx, curr_offset, 4);
448 if (sbytes < 0)
449 fprintf(stderr, "Failed to append zeros\n");
450 else
451 curr_offset += sbytes;
452 break;
453 case 'a':
454 sbytes = otrx_create_align(trx, curr_offset, strtol(optarg, NULL, 0));
455 if (sbytes < 0)
456 fprintf(stderr, "Failed to append zeros\n");
457 else
458 curr_offset += sbytes;
459 break;
460 case 'b':
461 sbytes = strtol(optarg, NULL, 0) - curr_offset;
462 if (sbytes < 0) {
463 fprintf(stderr, "Current TRX length is 0x%zx, can't pad it with zeros to 0x%lx\n", curr_offset, strtol(optarg, NULL, 0));
464 } else {
465 sbytes = otrx_create_append_zeros(trx, sbytes);
466 if (sbytes < 0)
467 fprintf(stderr, "Failed to append zeros\n");
468 else
469 curr_offset += sbytes;
470 }
471 break;
472 case 'M':
473 errno = 0;
474 magic = strtoul(optarg, &e, 0);
475 if (errno || (e == optarg) || *e)
476 fprintf(stderr, "illegal magic string %s\n", optarg);
477 else
478 hdr.magic = cpu_to_le32(magic);
479 break;
480 }
481 if (err)
482 break;
483 }
484
485 sbytes = otrx_create_align(trx, curr_offset, 0x1000);
486 if (sbytes < 0)
487 fprintf(stderr, "Failed to append zeros\n");
488 else
489 curr_offset += sbytes;
490
491 hdr.length = curr_offset;
492 otrx_create_write_hdr(trx, &hdr);
493 err_close:
494 fclose(trx);
495 out:
496 return err;
497 }
498
499 /**************************************************
500 * Extract
501 **************************************************/
502
503 static void otrx_extract_parse_options(int argc, char **argv) {
504 int c;
505
506 while ((c = getopt(argc, argv, "c:e:o:1:2:3:")) != -1) {
507 switch (c) {
508 case 'o':
509 trx_offset = atoi(optarg);
510 break;
511 case '1':
512 partition[0] = optarg;
513 break;
514 case '2':
515 partition[1] = optarg;
516 break;
517 case '3':
518 partition[2] = optarg;
519 break;
520 }
521 }
522 }
523
524 static int otrx_extract_copy(FILE *trx, size_t offset, size_t length, char *out_path) {
525 FILE *out;
526 size_t bytes;
527 uint8_t *buf;
528 int err = 0;
529
530 out = fopen(out_path, "w");
531 if (!out) {
532 fprintf(stderr, "Couldn't open %s\n", out_path);
533 err = -EACCES;
534 goto out;
535 }
536
537 buf = malloc(length);
538 if (!buf) {
539 fprintf(stderr, "Couldn't alloc %zu B buffer\n", length);
540 err = -ENOMEM;
541 goto err_close;
542 }
543
544 fseek(trx, offset, SEEK_SET);
545 bytes = fread(buf, 1, length, trx);
546 if (bytes != length) {
547 fprintf(stderr, "Couldn't read %zu B of data from %s\n", length, trx_path);
548 err = -ENOMEM;
549 goto err_free_buf;
550 };
551
552 bytes = fwrite(buf, 1, length, out);
553 if (bytes != length) {
554 fprintf(stderr, "Couldn't write %zu B to %s\n", length, out_path);
555 err = -ENOMEM;
556 goto err_free_buf;
557 }
558
559 printf("Extracted 0x%zx bytes into %s\n", length, out_path);
560
561 err_free_buf:
562 free(buf);
563 err_close:
564 fclose(out);
565 out:
566 return err;
567 }
568
569 static int otrx_extract(int argc, char **argv) {
570 struct otrx_ctx otrx = { };
571 int i;
572 int err = 0;
573
574 if (argc < 3) {
575 fprintf(stderr, "No TRX file passed\n");
576 err = -EINVAL;
577 goto err_out;
578 }
579 trx_path = argv[2];
580
581 optind = 3;
582 otrx_extract_parse_options(argc, argv);
583
584 err = otrx_open_parse(trx_path, "r", &otrx);
585 if (err) {
586 fprintf(stderr, "Couldn't open & parse %s: %d\n", trx_path, err);
587 err = -EACCES;
588 goto err_out;
589 }
590
591 for (i = 0; i < TRX_MAX_PARTS; i++) {
592 size_t length;
593
594 if (!partition[i])
595 continue;
596 if (!otrx.hdr.offset[i]) {
597 printf("TRX doesn't contain partition %d, can't extract %s\n", i + 1, partition[i]);
598 continue;
599 }
600
601 if (i + 1 >= TRX_MAX_PARTS || !otrx.hdr.offset[i + 1])
602 length = le32_to_cpu(otrx.hdr.length) - le32_to_cpu(otrx.hdr.offset[i]);
603 else
604 length = le32_to_cpu(otrx.hdr.offset[i + 1]) - le32_to_cpu(otrx.hdr.offset[i]);
605
606 otrx_extract_copy(otrx.fp, trx_offset + le32_to_cpu(otrx.hdr.offset[i]), length, partition[i]);
607 }
608
609 otrx_close(otrx.fp);
610 err_out:
611 return err;
612 }
613
614 /**************************************************
615 * Start
616 **************************************************/
617
618 static void usage() {
619 printf("Usage:\n");
620 printf("\n");
621 printf("Checking TRX file:\n");
622 printf("\totrx check <file> [options]\tcheck if file is a valid TRX\n");
623 printf("\t-o offset\t\t\toffset of TRX data in file (default: 0)\n");
624 printf("\n");
625 printf("Creating new TRX file:\n");
626 printf("\totrx create <file> [options] [partitions]\n");
627 printf("\t-f file\t\t\t\t[partition] start new partition with content copied from file\n");
628 printf("\t-A file\t\t\t\t[partition] append current partition with content copied from file\n");
629 printf("\t-a alignment\t\t\t[partition] align current partition\n");
630 printf("\t-b offset\t\t\t[partition] append zeros to partition till reaching absolute offset\n");
631 printf("\n");
632 printf("Extracting from TRX file:\n");
633 printf("\totrx extract <file> [options]\textract partitions from TRX file\n");
634 printf("\t-o offset\t\t\toffset of TRX data in file (default: 0)\n");
635 printf("\t-1 file\t\t\t\tfile to extract 1st partition to (optional)\n");
636 printf("\t-2 file\t\t\t\tfile to extract 2nd partition to (optional)\n");
637 printf("\t-3 file\t\t\t\tfile to extract 3rd partition to (optional)\n");
638 }
639
640 int main(int argc, char **argv) {
641 if (argc > 1) {
642 if (!strcmp(argv[1], "check"))
643 return otrx_check(argc, argv);
644 else if (!strcmp(argv[1], "create"))
645 return otrx_create(argc, argv);
646 else if (!strcmp(argv[1], "extract"))
647 return otrx_extract(argc, argv);
648 }
649
650 usage();
651 return 0;
652 }