82b4951f3af8e7d6c58cc52798abd3094c4ea7d9
   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        memset(&args, 0, sizeof(args));
 271        if (argc != 2 && argc != 3)
 272                usage(tar_tree_usage);
 273        if (argc == 3) {
 274                int baselen = strlen(argv[2]);
 275                base = xmalloc(baselen + 2);
 276                memcpy(base, argv[2], baselen);
 277                base[baselen] = '/';
 278                base[baselen + 1] = '\0';
 279        }
 280        args.base = base;
 281        parse_treeish_arg(argv + 1, &args, NULL);
 282
 283        result = write_tar_archive(&args);
 284        free(base);
 285
 286        return result;
 287}
 288
 289static int write_tar_entry(const unsigned char *sha1,
 290                           const char *base, int baselen,
 291                           const char *filename, unsigned mode, int stage)
 292{
 293        static struct strbuf path;
 294        int filenamelen = strlen(filename);
 295        void *buffer;
 296        char type[20];
 297        unsigned long size;
 298
 299        if (!path.alloc) {
 300                path.buf = xmalloc(PATH_MAX);
 301                path.alloc = PATH_MAX;
 302                path.len = path.eof = 0;
 303        }
 304        if (path.alloc < baselen + filenamelen) {
 305                free(path.buf);
 306                path.buf = xmalloc(baselen + filenamelen);
 307                path.alloc = baselen + filenamelen;
 308        }
 309        memcpy(path.buf, base, baselen);
 310        memcpy(path.buf + baselen, filename, filenamelen);
 311        path.len = baselen + filenamelen;
 312        if (S_ISDIR(mode)) {
 313                strbuf_append_string(&path, "/");
 314                buffer = NULL;
 315                size = 0;
 316        } else {
 317                buffer = read_sha1_file(sha1, type, &size);
 318                if (!buffer)
 319                        die("cannot read %s", sha1_to_hex(sha1));
 320        }
 321
 322        write_entry(sha1, &path, mode, buffer, size);
 323        free(buffer);
 324
 325        return READ_TREE_RECURSIVE;
 326}
 327
 328int write_tar_archive(struct archiver_args *args)
 329{
 330        int plen = args->base ? strlen(args->base) : 0;
 331
 332        git_config(git_tar_config);
 333
 334        archive_time = args->time;
 335        verbose = args->verbose;
 336
 337        if (args->commit_sha1)
 338                write_global_extended_header(args->commit_sha1);
 339
 340        if (args->base && plen > 0 && args->base[plen - 1] == '/') {
 341                char *base = xstrdup(args->base);
 342                int baselen = strlen(base);
 343
 344                while (baselen > 0 && base[baselen - 1] == '/')
 345                        base[--baselen] = '\0';
 346                write_tar_entry(args->tree->object.sha1, "", 0, base, 040777, 0);
 347                free(base);
 348        }
 349        read_tree_recursive(args->tree, args->base, plen, 0,
 350                            args->pathspec, write_tar_entry);
 351        write_trailer();
 352
 353        return 0;
 354}
 355
 356static const char *exec = "git-upload-tar";
 357
 358static int remote_tar(int argc, const char **argv)
 359{
 360        int fd[2], ret, len;
 361        pid_t pid;
 362        char buf[1024];
 363        char *url;
 364
 365        if (argc < 3 || 4 < argc)
 366                usage(tar_tree_usage);
 367
 368        /* --remote=<repo> */
 369        url = xstrdup(argv[1]+9);
 370        pid = git_connect(fd, url, exec);
 371        if (pid < 0)
 372                return 1;
 373
 374        packet_write(fd[1], "want %s\n", argv[2]);
 375        if (argv[3])
 376                packet_write(fd[1], "base %s\n", argv[3]);
 377        packet_flush(fd[1]);
 378
 379        len = packet_read_line(fd[0], buf, sizeof(buf));
 380        if (!len)
 381                die("git-tar-tree: expected ACK/NAK, got EOF");
 382        if (buf[len-1] == '\n')
 383                buf[--len] = 0;
 384        if (strcmp(buf, "ACK")) {
 385                if (5 < len && !strncmp(buf, "NACK ", 5))
 386                        die("git-tar-tree: NACK %s", buf + 5);
 387                die("git-tar-tree: protocol error");
 388        }
 389        /* expect a flush */
 390        len = packet_read_line(fd[0], buf, sizeof(buf));
 391        if (len)
 392                die("git-tar-tree: expected a flush");
 393
 394        /* Now, start reading from fd[0] and spit it out to stdout */
 395        ret = copy_fd(fd[0], 1);
 396        close(fd[0]);
 397
 398        ret |= finish_connect(pid);
 399        return !!ret;
 400}
 401
 402int cmd_tar_tree(int argc, const char **argv, const char *prefix)
 403{
 404        if (argc < 2)
 405                usage(tar_tree_usage);
 406        if (!strncmp("--remote=", argv[1], 9))
 407                return remote_tar(argc, argv);
 408        return generate_tar(argc, argv, prefix);
 409}
 410
 411/* ustar header + extended global header content */
 412#define HEADERSIZE (2 * RECORDSIZE)
 413
 414int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix)
 415{
 416        char buffer[HEADERSIZE];
 417        struct ustar_header *header = (struct ustar_header *)buffer;
 418        char *content = buffer + RECORDSIZE;
 419        ssize_t n;
 420
 421        n = xread(0, buffer, HEADERSIZE);
 422        if (n < HEADERSIZE)
 423                die("git-get-tar-commit-id: read error");
 424        if (header->typeflag[0] != 'g')
 425                return 1;
 426        if (memcmp(content, "52 comment=", 11))
 427                return 1;
 428
 429        n = xwrite(1, content + 11, 41);
 430        if (n < 41)
 431                die("git-get-tar-commit-id: write error");
 432
 433        return 0;
 434}