]>
Commit | Line | Data |
---|---|---|
e4fbbfe9 RS |
1 | /* |
2 | * Copyright (c) 2006 Rene Scharfe | |
3 | */ | |
4 | #include <time.h> | |
5 | #include "cache.h" | |
6 | #include "commit.h" | |
7 | #include "blob.h" | |
8 | #include "tree.h" | |
9 | #include "quote.h" | |
10 | #include "builtin.h" | |
ec06bff5 | 11 | #include "archive.h" |
e4fbbfe9 RS |
12 | |
13 | static const char zip_tree_usage[] = | |
14 | "git-zip-tree [-0|...|-9] <tree-ish> [ <base> ]"; | |
15 | ||
e0ffb248 | 16 | static int verbose; |
e4fbbfe9 RS |
17 | static int zip_date; |
18 | static int zip_time; | |
19 | ||
20 | static unsigned char *zip_dir; | |
21 | static unsigned int zip_dir_size; | |
22 | ||
23 | static unsigned int zip_offset; | |
24 | static unsigned int zip_dir_offset; | |
25 | static unsigned int zip_dir_entries; | |
26 | ||
27 | #define ZIP_DIRECTORY_MIN_SIZE (1024 * 1024) | |
28 | ||
29 | struct zip_local_header { | |
30 | unsigned char magic[4]; | |
31 | unsigned char version[2]; | |
32 | unsigned char flags[2]; | |
33 | unsigned char compression_method[2]; | |
34 | unsigned char mtime[2]; | |
35 | unsigned char mdate[2]; | |
36 | unsigned char crc32[4]; | |
37 | unsigned char compressed_size[4]; | |
38 | unsigned char size[4]; | |
39 | unsigned char filename_length[2]; | |
40 | unsigned char extra_length[2]; | |
41 | }; | |
42 | ||
43 | struct zip_dir_header { | |
44 | unsigned char magic[4]; | |
45 | unsigned char creator_version[2]; | |
46 | unsigned char version[2]; | |
47 | unsigned char flags[2]; | |
48 | unsigned char compression_method[2]; | |
49 | unsigned char mtime[2]; | |
50 | unsigned char mdate[2]; | |
51 | unsigned char crc32[4]; | |
52 | unsigned char compressed_size[4]; | |
53 | unsigned char size[4]; | |
54 | unsigned char filename_length[2]; | |
55 | unsigned char extra_length[2]; | |
56 | unsigned char comment_length[2]; | |
57 | unsigned char disk[2]; | |
58 | unsigned char attr1[2]; | |
59 | unsigned char attr2[4]; | |
60 | unsigned char offset[4]; | |
61 | }; | |
62 | ||
63 | struct zip_dir_trailer { | |
64 | unsigned char magic[4]; | |
65 | unsigned char disk[2]; | |
66 | unsigned char directory_start_disk[2]; | |
67 | unsigned char entries_on_this_disk[2]; | |
68 | unsigned char entries[2]; | |
69 | unsigned char size[4]; | |
70 | unsigned char offset[4]; | |
71 | unsigned char comment_length[2]; | |
72 | }; | |
73 | ||
74 | static void copy_le16(unsigned char *dest, unsigned int n) | |
75 | { | |
76 | dest[0] = 0xff & n; | |
77 | dest[1] = 0xff & (n >> 010); | |
78 | } | |
79 | ||
80 | static void copy_le32(unsigned char *dest, unsigned int n) | |
81 | { | |
82 | dest[0] = 0xff & n; | |
83 | dest[1] = 0xff & (n >> 010); | |
84 | dest[2] = 0xff & (n >> 020); | |
85 | dest[3] = 0xff & (n >> 030); | |
86 | } | |
87 | ||
88 | static void *zlib_deflate(void *data, unsigned long size, | |
89 | unsigned long *compressed_size) | |
90 | { | |
91 | z_stream stream; | |
92 | unsigned long maxsize; | |
93 | void *buffer; | |
94 | int result; | |
95 | ||
96 | memset(&stream, 0, sizeof(stream)); | |
97 | deflateInit(&stream, zlib_compression_level); | |
98 | maxsize = deflateBound(&stream, size); | |
99 | buffer = xmalloc(maxsize); | |
100 | ||
101 | stream.next_in = data; | |
102 | stream.avail_in = size; | |
103 | stream.next_out = buffer; | |
104 | stream.avail_out = maxsize; | |
105 | ||
106 | do { | |
107 | result = deflate(&stream, Z_FINISH); | |
108 | } while (result == Z_OK); | |
109 | ||
110 | if (result != Z_STREAM_END) { | |
111 | free(buffer); | |
112 | return NULL; | |
113 | } | |
114 | ||
115 | deflateEnd(&stream); | |
116 | *compressed_size = stream.total_out; | |
117 | ||
118 | return buffer; | |
119 | } | |
120 | ||
121 | static char *construct_path(const char *base, int baselen, | |
122 | const char *filename, int isdir, int *pathlen) | |
123 | { | |
124 | int filenamelen = strlen(filename); | |
125 | int len = baselen + filenamelen; | |
126 | char *path, *p; | |
127 | ||
128 | if (isdir) | |
129 | len++; | |
130 | p = path = xmalloc(len + 1); | |
131 | ||
132 | memcpy(p, base, baselen); | |
133 | p += baselen; | |
134 | memcpy(p, filename, filenamelen); | |
135 | p += filenamelen; | |
136 | if (isdir) | |
137 | *p++ = '/'; | |
138 | *p = '\0'; | |
139 | ||
140 | *pathlen = len; | |
141 | ||
142 | return path; | |
143 | } | |
144 | ||
145 | static int write_zip_entry(const unsigned char *sha1, | |
146 | const char *base, int baselen, | |
147 | const char *filename, unsigned mode, int stage) | |
148 | { | |
149 | struct zip_local_header header; | |
150 | struct zip_dir_header dirent; | |
151 | unsigned long compressed_size; | |
152 | unsigned long uncompressed_size; | |
153 | unsigned long crc; | |
154 | unsigned long direntsize; | |
155 | unsigned long size; | |
156 | int method; | |
157 | int result = -1; | |
158 | int pathlen; | |
159 | unsigned char *out; | |
160 | char *path; | |
161 | char type[20]; | |
162 | void *buffer = NULL; | |
163 | void *deflated = NULL; | |
164 | ||
165 | crc = crc32(0, Z_NULL, 0); | |
166 | ||
167 | path = construct_path(base, baselen, filename, S_ISDIR(mode), &pathlen); | |
e0ffb248 JH |
168 | if (verbose) |
169 | fprintf(stderr, "%s\n", path); | |
e4fbbfe9 RS |
170 | if (pathlen > 0xffff) { |
171 | error("path too long (%d chars, SHA1: %s): %s", pathlen, | |
172 | sha1_to_hex(sha1), path); | |
173 | goto out; | |
174 | } | |
175 | ||
176 | if (S_ISDIR(mode)) { | |
177 | method = 0; | |
178 | result = READ_TREE_RECURSIVE; | |
179 | out = NULL; | |
180 | uncompressed_size = 0; | |
181 | compressed_size = 0; | |
182 | } else if (S_ISREG(mode)) { | |
183 | method = zlib_compression_level == 0 ? 0 : 8; | |
184 | result = 0; | |
185 | buffer = read_sha1_file(sha1, type, &size); | |
186 | if (!buffer) | |
187 | die("cannot read %s", sha1_to_hex(sha1)); | |
188 | crc = crc32(crc, buffer, size); | |
189 | out = buffer; | |
190 | uncompressed_size = size; | |
191 | compressed_size = size; | |
192 | } else { | |
193 | error("unsupported file mode: 0%o (SHA1: %s)", mode, | |
194 | sha1_to_hex(sha1)); | |
195 | goto out; | |
196 | } | |
197 | ||
198 | if (method == 8) { | |
199 | deflated = zlib_deflate(buffer, size, &compressed_size); | |
200 | if (deflated && compressed_size - 6 < size) { | |
201 | /* ZLIB --> raw compressed data (see RFC 1950) */ | |
202 | /* CMF and FLG ... */ | |
203 | out = (unsigned char *)deflated + 2; | |
204 | compressed_size -= 6; /* ... and ADLER32 */ | |
205 | } else { | |
206 | method = 0; | |
207 | compressed_size = size; | |
208 | } | |
209 | } | |
210 | ||
211 | /* make sure we have enough free space in the dictionary */ | |
212 | direntsize = sizeof(struct zip_dir_header) + pathlen; | |
213 | while (zip_dir_size < zip_dir_offset + direntsize) { | |
214 | zip_dir_size += ZIP_DIRECTORY_MIN_SIZE; | |
215 | zip_dir = xrealloc(zip_dir, zip_dir_size); | |
216 | } | |
217 | ||
218 | copy_le32(dirent.magic, 0x02014b50); | |
219 | copy_le16(dirent.creator_version, 0); | |
220 | copy_le16(dirent.version, 20); | |
221 | copy_le16(dirent.flags, 0); | |
222 | copy_le16(dirent.compression_method, method); | |
223 | copy_le16(dirent.mtime, zip_time); | |
224 | copy_le16(dirent.mdate, zip_date); | |
225 | copy_le32(dirent.crc32, crc); | |
226 | copy_le32(dirent.compressed_size, compressed_size); | |
227 | copy_le32(dirent.size, uncompressed_size); | |
228 | copy_le16(dirent.filename_length, pathlen); | |
229 | copy_le16(dirent.extra_length, 0); | |
230 | copy_le16(dirent.comment_length, 0); | |
231 | copy_le16(dirent.disk, 0); | |
232 | copy_le16(dirent.attr1, 0); | |
233 | copy_le32(dirent.attr2, 0); | |
234 | copy_le32(dirent.offset, zip_offset); | |
235 | memcpy(zip_dir + zip_dir_offset, &dirent, sizeof(struct zip_dir_header)); | |
236 | zip_dir_offset += sizeof(struct zip_dir_header); | |
237 | memcpy(zip_dir + zip_dir_offset, path, pathlen); | |
238 | zip_dir_offset += pathlen; | |
239 | zip_dir_entries++; | |
240 | ||
241 | copy_le32(header.magic, 0x04034b50); | |
242 | copy_le16(header.version, 20); | |
243 | copy_le16(header.flags, 0); | |
244 | copy_le16(header.compression_method, method); | |
245 | copy_le16(header.mtime, zip_time); | |
246 | copy_le16(header.mdate, zip_date); | |
247 | copy_le32(header.crc32, crc); | |
248 | copy_le32(header.compressed_size, compressed_size); | |
249 | copy_le32(header.size, uncompressed_size); | |
250 | copy_le16(header.filename_length, pathlen); | |
251 | copy_le16(header.extra_length, 0); | |
252 | write_or_die(1, &header, sizeof(struct zip_local_header)); | |
253 | zip_offset += sizeof(struct zip_local_header); | |
254 | write_or_die(1, path, pathlen); | |
255 | zip_offset += pathlen; | |
256 | if (compressed_size > 0) { | |
257 | write_or_die(1, out, compressed_size); | |
258 | zip_offset += compressed_size; | |
259 | } | |
260 | ||
261 | out: | |
262 | free(buffer); | |
263 | free(deflated); | |
264 | free(path); | |
265 | ||
266 | return result; | |
267 | } | |
268 | ||
269 | static void write_zip_trailer(const unsigned char *sha1) | |
270 | { | |
271 | struct zip_dir_trailer trailer; | |
272 | ||
273 | copy_le32(trailer.magic, 0x06054b50); | |
274 | copy_le16(trailer.disk, 0); | |
275 | copy_le16(trailer.directory_start_disk, 0); | |
276 | copy_le16(trailer.entries_on_this_disk, zip_dir_entries); | |
277 | copy_le16(trailer.entries, zip_dir_entries); | |
278 | copy_le32(trailer.size, zip_dir_offset); | |
279 | copy_le32(trailer.offset, zip_offset); | |
280 | copy_le16(trailer.comment_length, sha1 ? 40 : 0); | |
281 | ||
282 | write_or_die(1, zip_dir, zip_dir_offset); | |
283 | write_or_die(1, &trailer, sizeof(struct zip_dir_trailer)); | |
284 | if (sha1) | |
285 | write_or_die(1, sha1_to_hex(sha1), 40); | |
286 | } | |
287 | ||
288 | static void dos_time(time_t *time, int *dos_date, int *dos_time) | |
289 | { | |
290 | struct tm *t = localtime(time); | |
291 | ||
292 | *dos_date = t->tm_mday + (t->tm_mon + 1) * 32 + | |
293 | (t->tm_year + 1900 - 1980) * 512; | |
294 | *dos_time = t->tm_sec / 2 + t->tm_min * 32 + t->tm_hour * 2048; | |
295 | } | |
296 | ||
297 | int cmd_zip_tree(int argc, const char **argv, const char *prefix) | |
298 | { | |
299 | unsigned char sha1[20]; | |
300 | struct tree *tree; | |
301 | struct commit *commit; | |
302 | time_t archive_time; | |
303 | char *base; | |
304 | int baselen; | |
305 | ||
306 | git_config(git_default_config); | |
307 | ||
308 | if (argc > 1 && argv[1][0] == '-') { | |
309 | if (isdigit(argv[1][1]) && argv[1][2] == '\0') { | |
310 | zlib_compression_level = argv[1][1] - '0'; | |
311 | argc--; | |
312 | argv++; | |
313 | } | |
314 | } | |
315 | ||
316 | switch (argc) { | |
317 | case 3: | |
9befac47 | 318 | base = xstrdup(argv[2]); |
e4fbbfe9 RS |
319 | baselen = strlen(base); |
320 | break; | |
321 | case 2: | |
9befac47 | 322 | base = xstrdup(""); |
e4fbbfe9 RS |
323 | baselen = 0; |
324 | break; | |
325 | default: | |
326 | usage(zip_tree_usage); | |
327 | } | |
328 | ||
329 | if (get_sha1(argv[1], sha1)) | |
330 | die("Not a valid object name %s", argv[1]); | |
331 | ||
332 | commit = lookup_commit_reference_gently(sha1, 1); | |
333 | archive_time = commit ? commit->date : time(NULL); | |
334 | dos_time(&archive_time, &zip_date, &zip_time); | |
335 | ||
336 | zip_dir = xmalloc(ZIP_DIRECTORY_MIN_SIZE); | |
337 | zip_dir_size = ZIP_DIRECTORY_MIN_SIZE; | |
338 | ||
339 | tree = parse_tree_indirect(sha1); | |
340 | if (!tree) | |
341 | die("not a tree object"); | |
342 | ||
343 | if (baselen > 0) { | |
344 | write_zip_entry(tree->object.sha1, "", 0, base, 040777, 0); | |
345 | base = xrealloc(base, baselen + 1); | |
346 | base[baselen] = '/'; | |
347 | baselen++; | |
348 | base[baselen] = '\0'; | |
349 | } | |
350 | read_tree_recursive(tree, base, baselen, 0, NULL, write_zip_entry); | |
351 | write_zip_trailer(commit ? commit->object.sha1 : NULL); | |
352 | ||
353 | free(zip_dir); | |
354 | free(base); | |
355 | ||
356 | return 0; | |
357 | } | |
ec06bff5 FBH |
358 | |
359 | int write_zip_archive(struct archiver_args *args) | |
360 | { | |
361 | int plen = strlen(args->base); | |
362 | ||
363 | dos_time(&args->time, &zip_date, &zip_time); | |
364 | ||
365 | zip_dir = xmalloc(ZIP_DIRECTORY_MIN_SIZE); | |
366 | zip_dir_size = ZIP_DIRECTORY_MIN_SIZE; | |
e0ffb248 | 367 | verbose = args->verbose; |
ec06bff5 FBH |
368 | |
369 | if (args->base && plen > 0 && args->base[plen - 1] == '/') { | |
326711c1 | 370 | char *base = xstrdup(args->base); |
ec06bff5 FBH |
371 | int baselen = strlen(base); |
372 | ||
373 | while (baselen > 0 && base[baselen - 1] == '/') | |
374 | base[--baselen] = '\0'; | |
375 | write_zip_entry(args->tree->object.sha1, "", 0, base, 040777, 0); | |
376 | free(base); | |
377 | } | |
378 | read_tree_recursive(args->tree, args->base, plen, 0, | |
379 | args->pathspec, write_zip_entry); | |
380 | write_zip_trailer(args->commit_sha1); | |
381 | ||
382 | free(zip_dir); | |
383 | ||
384 | return 0; | |
385 | } | |
854c4168 RS |
386 | |
387 | void *parse_extra_zip_args(int argc, const char **argv) | |
388 | { | |
389 | for (; argc > 0; argc--, argv++) { | |
390 | const char *arg = argv[0]; | |
391 | ||
392 | if (arg[0] == '-' && isdigit(arg[1]) && arg[2] == '\0') | |
393 | zlib_compression_level = arg[1] - '0'; | |
394 | else | |
395 | die("Unknown argument for zip format: %s", arg); | |
396 | } | |
397 | return NULL; | |
398 | } |