/* * firmware cutter for broadcom 43xx wireless driver files * * Copyright (c) 2005 Martin Langer , * 2005-2014 Michael Buesch * 2005 Alex Beregszaszi * 2007 Johannes Berg * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #if defined(__DragonFly__) || defined(__FreeBSD__) #include #elif defined(__APPLE__) #include #else #include #endif #include "md5.h" #include "fwcutter.h" #include "fwcutter_list.h" #if defined(__DragonFly__) || defined(__FreeBSD__) #define V3_FW_DIRNAME "v3" #define V4_FW_DIRNAME "v4" #else #define V3_FW_DIRNAME "b43legacy" #define V4_FW_DIRNAME "b43" #endif static struct cmdline_args cmdargs; /* check whether file will be listed/extracted from */ static int file_ok(const struct file *f) { return !(f->flags & FW_FLAG_UNSUPPORTED) || cmdargs.unsupported; } /* Convert a Big-Endian 16bit integer to CPU-endian */ static uint16_t from_be16(be16_t v) { uint16_t ret = 0; ret |= (uint16_t)(((uint8_t *)&v)[0]) << 8; ret |= (uint16_t)(((uint8_t *)&v)[1]); return ret; } /* Convert a CPU-endian 16bit integer to Big-Endian */ static be16_t to_be16(uint16_t v) { return (be16_t)from_be16((be16_t)v); } /* Convert a Big-Endian 32bit integer to CPU-endian */ static uint32_t from_be32(be32_t v) { uint32_t ret = 0; ret |= (uint32_t)(((uint8_t *)&v)[0]) << 24; ret |= (uint32_t)(((uint8_t *)&v)[1]) << 16; ret |= (uint32_t)(((uint8_t *)&v)[2]) << 8; ret |= (uint32_t)(((uint8_t *)&v)[3]); return ret; } /* Convert a CPU-endian 32bit integer to Big-Endian */ static be32_t to_be32(uint32_t v) { return (be32_t)from_be32((be32_t)v); } /* Convert a Little-Endian 32bit integer to CPU-endian */ static uint32_t from_le32(le32_t v) { uint32_t ret = 0; ret |= (uint32_t)(((uint8_t *)&v)[0]); ret |= (uint32_t)(((uint8_t *)&v)[1]) << 8; ret |= (uint32_t)(((uint8_t *)&v)[2]) << 16; ret |= (uint32_t)(((uint8_t *)&v)[3]) << 24; return ret; } /* Convert a CPU-endian 32bit integer to Little-Endian */ static le32_t to_le32(uint32_t v) { return (le32_t)from_le32((le32_t)v); } /* tiny disassembler */ static void print_ucode_version(struct insn *insn) { int val; /* * The instruction we're looking for is a store to memory * offset insn->op3 of the constant formed like `val' below. * 0x2de00 is the opcode for type 1, 0x378 is the opcode * for type 2 and 3. */ if (insn->opcode != 0x378 && insn->opcode != 0x2de00) return; val = ((0xFF & insn->op1) << 8) | (0xFF & insn->op2); /* * Memory offsets are word-offsets, for the meaning * see http://bcm-v4.sipsolutions.net/802.11/ObjectMemory */ switch (insn->op3) { case 0: printf(" ucode version: %d\n", val); break; case 1: printf(" ucode revision: %d\n", val); break; case 2: printf(" ucode date: %.4d-%.2d-%.2d\n", 2000 + (val >> 12), (val >> 8) & 0xF, val & 0xFF); break; case 3: printf(" ucode time: %.2d:%.2d:%.2d\n", val >> 11, (val >> 5) & 0x3f, val & 0x1f); break; } } static void disasm_ucode_1(uint64_t in, struct insn *out) { /* xxyyyzzz00oooooX -> ooooo Xxx yyy zzz * if we swap the upper and lower 32-bits first it becomes easier: * 00oooooxxxyyyzzz -> ooooo xxx yyy zzz */ in = (in >> 32) | (in << 32); out->op3 = in & 0xFFF; out->op2 = (in >> 12) & 0xFFF; out->op1 = (in >> 24) & 0xFFF; out->opcode = (in >> 36) & 0xFFFFF; /* the rest of the in word should be zero */ } static void disasm_ucode_2(uint64_t in, struct insn *out) { /* xxyyyzzz0000oooX -> ooo Xxx yyy zzz * if we swap the upper and lower 32-bits first it becomes easier: * 0000oooxxxyyyzzz -> ooo xxx yyy zzz */ in = (in >> 32) | (in << 32); out->op3 = in & 0xFFF; out->op2 = (in >> 12) & 0xFFF; out->op1 = (in >> 24) & 0xFFF; out->opcode = (in >> 36) & 0xFFF; /* the rest of the in word should be zero */ } static void disasm_ucode_3(uint64_t in, struct insn *out) { /* * like 2, but each operand has one bit more; appears * to use the same instruction set slightly extended */ in = (in >> 32) | (in << 32); out->op3 = in & 0x1FFF; out->op2 = (in >> 13) & 0x1FFF; out->op1 = (in >> 26) & 0x1FFF; out->opcode = (in >> 39) & 0xFFF; /* the rest of the in word should be zero */ } static void analyse_ucode(int ucode_rev, uint8_t *data, uint32_t len) { uint64_t *insns = (uint64_t*)data; struct insn insn; uint32_t i; for (i=0; ireg = bswap_16(iv->reg); iv->size = bswap_16(iv->size); iv->val = bswap_32(iv->val); } static uint8_t *read_object(FILE *f, const struct extract *extract) { uint8_t *buf; if (fseek(f, extract->offset, SEEK_SET)) { perror("failed to seek on file"); exit(2); } buf = malloc(extract->length); if (!buf) { perror("failed to allocate buffer"); exit(3); } if (fread(buf, 1, extract->length, f) != extract->length) { perror("failed to read complete data"); exit(3); } return buf; } static void build_ivs(struct b43_iv **_out, size_t *_out_size, struct iv *in, size_t in_size, struct fw_header *hdr, uint32_t flags) { struct iv *iv; struct b43_iv *out; uint32_t i; size_t out_size = 0; if (sizeof(struct b43_iv) != 6) { printf("sizeof(struct b43_iv) != 6\n"); exit(255); } out = malloc(in_size); if (!out) { perror("failed to allocate buffer"); exit(1); } *_out = out; for (i = 0; i < in_size / sizeof(*iv); i++, in++) { if (flags & FW_FLAG_LE) swap_endianness_iv(in); /* input-IV is BigEndian */ if (in->reg & to_be16(~FW_IV_OFFSET_MASK)) { printf("Input file IV offset > 0x%X\n", FW_IV_OFFSET_MASK); exit(1); } out->offset_size = in->reg; if (in->size == to_be16(4)) { out->offset_size |= to_be16(FW_IV_32BIT); out->data.d32 = in->val; out_size += sizeof(be16_t) + sizeof(be32_t); out = (struct b43_iv *)((uint8_t *)out + sizeof(be16_t) + sizeof(be32_t)); } else if (in->size == to_be16(2)) { if (in->val & to_be32(~0xFFFF)) { printf("Input file 16bit IV value overflow\n"); exit(1); } out->data.d16 = to_be16(from_be32(in->val)); out_size += sizeof(be16_t) + sizeof(be16_t); out = (struct b43_iv *)((uint8_t *)out + sizeof(be16_t) + sizeof(be16_t)); } else { printf("Input file IV size != 2|4\n"); exit(1); } } hdr->size = to_be32(i); *_out_size = out_size; } static void write_file(const char *name, uint8_t *buf, uint32_t len, const struct fw_header *hdr, uint32_t flags) { FILE *f; char nbuf[4096]; const char *dir; int r; if (flags & FW_FLAG_V4) dir = V4_FW_DIRNAME; else dir = V3_FW_DIRNAME; r = snprintf(nbuf, sizeof(nbuf), "%s/%s", cmdargs.target_dir, dir); if (r >= sizeof(nbuf)) { fprintf(stderr, "name too long"); exit(2); } r = mkdir(nbuf, 0770); if (r && errno != EEXIST) { perror("failed to create output directory"); exit(2); } r = snprintf(nbuf, sizeof(nbuf), "%s/%s/%s.fw", cmdargs.target_dir, dir, name); if (r >= sizeof(nbuf)) { fprintf(stderr, "name too long"); exit(2); } f = fopen(nbuf, "w"); if (!f) { perror("failed to open file"); exit(2); } if (fwrite(hdr, sizeof(*hdr), 1, f) != 1) { perror("failed to write file"); exit(2); } if (fwrite(buf, 1, len, f) != len) { perror("failed to write file"); exit(2); } fclose(f); } static void extract_or_identify(FILE *f, const char *dir, const struct extract *extract, uint32_t flags) { uint8_t *buf; size_t data_length; int ucode_rev = 0; struct fw_header hdr; memset(&hdr, 0, sizeof(hdr)); hdr.ver = FW_HDR_VER; printf("%s %s/%s.fw\n", cmdargs.mode == FWCM_IDENTIFY ? "Contains" : "Extracting", dir, extract->name); buf = read_object(f, extract); switch (extract->type) { case EXT_UCODE_3: ucode_rev += 1; case EXT_UCODE_2: ucode_rev += 1; case EXT_UCODE_1: ucode_rev += 1; data_length = extract->length; if (flags & FW_FLAG_LE) swap_endianness_ucode(buf, data_length); analyse_ucode(ucode_rev, buf, data_length); hdr.type = FW_TYPE_UCODE; hdr.size = to_be32(data_length); break; case EXT_PCM: data_length = extract->length; if (flags & FW_FLAG_LE) swap_endianness_pcm(buf, data_length); hdr.type = FW_TYPE_PCM; hdr.size = to_be32(data_length); break; case EXT_IV: { struct b43_iv *ivs; hdr.type = FW_TYPE_IV; build_ivs(&ivs, &data_length, (struct iv *)buf, extract->length, &hdr, flags); free(buf); buf = (uint8_t *)ivs; break; } default: exit(255); } if (cmdargs.mode == FWCM_EXTRACT_B43) write_file(extract->name, buf, data_length, &hdr, flags); free(buf); } static int brcmsmac_name_to_idx(const char *name) { if (strcmp("lcn0bsinitvals24", name) == 0) { return D11LCN0BSINITVALS24; } else if (strcmp("lcn0initvals24", name) == 0) { return D11LCN0INITVALS24; } else if (strcmp("lcn1bsinitvals24", name) == 0) { return D11LCN1BSINITVALS24; } else if (strcmp("lcn1initvals24", name) == 0) { return D11LCN1INITVALS24; } else if (strcmp("lcn2bsinitvals24", name) == 0) { return D11LCN2BSINITVALS24; } else if (strcmp("lcn2initvals24", name) == 0) { return D11LCN2INITVALS24; } else if (strcmp("n0absinitvals16", name) == 0) { return D11N0ABSINITVALS16; } else if (strcmp("n0bsinitvals16", name) == 0) { return D11N0BSINITVALS16; } else if (strcmp("n0initvals16", name) == 0) { return D11N0INITVALS16; } else if (strcmp("ucode16_mimo", name) == 0) { return D11UCODE_OVERSIGHT16_MIMO; } else if (strcmp("ucode24_lcn", name) == 0) { return D11UCODE_OVERSIGHT24_LCN; } return 0; } static int brcmsmac_name_to_size_idx(const char *name) { if (strcmp("ucode16_mimo", name) == 0) { return D11UCODE_OVERSIGHT16_MIMOSZ; } else if (strcmp("ucode24_lcn", name) == 0) { return D11UCODE_OVERSIGHT24_LCNSZ; } return 0; } static void brcmsmac_clear_file(void) { FILE *f; char nbuf[4096]; int r; r = snprintf(nbuf, sizeof(nbuf), "%s/brcm", cmdargs.target_dir); if (r >= sizeof(nbuf)) { fprintf(stderr, "name too long"); exit(2); } r = mkdir(nbuf, 0770); if (r && errno != EEXIST) { perror("failed to create output directory"); exit(2); } r = snprintf(nbuf, sizeof(nbuf), "%s/brcm/bcm43xx-0.fw", cmdargs.target_dir); if (r >= sizeof(nbuf)) { fprintf(stderr, "name too long"); exit(2); } f = fopen(nbuf, "w"); if (!f) { perror("failed to open data file"); exit(2); } fclose(f); r = snprintf(nbuf, sizeof(nbuf), "%s/brcm/bcm43xx_hdr-0.fw", cmdargs.target_dir); if (r >= sizeof(nbuf)) { fprintf(stderr, "name too long"); exit(2); } f = fopen(nbuf, "w"); fclose(f); } static void brcmsmac_write_file(int idx, uint8_t *buf, uint32_t len) { FILE *f; FILE *h; char nbuf[4096]; int r; int offset; struct firmware_hdr fw_hdr; r = snprintf(nbuf, sizeof(nbuf), "%s/brcm/bcm43xx-0.fw", cmdargs.target_dir); if (r >= sizeof(nbuf)) { fprintf(stderr, "name too long"); exit(2); } f = fopen(nbuf, "a"); if (!f) { perror("failed to open data file"); exit(2); } fseek(f, 0L, SEEK_END); r = snprintf(nbuf, sizeof(nbuf), "%s/brcm/bcm43xx_hdr-0.fw", cmdargs.target_dir); if (r >= sizeof(nbuf)) { fprintf(stderr, "name too long"); exit(2); } h = fopen(nbuf, "a"); if (!h) { perror("failed to open header file"); exit(2); } fseek(h, 0L, SEEK_END); offset = ftell(f); fw_hdr.offset = to_le32(offset); fw_hdr.len = to_le32(len); fw_hdr.idx = to_le32(idx); if (fwrite(&fw_hdr, sizeof(fw_hdr), 1, h) != 1) { perror("failed to write file"); exit(2); } fclose(h); if (fwrite(buf, 1, len, f) != len) { perror("failed to write file"); exit(2); } fclose(f); } static void brcmsmac_add_dummy_entries(void) { uint8_t buf[4] = {0}; brcmsmac_write_file(D11N0ABSINITVALS16, buf, 4); brcmsmac_write_file(D11UCODE_OVERSIGHT_BOMMAJOR, buf, 4); brcmsmac_write_file(D11UCODE_OVERSIGHT_BOMMINOR, buf, 4); } static void brcmsmac_extract(FILE *f, const struct extract *extract, uint32_t flags) { uint8_t *buf; size_t data_length; int ucode_rev = 0; int brcmsmac_idx = 0; be32_t size; brcmsmac_idx = brcmsmac_name_to_idx(extract->name); if (!brcmsmac_idx) return; printf("%s %s\n", cmdargs.mode == FWCM_IDENTIFY ? "Contains" : "Extracting", extract->name); buf = read_object(f, extract); switch (extract->type) { case EXT_UCODE_3: ucode_rev += 1; case EXT_UCODE_2: ucode_rev += 1; case EXT_UCODE_1: ucode_rev += 1; data_length = extract->length; if (flags & FW_FLAG_LE) swap_endianness_ucode(buf, data_length); analyse_ucode(ucode_rev, buf, data_length); swap_endianness_ucode(buf, data_length); size = to_le32(data_length); break; case EXT_PCM: data_length = extract->length; if (!(flags & FW_FLAG_LE)) swap_endianness_pcm(buf, data_length); size = to_le32(data_length); break; case EXT_IV: { data_length = extract->length; if (!(flags & FW_FLAG_LE)) { struct iv *in = (struct iv *)buf; int i; for (i = 0; i < data_length / sizeof(struct iv); i++, in++) swap_endianness_iv(in); } size = to_le32(data_length); break; } default: exit(255); } brcmsmac_write_file(brcmsmac_idx, buf, data_length); int size_idx = brcmsmac_name_to_size_idx(extract->name); if (size_idx) brcmsmac_write_file(size_idx, (uint8_t *)&size, 4); free(buf); } static void print_banner(void) { printf("b43-fwcutter version " FWCUTTER_VERSION "\n"); } static void print_file(const struct file *file) { char filename[30]; char shortname[30]; if (file->flags & FW_FLAG_V4) printf(V4_FW_DIRNAME "\t\t"); else printf(V3_FW_DIRNAME "\t"); if (strlen(file->name) > 20) { strncpy(shortname, file->name, 20); shortname[20] = '\0'; snprintf(filename, sizeof(filename), "%s..", shortname); } else strcpy (filename, file->name); printf("%s\t", filename); if (strlen(filename) < 8) printf("\t"); if (strlen(filename) < 16) printf("\t"); printf("%s\t", file->ucode_version); if (strlen(file->ucode_version) < 8) printf("\t"); printf("%s\n", file->md5); } static void print_supported_files(void) { int i; print_banner(); printf("\nExtracting firmware is possible " "from these binary driver files.\n" "Please read http://linuxwireless.org/en/users/Drivers/b43#devicefirmware\n\n"); printf("\t" "\t\t" "\t" "\n\n"); /* print for legacy driver first */ for (i = 0; i < ARRAY_SIZE(files); i++) if (file_ok(&files[i]) && !(files[i].flags & FW_FLAG_V4)) print_file(&files[i]); for (i = 0; i < ARRAY_SIZE(files); i++) if (file_ok(&files[i]) && files[i].flags & FW_FLAG_V4) print_file(&files[i]); printf("\n"); } static const struct file *find_file(FILE *fd) { unsigned char buffer[16384], signature[16]; struct MD5Context md5c; char md5sig[33]; int i; MD5Init(&md5c); while ((i = (int) fread(buffer, 1, sizeof(buffer), fd)) > 0) MD5Update(&md5c, buffer, (unsigned) i); MD5Final(signature, &md5c); snprintf(md5sig, sizeof(md5sig), "%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x" "%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x", signature[0], signature[1], signature[2], signature[3], signature[4], signature[5], signature[6], signature[7], signature[8], signature[9], signature[10], signature[11], signature[12], signature[13], signature[14], signature[15]); for (i = 0; i < ARRAY_SIZE(files); i++) { if (file_ok(&files[i]) && strcasecmp(md5sig, files[i].md5) == 0) { printf("This file is recognised as:\n"); printf(" filename : %s\n", files[i].name); printf(" version : %s\n", files[i].ucode_version); printf(" MD5 : %s\n", files[i].md5); return &files[i]; } } printf("Sorry, the input file is either wrong or " "not supported by b43-fwcutter.\n"); printf("This file has an unknown MD5sum %s.\n", md5sig); return NULL; } static void print_usage(int argc, char *argv[]) { print_banner(); printf("\nA tool to extract firmware for a Broadcom 43xx device\n"); printf("from a proprietary Broadcom 43xx device driver file.\n"); printf("\nUsage: %s [OPTION] [proprietary-driver-file]\n", argv[0]); printf(" --unsupported " "Allow working on extractable but unsupported drivers\n"); printf(" -l|--list " "List supported driver versions\n"); printf(" -b|--brcmsmac " "create firmware for brcmsmac\n"); printf(" -i|--identify " "Only identify the driver file (don't extract)\n"); printf(" -w|--target-dir DIR " "Extract and write firmware to DIR\n"); printf(" -v|--version " "Print b43-fwcutter version\n"); printf(" -h|--help " "Print this help\n"); printf("\nExample: %s -w /lib/firmware wl_apsta.o\n" " to extract the firmware blobs from wl_apsta.o and store\n" " the resulting firmware in /lib/firmware\n", argv[0]); } static int do_cmp_arg(char **argv, int *pos, const char *template, int allow_merged, char **param) { char *arg; char *next_arg; size_t arg_len, template_len; arg = argv[*pos]; next_arg = argv[*pos + 1]; arg_len = strlen(arg); template_len = strlen(template); if (param) { /* Maybe we have a merged parameter here. * A merged parameter is "-pfoobar" for example. */ if (allow_merged && arg_len > template_len) { if (memcmp(arg, template, template_len) == 0) { *param = arg + template_len; return ARG_MATCH; } return ARG_NOMATCH; } else if (arg_len != template_len) return ARG_NOMATCH; *param = next_arg; } if (strcmp(arg, template) == 0) { if (param) { /* Skip the parameter on the next iteration. */ (*pos)++; if (!*param) { printf("%s needs a parameter\n", arg); return ARG_ERROR; } } return ARG_MATCH; } return ARG_NOMATCH; } /* Simple and lean command line argument parsing. */ static int cmp_arg(char **argv, int *pos, const char *long_template, const char *short_template, char **param) { int err; if (long_template) { err = do_cmp_arg(argv, pos, long_template, 0, param); if (err == ARG_MATCH || err == ARG_ERROR) return err; } err = ARG_NOMATCH; if (short_template) err = do_cmp_arg(argv, pos, short_template, 1, param); return err; } static int parse_args(int argc, char *argv[]) { int i, res; char *param; if (argc < 2) goto out_usage; for (i = 1; i < argc; i++) { res = cmp_arg(argv, &i, "--list", "-l", NULL); if (res == ARG_MATCH) { cmdargs.mode = FWCM_LIST; continue; } else if (res == ARG_ERROR) goto out; res = cmp_arg(argv, &i, "--version", "-v", NULL); if (res == ARG_MATCH) { print_banner(); return 1; } else if (res == ARG_ERROR) goto out; res = cmp_arg(argv, &i, "--help", "-h", NULL); if (res == ARG_MATCH) goto out_usage; else if (res == ARG_ERROR) goto out; res = cmp_arg(argv, &i, "--identify", "-i", NULL); if (res == ARG_MATCH) { cmdargs.mode = FWCM_IDENTIFY; continue; } else if (res == ARG_ERROR) goto out; res = cmp_arg(argv, &i, "--brcmsmac", "-b", NULL); if (res == ARG_MATCH) { cmdargs.mode = FWCM_EXTRACT_BRCMSMAC; continue; } else if (res == ARG_ERROR) goto out; res = cmp_arg(argv, &i, "--unsupported", NULL, NULL); if (res == ARG_MATCH) { cmdargs.unsupported = 1; continue; } else if (res == ARG_ERROR) goto out; res = cmp_arg(argv, &i, "--target-dir", "-w", ¶m); if (res == ARG_MATCH) { cmdargs.target_dir = param; continue; } else if (res == ARG_ERROR) goto out; cmdargs.infile = argv[i]; break; } if (!cmdargs.infile && cmdargs.mode != FWCM_LIST) goto out_usage; return 0; out_usage: print_usage(argc, argv); out: return -1; } int main(int argc, char *argv[]) { FILE *fd; const struct file *file; const struct extract *extract; int err; const char *dir; cmdargs.target_dir = "."; err = parse_args(argc, argv); if (err == 1) return 0; else if (err != 0) return err; if (cmdargs.mode == FWCM_LIST) { print_supported_files(); return 0; } fd = fopen(cmdargs.infile, "rb"); if (!fd) { fprintf(stderr, "Cannot open input file %s\n", cmdargs.infile); return 2; } err = -1; file = find_file(fd); if (!file) goto out_close; if (file->flags & FW_FLAG_V4) dir = V4_FW_DIRNAME; else dir = V3_FW_DIRNAME; if (cmdargs.mode == FWCM_EXTRACT_BRCMSMAC) { brcmsmac_clear_file(); extract = file->extract; while (extract->name) { brcmsmac_extract(fd, extract, file->flags); extract++; } brcmsmac_add_dummy_entries(); } else { extract = file->extract; while (extract->name) { extract_or_identify(fd, dir, extract, file->flags); extract++; } } err = 0; out_close: fclose(fd); return err; }