builtin-tar-tree.con commit Merge branch 'js/diff' (fc5201a)
   1/*
   2 * Copyright (c) 2005, 2006 Rene Scharfe
   3 */
   4#include <time.h>
   5#include "cache.h"
   6#include "tree-walk.h"
   7#include "commit.h"
   8#include "strbuf.h"
   9#include "tar.h"
  10#include "builtin.h"
  11#include "pkt-line.h"
  12
  13#define RECORDSIZE      (512)
  14#define BLOCKSIZE       (RECORDSIZE * 20)
  15
  16static const char tar_tree_usage[] =
  17"git-tar-tree [--remote=<repo>] <ent> [basedir]";
  18
  19static char block[BLOCKSIZE];
  20static unsigned long offset;
  21
  22static time_t archive_time;
  23
  24/* tries hard to write, either succeeds or dies in the attempt */
  25static void reliable_write(void *buf, unsigned long size)
  26{
  27        while (size > 0) {
  28                long ret = xwrite(1, buf, size);
  29                if (ret < 0) {
  30                        if (errno == EPIPE)
  31                                exit(0);
  32                        die("git-tar-tree: %s", strerror(errno));
  33                } else if (!ret) {
  34                        die("git-tar-tree: disk full?");
  35                }
  36                size -= ret;
  37                buf += ret;
  38        }
  39}
  40
  41/* writes out the whole block, but only if it is full */
  42static void write_if_needed(void)
  43{
  44        if (offset == BLOCKSIZE) {
  45                reliable_write(block, BLOCKSIZE);
  46                offset = 0;
  47        }
  48}
  49
  50/* acquire the next record from the buffer; user must call write_if_needed() */
  51static char *get_record(void)
  52{
  53        char *p = block + offset;
  54        memset(p, 0, RECORDSIZE);
  55        offset += RECORDSIZE;
  56        return p;
  57}
  58
  59/*
  60 * The end of tar archives is marked by 1024 nul bytes and after that
  61 * follows the rest of the block (if any).
  62 */
  63static void write_trailer(void)
  64{
  65        get_record();
  66        write_if_needed();
  67        get_record();
  68        write_if_needed();
  69        while (offset) {
  70                get_record();
  71                write_if_needed();
  72        }
  73}
  74
  75/*
  76 * queues up writes, so that all our write(2) calls write exactly one
  77 * full block; pads writes to RECORDSIZE
  78 */
  79static void write_blocked(void *buf, unsigned long size)
  80{
  81        unsigned long tail;
  82
  83        if (offset) {
  84                unsigned long chunk = BLOCKSIZE - offset;
  85                if (size < chunk)
  86                        chunk = size;
  87                memcpy(block + offset, buf, chunk);
  88                size -= chunk;
  89                offset += chunk;
  90                buf += chunk;
  91                write_if_needed();
  92        }
  93        while (size >= BLOCKSIZE) {
  94                reliable_write(buf, BLOCKSIZE);
  95                size -= BLOCKSIZE;
  96                buf += BLOCKSIZE;
  97        }
  98        if (size) {
  99                memcpy(block + offset, buf, size);
 100                offset += size;
 101        }
 102        tail = offset % RECORDSIZE;
 103        if (tail)  {
 104                memset(block + offset, 0, RECORDSIZE - tail);
 105                offset += RECORDSIZE - tail;
 106        }
 107        write_if_needed();
 108}
 109
 110static void strbuf_append_string(struct strbuf *sb, const char *s)
 111{
 112        int slen = strlen(s);
 113        int total = sb->len + slen;
 114        if (total > sb->alloc) {
 115                sb->buf = xrealloc(sb->buf, total);
 116                sb->alloc = total;
 117        }
 118        memcpy(sb->buf + sb->len, s, slen);
 119        sb->len = total;
 120}
 121
 122/*
 123 * pax extended header records have the format "%u %s=%s\n".  %u contains
 124 * the size of the whole string (including the %u), the first %s is the
 125 * keyword, the second one is the value.  This function constructs such a
 126 * string and appends it to a struct strbuf.
 127 */
 128static void strbuf_append_ext_header(struct strbuf *sb, const char *keyword,
 129                                     const char *value, unsigned int valuelen)
 130{
 131        char *p;
 132        int len, total, tmp;
 133
 134        /* "%u %s=%s\n" */
 135        len = 1 + 1 + strlen(keyword) + 1 + valuelen + 1;
 136        for (tmp = len; tmp > 9; tmp /= 10)
 137                len++;
 138
 139        total = sb->len + len;
 140        if (total > sb->alloc) {
 141                sb->buf = xrealloc(sb->buf, total);
 142                sb->alloc = total;
 143        }
 144
 145        p = sb->buf;
 146        p += sprintf(p, "%u %s=", len, keyword);
 147        memcpy(p, value, valuelen);
 148        p += valuelen;
 149        *p = '\n';
 150        sb->len = total;
 151}
 152
 153static unsigned int ustar_header_chksum(const struct ustar_header *header)
 154{
 155        char *p = (char *)header;
 156        unsigned int chksum = 0;
 157        while (p < header->chksum)
 158                chksum += *p++;
 159        chksum += sizeof(header->chksum) * ' ';
 160        p += sizeof(header->chksum);
 161        while (p < (char *)header + sizeof(struct ustar_header))
 162                chksum += *p++;
 163        return chksum;
 164}
 165
 166static int get_path_prefix(const struct strbuf *path, int maxlen)
 167{
 168        int i = path->len;
 169        if (i > maxlen)
 170                i = maxlen;
 171        do {
 172                i--;
 173        } while (i > 0 && path->buf[i] != '/');
 174        return i;
 175}
 176
 177static void write_entry(const unsigned char *sha1, struct strbuf *path,
 178                        unsigned int mode, void *buffer, unsigned long size)
 179{
 180        struct ustar_header header;
 181        struct strbuf ext_header;
 182
 183        memset(&header, 0, sizeof(header));
 184        ext_header.buf = NULL;
 185        ext_header.len = ext_header.alloc = 0;
 186
 187        if (!sha1) {
 188                *header.typeflag = TYPEFLAG_GLOBAL_HEADER;
 189                mode = 0100666;
 190                strcpy(header.name, "pax_global_header");
 191        } else if (!path) {
 192                *header.typeflag = TYPEFLAG_EXT_HEADER;
 193                mode = 0100666;
 194                sprintf(header.name, "%s.paxheader", sha1_to_hex(sha1));
 195        } else {
 196                if (S_ISDIR(mode)) {
 197                        *header.typeflag = TYPEFLAG_DIR;
 198                        mode |= 0777;
 199                } else if (S_ISLNK(mode)) {
 200                        *header.typeflag = TYPEFLAG_LNK;
 201                        mode |= 0777;
 202                } else if (S_ISREG(mode)) {
 203                        *header.typeflag = TYPEFLAG_REG;
 204                        mode |= (mode & 0100) ? 0777 : 0666;
 205                } else {
 206                        error("unsupported file mode: 0%o (SHA1: %s)",
 207                              mode, sha1_to_hex(sha1));
 208                        return;
 209                }
 210                if (path->len > sizeof(header.name)) {
 211                        int plen = get_path_prefix(path, sizeof(header.prefix));
 212                        int rest = path->len - plen - 1;
 213                        if (plen > 0 && rest <= sizeof(header.name)) {
 214                                memcpy(header.prefix, path->buf, plen);
 215                                memcpy(header.name, path->buf + plen + 1, rest);
 216                        } else {
 217                                sprintf(header.name, "%s.data",
 218                                        sha1_to_hex(sha1));
 219                                strbuf_append_ext_header(&ext_header, "path",
 220                                                         path->buf, path->len);
 221                        }
 222                } else
 223                        memcpy(header.name, path->buf, path->len);
 224        }
 225
 226        if (S_ISLNK(mode) && buffer) {
 227                if (size > sizeof(header.linkname)) {
 228                        sprintf(header.linkname, "see %s.paxheader",
 229                                sha1_to_hex(sha1));
 230                        strbuf_append_ext_header(&ext_header, "linkpath",
 231                                                 buffer, size);
 232                } else
 233                        memcpy(header.linkname, buffer, size);
 234        }
 235
 236        sprintf(header.mode, "%07o", mode & 07777);
 237        sprintf(header.size, "%011lo", S_ISREG(mode) ? size : 0);
 238        sprintf(header.mtime, "%011lo", archive_time);
 239
 240        /* XXX: should we provide more meaningful info here? */
 241        sprintf(header.uid, "%07o", 0);
 242        sprintf(header.gid, "%07o", 0);
 243        safe_strncpy(header.uname, "git", sizeof(header.uname));
 244        safe_strncpy(header.gname, "git", sizeof(header.gname));
 245        sprintf(header.devmajor, "%07o", 0);
 246        sprintf(header.devminor, "%07o", 0);
 247
 248        memcpy(header.magic, "ustar", 6);
 249        memcpy(header.version, "00", 2);
 250
 251        sprintf(header.chksum, "%07o", ustar_header_chksum(&header));
 252
 253        if (ext_header.len > 0) {
 254                write_entry(sha1, NULL, 0, ext_header.buf, ext_header.len);
 255                free(ext_header.buf);
 256        }
 257        write_blocked(&header, sizeof(header));
 258        if (S_ISREG(mode) && buffer && size > 0)
 259                write_blocked(buffer, size);
 260}
 261
 262static void write_global_extended_header(const unsigned char *sha1)
 263{
 264        struct strbuf ext_header;
 265        ext_header.buf = NULL;
 266        ext_header.len = ext_header.alloc = 0;
 267        strbuf_append_ext_header(&ext_header, "comment", sha1_to_hex(sha1), 40);
 268        write_entry(NULL, NULL, 0, ext_header.buf, ext_header.len);
 269        free(ext_header.buf);
 270}
 271
 272static void traverse_tree(struct tree_desc *tree, struct strbuf *path)
 273{
 274        int pathlen = path->len;
 275        struct name_entry entry;
 276
 277        while (tree_entry(tree, &entry)) {
 278                void *eltbuf;
 279                char elttype[20];
 280                unsigned long eltsize;
 281
 282                eltbuf = read_sha1_file(entry.sha1, elttype, &eltsize);
 283                if (!eltbuf)
 284                        die("cannot read %s", sha1_to_hex(entry.sha1));
 285
 286                path->len = pathlen;
 287                strbuf_append_string(path, entry.path);
 288                if (S_ISDIR(entry.mode))
 289                        strbuf_append_string(path, "/");
 290
 291                write_entry(entry.sha1, path, entry.mode, eltbuf, eltsize);
 292
 293                if (S_ISDIR(entry.mode)) {
 294                        struct tree_desc subtree;
 295                        subtree.buf = eltbuf;
 296                        subtree.size = eltsize;
 297                        traverse_tree(&subtree, path);
 298                }
 299                free(eltbuf);
 300        }
 301}
 302
 303static int generate_tar(int argc, const char **argv, char** envp)
 304{
 305        unsigned char sha1[20], tree_sha1[20];
 306        struct commit *commit;
 307        struct tree_desc tree;
 308        struct strbuf current_path;
 309
 310        current_path.buf = xmalloc(PATH_MAX);
 311        current_path.alloc = PATH_MAX;
 312        current_path.len = current_path.eof = 0;
 313
 314        setup_git_directory();
 315        git_config(git_default_config);
 316
 317        switch (argc) {
 318        case 3:
 319                strbuf_append_string(&current_path, argv[2]);
 320                strbuf_append_string(&current_path, "/");
 321                /* FALLTHROUGH */
 322        case 2:
 323                if (get_sha1(argv[1], sha1))
 324                        die("Not a valid object name %s", argv[1]);
 325                break;
 326        default:
 327                usage(tar_tree_usage);
 328        }
 329
 330        commit = lookup_commit_reference_gently(sha1, 1);
 331        if (commit) {
 332                write_global_extended_header(commit->object.sha1);
 333                archive_time = commit->date;
 334        } else
 335                archive_time = time(NULL);
 336
 337        tree.buf = read_object_with_reference(sha1, tree_type, &tree.size,
 338                                              tree_sha1);
 339        if (!tree.buf)
 340                die("not a reference to a tag, commit or tree object: %s",
 341                    sha1_to_hex(sha1));
 342
 343        if (current_path.len > 0)
 344                write_entry(tree_sha1, &current_path, 040777, NULL, 0);
 345        traverse_tree(&tree, &current_path);
 346        write_trailer();
 347        free(current_path.buf);
 348        return 0;
 349}
 350
 351static const char *exec = "git-upload-tar";
 352
 353static int remote_tar(int argc, const char **argv)
 354{
 355        int fd[2], ret, len;
 356        pid_t pid;
 357        char buf[1024];
 358        char *url;
 359
 360        if (argc < 3 || 4 < argc)
 361                usage(tar_tree_usage);
 362
 363        /* --remote=<repo> */
 364        url = strdup(argv[1]+9);
 365        pid = git_connect(fd, url, exec);
 366        if (pid < 0)
 367                return 1;
 368
 369        packet_write(fd[1], "want %s\n", argv[2]);
 370        if (argv[3])
 371                packet_write(fd[1], "base %s\n", argv[3]);
 372        packet_flush(fd[1]);
 373
 374        len = packet_read_line(fd[0], buf, sizeof(buf));
 375        if (!len)
 376                die("git-tar-tree: expected ACK/NAK, got EOF");
 377        if (buf[len-1] == '\n')
 378                buf[--len] = 0;
 379        if (strcmp(buf, "ACK")) {
 380                if (5 < len && !strncmp(buf, "NACK ", 5))
 381                        die("git-tar-tree: NACK %s", buf + 5);
 382                die("git-tar-tree: protocol error");
 383        }
 384        /* expect a flush */
 385        len = packet_read_line(fd[0], buf, sizeof(buf));
 386        if (len)
 387                die("git-tar-tree: expected a flush");
 388
 389        /* Now, start reading from fd[0] and spit it out to stdout */
 390        ret = copy_fd(fd[0], 1);
 391        close(fd[0]);
 392
 393        ret |= finish_connect(pid);
 394        return !!ret;
 395}
 396
 397int cmd_tar_tree(int argc, const char **argv, char **envp)
 398{
 399        if (argc < 2)
 400                usage(tar_tree_usage);
 401        if (!strncmp("--remote=", argv[1], 9))
 402                return remote_tar(argc, argv);
 403        return generate_tar(argc, argv, envp);
 404}
 405
 406/* ustar header + extended global header content */
 407#define HEADERSIZE (2 * RECORDSIZE)
 408
 409int cmd_get_tar_commit_id(int argc, const char **argv, char **envp)
 410{
 411        char buffer[HEADERSIZE];
 412        struct ustar_header *header = (struct ustar_header *)buffer;
 413        char *content = buffer + RECORDSIZE;
 414        ssize_t n;
 415
 416        n = xread(0, buffer, HEADERSIZE);
 417        if (n < HEADERSIZE)
 418                die("git-get-tar-commit-id: read error");
 419        if (header->typeflag[0] != 'g')
 420                return 1;
 421        if (memcmp(content, "52 comment=", 11))
 422                return 1;
 423
 424        n = xwrite(1, content + 11, 41);
 425        if (n < 41)
 426                die("git-get-tar-commit-id: write error");
 427
 428        return 0;
 429}