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