]>
Commit | Line | Data |
---|---|---|
92747a90 | 1 | /* |
ae64bbc1 | 2 | * Copyright (c) 2005, 2006 Rene Scharfe |
92747a90 | 3 | */ |
731ab9cc RS |
4 | #include <time.h> |
5 | #include "cache.h" | |
c3f92812 | 6 | #include "commit.h" |
ae64bbc1 RS |
7 | #include "strbuf.h" |
8 | #include "tar.h" | |
56d1398a | 9 | #include "builtin.h" |
21754264 | 10 | #include "pkt-line.h" |
efd8696c | 11 | #include "archive.h" |
731ab9cc RS |
12 | |
13 | #define RECORDSIZE (512) | |
14 | #define BLOCKSIZE (RECORDSIZE * 20) | |
15 | ||
21754264 | 16 | static const char tar_tree_usage[] = |
3f0073a2 | 17 | "git-tar-tree [--remote=<repo>] <tree-ish> [basedir]"; |
731ab9cc RS |
18 | |
19 | static char block[BLOCKSIZE]; | |
20 | static unsigned long offset; | |
21 | ||
731ab9cc | 22 | static time_t archive_time; |
ce1a79b6 | 23 | static int tar_umask; |
e0ffb248 | 24 | static int verbose; |
731ab9cc | 25 | |
731ab9cc RS |
26 | /* writes out the whole block, but only if it is full */ |
27 | static void write_if_needed(void) | |
28 | { | |
29 | if (offset == BLOCKSIZE) { | |
7230e6d0 | 30 | write_or_die(1, block, BLOCKSIZE); |
731ab9cc RS |
31 | offset = 0; |
32 | } | |
33 | } | |
34 | ||
731ab9cc RS |
35 | /* |
36 | * queues up writes, so that all our write(2) calls write exactly one | |
37 | * full block; pads writes to RECORDSIZE | |
38 | */ | |
6698060c | 39 | static void write_blocked(const void *data, unsigned long size) |
731ab9cc | 40 | { |
6698060c | 41 | const char *buf = data; |
731ab9cc RS |
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) { | |
7230e6d0 | 55 | write_or_die(1, buf, BLOCKSIZE); |
731ab9cc RS |
56 | size -= BLOCKSIZE; |
57 | buf += BLOCKSIZE; | |
58 | } | |
59 | if (size) { | |
60 | memcpy(block + offset, buf, size); | |
731ab9cc RS |
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 | ||
37958be7 RS |
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 | */ | |
75 | static void write_trailer(void) | |
76 | { | |
77 | int tail = BLOCKSIZE - offset; | |
78 | memset(block + offset, 0, tail); | |
7230e6d0 | 79 | write_or_die(1, block, BLOCKSIZE); |
37958be7 RS |
80 | if (tail < 2 * RECORDSIZE) { |
81 | memset(block, 0, offset); | |
7230e6d0 | 82 | write_or_die(1, block, BLOCKSIZE); |
37958be7 RS |
83 | } |
84 | } | |
85 | ||
cb0c6df6 | 86 | static void strbuf_append_string(struct strbuf *sb, const char *s) |
731ab9cc | 87 | { |
cb0c6df6 RS |
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; | |
731ab9cc | 93 | } |
cb0c6df6 RS |
94 | memcpy(sb->buf + sb->len, s, slen); |
95 | sb->len = total; | |
731ab9cc RS |
96 | } |
97 | ||
ae64bbc1 RS |
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 | */ | |
104 | static void strbuf_append_ext_header(struct strbuf *sb, const char *keyword, | |
105 | const char *value, unsigned int valuelen) | |
71058b1f | 106 | { |
ae64bbc1 RS |
107 | char *p; |
108 | int len, total, tmp; | |
71058b1f | 109 | |
71058b1f | 110 | /* "%u %s=%s\n" */ |
ae64bbc1 RS |
111 | len = 1 + 1 + strlen(keyword) + 1 + valuelen + 1; |
112 | for (tmp = len; tmp > 9; tmp /= 10) | |
71058b1f | 113 | len++; |
71058b1f | 114 | |
ae64bbc1 RS |
115 | total = sb->len + len; |
116 | if (total > sb->alloc) { | |
117 | sb->buf = xrealloc(sb->buf, total); | |
118 | sb->alloc = total; | |
119 | } | |
71058b1f | 120 | |
ae64bbc1 RS |
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 | } | |
731ab9cc | 128 | |
ae64bbc1 | 129 | static unsigned int ustar_header_chksum(const struct ustar_header *header) |
731ab9cc | 130 | { |
ae64bbc1 RS |
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; | |
731ab9cc RS |
140 | } |
141 | ||
4c691724 | 142 | static int get_path_prefix(const struct strbuf *path, int maxlen) |
87fec8fc | 143 | { |
4c691724 RS |
144 | int i = path->len; |
145 | if (i > maxlen) | |
146 | i = maxlen; | |
17cf250a | 147 | do { |
4c691724 | 148 | i--; |
17cf250a | 149 | } while (i > 0 && path->buf[i] != '/'); |
4c691724 | 150 | return i; |
87fec8fc RS |
151 | } |
152 | ||
ae64bbc1 RS |
153 | static void write_entry(const unsigned char *sha1, struct strbuf *path, |
154 | unsigned int mode, void *buffer, unsigned long size) | |
731ab9cc | 155 | { |
ae64bbc1 RS |
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)); | |
731ab9cc | 171 | } else { |
e0ffb248 JH |
172 | if (verbose) |
173 | fprintf(stderr, "%.*s\n", path->len, path->buf); | |
ae64bbc1 RS |
174 | if (S_ISDIR(mode)) { |
175 | *header.typeflag = TYPEFLAG_DIR; | |
ce1a79b6 | 176 | mode = (mode | 0777) & ~tar_umask; |
ae64bbc1 RS |
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; | |
ce1a79b6 | 182 | mode = (mode | ((mode & 0100) ? 0777 : 0666)) & ~tar_umask; |
ae64bbc1 RS |
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)) { | |
4c691724 RS |
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 | } | |
ae64bbc1 RS |
200 | } else |
201 | memcpy(header.name, path->buf, path->len); | |
731ab9cc RS |
202 | } |
203 | ||
ae64bbc1 RS |
204 | if (S_ISLNK(mode) && buffer) { |
205 | if (size > sizeof(header.linkname)) { | |
206 | sprintf(header.linkname, "see %s.paxheader", | |
d5776d50 | 207 | sha1_to_hex(sha1)); |
ae64bbc1 RS |
208 | strbuf_append_ext_header(&ext_header, "linkpath", |
209 | buffer, size); | |
210 | } else | |
211 | memcpy(header.linkname, buffer, size); | |
d5776d50 RS |
212 | } |
213 | ||
ae64bbc1 RS |
214 | sprintf(header.mode, "%07o", mode & 07777); |
215 | sprintf(header.size, "%011lo", S_ISREG(mode) ? size : 0); | |
216 | sprintf(header.mtime, "%011lo", archive_time); | |
731ab9cc RS |
217 | |
218 | /* XXX: should we provide more meaningful info here? */ | |
ae64bbc1 RS |
219 | sprintf(header.uid, "%07o", 0); |
220 | sprintf(header.gid, "%07o", 0); | |
817151e6 PE |
221 | strlcpy(header.uname, "git", sizeof(header.uname)); |
222 | strlcpy(header.gname, "git", sizeof(header.gname)); | |
ae64bbc1 RS |
223 | sprintf(header.devmajor, "%07o", 0); |
224 | sprintf(header.devminor, "%07o", 0); | |
731ab9cc | 225 | |
ae64bbc1 RS |
226 | memcpy(header.magic, "ustar", 6); |
227 | memcpy(header.version, "00", 2); | |
731ab9cc | 228 | |
ae64bbc1 | 229 | sprintf(header.chksum, "%07o", ustar_header_chksum(&header)); |
731ab9cc | 230 | |
ae64bbc1 RS |
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 | } | |
731ab9cc | 239 | |
bf0f910d | 240 | static void write_global_extended_header(const unsigned char *sha1) |
87fec8fc | 241 | { |
ae64bbc1 RS |
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); | |
731ab9cc RS |
248 | } |
249 | ||
b5bf7cd6 | 250 | static int git_tar_config(const char *var, const char *value) |
ce1a79b6 WT |
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 | ||
a633fca0 | 264 | static int generate_tar(int argc, const char **argv, const char *prefix) |
731ab9cc | 265 | { |
87af29f0 RS |
266 | struct archiver_args args; |
267 | int result; | |
268 | char *base = NULL; | |
731ab9cc | 269 | |
ce1a79b6 | 270 | git_config(git_tar_config); |
53228a5f | 271 | |
87af29f0 RS |
272 | memset(&args, 0, sizeof(args)); |
273 | if (argc != 2 && argc != 3) | |
731ab9cc | 274 | usage(tar_tree_usage); |
87af29f0 RS |
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'; | |
731ab9cc | 281 | } |
87af29f0 RS |
282 | args.base = base; |
283 | parse_treeish_arg(argv + 1, &args, NULL); | |
731ab9cc | 284 | |
87af29f0 RS |
285 | result = write_tar_archive(&args); |
286 | free(base); | |
287 | ||
288 | return result; | |
731ab9cc | 289 | } |
21754264 | 290 | |
efd8696c FBH |
291 | static 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 | ||
330 | int write_tar_archive(struct archiver_args *args) | |
331 | { | |
87af29f0 | 332 | int plen = args->base ? strlen(args->base) : 0; |
efd8696c FBH |
333 | |
334 | git_config(git_tar_config); | |
335 | ||
336 | archive_time = args->time; | |
e0ffb248 | 337 | verbose = args->verbose; |
efd8696c FBH |
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] == '/') { | |
326711c1 | 343 | char *base = xstrdup(args->base); |
efd8696c FBH |
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 | ||
21754264 JH |
358 | static const char *exec = "git-upload-tar"; |
359 | ||
360 | static 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> */ | |
9befac47 | 371 | url = xstrdup(argv[1]+9); |
21754264 JH |
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 | ||
a633fca0 | 404 | int cmd_tar_tree(int argc, const char **argv, const char *prefix) |
21754264 JH |
405 | { |
406 | if (argc < 2) | |
407 | usage(tar_tree_usage); | |
408 | if (!strncmp("--remote=", argv[1], 9)) | |
409 | return remote_tar(argc, argv); | |
a633fca0 | 410 | return generate_tar(argc, argv, prefix); |
21754264 | 411 | } |
52ba03cb RS |
412 | |
413 | /* ustar header + extended global header content */ | |
414 | #define HEADERSIZE (2 * RECORDSIZE) | |
415 | ||
a633fca0 | 416 | int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix) |
52ba03cb RS |
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 | } |