123ba6841SJoseph Chen /* 223ba6841SJoseph Chen * (C) Copyright 2008-2015 Fuzhou Rockchip Electronics Co., Ltd 323ba6841SJoseph Chen * 423ba6841SJoseph Chen * SPDX-License-Identifier: GPL-2.0+ 523ba6841SJoseph Chen */ 623ba6841SJoseph Chen #include "compiler.h" 723ba6841SJoseph Chen #include <version.h> 823ba6841SJoseph Chen #include "sha.h" 923ba6841SJoseph Chen #include <u-boot/sha256.h> 1023ba6841SJoseph Chen #include <u-boot/crc.h> 1123ba6841SJoseph Chen #include <linux/sizes.h> 1223ba6841SJoseph Chen #include <linux/kconfig.h> 1323ba6841SJoseph Chen #include <config.h> 1423ba6841SJoseph Chen 1523ba6841SJoseph Chen extern uint32_t crc32_rk(uint32_t, const unsigned char *, uint32_t); 1623ba6841SJoseph Chen 1723ba6841SJoseph Chen #define OPT_PACK "--pack" 1823ba6841SJoseph Chen #define OPT_UNPACK "--unpack" 1923ba6841SJoseph Chen #define OPT_UBOOT "--uboot" 2023ba6841SJoseph Chen #define OPT_TRUSTOS "--trustos" 2123ba6841SJoseph Chen #define OPT_SIZE "--size" 2223ba6841SJoseph Chen #define OPT_VERSION "--version" 2323ba6841SJoseph Chen #define OPT_INFO "--info" 2423ba6841SJoseph Chen 2523ba6841SJoseph Chen /* pack or unpack */ 2623ba6841SJoseph Chen #define MODE_PACK 0 2723ba6841SJoseph Chen #define MODE_UNPACK 1 2823ba6841SJoseph Chen #define MODE_INFO 2 2923ba6841SJoseph Chen #define CONFIG_SECUREBOOT_SHA256 3023ba6841SJoseph Chen 3123ba6841SJoseph Chen /* image type */ 3223ba6841SJoseph Chen #define IMAGE_UBOOT 0 3323ba6841SJoseph Chen #define IMAGE_TRUST 1 3423ba6841SJoseph Chen 3523ba6841SJoseph Chen /* magic and hash size */ 3623ba6841SJoseph Chen #define LOADER_MAGIC_SIZE 8 3723ba6841SJoseph Chen #define LOADER_HASH_SIZE 32 3823ba6841SJoseph Chen 3923ba6841SJoseph Chen /* uboot image config */ 4023ba6841SJoseph Chen #define UBOOT_NAME "uboot" 4123ba6841SJoseph Chen #ifdef CONFIG_RK_NVME_BOOT_EN 4223ba6841SJoseph Chen #define UBOOT_NUM 2 4323ba6841SJoseph Chen #define UBOOT_MAX_SIZE 512 * 1024 4423ba6841SJoseph Chen #else 4523ba6841SJoseph Chen #define UBOOT_NUM 4 4623ba6841SJoseph Chen #define UBOOT_MAX_SIZE 1024 * 1024 4723ba6841SJoseph Chen #endif 4823ba6841SJoseph Chen 4923ba6841SJoseph Chen #define UBOOT_VERSION_STRING \ 5023ba6841SJoseph Chen U_BOOT_VERSION " (" U_BOOT_DATE " - " U_BOOT_TIME ")" CONFIG_IDENT_STRING 5123ba6841SJoseph Chen 5223ba6841SJoseph Chen #define RK_UBOOT_MAGIC "LOADER " 5323ba6841SJoseph Chen #define RK_UBOOT_RUNNING_ADDR CONFIG_SYS_TEXT_BASE 5423ba6841SJoseph Chen 5523ba6841SJoseph Chen /* trust image config */ 5623ba6841SJoseph Chen #define TRUST_NAME "trustos" 5723ba6841SJoseph Chen #define TRUST_NUM 4 5823ba6841SJoseph Chen #define TRUST_MAX_SIZE 1024 * 1024 5923ba6841SJoseph Chen #define TRUST_VERSION_STRING "Trust os" 6023ba6841SJoseph Chen 6123ba6841SJoseph Chen #define RK_TRUST_MAGIC "TOS " 6223ba6841SJoseph Chen #define RK_TRUST_RUNNING_ADDR (CONFIG_SYS_TEXT_BASE + SZ_128M + SZ_4M) 6323ba6841SJoseph Chen 6423ba6841SJoseph Chen typedef struct tag_second_loader_hdr { 6523ba6841SJoseph Chen uint8_t magic[LOADER_MAGIC_SIZE]; /* magic */ 6623ba6841SJoseph Chen uint32_t version; 6723ba6841SJoseph Chen uint32_t reserved0; 6823ba6841SJoseph Chen uint32_t loader_load_addr; /* physical load addr */ 6923ba6841SJoseph Chen uint32_t loader_load_size; /* size in bytes */ 7023ba6841SJoseph Chen uint32_t crc32; /* crc32 */ 7123ba6841SJoseph Chen uint32_t hash_len; /* 20 or 32 , 0 is no hash */ 7223ba6841SJoseph Chen uint8_t hash[LOADER_HASH_SIZE]; /* sha */ 7323ba6841SJoseph Chen 7423ba6841SJoseph Chen uint8_t reserved[1024 - 32 - 32]; 7523ba6841SJoseph Chen uint32_t signTag; /* 0x4E474953 */ 7623ba6841SJoseph Chen uint32_t signlen; /* maybe 128 or 256 */ 7723ba6841SJoseph Chen uint8_t rsaHash[256]; /* maybe 128 or 256, using max size 256 */ 7823ba6841SJoseph Chen uint8_t reserved2[2048 - 1024 - 256 - 8]; 7923ba6841SJoseph Chen } second_loader_hdr; 8023ba6841SJoseph Chen 8123ba6841SJoseph Chen void usage(const char *prog) 8223ba6841SJoseph Chen { 8323ba6841SJoseph Chen fprintf(stderr, "Usage: %s [--pack|--unpack] [--uboot|--trustos]\ 8423ba6841SJoseph Chen file_in " 8523ba6841SJoseph Chen "file_out [load_addr] [--size] [size number]\ 8623ba6841SJoseph Chen [--version] " 8723ba6841SJoseph Chen "[version] | [--info] [file]\n", 8823ba6841SJoseph Chen prog); 8923ba6841SJoseph Chen } 9023ba6841SJoseph Chen 9123ba6841SJoseph Chen unsigned int str2hex(char *str) 9223ba6841SJoseph Chen { 9323ba6841SJoseph Chen int i = 0; 9423ba6841SJoseph Chen unsigned int value = 0; 9523ba6841SJoseph Chen 9623ba6841SJoseph Chen if (*str == '0' && (*(str + 1) == 'x' || *(str + 1) == 'X')) 9723ba6841SJoseph Chen str += 2; 9823ba6841SJoseph Chen if (*str == 'x' || *str == 'X') 9923ba6841SJoseph Chen str += 1; 10023ba6841SJoseph Chen 10123ba6841SJoseph Chen for (i = 0; *str != '\0'; i++, ++str) { 10223ba6841SJoseph Chen if (*str >= '0' && *str <= '9') 10323ba6841SJoseph Chen value = value * 16 + *str - '0'; 10423ba6841SJoseph Chen else if (*str >= 'a' && *str <= 'f') 10523ba6841SJoseph Chen value = value * 16 + *str - 'a' + 10; 10623ba6841SJoseph Chen else if (*str >= 'A' && *str <= 'F') 10723ba6841SJoseph Chen value = value * 16 + *str - 'A' + 10; 10823ba6841SJoseph Chen else 10923ba6841SJoseph Chen break; 11023ba6841SJoseph Chen } 11123ba6841SJoseph Chen return value; 11223ba6841SJoseph Chen } 11323ba6841SJoseph Chen 11423ba6841SJoseph Chen int main(int argc, char *argv[]) 11523ba6841SJoseph Chen { 11623ba6841SJoseph Chen int mode = -1, image = -1; 11723ba6841SJoseph Chen int max_size, max_num; 11823ba6841SJoseph Chen int size, i; 11923ba6841SJoseph Chen uint32_t loader_addr, in_loader_addr = -1; 12023ba6841SJoseph Chen char *magic, *version, *name; 12123ba6841SJoseph Chen FILE *fi, *fo; 12223ba6841SJoseph Chen second_loader_hdr hdr; 12323ba6841SJoseph Chen char *buf = 0; 12423ba6841SJoseph Chen uint32_t in_size = 0, in_num = 0; 12523ba6841SJoseph Chen char *file_in = NULL, *file_out = NULL; 12623ba6841SJoseph Chen uint32_t curr_version = 0; 12723ba6841SJoseph Chen 12823ba6841SJoseph Chen if (argc < 3) { 12923ba6841SJoseph Chen usage(argv[0]); 13023ba6841SJoseph Chen exit(EXIT_FAILURE); 13123ba6841SJoseph Chen } 13223ba6841SJoseph Chen 13323ba6841SJoseph Chen for (i = 1; i < argc; i++) { 13423ba6841SJoseph Chen if (!strcmp(argv[i], OPT_PACK)) { 13523ba6841SJoseph Chen mode = MODE_PACK; 13623ba6841SJoseph Chen } else if (!strcmp(argv[i], OPT_UNPACK)) { 13723ba6841SJoseph Chen mode = MODE_UNPACK; 13823ba6841SJoseph Chen } else if (!strcmp(argv[i], OPT_UBOOT)) { 13923ba6841SJoseph Chen image = IMAGE_UBOOT; 14023ba6841SJoseph Chen file_in = argv[++i]; 14123ba6841SJoseph Chen file_out = argv[++i]; 14223ba6841SJoseph Chen /* detect whether loader address is delivered */ 14323ba6841SJoseph Chen if ((argv[i + 1]) && (strncmp(argv[i + 1], "--", 2))) 14423ba6841SJoseph Chen in_loader_addr = str2hex(argv[++i]); 14523ba6841SJoseph Chen } else if (!strcmp(argv[i], OPT_TRUSTOS)) { 14623ba6841SJoseph Chen image = IMAGE_TRUST; 14723ba6841SJoseph Chen file_in = argv[++i]; 14823ba6841SJoseph Chen file_out = argv[++i]; 14923ba6841SJoseph Chen /* detect whether loader address is delivered */ 15023ba6841SJoseph Chen if ((argv[i + 1]) && (strncmp(argv[i + 1], "--", 2))) 15123ba6841SJoseph Chen in_loader_addr = str2hex(argv[++i]); 15223ba6841SJoseph Chen } else if (!strcmp(argv[i], OPT_SIZE)) { 15323ba6841SJoseph Chen in_size = strtoul(argv[++i], NULL, 10); 15423ba6841SJoseph Chen /* 15523ba6841SJoseph Chen * Usually, it must be at 512kb align due to preloader 15623ba6841SJoseph Chen * detects every 512kb. But some product has critial 15723ba6841SJoseph Chen * flash size requirement, we have to make it small than 15823ba6841SJoseph Chen * 512KB. 15923ba6841SJoseph Chen */ 16023ba6841SJoseph Chen if (in_size % 64) { 16123ba6841SJoseph Chen usage(argv[0]); 16223ba6841SJoseph Chen exit(EXIT_FAILURE); 16323ba6841SJoseph Chen } 16423ba6841SJoseph Chen in_size *= 1024; 16523ba6841SJoseph Chen 16623ba6841SJoseph Chen in_num = strtoul(argv[++i], NULL, 10); 16723ba6841SJoseph Chen } else if (!strcmp(argv[i], OPT_VERSION)) { 16823ba6841SJoseph Chen curr_version = strtoul(argv[++i], NULL, 10); 16923ba6841SJoseph Chen printf("curr_version = 0x%x\n", curr_version); 17023ba6841SJoseph Chen } else if (!strcmp(argv[i], OPT_INFO)) { 17123ba6841SJoseph Chen mode = MODE_INFO; 17223ba6841SJoseph Chen file_in = argv[++i]; 17323ba6841SJoseph Chen } else { 17423ba6841SJoseph Chen usage(argv[0]); 17523ba6841SJoseph Chen exit(EXIT_FAILURE); 17623ba6841SJoseph Chen } 17723ba6841SJoseph Chen } 17823ba6841SJoseph Chen 17923ba6841SJoseph Chen /* config image information */ 18023ba6841SJoseph Chen if (image == IMAGE_UBOOT) { 18123ba6841SJoseph Chen name = UBOOT_NAME; 18223ba6841SJoseph Chen magic = RK_UBOOT_MAGIC; 18323ba6841SJoseph Chen version = UBOOT_VERSION_STRING; 18423ba6841SJoseph Chen max_size = in_size ? in_size : UBOOT_MAX_SIZE; 18523ba6841SJoseph Chen max_num = in_num ? in_num : UBOOT_NUM; 18623ba6841SJoseph Chen loader_addr = 18723ba6841SJoseph Chen (in_loader_addr == -1) ? RK_UBOOT_RUNNING_ADDR : in_loader_addr; 18823ba6841SJoseph Chen } else if (image == IMAGE_TRUST) { 18923ba6841SJoseph Chen name = TRUST_NAME; 19023ba6841SJoseph Chen magic = RK_TRUST_MAGIC; 19123ba6841SJoseph Chen version = TRUST_VERSION_STRING; 19223ba6841SJoseph Chen max_size = in_size ? in_size : TRUST_MAX_SIZE; 19323ba6841SJoseph Chen max_num = in_num ? in_num : TRUST_NUM; 19423ba6841SJoseph Chen loader_addr = 19523ba6841SJoseph Chen (in_loader_addr == -1) ? RK_TRUST_RUNNING_ADDR : in_loader_addr; 19623ba6841SJoseph Chen } else if (mode == MODE_INFO) { 19723ba6841SJoseph Chen 19823ba6841SJoseph Chen } else { 19923ba6841SJoseph Chen exit(EXIT_FAILURE); 20023ba6841SJoseph Chen } 20123ba6841SJoseph Chen 20223ba6841SJoseph Chen if (mode == MODE_PACK) { 20323ba6841SJoseph Chen buf = calloc(max_size, max_num); 20423ba6841SJoseph Chen if (!buf) { 20523ba6841SJoseph Chen perror(file_out); 20623ba6841SJoseph Chen exit(EXIT_FAILURE); 20723ba6841SJoseph Chen } 20823ba6841SJoseph Chen printf("\n load addr is 0x%x!\n", loader_addr); 20923ba6841SJoseph Chen if (!file_in || !file_out) { 21023ba6841SJoseph Chen usage(argv[0]); 21123ba6841SJoseph Chen exit(EXIT_FAILURE); 21223ba6841SJoseph Chen } 21323ba6841SJoseph Chen 21423ba6841SJoseph Chen /* file in */ 21523ba6841SJoseph Chen fi = fopen(file_in, "rb"); 21623ba6841SJoseph Chen if (!fi) { 21723ba6841SJoseph Chen perror(file_in); 21823ba6841SJoseph Chen exit(EXIT_FAILURE); 21923ba6841SJoseph Chen } 22023ba6841SJoseph Chen 22123ba6841SJoseph Chen /* file out */ 22223ba6841SJoseph Chen fo = fopen(file_out, "wb"); 22323ba6841SJoseph Chen if (!fo) { 22423ba6841SJoseph Chen perror(file_out); 22523ba6841SJoseph Chen exit(EXIT_FAILURE); 22623ba6841SJoseph Chen } 22723ba6841SJoseph Chen 22823ba6841SJoseph Chen printf("pack input %s \n", file_in); 22923ba6841SJoseph Chen fseek(fi, 0, SEEK_END); 23023ba6841SJoseph Chen size = ftell(fi); 23123ba6841SJoseph Chen fseek(fi, 0, SEEK_SET); 232*2bbbd780SJoseph Chen printf("pack file size: %d(%d KB)\n", size, size / 1024); 23323ba6841SJoseph Chen if (size > max_size - sizeof(second_loader_hdr)) { 23423ba6841SJoseph Chen perror(file_out); 23523ba6841SJoseph Chen exit(EXIT_FAILURE); 23623ba6841SJoseph Chen } 23723ba6841SJoseph Chen memset(&hdr, 0, sizeof(second_loader_hdr)); 23823ba6841SJoseph Chen memcpy((char *)hdr.magic, magic, LOADER_MAGIC_SIZE); 23923ba6841SJoseph Chen hdr.version = curr_version; 24023ba6841SJoseph Chen hdr.loader_load_addr = loader_addr; 24123ba6841SJoseph Chen if (!fread(buf + sizeof(second_loader_hdr), size, 1, fi)) 24223ba6841SJoseph Chen exit(EXIT_FAILURE); 24323ba6841SJoseph Chen 24423ba6841SJoseph Chen /* Aligned size to 4-byte, Rockchip HW Crypto need 4-byte align */ 24523ba6841SJoseph Chen size = (((size + 3) >> 2) << 2); 24623ba6841SJoseph Chen hdr.loader_load_size = size; 24723ba6841SJoseph Chen 24823ba6841SJoseph Chen hdr.crc32 = crc32_rk( 24923ba6841SJoseph Chen 0, (const unsigned char *)buf + sizeof(second_loader_hdr), size); 25023ba6841SJoseph Chen printf("crc = 0x%08x\n", hdr.crc32); 25123ba6841SJoseph Chen 25223ba6841SJoseph Chen #ifndef CONFIG_SECUREBOOT_SHA256 25323ba6841SJoseph Chen SHA_CTX ctx; 25423ba6841SJoseph Chen uint8_t *sha; 25523ba6841SJoseph Chen hdr.hash_len = (SHA_DIGEST_SIZE > LOADER_HASH_SIZE) ? LOADER_HASH_SIZE 25623ba6841SJoseph Chen : SHA_DIGEST_SIZE; 25723ba6841SJoseph Chen SHA_init(&ctx); 25823ba6841SJoseph Chen SHA_update(&ctx, buf + sizeof(second_loader_hdr), size); 25923ba6841SJoseph Chen if (hdr.version > 0) 26023ba6841SJoseph Chen SHA_update(&ctx, (void *)&hdr.version, 8); 26123ba6841SJoseph Chen 26223ba6841SJoseph Chen SHA_update(&ctx, &hdr.loader_load_addr, sizeof(hdr.loader_load_addr)); 26323ba6841SJoseph Chen SHA_update(&ctx, &hdr.loader_load_size, sizeof(hdr.loader_load_size)); 26423ba6841SJoseph Chen SHA_update(&ctx, &hdr.hash_len, sizeof(hdr.hash_len)); 26523ba6841SJoseph Chen sha = (uint8_t *)SHA_final(&ctx); 26623ba6841SJoseph Chen memcpy(hdr.hash, sha, hdr.hash_len); 26723ba6841SJoseph Chen #else 26823ba6841SJoseph Chen sha256_context ctx; 26923ba6841SJoseph Chen uint8_t hash[LOADER_HASH_SIZE]; 27023ba6841SJoseph Chen 27123ba6841SJoseph Chen memset(hash, 0, LOADER_HASH_SIZE); 27223ba6841SJoseph Chen 27323ba6841SJoseph Chen hdr.hash_len = 32; /* sha256 */ 27423ba6841SJoseph Chen sha256_starts(&ctx); 27523ba6841SJoseph Chen sha256_update(&ctx, (void *)buf + sizeof(second_loader_hdr), size); 27623ba6841SJoseph Chen if (hdr.version > 0) 27723ba6841SJoseph Chen sha256_update(&ctx, (void *)&hdr.version, 8); 27823ba6841SJoseph Chen 27923ba6841SJoseph Chen sha256_update(&ctx, (void *)&hdr.loader_load_addr, 28023ba6841SJoseph Chen sizeof(hdr.loader_load_addr)); 28123ba6841SJoseph Chen sha256_update(&ctx, (void *)&hdr.loader_load_size, 28223ba6841SJoseph Chen sizeof(hdr.loader_load_size)); 28323ba6841SJoseph Chen sha256_update(&ctx, (void *)&hdr.hash_len, sizeof(hdr.hash_len)); 28423ba6841SJoseph Chen sha256_finish(&ctx, hash); 28523ba6841SJoseph Chen memcpy(hdr.hash, hash, hdr.hash_len); 28623ba6841SJoseph Chen #endif /* CONFIG_SECUREBOOT_SHA256 */ 28723ba6841SJoseph Chen 28823ba6841SJoseph Chen printf("%s version: %s\n", name, version); 28923ba6841SJoseph Chen memcpy(buf, &hdr, sizeof(second_loader_hdr)); 29023ba6841SJoseph Chen for (i = 0; i < max_num; i++) 29123ba6841SJoseph Chen fwrite(buf, max_size, 1, fo); 29223ba6841SJoseph Chen 29323ba6841SJoseph Chen printf("pack %s success! \n", file_out); 29423ba6841SJoseph Chen fclose(fi); 29523ba6841SJoseph Chen fclose(fo); 29623ba6841SJoseph Chen } else if (mode == MODE_UNPACK) { 29723ba6841SJoseph Chen buf = calloc(max_size, max_num); 29823ba6841SJoseph Chen if (!buf) { 29923ba6841SJoseph Chen perror(file_out); 30023ba6841SJoseph Chen exit(EXIT_FAILURE); 30123ba6841SJoseph Chen } 30223ba6841SJoseph Chen if (!file_in || !file_out) { 30323ba6841SJoseph Chen usage(argv[0]); 30423ba6841SJoseph Chen exit(EXIT_FAILURE); 30523ba6841SJoseph Chen } 30623ba6841SJoseph Chen 30723ba6841SJoseph Chen /* file in */ 30823ba6841SJoseph Chen fi = fopen(file_in, "rb"); 30923ba6841SJoseph Chen if (!fi) { 31023ba6841SJoseph Chen perror(file_in); 31123ba6841SJoseph Chen exit(EXIT_FAILURE); 31223ba6841SJoseph Chen } 31323ba6841SJoseph Chen 31423ba6841SJoseph Chen /* file out */ 31523ba6841SJoseph Chen fo = fopen(file_out, "wb"); 31623ba6841SJoseph Chen if (!fo) { 31723ba6841SJoseph Chen perror(file_out); 31823ba6841SJoseph Chen exit(EXIT_FAILURE); 31923ba6841SJoseph Chen } 32023ba6841SJoseph Chen 32123ba6841SJoseph Chen printf("unpack input %s \n", file_in); 32223ba6841SJoseph Chen memset(&hdr, 0, sizeof(second_loader_hdr)); 32323ba6841SJoseph Chen if (!fread(&hdr, sizeof(second_loader_hdr), 1, fi)) 32423ba6841SJoseph Chen exit(EXIT_FAILURE); 32523ba6841SJoseph Chen 32623ba6841SJoseph Chen if (!fread(buf, hdr.loader_load_size, 1, fi)) 32723ba6841SJoseph Chen exit(EXIT_FAILURE); 32823ba6841SJoseph Chen 32923ba6841SJoseph Chen fwrite(buf, hdr.loader_load_size, 1, fo); 33023ba6841SJoseph Chen printf("unpack %s success! \n", file_out); 33123ba6841SJoseph Chen fclose(fi); 33223ba6841SJoseph Chen fclose(fo); 33323ba6841SJoseph Chen } else if (mode == MODE_INFO) { 33423ba6841SJoseph Chen second_loader_hdr *hdr; 33523ba6841SJoseph Chen 33623ba6841SJoseph Chen hdr = malloc(sizeof(struct tag_second_loader_hdr)); 33723ba6841SJoseph Chen if (hdr == NULL) { 33823ba6841SJoseph Chen printf("Memory error!\n"); 33923ba6841SJoseph Chen exit(EXIT_FAILURE); 34023ba6841SJoseph Chen } 34123ba6841SJoseph Chen /* file in */ 34223ba6841SJoseph Chen fi = fopen(file_in, "rb"); 34323ba6841SJoseph Chen if (!fi) { 34423ba6841SJoseph Chen perror(file_in); 34523ba6841SJoseph Chen exit(EXIT_FAILURE); 34623ba6841SJoseph Chen } 34723ba6841SJoseph Chen 34823ba6841SJoseph Chen if (!fread(hdr, sizeof(struct tag_second_loader_hdr), 1, fi)) 34923ba6841SJoseph Chen exit(EXIT_FAILURE); 35023ba6841SJoseph Chen 35123ba6841SJoseph Chen if (!(memcmp(RK_UBOOT_MAGIC, hdr->magic, 5)) || 35223ba6841SJoseph Chen !(memcmp(RK_TRUST_MAGIC, hdr->magic, 3))) { 35323ba6841SJoseph Chen printf("The image info:\n"); 35423ba6841SJoseph Chen printf("Rollback index is %d\n", hdr->version); 35523ba6841SJoseph Chen printf("Load Addr is 0x%x\n", hdr->loader_load_addr); 35623ba6841SJoseph Chen } else { 35723ba6841SJoseph Chen printf("Please input the correct file.\n"); 35823ba6841SJoseph Chen } 35923ba6841SJoseph Chen 36023ba6841SJoseph Chen fclose(fi); 36123ba6841SJoseph Chen free(hdr); 36223ba6841SJoseph Chen } 36323ba6841SJoseph Chen free(buf); 36423ba6841SJoseph Chen 36523ba6841SJoseph Chen return 0; 36623ba6841SJoseph Chen } 367