|  | /* | 
|  | * Copyright (c) 2005 Rene Scharfe | 
|  | */ | 
|  | #include <time.h> | 
|  | #include "cache.h" | 
|  |  | 
|  | #define RECORDSIZE	(512) | 
|  | #define BLOCKSIZE	(RECORDSIZE * 20) | 
|  |  | 
|  | #define TYPEFLAG_AUTO		'\0' | 
|  | #define TYPEFLAG_REG		'0' | 
|  | #define TYPEFLAG_LNK		'2' | 
|  | #define TYPEFLAG_DIR		'5' | 
|  | #define TYPEFLAG_GLOBAL_HEADER	'g' | 
|  | #define TYPEFLAG_EXT_HEADER	'x' | 
|  |  | 
|  | #define EXT_HEADER_PATH		1 | 
|  | #define EXT_HEADER_LINKPATH	2 | 
|  |  | 
|  | static const char *tar_tree_usage = "git-tar-tree <key> [basedir]"; | 
|  |  | 
|  | static char block[BLOCKSIZE]; | 
|  | static unsigned long offset; | 
|  |  | 
|  | static const char *basedir; | 
|  | static time_t archive_time; | 
|  |  | 
|  | struct path_prefix { | 
|  | struct path_prefix *prev; | 
|  | const char *name; | 
|  | }; | 
|  |  | 
|  | /* tries hard to write, either succeeds or dies in the attempt */ | 
|  | static void reliable_write(void *buf, unsigned long size) | 
|  | { | 
|  | while (size > 0) { | 
|  | long ret = write(1, buf, size); | 
|  | if (ret < 0) { | 
|  | if (errno == EAGAIN) | 
|  | continue; | 
|  | if (errno == EPIPE) | 
|  | exit(0); | 
|  | die("git-tar-tree: %s", strerror(errno)); | 
|  | } else if (!ret) { | 
|  | die("git-tar-tree: disk full?"); | 
|  | } | 
|  | size -= ret; | 
|  | buf += ret; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* writes out the whole block, but only if it is full */ | 
|  | static void write_if_needed(void) | 
|  | { | 
|  | if (offset == BLOCKSIZE) { | 
|  | reliable_write(block, BLOCKSIZE); | 
|  | offset = 0; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* acquire the next record from the buffer; user must call write_if_needed() */ | 
|  | static char *get_record(void) | 
|  | { | 
|  | char *p = block + offset; | 
|  | memset(p, 0, RECORDSIZE); | 
|  | offset += RECORDSIZE; | 
|  | return p; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * The end of tar archives is marked by 1024 nul bytes and after that | 
|  | * follows the rest of the block (if any). | 
|  | */ | 
|  | static void write_trailer(void) | 
|  | { | 
|  | memset(block + offset, 0, RECORDSIZE); | 
|  | offset += RECORDSIZE; | 
|  | write_if_needed(); | 
|  | memset(block + offset, 0, RECORDSIZE); | 
|  | offset += RECORDSIZE; | 
|  | write_if_needed(); | 
|  | if (offset) { | 
|  | memset(block + offset, 0, BLOCKSIZE - offset); | 
|  | reliable_write(block, BLOCKSIZE); | 
|  | offset = 0; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* | 
|  | * queues up writes, so that all our write(2) calls write exactly one | 
|  | * full block; pads writes to RECORDSIZE | 
|  | */ | 
|  | static void write_blocked(void *buf, unsigned long size) | 
|  | { | 
|  | unsigned long tail; | 
|  |  | 
|  | if (offset) { | 
|  | unsigned long chunk = BLOCKSIZE - offset; | 
|  | if (size < chunk) | 
|  | chunk = size; | 
|  | memcpy(block + offset, buf, chunk); | 
|  | size -= chunk; | 
|  | offset += chunk; | 
|  | buf += chunk; | 
|  | write_if_needed(); | 
|  | } | 
|  | while (size >= BLOCKSIZE) { | 
|  | reliable_write(buf, BLOCKSIZE); | 
|  | size -= BLOCKSIZE; | 
|  | buf += BLOCKSIZE; | 
|  | } | 
|  | if (size) { | 
|  | memcpy(block + offset, buf, size); | 
|  | buf += size; | 
|  | offset += size; | 
|  | } | 
|  | tail = offset % RECORDSIZE; | 
|  | if (tail)  { | 
|  | memset(block + offset, 0, RECORDSIZE - tail); | 
|  | offset += RECORDSIZE - tail; | 
|  | } | 
|  | write_if_needed(); | 
|  | } | 
|  |  | 
|  | static void append_string(char **p, const char *s) | 
|  | { | 
|  | unsigned int len = strlen(s); | 
|  | memcpy(*p, s, len); | 
|  | *p += len; | 
|  | } | 
|  |  | 
|  | static void append_char(char **p, char c) | 
|  | { | 
|  | **p = c; | 
|  | *p += 1; | 
|  | } | 
|  |  | 
|  | static void append_path_prefix(char **buffer, struct path_prefix *prefix) | 
|  | { | 
|  | if (!prefix) | 
|  | return; | 
|  | append_path_prefix(buffer, prefix->prev); | 
|  | append_string(buffer, prefix->name); | 
|  | append_char(buffer, '/'); | 
|  | } | 
|  |  | 
|  | static unsigned int path_prefix_len(struct path_prefix *prefix) | 
|  | { | 
|  | if (!prefix) | 
|  | return 0; | 
|  | return path_prefix_len(prefix->prev) + strlen(prefix->name) + 1; | 
|  | } | 
|  |  | 
|  | static void append_path(char **p, int is_dir, const char *basepath, | 
|  | struct path_prefix *prefix, const char *path) | 
|  | { | 
|  | if (basepath) { | 
|  | append_string(p, basepath); | 
|  | append_char(p, '/'); | 
|  | } | 
|  | append_path_prefix(p, prefix); | 
|  | append_string(p, path); | 
|  | if (is_dir) | 
|  | append_char(p, '/'); | 
|  | } | 
|  |  | 
|  | static unsigned int path_len(int is_dir, const char *basepath, | 
|  | struct path_prefix *prefix, const char *path) | 
|  | { | 
|  | unsigned int len = 0; | 
|  | if (basepath) | 
|  | len += strlen(basepath) + 1; | 
|  | len += path_prefix_len(prefix) + strlen(path); | 
|  | if (is_dir) | 
|  | len++; | 
|  | return len; | 
|  | } | 
|  |  | 
|  | static void append_extended_header_prefix(char **p, unsigned int size, | 
|  | const char *keyword) | 
|  | { | 
|  | int len = sprintf(*p, "%u %s=", size, keyword); | 
|  | *p += len; | 
|  | } | 
|  |  | 
|  | static unsigned int extended_header_len(const char *keyword, | 
|  | unsigned int valuelen) | 
|  | { | 
|  | /* "%u %s=%s\n" */ | 
|  | unsigned int len = 1 + 1 + strlen(keyword) + 1 + valuelen + 1; | 
|  | if (len > 9) | 
|  | len++; | 
|  | if (len > 99) | 
|  | len++; | 
|  | return len; | 
|  | } | 
|  |  | 
|  | static void append_extended_header(char **p, const char *keyword, | 
|  | const char *value, unsigned int len) | 
|  | { | 
|  | unsigned int size = extended_header_len(keyword, len); | 
|  | append_extended_header_prefix(p, size, keyword); | 
|  | memcpy(*p, value, len); | 
|  | *p += len; | 
|  | append_char(p, '\n'); | 
|  | } | 
|  |  | 
|  | static void write_header(const unsigned char *, char, const char *, struct path_prefix *, | 
|  | const char *, unsigned int, void *, unsigned long); | 
|  |  | 
|  | /* stores a pax extended header directly in the block buffer */ | 
|  | static void write_extended_header(const char *headerfilename, int is_dir, | 
|  | unsigned int flags, const char *basepath, | 
|  | struct path_prefix *prefix, | 
|  | const char *path, unsigned int namelen, | 
|  | void *content, unsigned int contentsize) | 
|  | { | 
|  | char *buffer, *p; | 
|  | unsigned int pathlen, size, linkpathlen = 0; | 
|  |  | 
|  | size = pathlen = extended_header_len("path", namelen); | 
|  | if (flags & EXT_HEADER_LINKPATH) { | 
|  | linkpathlen = extended_header_len("linkpath", contentsize); | 
|  | size += linkpathlen; | 
|  | } | 
|  | write_header(NULL, TYPEFLAG_EXT_HEADER, NULL, NULL, headerfilename, | 
|  | 0100600, NULL, size); | 
|  |  | 
|  | buffer = p = malloc(size); | 
|  | if (!buffer) | 
|  | die("git-tar-tree: %s", strerror(errno)); | 
|  | append_extended_header_prefix(&p, pathlen, "path"); | 
|  | append_path(&p, is_dir, basepath, prefix, path); | 
|  | append_char(&p, '\n'); | 
|  | if (flags & EXT_HEADER_LINKPATH) | 
|  | append_extended_header(&p, "linkpath", content, contentsize); | 
|  | write_blocked(buffer, size); | 
|  | free(buffer); | 
|  | } | 
|  |  | 
|  | static void write_global_extended_header(const unsigned char *sha1) | 
|  | { | 
|  | char *p; | 
|  | unsigned int size; | 
|  |  | 
|  | size = extended_header_len("comment", 40); | 
|  | write_header(NULL, TYPEFLAG_GLOBAL_HEADER, NULL, NULL, | 
|  | "pax_global_header", 0100600, NULL, size); | 
|  |  | 
|  | p = get_record(); | 
|  | append_extended_header(&p, "comment", sha1_to_hex(sha1), 40); | 
|  | write_if_needed(); | 
|  | } | 
|  |  | 
|  | /* stores a ustar header directly in the block buffer */ | 
|  | static void write_header(const unsigned char *sha1, char typeflag, const char *basepath, | 
|  | struct path_prefix *prefix, const char *path, | 
|  | unsigned int mode, void *buffer, unsigned long size) | 
|  | { | 
|  | unsigned int namelen; | 
|  | char *header = NULL; | 
|  | unsigned int checksum = 0; | 
|  | int i; | 
|  | unsigned int ext_header = 0; | 
|  |  | 
|  | if (typeflag == TYPEFLAG_AUTO) { | 
|  | if (S_ISDIR(mode)) | 
|  | typeflag = TYPEFLAG_DIR; | 
|  | else if (S_ISLNK(mode)) | 
|  | typeflag = TYPEFLAG_LNK; | 
|  | else | 
|  | typeflag = TYPEFLAG_REG; | 
|  | } | 
|  |  | 
|  | namelen = path_len(S_ISDIR(mode), basepath, prefix, path); | 
|  | if (namelen > 100) | 
|  | ext_header |= EXT_HEADER_PATH; | 
|  | if (typeflag == TYPEFLAG_LNK && size > 100) | 
|  | ext_header |= EXT_HEADER_LINKPATH; | 
|  |  | 
|  | /* the extended header must be written before the normal one */ | 
|  | if (ext_header) { | 
|  | char headerfilename[51]; | 
|  | sprintf(headerfilename, "%s.paxheader", sha1_to_hex(sha1)); | 
|  | write_extended_header(headerfilename, S_ISDIR(mode), | 
|  | ext_header, basepath, prefix, path, | 
|  | namelen, buffer, size); | 
|  | } | 
|  |  | 
|  | header = get_record(); | 
|  |  | 
|  | if (ext_header) { | 
|  | sprintf(header, "%s.data", sha1_to_hex(sha1)); | 
|  | } else { | 
|  | char *p = header; | 
|  | append_path(&p, S_ISDIR(mode), basepath, prefix, path); | 
|  | } | 
|  |  | 
|  | if (typeflag == TYPEFLAG_LNK) { | 
|  | if (ext_header & EXT_HEADER_LINKPATH) { | 
|  | sprintf(&header[157], "see %s.paxheader", | 
|  | sha1_to_hex(sha1)); | 
|  | } else { | 
|  | if (buffer) | 
|  | strncpy(&header[157], buffer, size); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (S_ISDIR(mode)) | 
|  | mode |= 0755;	/* GIT doesn't store permissions of dirs */ | 
|  | if (S_ISLNK(mode)) | 
|  | mode |= 0777;   /* ... nor of symlinks */ | 
|  | sprintf(&header[100], "%07o", mode & 07777); | 
|  |  | 
|  | /* XXX: should we provide more meaningful info here? */ | 
|  | sprintf(&header[108], "%07o", 0);	/* uid */ | 
|  | sprintf(&header[116], "%07o", 0);	/* gid */ | 
|  | strncpy(&header[265], "git", 31);	/* uname */ | 
|  | strncpy(&header[297], "git", 31);	/* gname */ | 
|  |  | 
|  | if (S_ISDIR(mode) || S_ISLNK(mode)) | 
|  | size = 0; | 
|  | sprintf(&header[124], "%011lo", size); | 
|  | sprintf(&header[136], "%011lo", archive_time); | 
|  |  | 
|  | header[156] = typeflag; | 
|  |  | 
|  | memcpy(&header[257], "ustar", 6); | 
|  | memcpy(&header[263], "00", 2); | 
|  |  | 
|  | printf(&header[329], "%07o", 0);	/* devmajor */ | 
|  | printf(&header[337], "%07o", 0);	/* devminor */ | 
|  |  | 
|  | memset(&header[148], ' ', 8); | 
|  | for (i = 0; i < RECORDSIZE; i++) | 
|  | checksum += header[i]; | 
|  | sprintf(&header[148], "%07o", checksum & 0x1fffff); | 
|  |  | 
|  | write_if_needed(); | 
|  | } | 
|  |  | 
|  | static void traverse_tree(void *buffer, unsigned long size, | 
|  | struct path_prefix *prefix) | 
|  | { | 
|  | struct path_prefix this_prefix; | 
|  | this_prefix.prev = prefix; | 
|  |  | 
|  | while (size) { | 
|  | int namelen = strlen(buffer)+1; | 
|  | void *eltbuf; | 
|  | char elttype[20]; | 
|  | unsigned long eltsize; | 
|  | unsigned char *sha1 = buffer + namelen; | 
|  | char *path = strchr(buffer, ' ') + 1; | 
|  | unsigned int mode; | 
|  |  | 
|  | if (size < namelen + 20 || sscanf(buffer, "%o", &mode) != 1) | 
|  | die("corrupt 'tree' file"); | 
|  | buffer = sha1 + 20; | 
|  | size -= namelen + 20; | 
|  |  | 
|  | eltbuf = read_sha1_file(sha1, elttype, &eltsize); | 
|  | if (!eltbuf) | 
|  | die("cannot read %s", sha1_to_hex(sha1)); | 
|  | write_header(sha1, TYPEFLAG_AUTO, basedir, prefix, path, | 
|  | mode, eltbuf, eltsize); | 
|  | if (!strcmp(elttype, "tree")) { | 
|  | this_prefix.name = path; | 
|  | traverse_tree(eltbuf, eltsize, &this_prefix); | 
|  | } else if (!strcmp(elttype, "blob") && !S_ISLNK(mode)) { | 
|  | write_blocked(eltbuf, eltsize); | 
|  | } | 
|  | free(eltbuf); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* get commit time from committer line of commit object */ | 
|  | static time_t commit_time(void * buffer, unsigned long size) | 
|  | { | 
|  | time_t result = 0; | 
|  | char *p = buffer; | 
|  |  | 
|  | while (size > 0) { | 
|  | char *endp = memchr(p, '\n', size); | 
|  | if (!endp || endp == p) | 
|  | break; | 
|  | *endp = '\0'; | 
|  | if (endp - p > 10 && !memcmp(p, "committer ", 10)) { | 
|  | char *nump = strrchr(p, '>'); | 
|  | if (!nump) | 
|  | break; | 
|  | nump++; | 
|  | result = strtoul(nump, &endp, 10); | 
|  | if (*endp != ' ') | 
|  | result = 0; | 
|  | break; | 
|  | } | 
|  | size -= endp - p - 1; | 
|  | p = endp + 1; | 
|  | } | 
|  | return result; | 
|  | } | 
|  |  | 
|  | int main(int argc, char **argv) | 
|  | { | 
|  | unsigned char sha1[20]; | 
|  | unsigned char commit_sha1[20]; | 
|  | void *buffer; | 
|  | unsigned long size; | 
|  |  | 
|  | switch (argc) { | 
|  | case 3: | 
|  | basedir = argv[2]; | 
|  | /* FALLTHROUGH */ | 
|  | case 2: | 
|  | if (get_sha1(argv[1], sha1) < 0) | 
|  | usage(tar_tree_usage); | 
|  | break; | 
|  | default: | 
|  | usage(tar_tree_usage); | 
|  | } | 
|  |  | 
|  | buffer = read_object_with_reference(sha1, "commit", &size, commit_sha1); | 
|  | if (buffer) { | 
|  | write_global_extended_header(commit_sha1); | 
|  | archive_time = commit_time(buffer, size); | 
|  | free(buffer); | 
|  | } | 
|  | buffer = read_object_with_reference(sha1, "tree", &size, NULL); | 
|  | if (!buffer) | 
|  | die("not a reference to a tag, commit or tree object: %s", | 
|  | sha1_to_hex(sha1)); | 
|  | if (!archive_time) | 
|  | archive_time = time(NULL); | 
|  | if (basedir) | 
|  | write_header("0", TYPEFLAG_DIR, NULL, NULL, basedir, 040755, | 
|  | NULL, 0); | 
|  | traverse_tree(buffer, size, NULL); | 
|  | free(buffer); | 
|  | write_trailer(); | 
|  | return 0; | 
|  | } |