kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

tar.c (3841B)


      1 #include "tar.h"
      2 
      3 #include <stdio.h>
      4 #include <string.h>
      5 
      6 #define TAR_BLOCK 512u
      7 #define TAR_NAME_OFF 0u
      8 #define TAR_MODE_OFF 100u
      9 #define TAR_UID_OFF 108u
     10 #define TAR_GID_OFF 116u
     11 #define TAR_SIZE_OFF 124u
     12 #define TAR_MTIME_OFF 136u
     13 #define TAR_CHKSUM_OFF 148u
     14 #define TAR_TYPE_OFF 156u
     15 #define TAR_MAGIC_OFF 257u
     16 #define TAR_CHKSUM_LEN 8u
     17 #define TAR_DEFAULT_MODE 0644u
     18 
     19 /* Round `n` up to the next multiple of TAR_BLOCK. */
     20 static size_t tar_round(size_t n) {
     21   return (n + (TAR_BLOCK - 1u)) & ~(size_t)(TAR_BLOCK - 1u);
     22 }
     23 
     24 static unsigned tar_checksum(const uint8_t* hdr) {
     25   unsigned sum = 0;
     26   size_t i;
     27   for (i = 0; i < TAR_BLOCK; ++i) {
     28     /* The checksum field itself is treated as spaces. */
     29     if (i >= TAR_CHKSUM_OFF && i < TAR_CHKSUM_OFF + TAR_CHKSUM_LEN) {
     30       sum += (unsigned)' ';
     31     } else {
     32       sum += hdr[i];
     33     }
     34   }
     35   return sum;
     36 }
     37 
     38 static int tar_write(KitWriter* out, const void* data, size_t n) {
     39   return kit_writer_write(out, data, n) == KIT_OK ? DIST_OK : DIST_ERR;
     40 }
     41 
     42 int dist_tar_append(KitWriter* out, const char* name, const uint8_t* data,
     43                     size_t size) {
     44   uint8_t hdr[TAR_BLOCK];
     45   size_t namelen = strlen(name);
     46   unsigned sum;
     47   static const uint8_t zero[TAR_BLOCK] = {0};
     48   size_t pad;
     49 
     50   if (namelen >= DIST_PATH_MAX) return DIST_ERR;
     51 
     52   memset(hdr, 0, sizeof hdr);
     53   memcpy(hdr + TAR_NAME_OFF, name, namelen);
     54   snprintf((char*)hdr + TAR_MODE_OFF, 8, "%07o", TAR_DEFAULT_MODE);
     55   snprintf((char*)hdr + TAR_UID_OFF, 8, "%07o", 0u);
     56   snprintf((char*)hdr + TAR_GID_OFF, 8, "%07o", 0u);
     57   snprintf((char*)hdr + TAR_SIZE_OFF, 12, "%011o", (unsigned)size);
     58   snprintf((char*)hdr + TAR_MTIME_OFF, 12, "%011o", 0u);
     59   hdr[TAR_TYPE_OFF] = '0';
     60   memcpy(hdr + TAR_MAGIC_OFF, "ustar", 5); /* trailing NUL already zeroed */
     61   hdr[TAR_MAGIC_OFF + 6] = '0';
     62   hdr[TAR_MAGIC_OFF + 7] = '0';
     63 
     64   sum = tar_checksum(hdr);
     65   /* "%06o\0 " — six octal digits, NUL, then a space. */
     66   snprintf((char*)hdr + TAR_CHKSUM_OFF, 7, "%06o", sum);
     67   hdr[TAR_CHKSUM_OFF + 7] = ' ';
     68 
     69   if (tar_write(out, hdr, TAR_BLOCK) != DIST_OK) return DIST_ERR;
     70   if (size && tar_write(out, data, size) != DIST_OK) return DIST_ERR;
     71   pad = tar_round(size) - size;
     72   if (pad && tar_write(out, zero, pad) != DIST_OK) return DIST_ERR;
     73   return DIST_OK;
     74 }
     75 
     76 int dist_tar_finish(KitWriter* out) {
     77   static const uint8_t zero[TAR_BLOCK] = {0};
     78   if (tar_write(out, zero, TAR_BLOCK) != DIST_OK) return DIST_ERR;
     79   if (tar_write(out, zero, TAR_BLOCK) != DIST_OK) return DIST_ERR;
     80   return DIST_OK;
     81 }
     82 
     83 /* Parse an octal field of `len` bytes (space/NUL terminated). */
     84 static size_t tar_parse_octal(const uint8_t* field, size_t len) {
     85   size_t v = 0;
     86   size_t i;
     87   for (i = 0; i < len; ++i) {
     88     uint8_t c = field[i];
     89     if (c == ' ' || c == '\0') break;
     90     if (c < '0' || c > '7') break;
     91     v = v * 8u + (size_t)(c - '0');
     92   }
     93   return v;
     94 }
     95 
     96 static int tar_block_is_zero(const uint8_t* b) {
     97   size_t i;
     98   for (i = 0; i < TAR_BLOCK; ++i) {
     99     if (b[i]) return 0;
    100   }
    101   return 1;
    102 }
    103 
    104 int dist_tar_iter(const uint8_t* data, size_t len, DistTarEntry* out,
    105                   size_t cap, size_t* count) {
    106   size_t off = 0;
    107   size_t n = 0;
    108   while (off + TAR_BLOCK <= len) {
    109     const uint8_t* hdr = data + off;
    110     size_t size;
    111     if (tar_block_is_zero(hdr)) break; /* end-of-archive marker */
    112     if (tar_checksum(hdr) !=
    113         tar_parse_octal(hdr + TAR_CHKSUM_OFF, TAR_CHKSUM_LEN)) {
    114       return DIST_ERR;
    115     }
    116     if (n >= cap) return DIST_ERR;
    117     size = tar_parse_octal(hdr + TAR_SIZE_OFF, 12);
    118     if (off + TAR_BLOCK + size > len) return DIST_ERR; /* truncated */
    119     memcpy(out[n].name, hdr + TAR_NAME_OFF, DIST_PATH_MAX);
    120     out[n].name[DIST_PATH_MAX] = '\0';
    121     out[n].data = data + off + TAR_BLOCK;
    122     out[n].size = size;
    123     ++n;
    124     off += TAR_BLOCK + tar_round(size);
    125   }
    126   *count = n;
    127   return DIST_OK;
    128 }