a5b9fc937c6c54a8155dd8d28772ecb5ba5dd303
   1#include <time.h>
   2#include "cache.h"
   3
   4#define RECORDSIZE      (512)
   5#define BLOCKSIZE       (RECORDSIZE * 20)
   6
   7#define TYPEFLAG_AUTO           '\0'
   8#define TYPEFLAG_REG            '0'
   9#define TYPEFLAG_DIR            '5'
  10#define TYPEFLAG_GLOBAL_HEADER  'g'
  11#define TYPEFLAG_EXT_HEADER     'x'
  12
  13static const char *tar_tree_usage = "tar-tree <key> [basedir]";
  14
  15static char block[BLOCKSIZE];
  16static unsigned long offset;
  17
  18static const char *basedir;
  19static time_t archive_time;
  20
  21struct path_prefix {
  22        struct path_prefix *prev;
  23        const char *name;
  24};
  25
  26/* tries hard to write, either succeeds or dies in the attempt */
  27static void reliable_write(void *buf, unsigned long size)
  28{
  29        while (size > 0) {
  30                long ret = write(1, buf, size);
  31                if (ret < 0) {
  32                        if (errno == EAGAIN)
  33                                continue;
  34                        if (errno == EPIPE)
  35                                exit(0);
  36                        die("tar-tree: %s", strerror(errno));
  37                } else if (!ret) {
  38                        die("tar-tree: disk full?");
  39                }
  40                size -= ret;
  41                buf += ret;
  42        }
  43}
  44
  45/* writes out the whole block, but only if it is full */
  46static void write_if_needed(void)
  47{
  48        if (offset == BLOCKSIZE) {
  49                reliable_write(block, BLOCKSIZE);
  50                offset = 0;
  51        }
  52}
  53
  54/* acquire the next record from the buffer; user must call write_if_needed() */
  55static char *get_record(void)
  56{
  57        char *p = block + offset;
  58        memset(p, 0, RECORDSIZE);
  59        offset += RECORDSIZE;
  60        return p;
  61}
  62
  63/*
  64 * The end of tar archives is marked by 1024 nul bytes and after that
  65 * follows the rest of the block (if any).
  66 */
  67static void write_trailer(void)
  68{
  69        memset(block + offset, 0, RECORDSIZE);
  70        offset += RECORDSIZE;
  71        write_if_needed();
  72        memset(block + offset, 0, RECORDSIZE);
  73        offset += RECORDSIZE;
  74        write_if_needed();
  75        if (offset) {
  76                memset(block + offset, 0, BLOCKSIZE - offset);
  77                reliable_write(block, BLOCKSIZE);
  78                offset = 0;
  79        }
  80}
  81
  82/*
  83 * queues up writes, so that all our write(2) calls write exactly one
  84 * full block; pads writes to RECORDSIZE
  85 */
  86static void write_blocked(void *buf, unsigned long size)
  87{
  88        unsigned long tail;
  89
  90        if (offset) {
  91                unsigned long chunk = BLOCKSIZE - offset;
  92                if (size < chunk)
  93                        chunk = size;
  94                memcpy(block + offset, buf, chunk);
  95                size -= chunk;
  96                offset += chunk;
  97                buf += chunk;
  98                write_if_needed();
  99        }
 100        while (size >= BLOCKSIZE) {
 101                reliable_write(buf, BLOCKSIZE);
 102                size -= BLOCKSIZE;
 103                buf += BLOCKSIZE;
 104        }
 105        if (size) {
 106                memcpy(block + offset, buf, size);
 107                buf += size;
 108                offset += size;
 109        }
 110        tail = offset % RECORDSIZE;
 111        if (tail)  {
 112                memset(block + offset, 0, RECORDSIZE - tail);
 113                offset += RECORDSIZE - tail;
 114        }
 115        write_if_needed();
 116}
 117
 118static void append_string(char **p, const char *s)
 119{
 120        unsigned int len = strlen(s);
 121        memcpy(*p, s, len);
 122        *p += len;
 123}
 124
 125static void append_char(char **p, char c)
 126{
 127        **p = c;
 128        *p += 1;
 129}
 130
 131static void append_long(char **p, long n)
 132{
 133        int len = sprintf(*p, "%ld", n);
 134        *p += len;
 135}
 136
 137static void append_path_prefix(char **buffer, struct path_prefix *prefix)
 138{
 139        if (!prefix)
 140                return;
 141        append_path_prefix(buffer, prefix->prev);
 142        append_string(buffer, prefix->name);
 143        append_char(buffer, '/');
 144}
 145
 146static unsigned int path_prefix_len(struct path_prefix *prefix)
 147{
 148        if (!prefix)
 149                return 0;
 150        return path_prefix_len(prefix->prev) + strlen(prefix->name) + 1;
 151}
 152
 153static void append_path(char **p, int is_dir, const char *basepath,
 154                        struct path_prefix *prefix, const char *path)
 155{
 156        if (basepath) {
 157                append_string(p, basepath);
 158                append_char(p, '/');
 159        }
 160        append_path_prefix(p, prefix);
 161        append_string(p, path);
 162        if (is_dir)
 163                append_char(p, '/');
 164}
 165
 166static unsigned int path_len(int is_dir, const char *basepath,
 167                             struct path_prefix *prefix, const char *path)
 168{
 169        unsigned int len = 0;
 170        if (basepath)
 171                len += strlen(basepath) + 1;
 172        len += path_prefix_len(prefix) + strlen(path);
 173        if (is_dir)
 174                len++;
 175        return len;
 176}
 177
 178static void write_header(const char *, char, const char *, struct path_prefix *,
 179                         const char *, unsigned int, unsigned long);
 180
 181/* stores a pax extended header directly in the block buffer */
 182static void write_extended_header(const char *headerfilename, int is_dir,
 183                                  const char *basepath,
 184                                  struct path_prefix *prefix,
 185                                  const char *path, unsigned int namelen)
 186{
 187        char *p;
 188        unsigned int size = 1 + 6 + namelen + 1;
 189        if (size > 9)
 190                size++;
 191        if (size > 99)
 192                size++;
 193        if (size > RECORDSIZE)
 194                die("tar-tree: extended header too big, wtf?");
 195        write_header(NULL, TYPEFLAG_EXT_HEADER, NULL, NULL, headerfilename,
 196                     0100600, size);
 197        p = get_record();
 198        append_long(&p, size);
 199        append_string(&p, " path=");
 200        append_path(&p, is_dir, basepath, prefix, path);
 201        append_char(&p, '\n');
 202        write_if_needed();
 203}
 204
 205static void write_global_extended_header(const char *sha1)
 206{
 207        char *p;
 208        write_header(NULL, TYPEFLAG_GLOBAL_HEADER, NULL, NULL,
 209                     "pax_global_header", 0100600, 52);
 210        p = get_record();
 211        append_long(&p, 52);    /* 2 + 9 + 40 + 1 */
 212        append_string(&p, " comment=");
 213        append_string(&p, sha1_to_hex(sha1));
 214        append_char(&p, '\n');
 215        write_if_needed();
 216}
 217
 218/* stores a ustar header directly in the block buffer */
 219static void write_header(const char *sha1, char typeflag, const char *basepath,
 220                         struct path_prefix *prefix, const char *path,
 221                         unsigned int mode, unsigned long size)
 222{
 223        unsigned int namelen; 
 224        char *p, *header = NULL;
 225        unsigned int checksum = 0;
 226        int i;
 227
 228        if (typeflag == TYPEFLAG_AUTO) {
 229                if (S_ISDIR(mode))
 230                        typeflag = TYPEFLAG_DIR;
 231                else
 232                        typeflag = TYPEFLAG_REG;
 233        }
 234
 235        namelen = path_len(S_ISDIR(mode), basepath, prefix, path);
 236        if (namelen > 500) {
 237                die("tar-tree: name too log of object %s\n", sha1_to_hex(sha1));
 238        } else if (namelen > 100) {
 239                char *sha1_hex = sha1_to_hex(sha1);
 240                char headerfilename[51];
 241                sprintf(headerfilename, "%s.paxheader", sha1_hex);
 242                /* the extended header must be written before the normal one */
 243                write_extended_header(headerfilename, S_ISDIR(mode), basepath,
 244                                      prefix, path, namelen);
 245
 246                header = get_record();
 247                sprintf(header, "%s.data", sha1_hex);
 248        } else {
 249                p = header = get_record();
 250                append_path(&p, S_ISDIR(mode), basepath, prefix, path);
 251        }
 252
 253        if (S_ISDIR(mode))
 254                mode |= 0755;   /* GIT doesn't store permissions of dirs */
 255        sprintf(&header[100], "%07o", mode & 07777);
 256
 257        /* XXX: should we provide more meaningful info here? */
 258        sprintf(&header[108], "%07o", 0);       /* uid */
 259        sprintf(&header[116], "%07o", 0);       /* gid */
 260        strncpy(&header[265], "git", 31);       /* uname */
 261        strncpy(&header[297], "git", 31);       /* gname */
 262
 263        sprintf(&header[124], "%011lo", S_ISDIR(mode) ? 0 : size);
 264        sprintf(&header[136], "%011lo", archive_time);
 265
 266        header[156] = typeflag;
 267
 268        memcpy(&header[257], "ustar", 6);
 269        memcpy(&header[263], "00", 2);
 270
 271        printf(&header[329], "%07o", 0);        /* devmajor */
 272        printf(&header[337], "%07o", 0);        /* devminor */
 273
 274        memset(&header[148], ' ', 8);
 275        for (i = 0; i < RECORDSIZE; i++)
 276                checksum += header[i];
 277        sprintf(&header[148], "%07o", checksum & 0x1fffff);
 278
 279        write_if_needed();
 280}
 281
 282static void traverse_tree(void *buffer, unsigned long size,
 283                          struct path_prefix *prefix)
 284{
 285        struct path_prefix this_prefix;
 286        this_prefix.prev = prefix;
 287
 288        while (size) {
 289                int namelen = strlen(buffer)+1;
 290                void *eltbuf;
 291                char elttype[20];
 292                unsigned long eltsize;
 293                unsigned char *sha1 = buffer + namelen;
 294                char *path = strchr(buffer, ' ') + 1;
 295                unsigned int mode;
 296
 297                if (size < namelen + 20 || sscanf(buffer, "%o", &mode) != 1)
 298                        die("corrupt 'tree' file");
 299                buffer = sha1 + 20;
 300                size -= namelen + 20;
 301
 302                eltbuf = read_sha1_file(sha1, elttype, &eltsize);
 303                if (!eltbuf)
 304                        die("cannot read %s", sha1_to_hex(sha1));
 305                write_header(sha1, TYPEFLAG_AUTO, basedir, prefix, path,
 306                             mode, eltsize);
 307                if (!strcmp(elttype, "tree")) {
 308                        this_prefix.name = path;
 309                        traverse_tree(eltbuf, eltsize, &this_prefix);
 310                } else if (!strcmp(elttype, "blob")) {
 311                        write_blocked(eltbuf, eltsize);
 312                }
 313                free(eltbuf);
 314        }
 315}
 316
 317/* get commit time from committer line of commit object */
 318time_t commit_time(void * buffer, unsigned long size)
 319{
 320        time_t result = 0;
 321        char *p = buffer;
 322
 323        while (size > 0) {
 324                char *endp = memchr(p, '\n', size);
 325                if (!endp || endp == p)
 326                        break;
 327                *endp = '\0';
 328                if (endp - p > 10 && !memcmp(p, "committer ", 10)) {
 329                        char *nump = strrchr(p, '>');
 330                        if (!nump)
 331                                break;
 332                        nump++;
 333                        result = strtoul(nump, &endp, 10);
 334                        if (*endp != ' ')
 335                                result = 0;
 336                        break;
 337                }
 338                size -= endp - p - 1;
 339                p = endp + 1;
 340        }
 341        return result;
 342}
 343
 344int main(int argc, char **argv)
 345{
 346        unsigned char sha1[20];
 347        unsigned char commit_sha1[20];
 348        void *buffer;
 349        unsigned long size;
 350
 351        switch (argc) {
 352        case 3:
 353                basedir = argv[2];
 354                /* FALLTHROUGH */
 355        case 2:
 356                if (get_sha1(argv[1], sha1) < 0)
 357                        usage(tar_tree_usage);
 358                break;
 359        default:
 360                usage(tar_tree_usage);
 361        }
 362
 363        sha1_file_directory = getenv(DB_ENVIRONMENT);
 364        if (!sha1_file_directory)
 365                sha1_file_directory = DEFAULT_DB_ENVIRONMENT;
 366
 367        buffer = read_object_with_reference(sha1, "commit", &size, commit_sha1);
 368        if (buffer) {
 369                write_global_extended_header(commit_sha1);
 370                archive_time = commit_time(buffer, size);
 371                free(buffer);
 372        }
 373        buffer = read_object_with_reference(sha1, "tree", &size, NULL);
 374        if (!buffer)
 375                die("not a reference to a tag, commit or tree object: %s",
 376                    sha1_to_hex(sha1));
 377        if (!archive_time)
 378                archive_time = time(NULL);
 379        if (basedir)
 380                write_header("0", TYPEFLAG_DIR, NULL, NULL, basedir, 040755, 0);
 381        traverse_tree(buffer, size, NULL);
 382        free(buffer);
 383        write_trailer();
 384        return 0;
 385}