blob: ff103ce5273ede71840d10f11dd7ed5f8be76d74 [file] [log] [blame]
Stefan Reinauerdb126f42019-11-18 18:25:45 -08001/*
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 Reinauerdb126f42019-11-18 18:25:45 -080012 */
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
25typedef 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
45static unsigned int checksum(tar_header_t *file)
46{
Stefan Reinauer79533882019-11-22 00:57:29 -080047 size_t i, chk_off = offsetof(tar_header_t, checksum);
Stefan Reinauerdb126f42019-11-18 18:25:45 -080048 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
59int 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
91static 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
97int tar_ls(TFILE *tfile)
98{
99 tar_for_each(tfile, tar_ls_entry, NULL);
100 return 0;
101}
102
Stefan Reinauer79533882019-11-22 00:57:29 -0800103TFILE *tar_find(TFILE *tfile, const char *name, int casesensitive)
Stefan Reinauerdb126f42019-11-18 18:25:45 -0800104{
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 */
144static 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
161static 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
189TFILE *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
281int tar_close(TFILE *tfile)
282{
283 if (tfile->alloc) {
284 free(tfile->address);
285 }
286 free(tfile);
287 return 0;
288}