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