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