Stefan Reinauer | db126f4 | 2019-11-18 18:25:45 -0800 | [diff] [blame] | 1 | /* |
| 2 | * Copyright 2019 Google LLC |
| 3 | * |
| 4 | * This program is free software; you can redistribute it and/or modify |
| 5 | * it under the terms of the GNU General Public License as published by |
| 6 | * the Free Software Foundation; version 2 of the License. |
| 7 | * |
| 8 | * This program is distributed in the hope that it will be useful, |
| 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 11 | * GNU General Public License for more details. |
Stefan Reinauer | db126f4 | 2019-11-18 18:25:45 -0800 | [diff] [blame] | 12 | */ |
| 13 | |
| 14 | #define _GNU_SOURCE |
| 15 | #include <stddef.h> |
| 16 | #include <stdlib.h> |
| 17 | #include <stdio.h> |
| 18 | #include <string.h> |
| 19 | |
| 20 | #include "em100.h" |
| 21 | #include "xz.h" |
| 22 | |
| 23 | #define ROUND_UP(n, inc) (n + (inc - n % inc) % inc) |
| 24 | |
| 25 | typedef struct { |
| 26 | char name[100]; |
| 27 | char mode[8]; |
| 28 | char owner[8]; |
| 29 | char group[8]; |
| 30 | char size[12]; |
| 31 | char mtime[12]; |
| 32 | char checksum[8]; |
| 33 | char type; |
| 34 | char linkname[100]; |
| 35 | char magic[6]; |
| 36 | char version[2]; |
| 37 | char uname[32]; |
| 38 | char gname[32]; |
| 39 | char devmajor[8]; |
| 40 | char devminor[8]; |
| 41 | char prefix[155]; |
| 42 | char padding[12]; |
| 43 | } __packed tar_header_t; |
| 44 | |
| 45 | static unsigned int checksum(tar_header_t *file) |
| 46 | { |
Stefan Reinauer | 7953388 | 2019-11-22 00:57:29 -0800 | [diff] [blame] | 47 | size_t i, chk_off = offsetof(tar_header_t, checksum); |
Stefan Reinauer | db126f4 | 2019-11-18 18:25:45 -0800 | [diff] [blame] | 48 | unsigned char *raw = (unsigned char *)file; |
| 49 | unsigned int chksum = 256; |
| 50 | |
| 51 | for (i = 0; i < sizeof(tar_header_t); i++) { |
| 52 | if (i >= chk_off && i < chk_off + 8) |
| 53 | continue; |
| 54 | chksum += raw[i]; |
| 55 | } |
| 56 | return chksum; |
| 57 | } |
| 58 | |
| 59 | int tar_for_each(TFILE *tfile, int (*run)(char *, TFILE *, void *, int), void *data) |
| 60 | { |
| 61 | size_t i = 0; |
| 62 | |
| 63 | while (i < tfile->length) { |
| 64 | tar_header_t *f = (tar_header_t *)(tfile->address + i); |
| 65 | /* null header at end of tar */ |
| 66 | if (f->name[0] == 0) |
| 67 | break; |
| 68 | |
| 69 | unsigned int size = strtol(f->size, NULL, 8); |
| 70 | unsigned int cksum = strtol(f->checksum, NULL, 8); |
| 71 | unsigned int ok = (checksum(f) == cksum); |
| 72 | |
| 73 | if (f->type == '0') { |
| 74 | TFILE s; |
| 75 | s.address = tfile->address + i + sizeof(tar_header_t); |
| 76 | s.length = size; |
| 77 | s.alloc = 0; |
| 78 | |
| 79 | if ((*run)(f->name, &s, data, ok)) |
| 80 | break; |
| 81 | } |
| 82 | |
| 83 | if (!ok) |
| 84 | break; |
| 85 | i += sizeof(tar_header_t) + ROUND_UP(size, 512); |
| 86 | } |
| 87 | |
| 88 | return 0; |
| 89 | } |
| 90 | |
| 91 | static int tar_ls_entry(char *name, TFILE *file __unused, void *data __unused, int ok) |
| 92 | { |
| 93 | printf("%s %s\n", name, ok?"✔":"✘"); |
| 94 | return 0; |
| 95 | } |
| 96 | |
| 97 | int tar_ls(TFILE *tfile) |
| 98 | { |
| 99 | tar_for_each(tfile, tar_ls_entry, NULL); |
| 100 | return 0; |
| 101 | } |
| 102 | |
Stefan Reinauer | 7953388 | 2019-11-22 00:57:29 -0800 | [diff] [blame] | 103 | TFILE *tar_find(TFILE *tfile, const char *name, int casesensitive) |
Stefan Reinauer | db126f4 | 2019-11-18 18:25:45 -0800 | [diff] [blame] | 104 | { |
| 105 | size_t i = 0; |
| 106 | TFILE *ret; |
| 107 | |
| 108 | while (i < tfile->length) { |
| 109 | tar_header_t *f = (tar_header_t *)(tfile->address + i); |
| 110 | if (f->name[0] == 0) /* null header at end of tar */ |
| 111 | break; |
| 112 | |
| 113 | unsigned int size = strtol(f->size, NULL, 8); |
| 114 | unsigned int cksum = strtol(f->checksum, NULL, 8); |
| 115 | unsigned int ok = (checksum(f) == cksum); |
| 116 | |
| 117 | if (!ok) |
| 118 | break; |
| 119 | |
| 120 | int compare = casesensitive ? strncmp(name, f->name, 100) : |
| 121 | strncasecmp(name, f->name, 100); |
| 122 | if (!compare && f->type == '0') { |
| 123 | ret = (TFILE *)malloc(sizeof(TFILE)); |
| 124 | if (!ret) { |
| 125 | perror("Out of memory.\n"); |
| 126 | return NULL; |
| 127 | } |
| 128 | |
| 129 | ret->address = tfile->address + i + sizeof(tar_header_t); |
| 130 | ret->length = size; |
| 131 | ret->alloc = 0; |
| 132 | |
| 133 | return ret; |
| 134 | } |
| 135 | i += sizeof(tar_header_t) + ROUND_UP(size, 512); |
| 136 | } |
| 137 | |
| 138 | return NULL; |
| 139 | } |
| 140 | |
| 141 | /* Finding the uncompressed size of an archive should really be part |
| 142 | * of the xz API. |
| 143 | */ |
| 144 | static uint32_t decode_vli(unsigned char **streamptr) |
| 145 | { |
| 146 | unsigned char *stream = *streamptr; |
| 147 | uint32_t val = 0; |
| 148 | int pos = 0; |
| 149 | |
| 150 | val = *stream & 0x7f; |
| 151 | do { |
| 152 | pos += 7; |
| 153 | stream++; |
| 154 | val |= ((uint32_t)*stream & 0x7f) << pos; |
| 155 | } while (stream[0] & 0x80); |
| 156 | *streamptr = stream+1; |
| 157 | |
| 158 | return val; |
| 159 | } |
| 160 | |
| 161 | static uint32_t uncompressed_size(unsigned char *stream, size_t length) |
| 162 | { |
| 163 | if (stream[length-2] != 0x59 || stream[length-1] != 0x5a) { |
| 164 | printf("Bad stream footer.\n"); |
| 165 | return 0; |
| 166 | } |
| 167 | unsigned char *bytes = stream + length - 8; |
| 168 | uint32_t backward_size = |
| 169 | bytes[0] | (bytes[1]<<8) | (bytes[2]<<16) | (bytes[3]<<24); |
| 170 | backward_size = (backward_size + 1) << 2; |
| 171 | bytes = stream + length - 12 /* stream footer */ - backward_size; |
| 172 | if (bytes[0] != 0x00) { |
| 173 | printf("Bad index indicator.\n"); |
| 174 | return 0; |
| 175 | } |
| 176 | if (bytes[1] != 0x01) { |
| 177 | printf("More than one index. I'm confused.\n"); |
| 178 | return 0; |
| 179 | } |
| 180 | bytes += 2; |
| 181 | |
| 182 | /* skip unpadded size */ |
| 183 | decode_vli(&bytes); |
| 184 | return decode_vli(&bytes); |
| 185 | |
| 186 | return 0; |
| 187 | } |
| 188 | |
| 189 | TFILE *tar_load_compressed(char *filename) |
| 190 | { |
| 191 | FILE *f; |
| 192 | long cfsize, fsize; |
| 193 | unsigned char *cfw, *fw; |
| 194 | |
| 195 | /* Load our file into memory */ |
| 196 | |
| 197 | f = fopen(filename, "rb"); |
| 198 | if (!f) { |
| 199 | perror(filename); |
| 200 | return NULL; |
| 201 | } |
| 202 | |
| 203 | fseek(f, 0, SEEK_END); |
| 204 | cfsize = ftell(f); |
| 205 | if (cfsize < 0) { |
| 206 | perror(filename); |
| 207 | fclose(f); |
| 208 | return NULL; |
| 209 | } |
| 210 | fseek(f, 0, SEEK_SET); |
| 211 | |
| 212 | cfw = malloc(cfsize); |
| 213 | if (!cfw) { |
| 214 | printf("Out of memory.\n"); |
| 215 | fclose(f); |
| 216 | return NULL; |
| 217 | } |
| 218 | if (fread(cfw, cfsize, 1, f) != 1) { |
| 219 | perror(filename); |
| 220 | fclose(f); |
| 221 | free(cfw); |
| 222 | return NULL; |
| 223 | } |
| 224 | fclose(f); |
| 225 | |
| 226 | fsize = uncompressed_size(cfw, cfsize); |
| 227 | fw = malloc(fsize); |
| 228 | if (!fw) { |
| 229 | printf("Out of memory.\n"); |
| 230 | free(cfw); |
| 231 | return NULL; |
| 232 | } |
| 233 | |
| 234 | /* Decompress xz */ |
| 235 | struct xz_buf b; |
| 236 | struct xz_dec *s; |
| 237 | enum xz_ret ret; |
| 238 | |
| 239 | xz_crc32_init(); |
| 240 | #ifdef XZ_USE_CRC64 |
| 241 | xz_crc64_init(); |
| 242 | #endif |
| 243 | s = xz_dec_init(XZ_SINGLE, 0); |
| 244 | if (s == NULL) { |
| 245 | printf("Decompression init failed.\n"); |
| 246 | free(cfw); |
| 247 | free(fw); |
| 248 | return NULL; |
| 249 | } |
| 250 | |
| 251 | b.in = cfw; |
| 252 | b.in_pos = 0; |
| 253 | b.in_size = cfsize; |
| 254 | b.out = fw; |
| 255 | b.out_pos = 0; |
| 256 | b.out_size = fsize; |
| 257 | |
| 258 | ret = xz_dec_run(s, &b); |
| 259 | if (ret != XZ_STREAM_END) { |
| 260 | printf("Decompression failed.\n"); |
| 261 | free(cfw); |
| 262 | free(fw); |
| 263 | return NULL; |
| 264 | } |
| 265 | free(cfw); |
| 266 | |
| 267 | /* Prepare answer */ |
| 268 | TFILE *tfile = malloc(sizeof(TFILE)); |
| 269 | if (tfile == NULL) { |
| 270 | printf("Out of memory.\n"); |
| 271 | free(fw); |
| 272 | return NULL; |
| 273 | } |
| 274 | tfile->address = fw; |
| 275 | tfile->length = fsize; |
| 276 | tfile->alloc = 1; |
| 277 | |
| 278 | return tfile; |
| 279 | } |
| 280 | |
| 281 | int tar_close(TFILE *tfile) |
| 282 | { |
| 283 | if (tfile->alloc) { |
| 284 | free(tfile->address); |
| 285 | } |
| 286 | free(tfile); |
| 287 | return 0; |
| 288 | } |