xref: /illumos-gate/usr/src/boot/libsa/pkgfs.c (revision 55fea89d)
1*55fea89dSDan Cross /*-
2199767f8SToomas Soome  * Copyright (c) 2007-2014, Juniper Networks, Inc.
3199767f8SToomas Soome  * All rights reserved.
4199767f8SToomas Soome  *
5199767f8SToomas Soome  * Redistribution and use in source and binary forms, with or without
6199767f8SToomas Soome  * modification, are permitted provided that the following conditions
7199767f8SToomas Soome  * are met:
8199767f8SToomas Soome  * 1. Redistributions of source code must retain the above copyright
9199767f8SToomas Soome  *    notice, this list of conditions and the following disclaimer.
10199767f8SToomas Soome  * 2. Redistributions in binary form must reproduce the above copyright
11199767f8SToomas Soome  *    notice, this list of conditions and the following disclaimer in the
12199767f8SToomas Soome  *    documentation and/or other materials provided with the distribution.
13199767f8SToomas Soome  *
14199767f8SToomas Soome  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15199767f8SToomas Soome  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16199767f8SToomas Soome  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17199767f8SToomas Soome  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18199767f8SToomas Soome  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19199767f8SToomas Soome  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20199767f8SToomas Soome  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21199767f8SToomas Soome  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22199767f8SToomas Soome  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23199767f8SToomas Soome  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24199767f8SToomas Soome  * SUCH DAMAGE.
25199767f8SToomas Soome  */
26199767f8SToomas Soome 
27199767f8SToomas Soome #include <sys/cdefs.h>
28199767f8SToomas Soome __FBSDID("$FreeBSD$");
29199767f8SToomas Soome 
30199767f8SToomas Soome #include "stand.h"
31199767f8SToomas Soome 
32199767f8SToomas Soome #include <sys/stat.h>
33199767f8SToomas Soome #include <sys/stdint.h>
34199767f8SToomas Soome #include <string.h>
35199767f8SToomas Soome #include <zlib.h>
36199767f8SToomas Soome 
37199767f8SToomas Soome #ifdef PKGFS_DEBUG
38199767f8SToomas Soome #define	DBG(x)	printf x
39199767f8SToomas Soome #else
40199767f8SToomas Soome #define	DBG(x)
41199767f8SToomas Soome #endif
42199767f8SToomas Soome 
43199767f8SToomas Soome static int   pkg_open(const char *, struct open_file *);
44199767f8SToomas Soome static int   pkg_close(struct open_file *);
45199767f8SToomas Soome static int   pkg_read(struct open_file *, void *, size_t, size_t *);
46199767f8SToomas Soome static off_t pkg_seek(struct open_file *, off_t, int);
47199767f8SToomas Soome static int   pkg_stat(struct open_file *, struct stat *);
48199767f8SToomas Soome static int   pkg_readdir(struct open_file *, struct dirent *);
49199767f8SToomas Soome 
50199767f8SToomas Soome struct fs_ops pkgfs_fsops = {
51199767f8SToomas Soome 	"pkg",
52*55fea89dSDan Cross 	pkg_open,
53*55fea89dSDan Cross 	pkg_close,
54199767f8SToomas Soome 	pkg_read,
55199767f8SToomas Soome 	null_write,
56199767f8SToomas Soome 	pkg_seek,
57199767f8SToomas Soome 	pkg_stat,
58199767f8SToomas Soome 	pkg_readdir
59199767f8SToomas Soome };
60199767f8SToomas Soome 
61199767f8SToomas Soome #define PKG_BUFSIZE	512
62199767f8SToomas Soome #define	PKG_MAXCACHESZ	4096
63199767f8SToomas Soome 
64199767f8SToomas Soome #define	PKG_FILEEXT	".tgz"
65199767f8SToomas Soome 
66199767f8SToomas Soome /*
67199767f8SToomas Soome  * Layout of POSIX 'ustar' header.
68199767f8SToomas Soome  */
69199767f8SToomas Soome struct ustar_hdr {
70199767f8SToomas Soome 	char	ut_name[100];
71199767f8SToomas Soome 	char	ut_mode[8];
72199767f8SToomas Soome 	char	ut_uid[8];
73199767f8SToomas Soome 	char	ut_gid[8];
74199767f8SToomas Soome 	char	ut_size[12];
75199767f8SToomas Soome 	char	ut_mtime[12];
76199767f8SToomas Soome 	char	ut_checksum[8];
77199767f8SToomas Soome 	char	ut_typeflag[1];
78199767f8SToomas Soome 	char	ut_linkname[100];
79199767f8SToomas Soome 	char	ut_magic[6];		/* For POSIX: "ustar\0" */
80199767f8SToomas Soome 	char	ut_version[2];		/* For POSIX: "00" */
81199767f8SToomas Soome 	char	ut_uname[32];
82199767f8SToomas Soome 	char	ut_gname[32];
83199767f8SToomas Soome 	char	ut_rdevmajor[8];
84199767f8SToomas Soome 	char	ut_rdevminor[8];
85199767f8SToomas Soome 	union {
86199767f8SToomas Soome 		struct {
87199767f8SToomas Soome 			char	prefix[155];
88199767f8SToomas Soome 		} posix;
89199767f8SToomas Soome 		struct {
90199767f8SToomas Soome 			char	atime[12];
91199767f8SToomas Soome 			char	ctime[12];
92199767f8SToomas Soome 			char	offset[12];
93199767f8SToomas Soome 			char	longnames[4];
94199767f8SToomas Soome 			char	unused[1];
95199767f8SToomas Soome 			struct gnu_sparse {
96199767f8SToomas Soome 				char	offset[12];
97199767f8SToomas Soome 				char	numbytes[12];
98199767f8SToomas Soome 			} sparse[4];
99199767f8SToomas Soome 			char	isextended[1];
100199767f8SToomas Soome 			char	realsize[12];
101199767f8SToomas Soome 		} gnu;
102199767f8SToomas Soome 	} u;
103199767f8SToomas Soome 	u_char __padding[12];
104199767f8SToomas Soome };
105199767f8SToomas Soome 
106199767f8SToomas Soome struct package;
107199767f8SToomas Soome 
108199767f8SToomas Soome struct tarfile
109199767f8SToomas Soome {
110199767f8SToomas Soome 	struct package *tf_pkg;
111199767f8SToomas Soome 	struct tarfile *tf_next;
112199767f8SToomas Soome 	struct ustar_hdr tf_hdr;
113199767f8SToomas Soome 	off_t	tf_ofs;
114199767f8SToomas Soome 	off_t	tf_size;
115199767f8SToomas Soome 	off_t	tf_fp;
116199767f8SToomas Soome 	size_t	tf_cachesz;
117199767f8SToomas Soome 	void	*tf_cache;
118199767f8SToomas Soome };
119199767f8SToomas Soome 
120199767f8SToomas Soome struct package
121199767f8SToomas Soome {
122199767f8SToomas Soome 	struct package *pkg_chain;
123199767f8SToomas Soome 	int	pkg_fd;
124199767f8SToomas Soome 	off_t	pkg_ofs;
125199767f8SToomas Soome 	z_stream pkg_zs;
126199767f8SToomas Soome 	struct tarfile *pkg_first;
127199767f8SToomas Soome 	struct tarfile *pkg_last;
128199767f8SToomas Soome 	u_char	pkg_buf[PKG_BUFSIZE];
129199767f8SToomas Soome };
130199767f8SToomas Soome 
131199767f8SToomas Soome static struct package *package = NULL;
132199767f8SToomas Soome 
133199767f8SToomas Soome static int new_package(int, struct package **);
134199767f8SToomas Soome 
135199767f8SToomas Soome void
pkgfs_cleanup(void)136199767f8SToomas Soome pkgfs_cleanup(void)
137199767f8SToomas Soome {
138199767f8SToomas Soome 	struct package *chain;
139199767f8SToomas Soome 	struct tarfile *tf, *tfn;
140199767f8SToomas Soome 
141199767f8SToomas Soome 	while (package != NULL) {
142199767f8SToomas Soome 		inflateEnd(&package->pkg_zs);
143199767f8SToomas Soome 		close(package->pkg_fd);
144199767f8SToomas Soome 
145199767f8SToomas Soome 		tf = package->pkg_first;
146199767f8SToomas Soome 		while (tf != NULL) {
147199767f8SToomas Soome 			tfn = tf->tf_next;
148199767f8SToomas Soome 			if (tf->tf_cachesz > 0)
149199767f8SToomas Soome 				free(tf->tf_cache);
150199767f8SToomas Soome 			free(tf);
151199767f8SToomas Soome 			tf = tfn;
152199767f8SToomas Soome 		}
153199767f8SToomas Soome 
154199767f8SToomas Soome 		chain = package->pkg_chain;
155199767f8SToomas Soome 		free(package);
156199767f8SToomas Soome 		package = chain;
157199767f8SToomas Soome 	}
158199767f8SToomas Soome }
159199767f8SToomas Soome 
160199767f8SToomas Soome int
pkgfs_init(const char * pkgname,struct fs_ops * proto)161199767f8SToomas Soome pkgfs_init(const char *pkgname, struct fs_ops *proto)
162199767f8SToomas Soome {
163199767f8SToomas Soome 	struct package *pkg;
164199767f8SToomas Soome 	int error, fd;
165199767f8SToomas Soome 
166199767f8SToomas Soome 	if (proto != &pkgfs_fsops)
167199767f8SToomas Soome 		pkgfs_cleanup();
168199767f8SToomas Soome 
169199767f8SToomas Soome 	exclusive_file_system = proto;
170199767f8SToomas Soome 
171199767f8SToomas Soome 	fd = open(pkgname, O_RDONLY);
172199767f8SToomas Soome 
173199767f8SToomas Soome 	exclusive_file_system = NULL;
174199767f8SToomas Soome 
175199767f8SToomas Soome 	if (fd == -1)
176199767f8SToomas Soome 		return (errno);
177199767f8SToomas Soome 
178199767f8SToomas Soome 	error = new_package(fd, &pkg);
179199767f8SToomas Soome 	if (error) {
180199767f8SToomas Soome 		close(fd);
181199767f8SToomas Soome 		return (error);
182199767f8SToomas Soome 	}
183199767f8SToomas Soome 
184199767f8SToomas Soome 	if (pkg == NULL)
185199767f8SToomas Soome 		return (EDOOFUS);
186199767f8SToomas Soome 
187199767f8SToomas Soome 	pkg->pkg_chain = package;
188199767f8SToomas Soome 	package = pkg;
189199767f8SToomas Soome 	exclusive_file_system = &pkgfs_fsops;
190199767f8SToomas Soome 	return (0);
191199767f8SToomas Soome }
192199767f8SToomas Soome 
193199767f8SToomas Soome static int get_mode(struct tarfile *);
194199767f8SToomas Soome static int get_zipped(struct package *, void *, size_t);
195199767f8SToomas Soome static int new_package(int, struct package **);
196199767f8SToomas Soome static struct tarfile *scan_tarfile(struct package *, struct tarfile *);
197199767f8SToomas Soome 
198199767f8SToomas Soome static int
pkg_open(const char * fn,struct open_file * f)199199767f8SToomas Soome pkg_open(const char *fn, struct open_file *f)
200199767f8SToomas Soome {
201199767f8SToomas Soome 	struct tarfile *tf;
202199767f8SToomas Soome 
203199767f8SToomas Soome 	if (fn == NULL || f == NULL)
204199767f8SToomas Soome 		return (EINVAL);
205199767f8SToomas Soome 
206199767f8SToomas Soome 	if (package == NULL)
207199767f8SToomas Soome 		return (ENXIO);
208199767f8SToomas Soome 
209199767f8SToomas Soome 	/*
210199767f8SToomas Soome 	 * We can only read from a package, so reject request to open
211199767f8SToomas Soome 	 * for write-only or read-write.
212199767f8SToomas Soome 	 */
213199767f8SToomas Soome 	if (f->f_flags != F_READ)
214199767f8SToomas Soome 		return (EPERM);
215199767f8SToomas Soome 
216199767f8SToomas Soome 	/*
217199767f8SToomas Soome 	 * Scan the file headers for the named file. We stop scanning
218199767f8SToomas Soome 	 * at the first filename that has the .pkg extension. This is
219199767f8SToomas Soome 	 * a package within a package. We assume we have all the files
220199767f8SToomas Soome 	 * we need up-front and without having to dig within nested
221199767f8SToomas Soome 	 * packages.
222199767f8SToomas Soome 	 *
223199767f8SToomas Soome 	 * Note that we preserve streaming properties as much as possible.
224199767f8SToomas Soome 	 */
225199767f8SToomas Soome 	while (*fn == '/')
226199767f8SToomas Soome 		fn++;
227199767f8SToomas Soome 
228199767f8SToomas Soome 	/*
229199767f8SToomas Soome 	 * Allow opening of the root directory for use by readdir()
230199767f8SToomas Soome 	 * to support listing files in the package.
231199767f8SToomas Soome 	 */
232199767f8SToomas Soome 	if (*fn == '\0') {
233199767f8SToomas Soome 		f->f_fsdata = NULL;
234199767f8SToomas Soome 		return (0);
235199767f8SToomas Soome 	}
236199767f8SToomas Soome 
237199767f8SToomas Soome 	tf = scan_tarfile(package, NULL);
238199767f8SToomas Soome 	while (tf != NULL) {
239199767f8SToomas Soome 		if (strcmp(fn, tf->tf_hdr.ut_name) == 0) {
240199767f8SToomas Soome 			f->f_fsdata = tf;
241199767f8SToomas Soome 			tf->tf_fp = 0;	/* Reset the file pointer. */
242199767f8SToomas Soome 			return (0);
243199767f8SToomas Soome 		}
244199767f8SToomas Soome 		tf = scan_tarfile(package, tf);
245199767f8SToomas Soome 	}
246199767f8SToomas Soome 	return (errno);
247199767f8SToomas Soome }
248199767f8SToomas Soome 
249199767f8SToomas Soome static int
pkg_close(struct open_file * f)250199767f8SToomas Soome pkg_close(struct open_file *f)
251199767f8SToomas Soome {
252199767f8SToomas Soome 	struct tarfile *tf;
253199767f8SToomas Soome 
254199767f8SToomas Soome 	tf = (struct tarfile *)f->f_fsdata;
255199767f8SToomas Soome 	if (tf == NULL)
256199767f8SToomas Soome 		return (0);
257199767f8SToomas Soome 
258199767f8SToomas Soome 	/*
259199767f8SToomas Soome 	 * Free up the cache if we read all of the file.
260199767f8SToomas Soome 	 */
261199767f8SToomas Soome 	if (tf->tf_fp == tf->tf_size && tf->tf_cachesz > 0) {
262199767f8SToomas Soome 		free(tf->tf_cache);
263199767f8SToomas Soome 		tf->tf_cachesz = 0;
264199767f8SToomas Soome 	}
265199767f8SToomas Soome 	return (0);
266199767f8SToomas Soome }
267199767f8SToomas Soome 
268199767f8SToomas Soome static int
pkg_read(struct open_file * f,void * buf,size_t size,size_t * res)269199767f8SToomas Soome pkg_read(struct open_file *f, void *buf, size_t size, size_t *res)
270199767f8SToomas Soome {
271199767f8SToomas Soome 	struct tarfile *tf;
272199767f8SToomas Soome 	char *p;
273199767f8SToomas Soome 	off_t fp;
274199767f8SToomas Soome 	size_t sz;
275199767f8SToomas Soome 
276199767f8SToomas Soome 	tf = (struct tarfile *)f->f_fsdata;
277199767f8SToomas Soome 	if (tf == NULL) {
278199767f8SToomas Soome 		if (res != NULL)
279199767f8SToomas Soome 			*res = size;
280199767f8SToomas Soome 		return (EBADF);
281199767f8SToomas Soome 	}
282199767f8SToomas Soome 
283199767f8SToomas Soome 	fp = tf->tf_fp;
284199767f8SToomas Soome 	p = buf;
285199767f8SToomas Soome 	sz = 0;
286199767f8SToomas Soome 	while (size > 0) {
287199767f8SToomas Soome 		sz = tf->tf_size - fp;
288199767f8SToomas Soome 		if (fp < tf->tf_cachesz && tf->tf_cachesz < tf->tf_size)
289199767f8SToomas Soome 			sz = tf->tf_cachesz - fp;
290199767f8SToomas Soome 		if (size < sz)
291199767f8SToomas Soome 			sz = size;
292199767f8SToomas Soome 		if (sz == 0)
293199767f8SToomas Soome 			break;
294199767f8SToomas Soome 
295199767f8SToomas Soome 		if (fp < tf->tf_cachesz) {
296199767f8SToomas Soome 			/* Satisfy the request from cache. */
297199767f8SToomas Soome 			memcpy(p, tf->tf_cache + fp, sz);
298199767f8SToomas Soome 			fp += sz;
299199767f8SToomas Soome 			p += sz;
300199767f8SToomas Soome 			size -= sz;
301199767f8SToomas Soome 			continue;
302199767f8SToomas Soome 		}
303199767f8SToomas Soome 
304199767f8SToomas Soome 		if (get_zipped(tf->tf_pkg, p, sz) == -1) {
305199767f8SToomas Soome 			sz = -1;
306199767f8SToomas Soome 			break;
307199767f8SToomas Soome 		}
308199767f8SToomas Soome 
309199767f8SToomas Soome 		fp += sz;
310199767f8SToomas Soome 		p += sz;
311199767f8SToomas Soome 		size -= sz;
312199767f8SToomas Soome 
313199767f8SToomas Soome 		if (tf->tf_cachesz != 0)
314199767f8SToomas Soome 			continue;
315199767f8SToomas Soome 
316199767f8SToomas Soome 		tf->tf_cachesz = (sz <= PKG_MAXCACHESZ) ? sz : PKG_MAXCACHESZ;
317199767f8SToomas Soome 		tf->tf_cache = malloc(tf->tf_cachesz);
318199767f8SToomas Soome 		if (tf->tf_cache != NULL)
319199767f8SToomas Soome 			memcpy(tf->tf_cache, buf, tf->tf_cachesz);
320199767f8SToomas Soome 		else
321199767f8SToomas Soome 			tf->tf_cachesz = 0;
322199767f8SToomas Soome 	}
323199767f8SToomas Soome 
324199767f8SToomas Soome 	tf->tf_fp = fp;
325199767f8SToomas Soome 	if (res != NULL)
326199767f8SToomas Soome 		*res = size;
327199767f8SToomas Soome 	return ((sz == -1) ? errno : 0);
328199767f8SToomas Soome }
329199767f8SToomas Soome 
330199767f8SToomas Soome static off_t
pkg_seek(struct open_file * f,off_t ofs,int whence)331199767f8SToomas Soome pkg_seek(struct open_file *f, off_t ofs, int whence)
332199767f8SToomas Soome {
333199767f8SToomas Soome 	char buf[512];
334199767f8SToomas Soome 	struct tarfile *tf;
335199767f8SToomas Soome 	off_t delta;
336199767f8SToomas Soome 	size_t sz, res;
337199767f8SToomas Soome 	int error;
338199767f8SToomas Soome 
339199767f8SToomas Soome 	tf = (struct tarfile *)f->f_fsdata;
340199767f8SToomas Soome 	if (tf == NULL) {
341199767f8SToomas Soome 		errno = EBADF;
342199767f8SToomas Soome 		return (-1);
343199767f8SToomas Soome 	}
344199767f8SToomas Soome 
345199767f8SToomas Soome 	switch (whence) {
346199767f8SToomas Soome 	case SEEK_SET:
347199767f8SToomas Soome 		delta = ofs - tf->tf_fp;
348199767f8SToomas Soome 		break;
349199767f8SToomas Soome 	case SEEK_CUR:
350199767f8SToomas Soome 		delta = ofs;
351199767f8SToomas Soome 		break;
352199767f8SToomas Soome 	case SEEK_END:
353199767f8SToomas Soome 		delta = tf->tf_size - tf->tf_fp + ofs;
354199767f8SToomas Soome 		break;
355199767f8SToomas Soome 	default:
356199767f8SToomas Soome 		errno = EINVAL;
357199767f8SToomas Soome 		return (-1);
358199767f8SToomas Soome 	}
359199767f8SToomas Soome 
360199767f8SToomas Soome 	if (delta < 0) {
361199767f8SToomas Soome 		DBG(("%s: negative file seek (%jd)\n", __func__,
362199767f8SToomas Soome 		    (intmax_t)delta));
363199767f8SToomas Soome 		errno = ESPIPE;
364199767f8SToomas Soome 		return (-1);
365199767f8SToomas Soome 	}
366199767f8SToomas Soome 
367199767f8SToomas Soome 	while (delta > 0 && tf->tf_fp < tf->tf_size) {
368199767f8SToomas Soome 		sz = (delta > sizeof(buf)) ? sizeof(buf) : delta;
369199767f8SToomas Soome 		error = pkg_read(f, buf, sz, &res);
370199767f8SToomas Soome 		if (error != 0) {
371199767f8SToomas Soome 			errno = error;
372199767f8SToomas Soome 			return (-1);
373199767f8SToomas Soome 		}
374199767f8SToomas Soome 		delta -= sz - res;
375199767f8SToomas Soome 	}
376199767f8SToomas Soome 
377199767f8SToomas Soome 	return (tf->tf_fp);
378199767f8SToomas Soome }
379199767f8SToomas Soome 
380199767f8SToomas Soome static int
pkg_stat(struct open_file * f,struct stat * sb)381199767f8SToomas Soome pkg_stat(struct open_file *f, struct stat *sb)
382199767f8SToomas Soome {
383199767f8SToomas Soome 	struct tarfile *tf;
384199767f8SToomas Soome 
385199767f8SToomas Soome 	tf = (struct tarfile *)f->f_fsdata;
386199767f8SToomas Soome 	if (tf == NULL)
387199767f8SToomas Soome 		return (EBADF);
388199767f8SToomas Soome 	memset(sb, 0, sizeof(*sb));
389199767f8SToomas Soome 	sb->st_mode = get_mode(tf);
390199767f8SToomas Soome 	sb->st_size = tf->tf_size;
391199767f8SToomas Soome 	sb->st_blocks = (tf->tf_size + 511) / 512;
392199767f8SToomas Soome 	return (0);
393199767f8SToomas Soome }
394199767f8SToomas Soome 
395199767f8SToomas Soome static int
pkg_readdir(struct open_file * f,struct dirent * d)396199767f8SToomas Soome pkg_readdir(struct open_file *f, struct dirent *d)
397199767f8SToomas Soome {
398199767f8SToomas Soome 	struct tarfile *tf;
399199767f8SToomas Soome 
400199767f8SToomas Soome 	tf = (struct tarfile *)f->f_fsdata;
401199767f8SToomas Soome 	if (tf != NULL)
402199767f8SToomas Soome 		return (EBADF);
403199767f8SToomas Soome 
404199767f8SToomas Soome 	tf = scan_tarfile(package, NULL);
405199767f8SToomas Soome 	if (tf == NULL)
406199767f8SToomas Soome 		return (ENOENT);
407199767f8SToomas Soome 
408199767f8SToomas Soome 	d->d_fileno = 0;
409199767f8SToomas Soome 	d->d_reclen = sizeof(*d);
410199767f8SToomas Soome 	d->d_type = DT_REG;
411199767f8SToomas Soome 	memcpy(d->d_name, tf->tf_hdr.ut_name, sizeof(d->d_name));
412199767f8SToomas Soome 	return (0);
413199767f8SToomas Soome }
414199767f8SToomas Soome 
415199767f8SToomas Soome /*
416199767f8SToomas Soome  * Low-level support functions.
417199767f8SToomas Soome  */
418199767f8SToomas Soome 
419199767f8SToomas Soome static int
get_byte(struct package * pkg,off_t * op)420199767f8SToomas Soome get_byte(struct package *pkg, off_t *op)
421199767f8SToomas Soome {
422199767f8SToomas Soome 	int c;
423199767f8SToomas Soome 
424199767f8SToomas Soome 	if (pkg->pkg_zs.avail_in == 0) {
425199767f8SToomas Soome 		c = read(pkg->pkg_fd, pkg->pkg_buf, PKG_BUFSIZE);
426199767f8SToomas Soome 		if (c <= 0)
427199767f8SToomas Soome 			return (-1);
428199767f8SToomas Soome 		pkg->pkg_zs.avail_in = c;
429199767f8SToomas Soome 		pkg->pkg_zs.next_in = pkg->pkg_buf;
430199767f8SToomas Soome 	}
431199767f8SToomas Soome 
432199767f8SToomas Soome 	c = *pkg->pkg_zs.next_in;
433199767f8SToomas Soome 	pkg->pkg_zs.next_in++;
434199767f8SToomas Soome 	pkg->pkg_zs.avail_in--;
435199767f8SToomas Soome 	(*op)++;
436199767f8SToomas Soome 	return (c);
437199767f8SToomas Soome }
438199767f8SToomas Soome 
439199767f8SToomas Soome static int
get_zipped(struct package * pkg,void * buf,size_t bufsz)440199767f8SToomas Soome get_zipped(struct package *pkg, void *buf, size_t bufsz)
441199767f8SToomas Soome {
442199767f8SToomas Soome 	int c;
443199767f8SToomas Soome 
444199767f8SToomas Soome 	pkg->pkg_zs.next_out = buf;
445199767f8SToomas Soome 	pkg->pkg_zs.avail_out = bufsz;
446199767f8SToomas Soome 
447199767f8SToomas Soome 	while (pkg->pkg_zs.avail_out) {
448199767f8SToomas Soome 		if (pkg->pkg_zs.avail_in == 0) {
449199767f8SToomas Soome 			c = read(pkg->pkg_fd, pkg->pkg_buf, PKG_BUFSIZE);
450199767f8SToomas Soome 			if (c <= 0) {
451199767f8SToomas Soome 				errno = EIO;
452199767f8SToomas Soome 				return (-1);
453199767f8SToomas Soome 			}
454199767f8SToomas Soome 			pkg->pkg_zs.avail_in = c;
455199767f8SToomas Soome 			pkg->pkg_zs.next_in = pkg->pkg_buf;
456199767f8SToomas Soome 		}
457199767f8SToomas Soome 
458199767f8SToomas Soome 		c = inflate(&pkg->pkg_zs, Z_SYNC_FLUSH);
459199767f8SToomas Soome 		if (c != Z_OK && c != Z_STREAM_END) {
460199767f8SToomas Soome 			errno = EIO;
461199767f8SToomas Soome 			return (-1);
462199767f8SToomas Soome 		}
463199767f8SToomas Soome 	}
464199767f8SToomas Soome 
465199767f8SToomas Soome 	pkg->pkg_ofs += bufsz;
466199767f8SToomas Soome 	return (0);
467199767f8SToomas Soome }
468199767f8SToomas Soome 
469199767f8SToomas Soome static int
cache_data(struct tarfile * tf)470199767f8SToomas Soome cache_data(struct tarfile *tf)
471199767f8SToomas Soome {
472199767f8SToomas Soome 	struct package *pkg;
473199767f8SToomas Soome 	size_t sz;
474199767f8SToomas Soome 
475199767f8SToomas Soome 	if (tf == NULL) {
476199767f8SToomas Soome 		DBG(("%s: no file to cache data for?\n", __func__));
477199767f8SToomas Soome 		errno = EINVAL;
478199767f8SToomas Soome 		return (-1);
479199767f8SToomas Soome 	}
480199767f8SToomas Soome 
481199767f8SToomas Soome 	pkg = tf->tf_pkg;
482199767f8SToomas Soome 	if (pkg == NULL) {
483199767f8SToomas Soome 		DBG(("%s: no package associated with file?\n", __func__));
484199767f8SToomas Soome 		errno = EINVAL;
485199767f8SToomas Soome 		return (-1);
486199767f8SToomas Soome 	}
487199767f8SToomas Soome 
488199767f8SToomas Soome 	if (tf->tf_ofs != pkg->pkg_ofs) {
489199767f8SToomas Soome 		DBG(("%s: caching after partial read of file %s?\n",
490199767f8SToomas Soome 		    __func__, tf->tf_hdr.ut_name));
491199767f8SToomas Soome 		errno = EINVAL;
492199767f8SToomas Soome 		return (-1);
493199767f8SToomas Soome 	}
494199767f8SToomas Soome 
495199767f8SToomas Soome 	/* We don't cache everything... */
496199767f8SToomas Soome 	if (tf->tf_size > PKG_MAXCACHESZ) {
497199767f8SToomas Soome 		errno = ENOMEM;
498199767f8SToomas Soome 		return (-1);
499199767f8SToomas Soome 	}
500199767f8SToomas Soome 
501199767f8SToomas Soome 	/* All files are padded to a multiple of 512 bytes. */
502199767f8SToomas Soome 	sz = (tf->tf_size + 0x1ff) & ~0x1ff;
503199767f8SToomas Soome 
504199767f8SToomas Soome 	tf->tf_cache = malloc(sz);
505199767f8SToomas Soome 	if (tf->tf_cache == NULL) {
506199767f8SToomas Soome 		DBG(("%s: could not allocate %d bytes\n", __func__, (int)sz));
507199767f8SToomas Soome 		errno = ENOMEM;
508199767f8SToomas Soome 		return (-1);
509199767f8SToomas Soome 	}
510199767f8SToomas Soome 
511199767f8SToomas Soome 	tf->tf_cachesz = sz;
512199767f8SToomas Soome 	return (get_zipped(pkg, tf->tf_cache, sz));
513199767f8SToomas Soome }
514199767f8SToomas Soome 
515199767f8SToomas Soome /*
516199767f8SToomas Soome  * Note that this implementation does not (and should not!) obey
517199767f8SToomas Soome  * locale settings; you cannot simply substitute strtol here, since
518199767f8SToomas Soome  * it does obey locale.
519199767f8SToomas Soome  */
520199767f8SToomas Soome static off_t
pkg_atol8(const char * p,unsigned char_cnt)521199767f8SToomas Soome pkg_atol8(const char *p, unsigned char_cnt)
522199767f8SToomas Soome {
523199767f8SToomas Soome         int64_t l, limit, last_digit_limit;
524199767f8SToomas Soome         int digit, sign, base;
525199767f8SToomas Soome 
526199767f8SToomas Soome         base = 8;
527199767f8SToomas Soome         limit = INT64_MAX / base;
528199767f8SToomas Soome         last_digit_limit = INT64_MAX % base;
529199767f8SToomas Soome 
530199767f8SToomas Soome         while (*p == ' ' || *p == '\t')
531199767f8SToomas Soome                 p++;
532199767f8SToomas Soome         if (*p == '-') {
533199767f8SToomas Soome                 sign = -1;
534199767f8SToomas Soome                 p++;
535199767f8SToomas Soome         } else
536199767f8SToomas Soome                 sign = 1;
537199767f8SToomas Soome 
538199767f8SToomas Soome         l = 0;
539199767f8SToomas Soome         digit = *p - '0';
540199767f8SToomas Soome         while (digit >= 0 && digit < base  && char_cnt-- > 0) {
541199767f8SToomas Soome                 if (l>limit || (l == limit && digit > last_digit_limit)) {
542199767f8SToomas Soome                         l = UINT64_MAX; /* Truncate on overflow. */
543199767f8SToomas Soome                         break;
544199767f8SToomas Soome                 }
545199767f8SToomas Soome                 l = (l * base) + digit;
546199767f8SToomas Soome                 digit = *++p - '0';
547199767f8SToomas Soome         }
548199767f8SToomas Soome         return (sign < 0) ? -l : l;
549199767f8SToomas Soome }
550199767f8SToomas Soome 
551199767f8SToomas Soome /*
552199767f8SToomas Soome  * Parse a base-256 integer.  This is just a straight signed binary
553199767f8SToomas Soome  * value in big-endian order, except that the high-order bit is
554199767f8SToomas Soome  * ignored.  Remember that "int64_t" may or may not be exactly 64
555199767f8SToomas Soome  * bits; the implementation here tries to avoid making any assumptions
556199767f8SToomas Soome  * about the actual size of an int64_t.  It does assume we're using
557199767f8SToomas Soome  * twos-complement arithmetic, though.
558199767f8SToomas Soome  */
559199767f8SToomas Soome static int64_t
pkg_atol256(const char * _p,unsigned char_cnt)560199767f8SToomas Soome pkg_atol256(const char *_p, unsigned char_cnt)
561199767f8SToomas Soome {
562199767f8SToomas Soome         int64_t l, upper_limit, lower_limit;
563199767f8SToomas Soome         const unsigned char *p = (const unsigned char *)_p;
564199767f8SToomas Soome 
565199767f8SToomas Soome         upper_limit = INT64_MAX / 256;
566199767f8SToomas Soome         lower_limit = INT64_MIN / 256;
567199767f8SToomas Soome 
568199767f8SToomas Soome         /* Pad with 1 or 0 bits, depending on sign. */
569199767f8SToomas Soome         if ((0x40 & *p) == 0x40)
570199767f8SToomas Soome                 l = (int64_t)-1;
571199767f8SToomas Soome         else
572199767f8SToomas Soome                 l = 0;
573199767f8SToomas Soome         l = (l << 6) | (0x3f & *p++);
574199767f8SToomas Soome         while (--char_cnt > 0) {
575199767f8SToomas Soome                 if (l > upper_limit) {
576199767f8SToomas Soome                         l = INT64_MAX; /* Truncate on overflow */
577199767f8SToomas Soome                         break;
578199767f8SToomas Soome                 } else if (l < lower_limit) {
579199767f8SToomas Soome                         l = INT64_MIN;
580199767f8SToomas Soome                         break;
581199767f8SToomas Soome                 }
582199767f8SToomas Soome                 l = (l << 8) | (0xff & (int64_t)*p++);
583199767f8SToomas Soome         }
584199767f8SToomas Soome         return (l);
585199767f8SToomas Soome }
586199767f8SToomas Soome 
587199767f8SToomas Soome static off_t
pkg_atol(const char * p,unsigned char_cnt)588199767f8SToomas Soome pkg_atol(const char *p, unsigned char_cnt)
589199767f8SToomas Soome {
590199767f8SToomas Soome 	/*
591199767f8SToomas Soome 	 * Technically, GNU pkg considers a field to be in base-256
592199767f8SToomas Soome 	 * only if the first byte is 0xff or 0x80.
593199767f8SToomas Soome 	 */
594199767f8SToomas Soome 	if (*p & 0x80)
595199767f8SToomas Soome 		return (pkg_atol256(p, char_cnt));
596199767f8SToomas Soome 	return (pkg_atol8(p, char_cnt));
597199767f8SToomas Soome }
598199767f8SToomas Soome 
599199767f8SToomas Soome static int
get_mode(struct tarfile * tf)600199767f8SToomas Soome get_mode(struct tarfile *tf)
601199767f8SToomas Soome {
602199767f8SToomas Soome 	return (pkg_atol(tf->tf_hdr.ut_mode, sizeof(tf->tf_hdr.ut_mode)));
603199767f8SToomas Soome }
604199767f8SToomas Soome 
605199767f8SToomas Soome /* GZip flag byte */
606199767f8SToomas Soome #define ASCII_FLAG	0x01 /* bit 0 set: file probably ascii text */
607199767f8SToomas Soome #define HEAD_CRC	0x02 /* bit 1 set: header CRC present */
608199767f8SToomas Soome #define EXTRA_FIELD	0x04 /* bit 2 set: extra field present */
609199767f8SToomas Soome #define ORIG_NAME	0x08 /* bit 3 set: original file name present */
610199767f8SToomas Soome #define COMMENT		0x10 /* bit 4 set: file comment present */
611199767f8SToomas Soome #define RESERVED	0xE0 /* bits 5..7: reserved */
612199767f8SToomas Soome 
613199767f8SToomas Soome static int
new_package(int fd,struct package ** pp)614199767f8SToomas Soome new_package(int fd, struct package **pp)
615199767f8SToomas Soome {
616199767f8SToomas Soome 	struct package *pkg;
617199767f8SToomas Soome 	off_t ofs;
618199767f8SToomas Soome 	int flags, i, error;
619199767f8SToomas Soome 
620199767f8SToomas Soome 	pkg = malloc(sizeof(*pkg));
621199767f8SToomas Soome 	if (pkg == NULL)
622199767f8SToomas Soome 		return (ENOMEM);
623199767f8SToomas Soome 
624199767f8SToomas Soome 	bzero(pkg, sizeof(*pkg));
625199767f8SToomas Soome 	pkg->pkg_fd = fd;
626199767f8SToomas Soome 
627199767f8SToomas Soome 	/*
628199767f8SToomas Soome 	 * Parse the header.
629199767f8SToomas Soome 	 */
630199767f8SToomas Soome 	error = EFTYPE;
631199767f8SToomas Soome 	ofs = 0;
632199767f8SToomas Soome 
633199767f8SToomas Soome 	/* Check megic. */
634199767f8SToomas Soome 	if (get_byte(pkg, &ofs) != 0x1f || get_byte(pkg, &ofs) != 0x8b)
635199767f8SToomas Soome 		goto fail;
636199767f8SToomas Soome 	/* Check method. */
637199767f8SToomas Soome 	if (get_byte(pkg, &ofs) != Z_DEFLATED)
638199767f8SToomas Soome 		goto fail;
639199767f8SToomas Soome 	/* Check flags. */
640199767f8SToomas Soome 	flags = get_byte(pkg, &ofs);
641199767f8SToomas Soome 	if (flags & RESERVED)
642199767f8SToomas Soome 		goto fail;
643199767f8SToomas Soome 
644199767f8SToomas Soome 	/* Skip time, xflags and OS code. */
645199767f8SToomas Soome 	for (i = 0; i < 6; i++) {
646199767f8SToomas Soome 		if (get_byte(pkg, &ofs) == -1)
647199767f8SToomas Soome 			goto fail;
648199767f8SToomas Soome 	}
649199767f8SToomas Soome 
650199767f8SToomas Soome 	/* Skip extra field. */
651199767f8SToomas Soome 	if (flags & EXTRA_FIELD) {
652199767f8SToomas Soome 		i = (get_byte(pkg, &ofs) & 0xff) |
653199767f8SToomas Soome 		    ((get_byte(pkg, &ofs) << 8) & 0xff);
654199767f8SToomas Soome 		while (i-- > 0) {
655199767f8SToomas Soome 			if (get_byte(pkg, &ofs) == -1)
656199767f8SToomas Soome 				goto fail;
657199767f8SToomas Soome 		}
658199767f8SToomas Soome 	}
659199767f8SToomas Soome 
660199767f8SToomas Soome 	/* Skip original file name. */
661199767f8SToomas Soome 	if (flags & ORIG_NAME) {
662199767f8SToomas Soome 		do {
663199767f8SToomas Soome 			i = get_byte(pkg, &ofs);
664199767f8SToomas Soome 		} while (i != 0 && i != -1);
665199767f8SToomas Soome 		if (i == -1)
666199767f8SToomas Soome 			goto fail;
667199767f8SToomas Soome 	}
668199767f8SToomas Soome 
669199767f8SToomas Soome 	/* Print the comment if it's there. */
670199767f8SToomas Soome 	if (flags & COMMENT) {
671199767f8SToomas Soome 		while (1) {
672199767f8SToomas Soome 			i = get_byte(pkg, &ofs);
673199767f8SToomas Soome 			if (i == -1)
674199767f8SToomas Soome 				goto fail;
675199767f8SToomas Soome 			if (i == 0)
676199767f8SToomas Soome 				break;
677199767f8SToomas Soome 			putchar(i);
678199767f8SToomas Soome 		}
679199767f8SToomas Soome 	}
680199767f8SToomas Soome 
681199767f8SToomas Soome 	/* Skip the CRC. */
682199767f8SToomas Soome 	if (flags & HEAD_CRC) {
683199767f8SToomas Soome 		if (get_byte(pkg, &ofs) == -1)
684199767f8SToomas Soome 			goto fail;
685199767f8SToomas Soome 		if (get_byte(pkg, &ofs) == -1)
686199767f8SToomas Soome 			goto fail;
687199767f8SToomas Soome 	}
688199767f8SToomas Soome 
689199767f8SToomas Soome 	/*
690199767f8SToomas Soome 	 * Done parsing the ZIP header. Spkgt the inflation engine.
691199767f8SToomas Soome 	 */
692199767f8SToomas Soome 	error = inflateInit2(&pkg->pkg_zs, -15);
693199767f8SToomas Soome 	if (error != Z_OK)
694199767f8SToomas Soome 		goto fail;
695199767f8SToomas Soome 
696199767f8SToomas Soome 	*pp = pkg;
697199767f8SToomas Soome 	return (0);
698199767f8SToomas Soome 
699199767f8SToomas Soome  fail:
700199767f8SToomas Soome 	free(pkg);
701199767f8SToomas Soome 	return (error);
702199767f8SToomas Soome }
703199767f8SToomas Soome 
704199767f8SToomas Soome static struct tarfile *
scan_tarfile(struct package * pkg,struct tarfile * last)705199767f8SToomas Soome scan_tarfile(struct package *pkg, struct tarfile *last)
706199767f8SToomas Soome {
707199767f8SToomas Soome 	char buf[512];
708199767f8SToomas Soome 	struct tarfile *cur;
709199767f8SToomas Soome 	off_t ofs;
710199767f8SToomas Soome 	size_t sz;
711199767f8SToomas Soome 
712199767f8SToomas Soome 	cur = (last != NULL) ? last->tf_next : pkg->pkg_first;
713199767f8SToomas Soome 	if (cur == NULL) {
714199767f8SToomas Soome 		ofs = (last != NULL) ? last->tf_ofs + last->tf_size :
715199767f8SToomas Soome 		    pkg->pkg_ofs;
716199767f8SToomas Soome 		ofs = (ofs + 0x1ff) & ~0x1ff;
717199767f8SToomas Soome 
718199767f8SToomas Soome 		/* Check if we've reached EOF. */
719199767f8SToomas Soome 		if (ofs < pkg->pkg_ofs) {
720199767f8SToomas Soome 			errno = ENOSPC;
721199767f8SToomas Soome 			return (NULL);
722199767f8SToomas Soome 		}
723199767f8SToomas Soome 
724199767f8SToomas Soome 		if (ofs != pkg->pkg_ofs) {
725199767f8SToomas Soome 			if (last != NULL && pkg->pkg_ofs == last->tf_ofs) {
726199767f8SToomas Soome 				if (cache_data(last) == -1)
727199767f8SToomas Soome 					return (NULL);
728199767f8SToomas Soome 			} else {
729199767f8SToomas Soome 				sz = ofs - pkg->pkg_ofs;
730199767f8SToomas Soome 				while (sz != 0) {
731199767f8SToomas Soome 					if (sz > sizeof(buf))
732199767f8SToomas Soome 						sz = sizeof(buf);
733199767f8SToomas Soome 					if (get_zipped(pkg, buf, sz) == -1)
734199767f8SToomas Soome 						return (NULL);
735199767f8SToomas Soome 					sz = ofs - pkg->pkg_ofs;
736199767f8SToomas Soome 				}
737199767f8SToomas Soome 			}
738199767f8SToomas Soome 		}
739199767f8SToomas Soome 
740199767f8SToomas Soome 		cur = malloc(sizeof(*cur));
741199767f8SToomas Soome 		if (cur == NULL)
742199767f8SToomas Soome 			return (NULL);
743199767f8SToomas Soome 		memset(cur, 0, sizeof(*cur));
744199767f8SToomas Soome 		cur->tf_pkg = pkg;
745199767f8SToomas Soome 
746199767f8SToomas Soome 		while (1) {
747199767f8SToomas Soome 			if (get_zipped(pkg, &cur->tf_hdr,
748199767f8SToomas Soome 			    sizeof(cur->tf_hdr)) == -1) {
749199767f8SToomas Soome 				free(cur);
750199767f8SToomas Soome 				return (NULL);
751199767f8SToomas Soome 			}
752199767f8SToomas Soome 
753199767f8SToomas Soome 			/*
754199767f8SToomas Soome 			 * There are always 2 empty blocks appended to
755199767f8SToomas Soome 			 * a PKG. It marks the end of the archive.
756199767f8SToomas Soome 			 */
757199767f8SToomas Soome 			if (strncmp(cur->tf_hdr.ut_magic, "ustar", 5) != 0) {
758199767f8SToomas Soome 				free(cur);
759199767f8SToomas Soome 				errno = ENOSPC;
760199767f8SToomas Soome 				return (NULL);
761199767f8SToomas Soome 			}
762199767f8SToomas Soome 
763199767f8SToomas Soome 			cur->tf_ofs = pkg->pkg_ofs;
764199767f8SToomas Soome 			cur->tf_size = pkg_atol(cur->tf_hdr.ut_size,
765199767f8SToomas Soome 			    sizeof(cur->tf_hdr.ut_size));
766199767f8SToomas Soome 
767199767f8SToomas Soome 			if (cur->tf_hdr.ut_name[0] != '+')
768199767f8SToomas Soome 				break;
769199767f8SToomas Soome 
770199767f8SToomas Soome 			/*
771199767f8SToomas Soome 			 * Skip package meta-files.
772199767f8SToomas Soome 			 */
773199767f8SToomas Soome 			ofs = cur->tf_ofs + cur->tf_size;
774199767f8SToomas Soome 			ofs = (ofs + 0x1ff) & ~0x1ff;
775199767f8SToomas Soome 			while (pkg->pkg_ofs < ofs) {
776199767f8SToomas Soome 				if (get_zipped(pkg, buf, sizeof(buf)) == -1) {
777199767f8SToomas Soome 					free(cur);
778199767f8SToomas Soome 					return (NULL);
779199767f8SToomas Soome 				}
780199767f8SToomas Soome 			}
781199767f8SToomas Soome 		}
782199767f8SToomas Soome 
783199767f8SToomas Soome 		if (last != NULL)
784199767f8SToomas Soome 			last->tf_next = cur;
785199767f8SToomas Soome 		else
786199767f8SToomas Soome 			pkg->pkg_first = cur;
787199767f8SToomas Soome 		pkg->pkg_last = cur;
788199767f8SToomas Soome 	}
789199767f8SToomas Soome 
790199767f8SToomas Soome 	return (cur);
791199767f8SToomas Soome }
792