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