blob: ff103ce5273ede71840d10f11dd7ed5f8be76d74 [file] [log] [blame]
/*
* Copyright 2019 Google LLC
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#define _GNU_SOURCE
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "em100.h"
#include "xz.h"
#define ROUND_UP(n, inc) (n + (inc - n % inc) % inc)
typedef struct {
char name[100];
char mode[8];
char owner[8];
char group[8];
char size[12];
char mtime[12];
char checksum[8];
char type;
char linkname[100];
char magic[6];
char version[2];
char uname[32];
char gname[32];
char devmajor[8];
char devminor[8];
char prefix[155];
char padding[12];
} __packed tar_header_t;
static unsigned int checksum(tar_header_t *file)
{
size_t i, chk_off = offsetof(tar_header_t, checksum);
unsigned char *raw = (unsigned char *)file;
unsigned int chksum = 256;
for (i = 0; i < sizeof(tar_header_t); i++) {
if (i >= chk_off && i < chk_off + 8)
continue;
chksum += raw[i];
}
return chksum;
}
int tar_for_each(TFILE *tfile, int (*run)(char *, TFILE *, void *, int), void *data)
{
size_t i = 0;
while (i < tfile->length) {
tar_header_t *f = (tar_header_t *)(tfile->address + i);
/* null header at end of tar */
if (f->name[0] == 0)
break;
unsigned int size = strtol(f->size, NULL, 8);
unsigned int cksum = strtol(f->checksum, NULL, 8);
unsigned int ok = (checksum(f) == cksum);
if (f->type == '0') {
TFILE s;
s.address = tfile->address + i + sizeof(tar_header_t);
s.length = size;
s.alloc = 0;
if ((*run)(f->name, &s, data, ok))
break;
}
if (!ok)
break;
i += sizeof(tar_header_t) + ROUND_UP(size, 512);
}
return 0;
}
static int tar_ls_entry(char *name, TFILE *file __unused, void *data __unused, int ok)
{
printf("%s %s\n", name, ok?"✔":"✘");
return 0;
}
int tar_ls(TFILE *tfile)
{
tar_for_each(tfile, tar_ls_entry, NULL);
return 0;
}
TFILE *tar_find(TFILE *tfile, const char *name, int casesensitive)
{
size_t i = 0;
TFILE *ret;
while (i < tfile->length) {
tar_header_t *f = (tar_header_t *)(tfile->address + i);
if (f->name[0] == 0) /* null header at end of tar */
break;
unsigned int size = strtol(f->size, NULL, 8);
unsigned int cksum = strtol(f->checksum, NULL, 8);
unsigned int ok = (checksum(f) == cksum);
if (!ok)
break;
int compare = casesensitive ? strncmp(name, f->name, 100) :
strncasecmp(name, f->name, 100);
if (!compare && f->type == '0') {
ret = (TFILE *)malloc(sizeof(TFILE));
if (!ret) {
perror("Out of memory.\n");
return NULL;
}
ret->address = tfile->address + i + sizeof(tar_header_t);
ret->length = size;
ret->alloc = 0;
return ret;
}
i += sizeof(tar_header_t) + ROUND_UP(size, 512);
}
return NULL;
}
/* Finding the uncompressed size of an archive should really be part
* of the xz API.
*/
static uint32_t decode_vli(unsigned char **streamptr)
{
unsigned char *stream = *streamptr;
uint32_t val = 0;
int pos = 0;
val = *stream & 0x7f;
do {
pos += 7;
stream++;
val |= ((uint32_t)*stream & 0x7f) << pos;
} while (stream[0] & 0x80);
*streamptr = stream+1;
return val;
}
static uint32_t uncompressed_size(unsigned char *stream, size_t length)
{
if (stream[length-2] != 0x59 || stream[length-1] != 0x5a) {
printf("Bad stream footer.\n");
return 0;
}
unsigned char *bytes = stream + length - 8;
uint32_t backward_size =
bytes[0] | (bytes[1]<<8) | (bytes[2]<<16) | (bytes[3]<<24);
backward_size = (backward_size + 1) << 2;
bytes = stream + length - 12 /* stream footer */ - backward_size;
if (bytes[0] != 0x00) {
printf("Bad index indicator.\n");
return 0;
}
if (bytes[1] != 0x01) {
printf("More than one index. I'm confused.\n");
return 0;
}
bytes += 2;
/* skip unpadded size */
decode_vli(&bytes);
return decode_vli(&bytes);
return 0;
}
TFILE *tar_load_compressed(char *filename)
{
FILE *f;
long cfsize, fsize;
unsigned char *cfw, *fw;
/* Load our file into memory */
f = fopen(filename, "rb");
if (!f) {
perror(filename);
return NULL;
}
fseek(f, 0, SEEK_END);
cfsize = ftell(f);
if (cfsize < 0) {
perror(filename);
fclose(f);
return NULL;
}
fseek(f, 0, SEEK_SET);
cfw = malloc(cfsize);
if (!cfw) {
printf("Out of memory.\n");
fclose(f);
return NULL;
}
if (fread(cfw, cfsize, 1, f) != 1) {
perror(filename);
fclose(f);
free(cfw);
return NULL;
}
fclose(f);
fsize = uncompressed_size(cfw, cfsize);
fw = malloc(fsize);
if (!fw) {
printf("Out of memory.\n");
free(cfw);
return NULL;
}
/* Decompress xz */
struct xz_buf b;
struct xz_dec *s;
enum xz_ret ret;
xz_crc32_init();
#ifdef XZ_USE_CRC64
xz_crc64_init();
#endif
s = xz_dec_init(XZ_SINGLE, 0);
if (s == NULL) {
printf("Decompression init failed.\n");
free(cfw);
free(fw);
return NULL;
}
b.in = cfw;
b.in_pos = 0;
b.in_size = cfsize;
b.out = fw;
b.out_pos = 0;
b.out_size = fsize;
ret = xz_dec_run(s, &b);
if (ret != XZ_STREAM_END) {
printf("Decompression failed.\n");
free(cfw);
free(fw);
return NULL;
}
free(cfw);
/* Prepare answer */
TFILE *tfile = malloc(sizeof(TFILE));
if (tfile == NULL) {
printf("Out of memory.\n");
free(fw);
return NULL;
}
tfile->address = fw;
tfile->length = fsize;
tfile->alloc = 1;
return tfile;
}
int tar_close(TFILE *tfile)
{
if (tfile->alloc) {
free(tfile->address);
}
free(tfile);
return 0;
}