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