xref: /plan9-contrib/sys/src/cmd/gzip/zip.c (revision 7dd7cddf99dd7472612f1413b4da293630e6b1bc)
1 #include <u.h>
2 #include <libc.h>
3 #include <bio.h>
4 #include "zip.h"
5 #include "deflate.h"
6 
7 enum
8 {
9 	HeadAlloc	= 64,
10 	NDirs		= 64,
11 };
12 
13 static	void	zip(Biobuf *bout, char *file, int stdout);
14 static	void	zipDir(Biobuf *bout, int fd, ZipHead *zh, int stdout);
15 static	int	crcread(void *fd, void *buf, int n);
16 static	int	zwrite(void *bout, void *buf, int n);
17 static	void	put4(Biobuf *b, ulong v);
18 static	void	put2(Biobuf *b, int v);
19 static	void	put1(Biobuf *b, int v);
20 static	void	header(Biobuf *bout, ZipHead *zh);
21 static	void	trailer(Biobuf *bout, ZipHead *zh, vlong off);
22 static	void	putCDir(Biobuf *bout);
23 
24 static	void	error(char*, ...);
25 #pragma	varargck	argpos	error	1
26 
27 static	Biobuf	bout;
28 static	ulong	crc;
29 static	int	debug;
30 static	int	eof;
31 static	int	level;
32 static	int	nzheads;
33 static	ulong	totr;
34 static	ulong	totw;
35 static	int	verbose;
36 static	int	zhalloc;
37 static	ZipHead	*zheads;
38 static	jmp_buf	zjmp;
39 
40 void
41 usage(void)
42 {
43 	fprint(2, "usage: zip [-vD] [-1-9] [-f zipfile] file ...\n");
44 	exits("usage");
45 }
46 
47 void
48 main(int argc, char *argv[])
49 {
50 	char *zfile;
51 	int i, fd;
52 
53 	zfile = nil;
54 	level = 6;
55 	ARGBEGIN{
56 	case 'D':
57 		debug++;
58 		break;
59 	case 'f':
60 		zfile = ARGF();
61 		if(zfile == nil)
62 			usage();
63 		break;
64 	case 'v':
65 		verbose++;
66 		break;
67 	case '1': case '2': case '3': case '4':
68 	case '5': case '6': case '7': case '8': case '9':
69 		level = ARGC() - '0';
70 		break;
71 	default:
72 		usage();
73 		break;
74 	}ARGEND
75 
76 	if(argc == 0)
77 		usage();
78 
79 	mkcrctab(ZCrcPoly);
80 	deflateinit();
81 
82 	if(zfile == nil)
83 		fd = 1;
84 	else{
85 		fd = create(zfile, OWRITE, 0664);
86 		if(fd < 0)
87 			sysfatal("can't create %s: %r\n", zfile);
88 	}
89 	Binit(&bout, fd, OWRITE);
90 
91 	if(setjmp(zjmp)){
92 		if(zfile != nil){
93 			fprint(2, "zip: removing output file %s\n", zfile);
94 			remove(zfile);
95 		}
96 		exits("errors");
97 	}
98 
99 	for(i = 0; i < argc; i++)
100 		zip(&bout, argv[i], zfile == nil);
101 
102 	putCDir(&bout);
103 
104 	exits(nil);
105 }
106 
107 static void
108 zip(Biobuf *bout, char *file, int stdout)
109 {
110 	Tm *t;
111 	ZipHead *zh;
112 	Dir dir;
113 	vlong off;
114 	int fd;
115 
116 	fd = open(file, OREAD);
117 	if(fd < 0)
118 		error("can't open %s: %r", file);
119 	if(dirfstat(fd, &dir) < 0)
120 		error("can't stat %s: %r", file);
121 
122 	/*
123 	 * create the header
124 	 */
125 	if(nzheads >= zhalloc){
126 		zhalloc += HeadAlloc;
127 		zheads = realloc(zheads, zhalloc * sizeof(ZipHead));
128 		if(zheads == nil)
129 			error("out of memory");
130 	}
131 	zh = &zheads[nzheads++];
132 	zh->madeos = ZDos;
133 	zh->madevers = (2 * 10) + 0;
134 	zh->extos = ZDos;
135 	zh->extvers = (2 * 10) + 0;
136 
137 	t = localtime(dir.mtime);
138 	zh->modtime = (t->hour<<11) | (t->min<<5) | (t->sec>>1);
139 	zh->moddate = ((t->year-80)<<9) | ((t->mon+1)<<5) | t->mday;
140 
141 	zh->flags = 0;
142 	zh->crc = 0;
143 	zh->csize = 0;
144 	zh->uncsize = 0;
145 	zh->file = strdup(file);
146 	if(zh->file == nil)
147 		error("out of memory");
148 	zh->iattr = 0;
149 	zh->eattr = ZDArch;
150 	if((dir.mode & 0700) == 0)
151 		zh->eattr |= ZDROnly;
152 	zh->off = Boffset(bout);
153 
154 	if(dir.mode & CHDIR){
155 		zh->eattr |= ZDDir;
156 		zh->meth = 0;
157 		zipDir(bout, fd, zh, stdout);
158 	}else{
159 		zh->meth = 8;
160 		if(stdout)
161 			zh->flags |= ZTrailInfo;
162 		off = Boffset(bout);
163 		header(bout, zh);
164 
165 		crc = 0;
166 		eof = 0;
167 		totr = 0;
168 		totw = 0;
169 		if(!deflate(bout, zwrite, (void*)fd, crcread, level, debug))
170 			error("deflate failed: %r");
171 
172 		zh->csize = totw;
173 		zh->uncsize = totr;
174 		zh->crc = crc;
175 		trailer(bout, zh, off);
176 	}
177 	close(fd);
178 
179 }
180 
181 static void
182 zipDir(Biobuf *bout, int fd, ZipHead *zh, int stdout)
183 {
184 	Dir dirs[NDirs];
185 	char *file, *pfile;
186 	int i, nf, nd;
187 
188 	nf = strlen(zh->file) + 1;
189 	if(strcmp(zh->file, ".") == 0){
190 		nzheads--;
191 		free(zh->file);
192 		pfile = "";
193 		nf = 1;
194 	}else{
195 		nf++;
196 		pfile = malloc(nf);
197 		if(pfile == nil)
198 			error("out of memory");
199 		snprint(pfile, nf, "%s/", zh->file);
200 		free(zh->file);
201 		zh->file = pfile;
202 		header(bout, zh);
203 	}
204 
205 	nf += NAMELEN;
206 	file = malloc(nf);
207 	if(file == nil)
208 		error("out of memory");
209 	while((nd = dirread(fd, dirs, sizeof(Dir) * NDirs)) >= sizeof(Dir)){
210 		nd /= sizeof(Dir);
211 		for(i = 0; i < nd; i++){
212 			snprint(file, nf, "%s%s", pfile, dirs[i].name);
213 			zip(bout, file, stdout);
214 		}
215 	}
216 }
217 
218 static void
219 header(Biobuf *bout, ZipHead *zh)
220 {
221 	int flen;
222 
223 	if(verbose)
224 		fprint(2, "adding %s\n", zh->file);
225 	put4(bout, ZHeader);
226 	put1(bout, zh->extvers);
227 	put1(bout, zh->extos);
228 	put2(bout, zh->flags);
229 	put2(bout, zh->meth);
230 	put2(bout, zh->modtime);
231 	put2(bout, zh->moddate);
232 	put4(bout, zh->crc);
233 	put4(bout, zh->csize);
234 	put4(bout, zh->uncsize);
235 	flen = strlen(zh->file);
236 	put2(bout, flen);
237 	put2(bout, 0);
238 	if(Bwrite(bout, zh->file, flen) != flen)
239 		error("write error");
240 }
241 
242 static void
243 trailer(Biobuf *bout, ZipHead *zh, vlong off)
244 {
245 	vlong coff;
246 
247 	coff = -1;
248 	if(!(zh->flags & ZTrailInfo)){
249 		coff = Boffset(bout);
250 		if(Bseek(bout, off + ZHeadCrc, 0) < 0)
251 			error("can't seek in archive");
252 	}
253 	put4(bout, zh->crc);
254 	put4(bout, zh->csize);
255 	put4(bout, zh->uncsize);
256 	if(!(zh->flags & ZTrailInfo)){
257 		if(Bseek(bout, coff, 0) < 0)
258 			error("can't seek in archive");
259 	}
260 }
261 
262 static void
263 cheader(Biobuf *bout, ZipHead *zh)
264 {
265 	int flen;
266 
267 	put4(bout, ZCHeader);
268 	put1(bout, zh->madevers);
269 	put1(bout, zh->madeos);
270 	put1(bout, zh->extvers);
271 	put1(bout, zh->extos);
272 	put2(bout, zh->flags & ~ZTrailInfo);
273 	put2(bout, zh->meth);
274 	put2(bout, zh->modtime);
275 	put2(bout, zh->moddate);
276 	put4(bout, zh->crc);
277 	put4(bout, zh->csize);
278 	put4(bout, zh->uncsize);
279 	flen = strlen(zh->file);
280 	put2(bout, flen);
281 	put2(bout, 0);
282 	put2(bout, 0);
283 	put2(bout, 0);
284 	put2(bout, zh->iattr);
285 	put4(bout, zh->eattr);
286 	put4(bout, zh->off);
287 	if(Bwrite(bout, zh->file, flen) != flen)
288 		error("write error");
289 }
290 
291 static void
292 putCDir(Biobuf *bout)
293 {
294 	vlong hoff, ecoff;
295 	int i;
296 
297 	hoff = Boffset(bout);
298 
299 	for(i = 0; i < nzheads; i++)
300 		cheader(bout, &zheads[i]);
301 
302 	ecoff = Boffset(bout);
303 
304 	if(nzheads >= (1 << 16))
305 		error("too many entries in zip file: max %d", (1 << 16) - 1);
306 	put4(bout, ZECHeader);
307 	put2(bout, 0);
308 	put2(bout, 0);
309 	put2(bout, nzheads);
310 	put2(bout, nzheads);
311 	put4(bout, ecoff - hoff);
312 	put4(bout, hoff);
313 	put2(bout, 0);
314 }
315 
316 static int
317 crcread(void *fd, void *buf, int n)
318 {
319 	int nr, m;
320 
321 	nr = 0;
322 	for(; !eof && n > 0; n -= m){
323 		m = read((int)fd, (char*)buf+nr, n);
324 		if(m <= 0){
325 			eof = 1;
326 			break;
327 		}
328 		nr += m;
329 	}
330 	crc = blockcrc(crc, buf, nr);
331 	totr += nr;
332 	return nr;
333 }
334 
335 static int
336 zwrite(void *bout, void *buf, int n)
337 {
338 	if(n != Bwrite(bout, buf, n)){
339 		eof = 1;
340 		return -1;
341 	}
342 	totw += n;
343 	return n;
344 }
345 
346 static void
347 put4(Biobuf *b, ulong v)
348 {
349 	int i;
350 
351 	for(i = 0; i < 4; i++){
352 		if(Bputc(b, v) < 0)
353 			error("write error");
354 		v >>= 8;
355 	}
356 }
357 
358 static void
359 put2(Biobuf *b, int v)
360 {
361 	int i;
362 
363 	for(i = 0; i < 2; i++){
364 		if(Bputc(b, v) < 0)
365 			error("write error");
366 		v >>= 8;
367 	}
368 }
369 
370 static void
371 put1(Biobuf *b, int v)
372 {
373 	if(Bputc(b, v)< 0)
374 		error("unexpected eof reading file information");
375 }
376 
377 static void
378 error(char *fmt, ...)
379 {
380 	char buf[1024];
381 	va_list arg;
382 
383 	va_start(arg, fmt);
384 	doprint(buf, buf+sizeof(buf), fmt, arg);
385 	va_end(arg);
386 	fprint(2, "zip: %s\n", buf);
387 
388 	longjmp(zjmp, 1);
389 }
390