fwcutter: Use ARRAY_SIZE
[b43-tools.git] / fwcutter / fwcutter.c
1 /*
2  * firmware cutter for broadcom 43xx wireless driver files
3  * 
4  * Copyright (c) 2005 Martin Langer <martin-langer@gmx.de>,
5  *               2005-2007 Michael Buesch <mb@bu3sch.de>
6  *               2005 Alex Beregszaszi
7  *               2007 Johannes Berg <johannes@sipsolutions.net>
8  *
9  *   Redistribution and use in source and binary forms, with or without
10  *   modification, are permitted provided that the following conditions
11  *   are met:
12  *
13  *     1. Redistributions of source code must retain the above copyright
14  *        notice, this list of conditions and the following disclaimer.
15  *     2. Redistributions in binary form must reproduce the above
16  *        copyright notice, this list of conditions and the following
17  *        disclaimer in the documentation and/or other materials provided
18  *        with the distribution.
19  *
20  *   THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
21  *   WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
22  *   OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23  *   DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
24  *   DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25  *   DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
26  *   GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27  *   INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
28  *   WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
29  *   OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
30  *   EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32
33 #include <stdlib.h>
34 #include <ctype.h>
35 #include <string.h>
36 #include <stdio.h>
37 #include <errno.h>
38 #include <sys/stat.h>
39 #include <sys/types.h>
40
41 #ifdef __DragonFly__
42 #include <sys/endian.h>
43 #else
44 #include <byteswap.h>
45 #endif
46
47 #include "md5.h"
48 #include "fwcutter.h"
49 #include "fwcutter_list.h"
50
51 #ifdef __DragonFly__
52 #define V3_FW_DIRNAME   "v3"
53 #define V4_FW_DIRNAME   "v4"
54 #else
55 #define V3_FW_DIRNAME   "b43legacy"
56 #define V4_FW_DIRNAME   "b43"
57 #endif
58
59 static struct cmdline_args cmdargs;
60
61
62 /* check whether file will be listed/extracted from */
63 static int file_ok(const struct file *f)
64 {
65         return !(f->flags & FW_FLAG_UNSUPPORTED) || cmdargs.unsupported;
66 }
67
68 /* Convert a CPU-endian 16bit integer to Big-Endian */
69 static be16_t to_be16(uint16_t v)
70 {
71         uint8_t ret[2];
72
73         ret[0] = (v & 0xFF00) >> 8;
74         ret[1] = (v & 0x00FF);
75
76         return *((be16_t *)ret);
77 }
78
79 #if 0
80 /* Convert a Big-Endian 16bit integer to CPU-endian */
81 static uint16_t from_be16(be16_t v)
82 {
83         uint16_t ret = 0;
84
85         ret |= (uint16_t)(((uint8_t *)&v)[0]) << 8;
86         ret |= (uint16_t)(((uint8_t *)&v)[1]);
87
88         return ret;
89 }
90 #endif
91
92 /* Convert a CPU-endian 32bit integer to Big-Endian */
93 static be32_t to_be32(uint32_t v)
94 {
95         uint8_t ret[4];
96
97         ret[0] = (v & 0xFF000000) >> 24;
98         ret[1] = (v & 0x00FF0000) >> 16;
99         ret[2] = (v & 0x0000FF00) >> 8;
100         ret[3] = (v & 0x000000FF);
101
102         return *((be32_t *)ret);
103 }
104
105 /* Convert a Big-Endian 32bit integer to CPU-endian */
106 static uint32_t from_be32(be32_t v)
107 {
108         uint32_t ret = 0;
109
110         ret |= (uint32_t)(((uint8_t *)&v)[0]) << 24;
111         ret |= (uint32_t)(((uint8_t *)&v)[1]) << 16;
112         ret |= (uint32_t)(((uint8_t *)&v)[2]) << 8;
113         ret |= (uint32_t)(((uint8_t *)&v)[3]);
114
115         return ret;
116 }
117
118 /* tiny disassembler */
119 static void print_ucode_version(struct insn *insn)
120 {
121         int val;
122
123         /*
124          * The instruction we're looking for is a store to memory
125          * offset insn->op3 of the constant formed like `val' below.
126          * 0x2de00 is the opcode for type 1, 0x378 is the opcode
127          * for type 2 and 3.
128          */
129         if (insn->opcode != 0x378 && insn->opcode != 0x2de00)
130                 return;
131
132         val = ((0xFF & insn->op1) << 8) | (0xFF & insn->op2);
133
134         /*
135          * Memory offsets are word-offsets, for the meaning
136          * see http://bcm-v4.sipsolutions.net/802.11/ObjectMemory
137          */
138         switch (insn->op3) {
139         case 0:
140                 printf("  ucode version:  %d\n", val);
141                 break;
142         case 1:
143                 printf("  ucode revision: %d\n", val);
144                 break;
145         case 2:
146                 printf("  ucode date:     %.4d-%.2d-%.2d\n",
147                        2000 + (val >> 12), (val >> 8) & 0xF, val & 0xFF);
148                 break;
149         case 3:
150                 printf("  ucode time:     %.2d:%.2d:%.2d\n",
151                        val >> 11, (val >> 5) & 0x3f, val & 0x1f);
152                 break;
153         }
154 }
155
156 static void disasm_ucode_1(uint64_t in, struct insn *out)
157 {
158         /* xxyyyzzz00oooooX -> ooooo Xxx yyy zzz
159          * if we swap the upper and lower 32-bits first it becomes easier:
160          * 00oooooxxxyyyzzz -> ooooo xxx yyy zzz
161          */
162         in = (in >> 32) | (in << 32);
163
164         out->op3        = in & 0xFFF;
165         out->op2        = (in >> 12) & 0xFFF;
166         out->op1        = (in >> 24) & 0xFFF;
167         out->opcode     = (in >> 36) & 0xFFFFF;
168         /* the rest of the in word should be zero */
169 }
170
171 static void disasm_ucode_2(uint64_t in, struct insn *out)
172 {
173         /* xxyyyzzz0000oooX -> ooo Xxx yyy zzz
174          * if we swap the upper and lower 32-bits first it becomes easier:
175          * 0000oooxxxyyyzzz -> ooo xxx yyy zzz
176          */
177         in = (in >> 32) | (in << 32);
178
179         out->op3        = in & 0xFFF;
180         out->op2        = (in >> 12) & 0xFFF;
181         out->op1        = (in >> 24) & 0xFFF;
182         out->opcode     = (in >> 36) & 0xFFF;
183         /* the rest of the in word should be zero */
184 }
185
186 static void disasm_ucode_3(uint64_t in, struct insn *out)
187 {
188         /*
189          * like 2, but each operand has one bit more; appears
190          * to use the same instruction set slightly extended
191          */
192         in = (in >> 32) | (in << 32);
193
194         out->op3        = in & 0x1FFF;
195         out->op2        = (in >> 13) & 0x1FFF;
196         out->op1        = (in >> 26) & 0x1FFF;
197         out->opcode     = (in >> 39) & 0xFFF;
198         /* the rest of the in word should be zero */
199 }
200
201 static void analyse_ucode(int ucode_rev, uint8_t *data, uint32_t len)
202 {
203         uint64_t *insns = (uint64_t*)data;
204         struct insn insn;
205         uint32_t i;
206
207         for (i=0; i<len/sizeof(*insns); i++) {
208                 switch (ucode_rev) {
209                 case 1:
210                         disasm_ucode_1(insns[i], &insn);
211                         print_ucode_version(&insn);
212                         break;
213                 case 2:
214                         disasm_ucode_2(insns[i], &insn);
215                         print_ucode_version(&insn);
216                         break;
217                 case 3:
218                         disasm_ucode_3(insns[i], &insn);
219                         print_ucode_version(&insn);
220                         break;
221                 }
222         }
223 }
224
225 static void swap_endianness_ucode(uint8_t *buf, uint32_t len)
226 {
227         uint32_t *buf32 = (uint32_t*)buf;
228         uint32_t i;
229
230         for (i=0; i<len/4; i++)
231                 buf32[i] = bswap_32(buf32[i]);
232 }
233
234 #define swap_endianness_pcm swap_endianness_ucode
235
236 static void swap_endianness_iv(struct iv *iv)
237 {
238         iv->reg = bswap_16(iv->reg);
239         iv->size = bswap_16(iv->size);
240         iv->val = bswap_32(iv->val);
241 }
242
243 static void build_ivs(struct b43_iv **_out, size_t *_out_size,
244                       struct iv *in, size_t in_size,
245                       struct fw_header *hdr,
246                       uint32_t flags)
247 {
248         struct iv *iv;
249         struct b43_iv *out;
250         uint32_t i;
251         size_t out_size = 0;
252
253         if (sizeof(struct b43_iv) != 6) {
254                 printf("sizeof(struct b43_iv) != 6\n");
255                 exit(255);
256         }
257
258         out = malloc(in_size);
259         if (!out) {
260                 perror("failed to allocate buffer");
261                 exit(1);
262         }
263         *_out = out;
264         for (i = 0; i < in_size / sizeof(*iv); i++, in++) {
265                 if (flags & FW_FLAG_LE)
266                         swap_endianness_iv(in);
267                 /* input-IV is BigEndian */
268                 if (in->reg & to_be16(~FW_IV_OFFSET_MASK)) {
269                         printf("Input file IV offset > 0x%X\n", FW_IV_OFFSET_MASK);
270                         exit(1);
271                 }
272                 out->offset_size = in->reg;
273                 if (in->size == to_be16(4)) {
274                         out->offset_size |= to_be16(FW_IV_32BIT);
275                         out->data.d32 = in->val;
276
277                         out_size += sizeof(be16_t) + sizeof(be32_t);
278                         out = (struct b43_iv *)((uint8_t *)out + sizeof(be16_t) + sizeof(be32_t));
279                 } else if (in->size == to_be16(2)) {
280                         if (in->val & to_be32(~0xFFFF)) {
281                                 printf("Input file 16bit IV value overflow\n");
282                                 exit(1);
283                         }
284                         out->data.d16 = to_be16(from_be32(in->val));
285
286                         out_size += sizeof(be16_t) + sizeof(be16_t);
287                         out = (struct b43_iv *)((uint8_t *)out + sizeof(be16_t) + sizeof(be16_t));
288                 } else {
289                         printf("Input file IV size != 2|4\n");
290                         exit(1);
291                 }
292         }
293         hdr->size = to_be32(i);
294         *_out_size = out_size;
295 }
296
297 static void write_file(const char *name, uint8_t *buf, uint32_t len,
298                        const struct fw_header *hdr, uint32_t flags)
299 {
300         FILE *f;
301         char nbuf[4096];
302         const char *dir;
303         int r;
304
305         if (flags & FW_FLAG_V4)
306                 dir = V4_FW_DIRNAME;
307         else
308                 dir = V3_FW_DIRNAME;
309
310         r = snprintf(nbuf, sizeof(nbuf),
311                      "%s/%s", cmdargs.target_dir, dir);
312         if (r >= sizeof(nbuf)) {
313                 fprintf(stderr, "name too long");
314                 exit(2);
315         }
316
317         r = mkdir(nbuf, 0770);
318         if (r && errno != EEXIST) {
319                 perror("failed to create output directory");
320                 exit(2);
321         }
322
323         r = snprintf(nbuf, sizeof(nbuf),
324                      "%s/%s/%s.fw", cmdargs.target_dir, dir, name);
325         if (r >= sizeof(nbuf)) {
326                 fprintf(stderr, "name too long");
327                 exit(2);
328         }
329         f = fopen(nbuf, "w");
330         if (!f) {
331                 perror("failed to open file");
332                 exit(2);
333         }
334         if (fwrite(hdr, sizeof(*hdr), 1, f) != 1) {
335                 perror("failed to write file");
336                 exit(2);
337         }
338         if (fwrite(buf, 1, len, f) != len) {
339                 perror("failed to write file");
340                 exit(2);
341         }
342         fclose(f);
343 }
344
345 static void extract_or_identify(FILE *f, const struct extract *extract,
346                                 uint32_t flags)
347 {
348         uint8_t *buf;
349         size_t data_length;
350         int ucode_rev = 0;
351         struct fw_header hdr;
352
353         memset(&hdr, 0, sizeof(hdr));
354         hdr.ver = FW_HDR_VER;
355
356         if (fseek(f, extract->offset, SEEK_SET)) {
357                 perror("failed to seek on file");
358                 exit(2);
359         }
360
361         buf = malloc(extract->length);
362         if (!buf) {
363                 perror("failed to allocate buffer");
364                 exit(3);
365         }
366         if (fread(buf, 1, extract->length, f) != extract->length) {
367                 perror("failed to read complete data");
368                 exit(3);
369         }
370
371         switch (extract->type) {
372         case EXT_UCODE_3:
373                 ucode_rev += 1;
374         case EXT_UCODE_2:
375                 ucode_rev += 1;
376         case EXT_UCODE_1:
377                 ucode_rev += 1;
378                 data_length = extract->length;
379                 if (flags & FW_FLAG_LE)
380                         swap_endianness_ucode(buf, data_length);
381                 analyse_ucode(ucode_rev, buf, data_length);
382                 hdr.type = FW_TYPE_UCODE;
383                 hdr.size = to_be32(data_length);
384                 break;
385         case EXT_PCM:
386                 data_length = extract->length;
387                 if (flags & FW_FLAG_LE)
388                         swap_endianness_pcm(buf, data_length);
389                 hdr.type = FW_TYPE_PCM;
390                 hdr.size = to_be32(data_length);
391                 break;
392         case EXT_IV: {
393                 struct b43_iv *ivs;
394
395                 hdr.type = FW_TYPE_IV;
396                 build_ivs(&ivs, &data_length,
397                           (struct iv *)buf, extract->length,
398                           &hdr, flags);
399                 free(buf);
400                 buf = (uint8_t *)ivs;
401                 break;
402         }
403         default:
404                 exit(255);
405         }
406
407         if (cmdargs.mode == FWCM_EXTRACT)
408                 write_file(extract->name, buf, data_length, &hdr, flags);
409
410         free(buf);
411 }
412
413 static void print_banner(void)
414 {
415         printf("b43-fwcutter version " FWCUTTER_VERSION "\n");
416 }
417
418 static void print_file(const struct file *file)
419 {
420         char filename[30];
421         char shortname[30];
422
423         if (file->flags & FW_FLAG_V4)
424                 printf(V4_FW_DIRNAME "\t\t");
425         else
426                 printf(V3_FW_DIRNAME "\t");
427
428         if (strlen(file->name) > 20) {
429                 strncpy(shortname, file->name, 20);
430                 shortname[20] = '\0';
431                 snprintf(filename, sizeof(filename), "%s..", shortname);
432         } else
433                 strcpy (filename, file->name);
434
435         printf("%s\t", filename);
436         if (strlen(filename) < 8) printf("\t");
437         if (strlen(filename) < 16) printf("\t");
438
439         printf("%s\t", file->ucode_version);
440         if (strlen(file->ucode_version) < 8) printf("\t");
441
442         printf("%s\t", file->id);
443
444         printf("%s\n", file->md5);
445 }
446
447 static void print_supported_files(void)
448 {
449         int i;
450
451         print_banner();
452         printf("\nExtracting firmware is possible "
453                "from these binary driver files.\n"
454                "The <ID> column shows the unique identifier string "
455                "for your firmware.\nYou must select the firmware with the "
456                "same ID as printed by the kernel driver on modprobe.\n"
457                "Note that only recent drivers print such a message on modprobe.\n"
458                "Please read http://linuxwireless.org/en/users/Drivers/b43#devicefirmware\n\n");
459         printf("<driver>\t"
460                "<filename>\t\t"
461                "<microcode>\t"
462                "<ID>\t"
463                "<MD5 checksum>\n\n");
464         /* print for legacy driver first */
465         for (i = 0; i < ARRAY_SIZE(files); i++)
466                 if (file_ok(&files[i]) && !(files[i].flags & FW_FLAG_V4))
467                         print_file(&files[i]);
468         for (i = 0; i < ARRAY_SIZE(files); i++)
469                 if (file_ok(&files[i]) && files[i].flags & FW_FLAG_V4)
470                         print_file(&files[i]);
471         printf("\n");
472 }
473
474 static const struct file *find_file(FILE *fd)
475 {
476         unsigned char buffer[16384], signature[16];
477         struct MD5Context md5c;
478         char md5sig[33];
479         int i;
480
481         MD5Init(&md5c);
482         while ((i = (int) fread(buffer, 1, sizeof(buffer), fd)) > 0)
483                 MD5Update(&md5c, buffer, (unsigned) i);
484         MD5Final(signature, &md5c);
485
486         snprintf(md5sig, sizeof(md5sig),
487                  "%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x"
488                  "%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x",
489                  signature[0], signature[1], signature[2], signature[3],
490                  signature[4], signature[5], signature[6], signature[7],
491                  signature[8], signature[9], signature[10], signature[11],
492                  signature[12], signature[13], signature[14], signature[15]);
493
494         for (i = 0; i < ARRAY_SIZE(files); i++) {
495                 if (file_ok(&files[i]) &&
496                     strcasecmp(md5sig, files[i].md5) == 0) {
497                         printf("This file is recognised as:\n");
498                         printf("  ID         :  %s\n", files[i].id);
499                         printf("  filename   :  %s\n", files[i].name);
500                         printf("  version    :  %s\n", files[i].ucode_version);
501                         printf("  MD5        :  %s\n", files[i].md5);
502                         return &files[i];
503                 }
504         }
505         printf("Sorry, the input file is either wrong or "
506                "not supported by b43-fwcutter.\n");
507         printf("This file has an unknown MD5sum %s.\n", md5sig);
508
509         return NULL;
510 }
511
512 static void print_usage(int argc, char *argv[])
513 {
514         print_banner();
515         printf("\nA tool to extract firmware for a Broadcom 43xx device\n");
516         printf("from a proprietary Broadcom 43xx device driver file.\n");
517         printf("\nUsage: %s [OPTION] [proprietary-driver-file]\n", argv[0]);
518         printf("  --unsupported         "
519                "Allow working on extractable but unsupported drivers\n");
520         printf("  -l|--list             "
521                "List supported driver versions\n");
522         printf("  -i|--identify         "
523                "Only identify the driver file (don't extract)\n");
524         printf("  -w|--target-dir DIR   "
525                "Extract and write firmware to DIR\n");
526         printf("  -v|--version          "
527                "Print b43-fwcutter version\n");
528         printf("  -h|--help             "
529                "Print this help\n");
530         printf("\nExample: %s -w /lib/firmware wl_apsta.o\n"
531                "         to extract the firmware blobs from wl_apsta.o and store\n"
532                "         the resulting firmware in /lib/firmware\n",
533                argv[0]);
534 }
535
536 static int do_cmp_arg(char **argv, int *pos,
537                       const char *template,
538                       int allow_merged,
539                       char **param)
540 {
541         char *arg;
542         char *next_arg;
543         size_t arg_len, template_len;
544
545         arg = argv[*pos];
546         next_arg = argv[*pos + 1];
547         arg_len = strlen(arg);
548         template_len = strlen(template);
549
550         if (param) {
551                 /* Maybe we have a merged parameter here.
552                  * A merged parameter is "-pfoobar" for example.
553                  */
554                 if (allow_merged && arg_len > template_len) {
555                         if (memcmp(arg, template, template_len) == 0) {
556                                 *param = arg + template_len;
557                                 return ARG_MATCH;
558                         }
559                         return ARG_NOMATCH;
560                 } else if (arg_len != template_len)
561                         return ARG_NOMATCH;
562                 *param = next_arg;
563         }
564         if (strcmp(arg, template) == 0) {
565                 if (param) {
566                         /* Skip the parameter on the next iteration. */
567                         (*pos)++;
568                         if (!*param) {
569                                 printf("%s needs a parameter\n", arg);
570                                 return ARG_ERROR;
571                         }
572                 }
573                 return ARG_MATCH;
574         }
575
576         return ARG_NOMATCH;
577 }
578
579 /* Simple and lean command line argument parsing. */
580 static int cmp_arg(char **argv, int *pos,
581                    const char *long_template,
582                    const char *short_template,
583                    char **param)
584 {
585         int err;
586
587         if (long_template) {
588                 err = do_cmp_arg(argv, pos, long_template, 0, param);
589                 if (err == ARG_MATCH || err == ARG_ERROR)
590                         return err;
591         }
592         err = ARG_NOMATCH;
593         if (short_template)
594                 err = do_cmp_arg(argv, pos, short_template, 1, param);
595         return err;
596 }
597
598 static int parse_args(int argc, char *argv[])
599 {
600         int i, res;
601         char *param;
602
603         if (argc < 2)
604                 goto out_usage;
605         for (i = 1; i < argc; i++) {
606                 res = cmp_arg(argv, &i, "--list", "-l", NULL);
607                 if (res == ARG_MATCH) {
608                         cmdargs.mode = FWCM_LIST;
609                         continue;
610                 } else if (res == ARG_ERROR)
611                         goto out;
612
613                 res = cmp_arg(argv, &i, "--version", "-v", NULL);
614                 if (res == ARG_MATCH) {
615                         print_banner();
616                         return 1;
617                 } else if (res == ARG_ERROR)
618                         goto out;
619
620                 res = cmp_arg(argv, &i, "--help", "-h", NULL);
621                 if (res == ARG_MATCH)
622                         goto out_usage;
623                 else if (res == ARG_ERROR)
624                         goto out;
625
626                 res = cmp_arg(argv, &i, "--identify", "-i", NULL);
627                 if (res == ARG_MATCH) {
628                         cmdargs.mode = FWCM_IDENTIFY;
629                         continue;
630                 } else if (res == ARG_ERROR)
631                         goto out;
632
633                 res = cmp_arg(argv, &i, "--unsupported", NULL, NULL);
634                 if (res == ARG_MATCH) {
635                         cmdargs.unsupported = 1;
636                         continue;
637                 } else if (res == ARG_ERROR)
638                         goto out;
639
640                 res = cmp_arg(argv, &i, "--target-dir", "-w", &param);
641                 if (res == ARG_MATCH) {
642                         cmdargs.target_dir = param;
643                         continue;
644                 } else if (res == ARG_ERROR)
645                         goto out;
646
647                 cmdargs.infile = argv[i];
648                 break;
649         }
650
651         if (!cmdargs.infile && cmdargs.mode != FWCM_LIST)
652                 goto out_usage;
653         return 0;
654
655 out_usage:
656         print_usage(argc, argv);
657 out:
658         return -1;      
659 }
660
661 int main(int argc, char *argv[])
662 {
663         FILE *fd;
664         const struct file *file;
665         const struct extract *extract;
666         int err;
667         const char *dir;
668
669         cmdargs.target_dir = ".";
670         err = parse_args(argc, argv);
671         if (err == 1)
672                 return 0;
673         else if (err != 0)
674                 return err;
675
676         if (cmdargs.mode == FWCM_LIST) {
677                 print_supported_files();
678                 return 0;
679         }
680
681         fd = fopen(cmdargs.infile, "rb");
682         if (!fd) {
683                 fprintf(stderr, "Cannot open input file %s\n", cmdargs.infile);
684                 return 2;
685         }
686
687         err = -1;
688         file = find_file(fd);
689         if (!file)
690                 goto out_close;
691
692         if (file->flags & FW_FLAG_V4)
693                 dir = V4_FW_DIRNAME;
694         else
695                 dir = V3_FW_DIRNAME;
696
697         extract = file->extract;
698         while (extract->name) {
699                 printf("%s %s/%s.fw\n",
700                        cmdargs.mode == FWCM_IDENTIFY ? "Contains" : "Extracting",
701                        dir, extract->name);
702                 extract_or_identify(fd, extract, file->flags);
703                 extract++;
704         }
705
706         err = 0;
707 out_close:
708         fclose(fd);
709
710         return err;
711 }