builtin-tar-tree.con commit Use xstrdup instead of strdup in builtin-{tar,zip}-tree.c (326711c)
   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#include "archive.h"
  13
  14#define RECORDSIZE      (512)
  15#define BLOCKSIZE       (RECORDSIZE * 20)
  16
  17static const char tar_tree_usage[] =
  18"git-tar-tree [--remote=<repo>] <tree-ish> [basedir]";
  19
  20static char block[BLOCKSIZE];
  21static unsigned long offset;
  22
  23static time_t archive_time;
  24static int tar_umask;
  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 (S_ISDIR(mode)) {
 173                        *header.typeflag = TYPEFLAG_DIR;
 174                        mode = (mode | 0777) & ~tar_umask;
 175                } else if (S_ISLNK(mode)) {
 176                        *header.typeflag = TYPEFLAG_LNK;
 177                        mode |= 0777;
 178                } else if (S_ISREG(mode)) {
 179                        *header.typeflag = TYPEFLAG_REG;
 180                        mode = (mode | ((mode & 0100) ? 0777 : 0666)) & ~tar_umask;
 181                } else {
 182                        error("unsupported file mode: 0%o (SHA1: %s)",
 183                              mode, sha1_to_hex(sha1));
 184                        return;
 185                }
 186                if (path->len > sizeof(header.name)) {
 187                        int plen = get_path_prefix(path, sizeof(header.prefix));
 188                        int rest = path->len - plen - 1;
 189                        if (plen > 0 && rest <= sizeof(header.name)) {
 190                                memcpy(header.prefix, path->buf, plen);
 191                                memcpy(header.name, path->buf + plen + 1, rest);
 192                        } else {
 193                                sprintf(header.name, "%s.data",
 194                                        sha1_to_hex(sha1));
 195                                strbuf_append_ext_header(&ext_header, "path",
 196                                                         path->buf, path->len);
 197                        }
 198                } else
 199                        memcpy(header.name, path->buf, path->len);
 200        }
 201
 202        if (S_ISLNK(mode) && buffer) {
 203                if (size > sizeof(header.linkname)) {
 204                        sprintf(header.linkname, "see %s.paxheader",
 205                                sha1_to_hex(sha1));
 206                        strbuf_append_ext_header(&ext_header, "linkpath",
 207                                                 buffer, size);
 208                } else
 209                        memcpy(header.linkname, buffer, size);
 210        }
 211
 212        sprintf(header.mode, "%07o", mode & 07777);
 213        sprintf(header.size, "%011lo", S_ISREG(mode) ? size : 0);
 214        sprintf(header.mtime, "%011lo", archive_time);
 215
 216        /* XXX: should we provide more meaningful info here? */
 217        sprintf(header.uid, "%07o", 0);
 218        sprintf(header.gid, "%07o", 0);
 219        strlcpy(header.uname, "git", sizeof(header.uname));
 220        strlcpy(header.gname, "git", sizeof(header.gname));
 221        sprintf(header.devmajor, "%07o", 0);
 222        sprintf(header.devminor, "%07o", 0);
 223
 224        memcpy(header.magic, "ustar", 6);
 225        memcpy(header.version, "00", 2);
 226
 227        sprintf(header.chksum, "%07o", ustar_header_chksum(&header));
 228
 229        if (ext_header.len > 0) {
 230                write_entry(sha1, NULL, 0, ext_header.buf, ext_header.len);
 231                free(ext_header.buf);
 232        }
 233        write_blocked(&header, sizeof(header));
 234        if (S_ISREG(mode) && buffer && size > 0)
 235                write_blocked(buffer, size);
 236}
 237
 238static void write_global_extended_header(const unsigned char *sha1)
 239{
 240        struct strbuf ext_header;
 241        ext_header.buf = NULL;
 242        ext_header.len = ext_header.alloc = 0;
 243        strbuf_append_ext_header(&ext_header, "comment", sha1_to_hex(sha1), 40);
 244        write_entry(NULL, NULL, 0, ext_header.buf, ext_header.len);
 245        free(ext_header.buf);
 246}
 247
 248static void traverse_tree(struct tree_desc *tree, struct strbuf *path)
 249{
 250        int pathlen = path->len;
 251        struct name_entry entry;
 252
 253        while (tree_entry(tree, &entry)) {
 254                void *eltbuf;
 255                char elttype[20];
 256                unsigned long eltsize;
 257
 258                eltbuf = read_sha1_file(entry.sha1, elttype, &eltsize);
 259                if (!eltbuf)
 260                        die("cannot read %s", sha1_to_hex(entry.sha1));
 261
 262                path->len = pathlen;
 263                strbuf_append_string(path, entry.path);
 264                if (S_ISDIR(entry.mode))
 265                        strbuf_append_string(path, "/");
 266
 267                write_entry(entry.sha1, path, entry.mode, eltbuf, eltsize);
 268
 269                if (S_ISDIR(entry.mode)) {
 270                        struct tree_desc subtree;
 271                        subtree.buf = eltbuf;
 272                        subtree.size = eltsize;
 273                        traverse_tree(&subtree, path);
 274                }
 275                free(eltbuf);
 276        }
 277}
 278
 279static int git_tar_config(const char *var, const char *value)
 280{
 281        if (!strcmp(var, "tar.umask")) {
 282                if (!strcmp(value, "user")) {
 283                        tar_umask = umask(0);
 284                        umask(tar_umask);
 285                } else {
 286                        tar_umask = git_config_int(var, value);
 287                }
 288                return 0;
 289        }
 290        return git_default_config(var, value);
 291}
 292
 293static int generate_tar(int argc, const char **argv, const char *prefix)
 294{
 295        unsigned char sha1[20], tree_sha1[20];
 296        struct commit *commit;
 297        struct tree_desc tree;
 298        struct strbuf current_path;
 299        void *buffer;
 300
 301        current_path.buf = xmalloc(PATH_MAX);
 302        current_path.alloc = PATH_MAX;
 303        current_path.len = current_path.eof = 0;
 304
 305        git_config(git_tar_config);
 306
 307        switch (argc) {
 308        case 3:
 309                strbuf_append_string(&current_path, argv[2]);
 310                strbuf_append_string(&current_path, "/");
 311                /* FALLTHROUGH */
 312        case 2:
 313                if (get_sha1(argv[1], sha1))
 314                        die("Not a valid object name %s", argv[1]);
 315                break;
 316        default:
 317                usage(tar_tree_usage);
 318        }
 319
 320        commit = lookup_commit_reference_gently(sha1, 1);
 321        if (commit) {
 322                write_global_extended_header(commit->object.sha1);
 323                archive_time = commit->date;
 324        } else
 325                archive_time = time(NULL);
 326
 327        tree.buf = buffer = read_object_with_reference(sha1, tree_type,
 328                                                       &tree.size, tree_sha1);
 329        if (!tree.buf)
 330                die("not a reference to a tag, commit or tree object: %s",
 331                    sha1_to_hex(sha1));
 332
 333        if (current_path.len > 0)
 334                write_entry(tree_sha1, &current_path, 040777, NULL, 0);
 335        traverse_tree(&tree, &current_path);
 336        write_trailer();
 337        free(buffer);
 338        free(current_path.buf);
 339        return 0;
 340}
 341
 342static int write_tar_entry(const unsigned char *sha1,
 343                           const char *base, int baselen,
 344                           const char *filename, unsigned mode, int stage)
 345{
 346        static struct strbuf path;
 347        int filenamelen = strlen(filename);
 348        void *buffer;
 349        char type[20];
 350        unsigned long size;
 351
 352        if (!path.alloc) {
 353                path.buf = xmalloc(PATH_MAX);
 354                path.alloc = PATH_MAX;
 355                path.len = path.eof = 0;
 356        }
 357        if (path.alloc < baselen + filenamelen) {
 358                free(path.buf);
 359                path.buf = xmalloc(baselen + filenamelen);
 360                path.alloc = baselen + filenamelen;
 361        }
 362        memcpy(path.buf, base, baselen);
 363        memcpy(path.buf + baselen, filename, filenamelen);
 364        path.len = baselen + filenamelen;
 365        if (S_ISDIR(mode)) {
 366                strbuf_append_string(&path, "/");
 367                buffer = NULL;
 368                size = 0;
 369        } else {
 370                buffer = read_sha1_file(sha1, type, &size);
 371                if (!buffer)
 372                        die("cannot read %s", sha1_to_hex(sha1));
 373        }
 374
 375        write_entry(sha1, &path, mode, buffer, size);
 376        free(buffer);
 377
 378        return READ_TREE_RECURSIVE;
 379}
 380
 381int write_tar_archive(struct archiver_args *args)
 382{
 383        int plen = strlen(args->base);
 384
 385        git_config(git_tar_config);
 386
 387        archive_time = args->time;
 388
 389        if (args->commit_sha1)
 390                write_global_extended_header(args->commit_sha1);
 391
 392        if (args->base && plen > 0 && args->base[plen - 1] == '/') {
 393                char *base = xstrdup(args->base);
 394                int baselen = strlen(base);
 395
 396                while (baselen > 0 && base[baselen - 1] == '/')
 397                        base[--baselen] = '\0';
 398                write_tar_entry(args->tree->object.sha1, "", 0, base, 040777, 0);
 399                free(base);
 400        }
 401        read_tree_recursive(args->tree, args->base, plen, 0,
 402                            args->pathspec, write_tar_entry);
 403        write_trailer();
 404
 405        return 0;
 406}
 407
 408static const char *exec = "git-upload-tar";
 409
 410static int remote_tar(int argc, const char **argv)
 411{
 412        int fd[2], ret, len;
 413        pid_t pid;
 414        char buf[1024];
 415        char *url;
 416
 417        if (argc < 3 || 4 < argc)
 418                usage(tar_tree_usage);
 419
 420        /* --remote=<repo> */
 421        url = xstrdup(argv[1]+9);
 422        pid = git_connect(fd, url, exec);
 423        if (pid < 0)
 424                return 1;
 425
 426        packet_write(fd[1], "want %s\n", argv[2]);
 427        if (argv[3])
 428                packet_write(fd[1], "base %s\n", argv[3]);
 429        packet_flush(fd[1]);
 430
 431        len = packet_read_line(fd[0], buf, sizeof(buf));
 432        if (!len)
 433                die("git-tar-tree: expected ACK/NAK, got EOF");
 434        if (buf[len-1] == '\n')
 435                buf[--len] = 0;
 436        if (strcmp(buf, "ACK")) {
 437                if (5 < len && !strncmp(buf, "NACK ", 5))
 438                        die("git-tar-tree: NACK %s", buf + 5);
 439                die("git-tar-tree: protocol error");
 440        }
 441        /* expect a flush */
 442        len = packet_read_line(fd[0], buf, sizeof(buf));
 443        if (len)
 444                die("git-tar-tree: expected a flush");
 445
 446        /* Now, start reading from fd[0] and spit it out to stdout */
 447        ret = copy_fd(fd[0], 1);
 448        close(fd[0]);
 449
 450        ret |= finish_connect(pid);
 451        return !!ret;
 452}
 453
 454int cmd_tar_tree(int argc, const char **argv, const char *prefix)
 455{
 456        if (argc < 2)
 457                usage(tar_tree_usage);
 458        if (!strncmp("--remote=", argv[1], 9))
 459                return remote_tar(argc, argv);
 460        return generate_tar(argc, argv, prefix);
 461}
 462
 463/* ustar header + extended global header content */
 464#define HEADERSIZE (2 * RECORDSIZE)
 465
 466int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix)
 467{
 468        char buffer[HEADERSIZE];
 469        struct ustar_header *header = (struct ustar_header *)buffer;
 470        char *content = buffer + RECORDSIZE;
 471        ssize_t n;
 472
 473        n = xread(0, buffer, HEADERSIZE);
 474        if (n < HEADERSIZE)
 475                die("git-get-tar-commit-id: read error");
 476        if (header->typeflag[0] != 'g')
 477                return 1;
 478        if (memcmp(content, "52 comment=", 11))
 479                return 1;
 480
 481        n = xwrite(1, content + 11, 41);
 482        if (n < 41)
 483                die("git-get-tar-commit-id: write error");
 484
 485        return 0;
 486}