/* abuild-tar.c - A TAR mangling utility for .APK packages * * Copyright (C) 2009-2014 Timo Teräs * All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 as published * by the Free Software Foundation. See http://www.gnu.org/ for details. */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #ifndef VERSION #define VERSION "" #endif struct tar_header { /* ustar header, Posix 1003.1 */ char name[100]; /* 0-99 */ char mode[8]; /* 100-107 */ char uid[8]; /* 108-115 */ char gid[8]; /* 116-123 */ char size[12]; /* 124-135 */ char mtime[12]; /* 136-147 */ char chksum[8]; /* 148-155 */ char typeflag; /* 156-156 */ char linkname[100]; /* 157-256 */ char magic[8]; /* 257-264 */ char uname[32]; /* 265-296 */ char gname[32]; /* 297-328 */ char devmajor[8]; /* 329-336 */ char devminor[8]; /* 337-344 */ char prefix[155]; /* 345-499 */ char padding[12]; /* 500-511 */ }; #define GET_OCTAL(s) get_octal(s, sizeof(s)) #define PUT_OCTAL(s,v) put_octal(s, sizeof(s), v) static inline int dx(int c) { if (c >= '0' && c <= '9') return c - '0'; if (c >= 'a' && c <= 'f') return c - 'a' + 0xa; if (c >= 'A' && c <= 'F') return c - 'A' + 0xa; return -1; } static size_t get_octal(char *s, size_t l) { size_t val; int ch; val = 0; while (l && s[0] != 0) { ch = dx(s[0]); if (ch < 0 || ch >= 8) break; val *= 8; val += ch; s++; l--; } return val; } static void put_octal(char *s, size_t l, size_t value) { char *ptr = &s[l - 1]; *(ptr--) = '\0'; while (value != 0 && ptr >= s) { *(ptr--) = '0' + (value % 8); value /= 8; } while (ptr >= s) *(ptr--) = '0'; } static void tarhdr_checksum(struct tar_header *hdr) { const unsigned char *src; size_t chksum, i; /* Recalculate checksum */ memset(hdr->chksum, ' ', sizeof(hdr->chksum)); src = (const unsigned char *) hdr; for (i = chksum = 0; i < sizeof(*hdr); i++) chksum += src[i]; put_octal(hdr->chksum, sizeof(hdr->chksum)-1, chksum); } static int usage(FILE *out) { fprintf(out, "abuild-tar " VERSION "\n" "\n" "usage: abuild-tar [--hash[=]] [--cut]\n" "\n" "options:\n" " --hash[=sha1|md5] Read tar archive from stdin, precalculate hash for \n" " regular entries and output tar archive on stdout\n" " --cut Remove the end of file tar record\n" "\n"); return 1; } static ssize_t full_read(int fd, void *buf, size_t count) { ssize_t total, n; char *p = buf; total = 0; do { n = read(fd, p, count); if (n < 0 && errno == EINTR) continue; if (n <= 0) break; p += n; total += n; count -= n; } while (1); if (total == 0 && n < 0) return -errno; return total; } static ssize_t full_write(int fd, const void *buf, size_t count) { ssize_t total, n; const char *p = buf; total = 0; do { n = write(fd, p, count); if (n < 0 && errno == EINTR) continue; if (n <= 0) break; p += n; total += n; count -= n; } while (1); if (total == 0 && n < 0) return -errno; return total; } #if defined(__linux__) static ssize_t full_splice(int from_fd, int to_fd, size_t count) { ssize_t total, n; total = 0; do { n = splice(from_fd, NULL, to_fd, NULL, count, 0); if (n < 0 && errno == EINTR) continue; if (n <= 0) break; count -= n; total += n; } while (1); if (total == 0 && n < 0) return -errno; return total; } #else #define full_splice(from_fd, to_fd, count) -1 #endif #define BUF_INITIALIZER {0} struct buf { char *ptr; size_t size; size_t alloc; }; static void buf_free(struct buf *b) { free(b->ptr); } static int buf_resize(struct buf *b, size_t newsize) { void *ptr; if (b->alloc >= newsize) return 0; ptr = realloc(b->ptr, newsize); if (!ptr) return -ENOMEM; b->ptr = ptr; b->alloc = newsize; return 0; } static int buf_padto(struct buf *b, size_t alignment) { size_t oldsize, newsize; oldsize = b->size; newsize = (oldsize + alignment - 1) & -alignment; if (buf_resize(b, newsize)) return -ENOMEM; b->size = newsize; if (b->ptr) memset(b->ptr + oldsize, 0, newsize - oldsize); return 0; } static int buf_read_fd(struct buf *b, int fd, size_t size) { ssize_t r; r = buf_resize(b, size); if (r) return r; r = full_read(fd, b->ptr, size); if (r == size) { b->size = r; return 0; } b->size = 0; if (r < 0) return r; return -EIO; } static int buf_write_fd(struct buf *b, int fd) { ssize_t r; r = full_write(fd, b->ptr, b->size); if (r == b->size) return 0; if (r < 0) return r; return -ENOSPC; } static int buf_add_ext_header_hexdump(struct buf *b, const char *hdr, const unsigned char *value, int valuelen) { int i, len; /* "%u %s=%s\n" */ len = 1 + 1 + strlen(hdr) + 1 + valuelen*2 + 1; for (i = len; i > 9; i /= 10) len++; if (buf_resize(b, b->size + len)) return -ENOMEM; if (b->ptr == NULL) return 0; b->size += snprintf(&b->ptr[b->size], len, "%u %s=", len, hdr); for (i = 0; i < valuelen; i++) b->size += snprintf(&b->ptr[b->size], 3, "%02x", (int)value[i]); b->ptr[b->size++] = '\n'; return 0; } static int do_it(const EVP_MD *md, int cut) { char checksumhdr[32]; unsigned char digest[EVP_MAX_MD_SIZE]; struct buf data = BUF_INITIALIZER, pax = BUF_INITIALIZER; struct tar_header hdr, paxhdr; size_t name_len, size, aligned_size; int dohash = 0, r, ret = 1; memset(&paxhdr, 0, sizeof(paxhdr)); if (md) snprintf(checksumhdr, sizeof(checksumhdr), "APK-TOOLS.checksum.%s", EVP_MD_name(md)); do { if (full_read(STDIN_FILENO, &hdr, sizeof(hdr)) != sizeof(hdr)) goto err; if (cut && hdr.name[0] == 0) break; size = GET_OCTAL(hdr.size); aligned_size = (size + 511) & ~511; if (hdr.typeflag == 'x') { memcpy(&paxhdr, &hdr, sizeof(hdr)); if (buf_read_fd(&pax, STDIN_FILENO, aligned_size)) goto err; pax.size = size; continue; } dohash = md && (hdr.typeflag == '0' || hdr.typeflag == '7' || hdr.typeflag == '2'); if (dohash) { if (buf_read_fd(&data, STDIN_FILENO, aligned_size)) goto err; if (hdr.typeflag != '2') { EVP_Digest(data.ptr, size, digest, NULL, md, NULL); } else { name_len = strnlen(hdr.linkname, sizeof(hdr.linkname)); EVP_Digest(hdr.linkname, name_len, digest, NULL, md, NULL); } buf_add_ext_header_hexdump(&pax, checksumhdr, digest, EVP_MD_size(md)); PUT_OCTAL(paxhdr.size, pax.size); tarhdr_checksum(&paxhdr); } if (pax.size) { /* write pax header + content */ if (full_write(STDOUT_FILENO, &paxhdr, sizeof(paxhdr)) != sizeof(paxhdr) || buf_padto(&pax, 512) || buf_write_fd(&pax, STDOUT_FILENO)) goto err; } if (full_write(STDOUT_FILENO, &hdr, sizeof(hdr)) != sizeof(hdr)) goto err; if (dohash) { if (buf_write_fd(&data, STDOUT_FILENO)) goto err; } else if (aligned_size != 0) { r = full_splice(STDIN_FILENO, STDOUT_FILENO, aligned_size); if (r == -1) { while (aligned_size > 0) { if (full_read(STDIN_FILENO, &hdr, sizeof(hdr)) != sizeof(hdr)) goto err; if (full_write(STDOUT_FILENO, &hdr, sizeof(hdr)) != sizeof(hdr)) goto err; aligned_size -= sizeof(hdr); } } else if (r != aligned_size) goto err; } memset(&paxhdr, 0, sizeof(paxhdr)); pax.size = 0; } while (1); ret = 0; err: buf_free(&data); buf_free(&pax); return ret; } int main(int argc, char **argv) { static int cut = 0, help = 0; static const struct option options[] = { { "hash", optional_argument }, { "help", no_argument, &help, 1 }, { "cut", no_argument, &cut, 1 }, { NULL } }; const EVP_MD *md = NULL; char *digest = NULL; int ndx; OpenSSL_add_all_algorithms(); #if defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR < 3 ENGINE_load_builtin_engines(); ENGINE_register_all_complete(); #endif int c; while ((c = getopt_long(argc, argv, "", options, &ndx)) != -1) { if (c == '?') return usage(stderr); if (ndx == 0) digest = optarg ? optarg : "sha1"; } if (help) return usage(stdout) && 0; if (digest == NULL && cut == 0) return usage(stderr); if (isatty(STDIN_FILENO)) return usage(stderr); if (digest != NULL) { md = EVP_get_digestbyname(digest); if (md == NULL) return usage(stderr); } return do_it(md, cut); }