Merge with http://members.cox.net/junkio/git-jc.git
[git] / tar-tree.c
1 /*
2  * Copyright (c) 2005 Rene Scharfe
3  */
4 #include <time.h>
5 #include "cache.h"
6
7 #define RECORDSIZE      (512)
8 #define BLOCKSIZE       (RECORDSIZE * 20)
9
10 #define TYPEFLAG_AUTO           '\0'
11 #define TYPEFLAG_REG            '0'
12 #define TYPEFLAG_LNK            '2'
13 #define TYPEFLAG_DIR            '5'
14 #define TYPEFLAG_GLOBAL_HEADER  'g'
15 #define TYPEFLAG_EXT_HEADER     'x'
16
17 #define EXT_HEADER_PATH         1
18 #define EXT_HEADER_LINKPATH     2
19
20 static const char *tar_tree_usage = "tar-tree <key> [basedir]";
21
22 static char block[BLOCKSIZE];
23 static unsigned long offset;
24
25 static const char *basedir;
26 static time_t archive_time;
27
28 struct path_prefix {
29         struct path_prefix *prev;
30         const char *name;
31 };
32
33 /* tries hard to write, either succeeds or dies in the attempt */
34 static void reliable_write(void *buf, unsigned long size)
35 {
36         while (size > 0) {
37                 long ret = write(1, buf, size);
38                 if (ret < 0) {
39                         if (errno == EAGAIN)
40                                 continue;
41                         if (errno == EPIPE)
42                                 exit(0);
43                         die("tar-tree: %s", strerror(errno));
44                 } else if (!ret) {
45                         die("tar-tree: disk full?");
46                 }
47                 size -= ret;
48                 buf += ret;
49         }
50 }
51
52 /* writes out the whole block, but only if it is full */
53 static void write_if_needed(void)
54 {
55         if (offset == BLOCKSIZE) {
56                 reliable_write(block, BLOCKSIZE);
57                 offset = 0;
58         }
59 }
60
61 /* acquire the next record from the buffer; user must call write_if_needed() */
62 static char *get_record(void)
63 {
64         char *p = block + offset;
65         memset(p, 0, RECORDSIZE);
66         offset += RECORDSIZE;
67         return p;
68 }
69
70 /*
71  * The end of tar archives is marked by 1024 nul bytes and after that
72  * follows the rest of the block (if any).
73  */
74 static void write_trailer(void)
75 {
76         memset(block + offset, 0, RECORDSIZE);
77         offset += RECORDSIZE;
78         write_if_needed();
79         memset(block + offset, 0, RECORDSIZE);
80         offset += RECORDSIZE;
81         write_if_needed();
82         if (offset) {
83                 memset(block + offset, 0, BLOCKSIZE - offset);
84                 reliable_write(block, BLOCKSIZE);
85                 offset = 0;
86         }
87 }
88
89 /*
90  * queues up writes, so that all our write(2) calls write exactly one
91  * full block; pads writes to RECORDSIZE
92  */
93 static void write_blocked(void *buf, unsigned long size)
94 {
95         unsigned long tail;
96
97         if (offset) {
98                 unsigned long chunk = BLOCKSIZE - offset;
99                 if (size < chunk)
100                         chunk = size;
101                 memcpy(block + offset, buf, chunk);
102                 size -= chunk;
103                 offset += chunk;
104                 buf += chunk;
105                 write_if_needed();
106         }
107         while (size >= BLOCKSIZE) {
108                 reliable_write(buf, BLOCKSIZE);
109                 size -= BLOCKSIZE;
110                 buf += BLOCKSIZE;
111         }
112         if (size) {
113                 memcpy(block + offset, buf, size);
114                 buf += size;
115                 offset += size;
116         }
117         tail = offset % RECORDSIZE;
118         if (tail)  {
119                 memset(block + offset, 0, RECORDSIZE - tail);
120                 offset += RECORDSIZE - tail;
121         }
122         write_if_needed();
123 }
124
125 static void append_string(char **p, const char *s)
126 {
127         unsigned int len = strlen(s);
128         memcpy(*p, s, len);
129         *p += len;
130 }
131
132 static void append_char(char **p, char c)
133 {
134         **p = c;
135         *p += 1;
136 }
137
138 static void append_path_prefix(char **buffer, struct path_prefix *prefix)
139 {
140         if (!prefix)
141                 return;
142         append_path_prefix(buffer, prefix->prev);
143         append_string(buffer, prefix->name);
144         append_char(buffer, '/');
145 }
146
147 static unsigned int path_prefix_len(struct path_prefix *prefix)
148 {
149         if (!prefix)
150                 return 0;
151         return path_prefix_len(prefix->prev) + strlen(prefix->name) + 1;
152 }
153
154 static void append_path(char **p, int is_dir, const char *basepath,
155                         struct path_prefix *prefix, const char *path)
156 {
157         if (basepath) {
158                 append_string(p, basepath);
159                 append_char(p, '/');
160         }
161         append_path_prefix(p, prefix);
162         append_string(p, path);
163         if (is_dir)
164                 append_char(p, '/');
165 }
166
167 static unsigned int path_len(int is_dir, const char *basepath,
168                              struct path_prefix *prefix, const char *path)
169 {
170         unsigned int len = 0;
171         if (basepath)
172                 len += strlen(basepath) + 1;
173         len += path_prefix_len(prefix) + strlen(path);
174         if (is_dir)
175                 len++;
176         return len;
177 }
178
179 static void append_extended_header_prefix(char **p, unsigned int size,
180                                           const char *keyword)
181 {
182         int len = sprintf(*p, "%u %s=", size, keyword);
183         *p += len;
184 }
185
186 static unsigned int extended_header_len(const char *keyword,
187                                         unsigned int valuelen)
188 {
189         /* "%u %s=%s\n" */
190         unsigned int len = 1 + 1 + strlen(keyword) + 1 + valuelen + 1;
191         if (len > 9)
192                 len++;
193         if (len > 99)
194                 len++;
195         return len;
196 }
197
198 static void append_extended_header(char **p, const char *keyword,
199                                    const char *value, unsigned int len)
200 {
201         unsigned int size = extended_header_len(keyword, len);
202         append_extended_header_prefix(p, size, keyword);
203         memcpy(*p, value, len);
204         *p += len;
205         append_char(p, '\n');
206 }
207
208 static void write_header(const char *, char, const char *, struct path_prefix *,
209                          const char *, unsigned int, void *, unsigned long);
210
211 /* stores a pax extended header directly in the block buffer */
212 static void write_extended_header(const char *headerfilename, int is_dir,
213                                   unsigned int flags, const char *basepath,
214                                   struct path_prefix *prefix,
215                                   const char *path, unsigned int namelen,
216                                   void *content, unsigned int contentsize)
217 {
218         char *buffer, *p;
219         unsigned int pathlen, size, linkpathlen = 0;
220
221         size = pathlen = extended_header_len("path", namelen);
222         if (flags & EXT_HEADER_LINKPATH) {
223                 linkpathlen = extended_header_len("linkpath", contentsize);
224                 size += linkpathlen;
225         }
226         write_header(NULL, TYPEFLAG_EXT_HEADER, NULL, NULL, headerfilename,
227                      0100600, NULL, size);
228
229         buffer = p = malloc(size);
230         if (!buffer)
231                 die("git-tar-tree: %s", strerror(errno));
232         append_extended_header_prefix(&p, pathlen, "path");
233         append_path(&p, is_dir, basepath, prefix, path);
234         append_char(&p, '\n');
235         if (flags & EXT_HEADER_LINKPATH)
236                 append_extended_header(&p, "linkpath", content, contentsize);
237         write_blocked(buffer, size);
238         free(buffer);
239 }
240
241 static void write_global_extended_header(const char *sha1)
242 {
243         char *p;
244         unsigned int size;
245
246         size = extended_header_len("comment", 40);
247         write_header(NULL, TYPEFLAG_GLOBAL_HEADER, NULL, NULL,
248                      "pax_global_header", 0100600, NULL, size);
249
250         p = get_record();
251         append_extended_header(&p, "comment", sha1_to_hex(sha1), 40);
252         write_if_needed();
253 }
254
255 /* stores a ustar header directly in the block buffer */
256 static void write_header(const char *sha1, char typeflag, const char *basepath,
257                          struct path_prefix *prefix, const char *path,
258                          unsigned int mode, void *buffer, unsigned long size)
259 {
260         unsigned int namelen; 
261         char *header = NULL;
262         unsigned int checksum = 0;
263         int i;
264         unsigned int ext_header = 0;
265
266         if (typeflag == TYPEFLAG_AUTO) {
267                 if (S_ISDIR(mode))
268                         typeflag = TYPEFLAG_DIR;
269                 else if (S_ISLNK(mode))
270                         typeflag = TYPEFLAG_LNK;
271                 else
272                         typeflag = TYPEFLAG_REG;
273         }
274
275         namelen = path_len(S_ISDIR(mode), basepath, prefix, path);
276         if (namelen > 100)
277                 ext_header |= EXT_HEADER_PATH;
278         if (typeflag == TYPEFLAG_LNK && size > 100)
279                 ext_header |= EXT_HEADER_LINKPATH;
280
281         /* the extended header must be written before the normal one */
282         if (ext_header) {
283                 char headerfilename[51];
284                 sprintf(headerfilename, "%s.paxheader", sha1_to_hex(sha1));
285                 write_extended_header(headerfilename, S_ISDIR(mode),
286                                       ext_header, basepath, prefix, path,
287                                       namelen, buffer, size);
288         }
289
290         header = get_record();
291
292         if (ext_header) {
293                 sprintf(header, "%s.data", sha1_to_hex(sha1));
294         } else {
295                 char *p = header;
296                 append_path(&p, S_ISDIR(mode), basepath, prefix, path);
297         }
298
299         if (typeflag == TYPEFLAG_LNK) {
300                 if (ext_header & EXT_HEADER_LINKPATH) {
301                         sprintf(&header[157], "see %s.paxheader",
302                                 sha1_to_hex(sha1));
303                 } else {
304                         if (buffer)
305                                 strncpy(&header[157], buffer, size);
306                 }
307         }
308
309         if (S_ISDIR(mode))
310                 mode |= 0755;   /* GIT doesn't store permissions of dirs */
311         if (S_ISLNK(mode))
312                 mode |= 0777;   /* ... nor of symlinks */
313         sprintf(&header[100], "%07o", mode & 07777);
314
315         /* XXX: should we provide more meaningful info here? */
316         sprintf(&header[108], "%07o", 0);       /* uid */
317         sprintf(&header[116], "%07o", 0);       /* gid */
318         strncpy(&header[265], "git", 31);       /* uname */
319         strncpy(&header[297], "git", 31);       /* gname */
320
321         if (S_ISDIR(mode) || S_ISLNK(mode))
322                 size = 0;
323         sprintf(&header[124], "%011lo", size);
324         sprintf(&header[136], "%011lo", archive_time);
325
326         header[156] = typeflag;
327
328         memcpy(&header[257], "ustar", 6);
329         memcpy(&header[263], "00", 2);
330
331         printf(&header[329], "%07o", 0);        /* devmajor */
332         printf(&header[337], "%07o", 0);        /* devminor */
333
334         memset(&header[148], ' ', 8);
335         for (i = 0; i < RECORDSIZE; i++)
336                 checksum += header[i];
337         sprintf(&header[148], "%07o", checksum & 0x1fffff);
338
339         write_if_needed();
340 }
341
342 static void traverse_tree(void *buffer, unsigned long size,
343                           struct path_prefix *prefix)
344 {
345         struct path_prefix this_prefix;
346         this_prefix.prev = prefix;
347
348         while (size) {
349                 int namelen = strlen(buffer)+1;
350                 void *eltbuf;
351                 char elttype[20];
352                 unsigned long eltsize;
353                 unsigned char *sha1 = buffer + namelen;
354                 char *path = strchr(buffer, ' ') + 1;
355                 unsigned int mode;
356
357                 if (size < namelen + 20 || sscanf(buffer, "%o", &mode) != 1)
358                         die("corrupt 'tree' file");
359                 buffer = sha1 + 20;
360                 size -= namelen + 20;
361
362                 eltbuf = read_sha1_file(sha1, elttype, &eltsize);
363                 if (!eltbuf)
364                         die("cannot read %s", sha1_to_hex(sha1));
365                 write_header(sha1, TYPEFLAG_AUTO, basedir, prefix, path,
366                              mode, eltbuf, eltsize);
367                 if (!strcmp(elttype, "tree")) {
368                         this_prefix.name = path;
369                         traverse_tree(eltbuf, eltsize, &this_prefix);
370                 } else if (!strcmp(elttype, "blob") && !S_ISLNK(mode)) {
371                         write_blocked(eltbuf, eltsize);
372                 }
373                 free(eltbuf);
374         }
375 }
376
377 /* get commit time from committer line of commit object */
378 time_t commit_time(void * buffer, unsigned long size)
379 {
380         time_t result = 0;
381         char *p = buffer;
382
383         while (size > 0) {
384                 char *endp = memchr(p, '\n', size);
385                 if (!endp || endp == p)
386                         break;
387                 *endp = '\0';
388                 if (endp - p > 10 && !memcmp(p, "committer ", 10)) {
389                         char *nump = strrchr(p, '>');
390                         if (!nump)
391                                 break;
392                         nump++;
393                         result = strtoul(nump, &endp, 10);
394                         if (*endp != ' ')
395                                 result = 0;
396                         break;
397                 }
398                 size -= endp - p - 1;
399                 p = endp + 1;
400         }
401         return result;
402 }
403
404 int main(int argc, char **argv)
405 {
406         unsigned char sha1[20];
407         unsigned char commit_sha1[20];
408         void *buffer;
409         unsigned long size;
410
411         switch (argc) {
412         case 3:
413                 basedir = argv[2];
414                 /* FALLTHROUGH */
415         case 2:
416                 if (get_sha1(argv[1], sha1) < 0)
417                         usage(tar_tree_usage);
418                 break;
419         default:
420                 usage(tar_tree_usage);
421         }
422
423         buffer = read_object_with_reference(sha1, "commit", &size, commit_sha1);
424         if (buffer) {
425                 write_global_extended_header(commit_sha1);
426                 archive_time = commit_time(buffer, size);
427                 free(buffer);
428         }
429         buffer = read_object_with_reference(sha1, "tree", &size, NULL);
430         if (!buffer)
431                 die("not a reference to a tag, commit or tree object: %s",
432                     sha1_to_hex(sha1));
433         if (!archive_time)
434                 archive_time = time(NULL);
435         if (basedir)
436                 write_header("0", TYPEFLAG_DIR, NULL, NULL, basedir, 040755,
437                              NULL, 0);
438         traverse_tree(buffer, size, NULL);
439         free(buffer);
440         write_trailer();
441         return 0;
442 }