unos-repository

APK repository for unos
git clone git://git.finwo.net/misc/unos-repository
Log | Files | Refs | README

pax-tar.c (11537B)


      1 /* mk/pax-tar.c - data-tar writer for the UNOS apk driver.
      2  *
      3  * Usage: pax-tar [--no-checksum] <stagedir> <out.tar>
      4  *
      5  * Writes a deterministic tar of <stagedir>: entries sorted by name,
      6  * uid/gid 0, user/group root. Default mode adds a per-file SHA1 in an
      7  * `APK-TOOLS.checksum.SHA1` pax extended-header record ahead of every
      8  * regular file (the apk v2 data-segment layout `abuild-tar` produces);
      9  * `--no-checksum` skips those records (control and signature segments).
     10  *
     11  * Only regular files, directories and symlinks are accepted; anything else
     12  * is a loud error. SHA1 is implemented below from FIPS 180-4 (no crypto
     13  * dependency); its output is verified against the system sha1sum during
     14  * driver testing. File mtimes come from SOURCE_DATE_EPOCH when set, else
     15  * from the filesystem.
     16  */
     17 #define _POSIX_C_SOURCE 200809L
     18 #include <dirent.h>
     19 #include <stdint.h>
     20 #include <stdio.h>
     21 #include <stdlib.h>
     22 #include <string.h>
     23 #include <sys/stat.h>
     24 #include <unistd.h>
     25 
     26 /* ---------------- SHA1 (FIPS 180-4) ---------------- */
     27 
     28 typedef struct {
     29   uint32_t h[5];
     30   uint64_t len;      /* total message length in bytes */
     31   uint8_t buf[64];
     32   size_t buflen;
     33 } sha1_t;
     34 
     35 static uint32_t rol(uint32_t v, int n) { return (v << n) | (v >> (32 - n)); }
     36 
     37 static void sha1_block(sha1_t *c, const uint8_t *p) {
     38   uint32_t w[80];
     39   for (int i = 0; i < 16; i++)
     40     w[i] = ((uint32_t)p[4 * i] << 24) | ((uint32_t)p[4 * i + 1] << 16) |
     41            ((uint32_t)p[4 * i + 2] << 8) | p[4 * i + 3];
     42   for (int i = 16; i < 80; i++)
     43     w[i] = rol(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1);
     44   uint32_t a = c->h[0], b = c->h[1], v = c->h[2], d = c->h[3], e = c->h[4];
     45   for (int i = 0; i < 80; i++) {
     46     uint32_t f, k;
     47     if (i < 20) {
     48       f = (b & v) | (~b & d);
     49       k = 0x5a827999;
     50     } else if (i < 40) {
     51       f = b ^ v ^ d;
     52       k = 0x6ed9eba1;
     53     } else if (i < 60) {
     54       f = (b & v) | (b & d) | (v & d);
     55       k = 0x8f1bbcdc;
     56     } else {
     57       f = b ^ v ^ d;
     58       k = 0xca62c1d6;
     59     }
     60     uint32_t t = rol(a, 5) + f + e + k + w[i];
     61     e = d;
     62     d = v;
     63     v = rol(b, 30);
     64     b = a;
     65     a = t;
     66   }
     67   c->h[0] += a;
     68   c->h[1] += b;
     69   c->h[2] += v;
     70   c->h[3] += d;
     71   c->h[4] += e;
     72 }
     73 
     74 static void sha1_init(sha1_t *c) {
     75   c->h[0] = 0x67452301;
     76   c->h[1] = 0xefcdab89;
     77   c->h[2] = 0x98badcfe;
     78   c->h[3] = 0x10325476;
     79   c->h[4] = 0xc3d2e1f0;
     80   c->len = 0;
     81   c->buflen = 0;
     82 }
     83 
     84 static void sha1_update(sha1_t *c, const uint8_t *data, size_t n) {
     85   c->len += n;
     86   while (n > 0) {
     87     size_t take = 64 - c->buflen;
     88     if (take > n)
     89       take = n;
     90     memcpy(c->buf + c->buflen, data, take);
     91     c->buflen += take;
     92     data += take;
     93     n -= take;
     94     if (c->buflen == 64) {
     95       sha1_block(c, c->buf);
     96       c->buflen = 0;
     97     }
     98   }
     99 }
    100 
    101 static void sha1_final(sha1_t *c, uint8_t out[20]) {
    102   uint64_t bits = c->len * 8;
    103   uint8_t one = 0x80;
    104   sha1_update(c, &one, 1);
    105   uint8_t zero = 0;
    106   while (c->buflen != 56)
    107     sha1_update(c, &zero, 1);
    108   uint8_t lenbuf[8];
    109   for (int i = 0; i < 8; i++)
    110     lenbuf[i] = (uint8_t)(bits >> (56 - 8 * i));
    111   /* feed length directly: buffer is at 56, one block completes it */
    112   memcpy(c->buf + 56, lenbuf, 8);
    113   sha1_block(c, c->buf);
    114   for (int i = 0; i < 5; i++) {
    115     out[4 * i] = (uint8_t)(c->h[i] >> 24);
    116     out[4 * i + 1] = (uint8_t)(c->h[i] >> 16);
    117     out[4 * i + 2] = (uint8_t)(c->h[i] >> 8);
    118     out[4 * i + 3] = (uint8_t)c->h[i];
    119   }
    120 }
    121 
    122 static void sha1_buf_hex(const uint8_t *data, size_t n, char out[41]) {
    123   sha1_t c;
    124   sha1_init(&c);
    125   sha1_update(&c, data, n);
    126   uint8_t digest[20];
    127   sha1_final(&c, digest);
    128   for (int i = 0; i < 20; i++)
    129     sprintf(out + 2 * i, "%02x", digest[i]);
    130   out[40] = '\0';
    131 }
    132 
    133 static void sha1_file_hex(const char *path, char out[41]) {
    134   FILE *f = fopen(path, "rb");
    135   if (!f) {
    136     perror(path);
    137     exit(1);
    138   }
    139   sha1_t c;
    140   sha1_init(&c);
    141   uint8_t buf[65536];
    142   size_t n;
    143   while ((n = fread(buf, 1, sizeof buf, f)) > 0)
    144     sha1_update(&c, buf, n);
    145   if (ferror(f)) {
    146     perror(path);
    147     exit(1);
    148   }
    149   fclose(f);
    150   uint8_t digest[20];
    151   sha1_final(&c, digest);
    152   for (int i = 0; i < 20; i++)
    153     sprintf(out + 2 * i, "%02x", digest[i]);
    154   out[40] = '\0';
    155 }
    156 
    157 /* ---------------- tar writer ---------------- */
    158 
    159 typedef struct {
    160   char *arc;   /* archive name, no leading ./ */
    161   char *full;  /* filesystem path */
    162   struct stat st;
    163 } entry_t;
    164 
    165 static entry_t *entries;
    166 static size_t nentries, capentries;
    167 
    168 static void push_entry(const char *arc, const char *full, struct stat *st) {
    169   if (nentries == capentries) {
    170     capentries = capentries ? capentries * 2 : 256;
    171     entries = realloc(entries, capentries * sizeof *entries);
    172     if (!entries) {
    173       perror("realloc");
    174       exit(1);
    175     }
    176   }
    177   entries[nentries].arc = strdup(arc);
    178   entries[nentries].full = strdup(full);
    179   entries[nentries].st = *st;
    180   nentries++;
    181 }
    182 
    183 static void walk(const char *full, const char *arc) {
    184   DIR *d = opendir(full);
    185   if (!d) {
    186     perror(full);
    187     exit(1);
    188   }
    189   struct dirent *de;
    190   while ((de = readdir(d))) {
    191     if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, ".."))
    192       continue;
    193     char sub_full[8192], sub_arc[8192];
    194     snprintf(sub_full, sizeof sub_full, "%s/%s", full, de->d_name);
    195     if (arc[0])
    196       snprintf(sub_arc, sizeof sub_arc, "%s/%s", arc, de->d_name);
    197     else
    198       snprintf(sub_arc, sizeof sub_arc, "%s", de->d_name);
    199     struct stat st;
    200     if (lstat(sub_full, &st) != 0) {
    201       perror(sub_full);
    202       exit(1);
    203     }
    204     if (S_ISDIR(st.st_mode))
    205       walk(sub_full, sub_arc);
    206     push_entry(sub_arc, sub_full, &st);
    207   }
    208   closedir(d);
    209 }
    210 
    211 static int cmp_entry(const void *a, const void *b) {
    212   return strcmp(((const entry_t *)a)->arc, ((const entry_t *)b)->arc);
    213 }
    214 
    215 static void put_oct(char *dst, size_t w, uint64_t v) {
    216   /* w includes the trailing NUL; value left-padded with '0' */
    217   dst[w - 1] = '\0';
    218   for (size_t i = w - 1; i > 0;) {
    219     i--;
    220     dst[i] = (char)('0' + (v & 7));
    221     v >>= 3;
    222   }
    223 }
    224 
    225 static void write_zeros(FILE *out, size_t n) {
    226   static const char z[512];
    227   while (n > 0) {
    228     size_t take = n > sizeof z ? sizeof z : n;
    229     if (fwrite(z, 1, take, out) != take) {
    230       perror("fwrite");
    231       exit(1);
    232     }
    233     n -= take;
    234   }
    235 }
    236 
    237 /* Split name into ustar name/prefix at a '/' boundary. Dies if impossible. */
    238 static void split_name(const char *arc, char *name, char *prefix) {
    239   if (strlen(arc) <= 100) {
    240     strcpy(name, arc);
    241     prefix[0] = '\0';
    242     return;
    243   }
    244   size_t len = strlen(arc);
    245   /* latest '/' that leaves <=155 prefix bytes and <=100 name bytes */
    246   size_t cut = 0;
    247   for (size_t i = 0; i < len; i++) {
    248     if (arc[i] == '/' && i <= 155 && len - (i + 1) <= 100)
    249       cut = i;
    250   }
    251   if (!cut) {
    252     fprintf(stderr, "pax-tar: name too long: %s\n", arc);
    253     exit(1);
    254   }
    255   memcpy(prefix, arc, cut);
    256   prefix[cut] = '\0';
    257   strcpy(name, arc + cut + 1);
    258 }
    259 
    260 static void write_header(FILE *out, const char *arc, mode_t mode,
    261                          uint64_t size, uint64_t mtime, char type,
    262                          const char *linkname) {
    263   char blk[512];
    264   char name[100], prefix[155];
    265   memset(blk, 0, sizeof blk);
    266   split_name(arc, name, prefix);
    267   memcpy(blk + 0, name, strlen(name));
    268   put_oct(blk + 100, 8, (uint64_t)mode & 07777);
    269   put_oct(blk + 108, 8, 0);
    270   put_oct(blk + 116, 8, 0);
    271   put_oct(blk + 124, 12, size);
    272   put_oct(blk + 136, 12, mtime);
    273   memset(blk + 148, ' ', 8);
    274   blk[156] = type;
    275   if (linkname)
    276     memcpy(blk + 157, linkname, strlen(linkname));
    277   memcpy(blk + 257, "ustar", 5);
    278   blk[262] = '\0';
    279   memcpy(blk + 263, "00", 2);
    280   memcpy(blk + 265, "root", 4);
    281   memcpy(blk + 297, "root", 4);
    282   memcpy(blk + 345, prefix, strlen(prefix));
    283   unsigned sum = 0;
    284   for (int i = 0; i < 512; i++)
    285     sum += (unsigned char)blk[i];
    286   char chk[8];
    287   snprintf(chk, sizeof chk, "%06o", sum);
    288   memcpy(blk + 148, chk, 6);
    289   blk[154] = '\0';
    290   blk[155] = ' ';
    291   if (fwrite(blk, 1, sizeof blk, out) != sizeof blk) {
    292     perror("fwrite");
    293     exit(1);
    294   }
    295 }
    296 
    297 /* pax extended-header record: "<len> <keyword>=<value>\n" */
    298 static void write_pax_record(FILE *out, const char *keyword,
    299                              const char *value, uint64_t mtime) {
    300   size_t kv = strlen(keyword) + 1 + strlen(value) + 1; /* "k=v\n" */
    301   size_t len = kv + 2; /* first guess for digits + space */
    302   char digits[32];
    303   for (;;) {
    304     snprintf(digits, sizeof digits, "%zu", len);
    305     size_t need = kv + strlen(digits) + 1;
    306     if (need == len)
    307       break;
    308     len = need;
    309   }
    310   char *rec = malloc(len + 1);
    311   snprintf(rec, len + 1, "%s %s=%s\n", digits, keyword, value);
    312   /* extended-header header block uses typeflag 'x'; name content-free */
    313   write_header(out, "pax-header", 0, len, mtime, 'x', NULL);
    314   if (fwrite(rec, 1, len, out) != len) {
    315     perror("fwrite");
    316     exit(1);
    317   }
    318   free(rec);
    319   write_zeros(out, (512 - (len % 512)) % 512);
    320 }
    321 
    322 static uint64_t now_or_epoch(void) {
    323   const char *e = getenv("SOURCE_DATE_EPOCH");
    324   if (e && e[0])
    325     return (uint64_t)atoll(e);
    326   return 0; /* 0 = keep each file's own mtime */
    327 }
    328 
    329 int main(int argc, char **argv) {
    330   int checksums = 1;
    331   if (argc == 4 && !strcmp(argv[1], "--no-checksum")) {
    332     checksums = 0;
    333     argv++;
    334     argc--;
    335   }
    336   if (argc != 3) {
    337     fprintf(stderr, "usage: pax-tar [--no-checksum] <stagedir> <out.tar>\n");
    338     return 1;
    339   }
    340   uint64_t epoch = now_or_epoch();
    341   walk(argv[1], "");
    342   qsort(entries, nentries, sizeof *entries, cmp_entry);
    343 
    344   FILE *out = fopen(argv[2], "wb");
    345   if (!out) {
    346     perror(argv[2]);
    347     return 1;
    348   }
    349   for (size_t i = 0; i < nentries; i++) {
    350     entry_t *e = &entries[i];
    351     uint64_t mtime =
    352         epoch ? epoch : (uint64_t)e->st.st_mtime;
    353     if (S_ISDIR(e->st.st_mode)) {
    354       char *slash = malloc(strlen(e->arc) + 2);
    355       sprintf(slash, "%s/", e->arc);
    356       write_header(out, slash, e->st.st_mode, 0, mtime, '5', NULL);
    357       free(slash);
    358     } else if (S_ISREG(e->st.st_mode)) {
    359       char hex[41];
    360       uint64_t fsize = (uint64_t)e->st.st_size;
    361       if (checksums) {
    362         sha1_file_hex(e->full, hex);
    363         write_pax_record(out, "APK-TOOLS.checksum.SHA1", hex, mtime);
    364       }
    365       write_header(out, e->arc, e->st.st_mode, fsize,
    366                    mtime, '0', NULL);
    367       FILE *f = fopen(e->full, "rb");
    368       if (!f) {
    369         perror(e->full);
    370         return 1;
    371       }
    372       char buf[65536];
    373       size_t n;
    374       uint64_t left = fsize;
    375       while (left > 0 &&
    376              (n = fread(buf, 1, left > sizeof buf ? sizeof buf : left, f)) >
    377                  0) {
    378         if (fwrite(buf, 1, n, out) != n) {
    379           perror("fwrite");
    380           return 1;
    381         }
    382         left -= n;
    383       }
    384       fclose(f);
    385       if (left != 0) {
    386         fprintf(stderr, "pax-tar: short read: %s\n", e->full);
    387         return 1;
    388       }
    389       write_zeros(out, (512 - (fsize % 512)) % 512);
    390     } else if (S_ISLNK(e->st.st_mode)) {
    391       char target[1024];
    392       ssize_t n = readlink(e->full, target, sizeof target - 1);
    393       if (n < 0) {
    394         perror(e->full);
    395         return 1;
    396       }
    397       target[n] = '\0';
    398       if (checksums) {
    399         /* abuild-tar hashes the link target string for symlinks */
    400         char hex[41];
    401         sha1_buf_hex((uint8_t *)target, (size_t)n, hex);
    402         write_pax_record(out, "APK-TOOLS.checksum.SHA1", hex, mtime);
    403       }
    404       write_header(out, e->arc, e->st.st_mode, 0, mtime, '2', target);
    405     } else {
    406       fprintf(stderr, "pax-tar: unsupported file type: %s\n", e->full);
    407       return 1;
    408     }
    409   }
    410   write_zeros(out, 1024); /* end-of-tar */
    411   if (fclose(out) != 0) {
    412     perror("fclose");
    413     return 1;
    414   }
    415   return 0;
    416 }