freebsd-dev/stand/libsa/pkgfs.c
Simon J. Gerraty 723f904176 Improve interaction of vectx and tftp
On slow platforms, it helps to spread the hashing load
over time so that tftp does not timeout.

Also, some .4th files are too big to fit in cache of pkgfs,
so increase cache size and ensure fully populated.

Reviewed by:	stevek
MFC after:	1 week
Differential Revision: https://reviews.freebsd.org/D24287
2020-04-07 16:56:34 +00:00

823 lines
17 KiB
C

/*-
* Copyright (c) 2007-2014, Juniper Networks, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include "stand.h"
#include <sys/stat.h>
#include <sys/stdint.h>
#include <string.h>
#include <zlib.h>
#ifdef PKGFS_DEBUG
#define DBG(x) printf x
#else
#define DBG(x)
#endif
static int pkg_open(const char *, struct open_file *);
static int pkg_close(struct open_file *);
static int pkg_read(struct open_file *, void *, size_t, size_t *);
static off_t pkg_seek(struct open_file *, off_t, int);
static int pkg_stat(struct open_file *, struct stat *);
static int pkg_readdir(struct open_file *, struct dirent *);
static off_t pkg_atol(const char *, unsigned);
struct fs_ops pkgfs_fsops = {
"pkg",
pkg_open,
pkg_close,
pkg_read,
null_write,
pkg_seek,
pkg_stat,
pkg_readdir
};
#define PKG_BUFSIZE 512
#define PKG_MAXCACHESZ (16384 * 3)
#define PKG_FILEEXT ".tgz"
/*
* Layout of POSIX 'ustar' header.
*/
struct ustar_hdr {
char ut_name[100];
char ut_mode[8];
char ut_uid[8];
char ut_gid[8];
char ut_size[12];
char ut_mtime[12];
char ut_checksum[8];
char ut_typeflag[1];
char ut_linkname[100];
char ut_magic[6]; /* For POSIX: "ustar\0" */
char ut_version[2]; /* For POSIX: "00" */
char ut_uname[32];
char ut_gname[32];
char ut_rdevmajor[8];
char ut_rdevminor[8];
union {
struct {
char prefix[155];
} posix;
struct {
char atime[12];
char ctime[12];
char offset[12];
char longnames[4];
char unused[1];
struct gnu_sparse {
char offset[12];
char numbytes[12];
} sparse[4];
char isextended[1];
char realsize[12];
} gnu;
} u;
u_char __padding[12];
};
struct package;
struct tarfile
{
struct package *tf_pkg;
struct tarfile *tf_next;
struct ustar_hdr tf_hdr;
off_t tf_ofs;
off_t tf_size;
off_t tf_fp;
size_t tf_cachesz;
void *tf_cache;
};
struct package
{
struct package *pkg_chain;
int pkg_fd;
off_t pkg_ofs;
z_stream pkg_zs;
struct tarfile *pkg_first;
struct tarfile *pkg_last;
u_char pkg_buf[PKG_BUFSIZE];
};
static struct package *package = NULL;
static int new_package(int, struct package **);
static int cache_data(struct tarfile *tf, int);
void
pkgfs_cleanup(void)
{
struct package *chain;
struct tarfile *tf, *tfn;
while (package != NULL) {
inflateEnd(&package->pkg_zs);
close(package->pkg_fd);
tf = package->pkg_first;
while (tf != NULL) {
tfn = tf->tf_next;
if (tf->tf_cachesz > 0)
free(tf->tf_cache);
free(tf);
tf = tfn;
}
chain = package->pkg_chain;
free(package);
package = chain;
}
}
int
pkgfs_init(const char *pkgname, struct fs_ops *proto)
{
struct package *pkg;
int error, fd;
pkg = NULL;
if (proto != &pkgfs_fsops)
pkgfs_cleanup();
exclusive_file_system = proto;
fd = open(pkgname, O_RDONLY);
exclusive_file_system = NULL;
if (fd == -1)
return (errno);
error = new_package(fd, &pkg);
if (error) {
close(fd);
return (error);
}
if (pkg == NULL)
return (EDOOFUS);
pkg->pkg_chain = package;
package = pkg;
exclusive_file_system = &pkgfs_fsops;
return (0);
}
static int get_mode(struct tarfile *);
static int get_zipped(struct package *, void *, size_t);
static int new_package(int, struct package **);
static struct tarfile *scan_tarfile(struct package *, struct tarfile *);
static int
pkg_open(const char *fn, struct open_file *f)
{
struct tarfile *tf;
if (fn == NULL || f == NULL)
return (EINVAL);
if (package == NULL)
return (ENXIO);
/*
* We can only read from a package, so reject request to open
* for write-only or read-write.
*/
if (f->f_flags != F_READ)
return (EPERM);
/*
* Scan the file headers for the named file. We stop scanning
* at the first filename that has the .pkg extension. This is
* a package within a package. We assume we have all the files
* we need up-front and without having to dig within nested
* packages.
*
* Note that we preserve streaming properties as much as possible.
*/
while (*fn == '/')
fn++;
/*
* Allow opening of the root directory for use by readdir()
* to support listing files in the package.
*/
if (*fn == '\0') {
f->f_fsdata = NULL;
return (0);
}
tf = scan_tarfile(package, NULL);
while (tf != NULL) {
if (strcmp(fn, tf->tf_hdr.ut_name) == 0) {
f->f_fsdata = tf;
tf->tf_fp = 0; /* Reset the file pointer. */
return (0);
}
tf = scan_tarfile(package, tf);
}
return (errno);
}
static int
pkg_close(struct open_file *f)
{
struct tarfile *tf;
tf = (struct tarfile *)f->f_fsdata;
if (tf == NULL)
return (0);
/*
* Free up the cache if we read all of the file.
*/
if (tf->tf_fp == tf->tf_size && tf->tf_cachesz > 0) {
free(tf->tf_cache);
tf->tf_cachesz = 0;
}
return (0);
}
static int
pkg_read(struct open_file *f, void *buf, size_t size, size_t *res)
{
struct tarfile *tf;
char *p;
off_t fp;
size_t sz;
tf = (struct tarfile *)f->f_fsdata;
if (tf == NULL) {
if (res != NULL)
*res = size;
return (EBADF);
}
if (tf->tf_cachesz == 0)
cache_data(tf, 1);
fp = tf->tf_fp;
p = buf;
sz = 0;
while (size > 0) {
sz = tf->tf_size - fp;
if (fp < tf->tf_cachesz && tf->tf_cachesz < tf->tf_size)
sz = tf->tf_cachesz - fp;
if (size < sz)
sz = size;
if (sz == 0)
break;
if (fp < tf->tf_cachesz) {
/* Satisfy the request from cache. */
memcpy(p, tf->tf_cache + fp, sz);
fp += sz;
p += sz;
size -= sz;
continue;
}
if (get_zipped(tf->tf_pkg, p, sz) == -1) {
sz = -1;
break;
}
fp += sz;
p += sz;
size -= sz;
}
tf->tf_fp = fp;
if (res != NULL)
*res = size;
return ((sz == -1) ? errno : 0);
}
static off_t
pkg_seek(struct open_file *f, off_t ofs, int whence)
{
char buf[512];
struct tarfile *tf;
off_t delta;
off_t nofs;
size_t sz, res;
int error;
tf = (struct tarfile *)f->f_fsdata;
if (tf == NULL) {
errno = EBADF;
return (-1);
}
switch (whence) {
case SEEK_SET:
delta = ofs - tf->tf_fp;
break;
case SEEK_CUR:
delta = ofs;
break;
case SEEK_END:
delta = tf->tf_size - tf->tf_fp + ofs;
break;
default:
errno = EINVAL;
return (-1);
}
if (delta < 0) {
/* seeking backwards - ok if within cache */
if (tf->tf_cachesz > 0 && tf->tf_fp <= tf->tf_cachesz) {
nofs = tf->tf_fp + delta;
if (nofs >= 0) {
tf->tf_fp = nofs;
return (tf->tf_fp);
}
}
DBG(("%s: negative file seek (%jd)\n", __func__,
(intmax_t)delta));
errno = ESPIPE;
return (-1);
}
while (delta > 0 && tf->tf_fp < tf->tf_size) {
sz = (delta > sizeof(buf)) ? sizeof(buf) : delta;
error = pkg_read(f, buf, sz, &res);
if (error != 0) {
errno = error;
return (-1);
}
delta -= sz - res;
}
return (tf->tf_fp);
}
static int
pkg_stat(struct open_file *f, struct stat *sb)
{
struct tarfile *tf;
tf = (struct tarfile *)f->f_fsdata;
if (tf == NULL)
return (EBADF);
memset(sb, 0, sizeof(*sb));
sb->st_mode = get_mode(tf);
if ((sb->st_mode & S_IFMT) == 0) {
/* tar file bug - assume regular file */
sb->st_mode |= S_IFREG;
}
sb->st_size = tf->tf_size;
sb->st_blocks = (tf->tf_size + 511) / 512;
sb->st_mtime = pkg_atol(tf->tf_hdr.ut_mtime, 12);
sb->st_dev = (off_t)tf->tf_pkg;
sb->st_ino = tf->tf_ofs; /* unique per tf_pkg */
return (0);
}
static int
pkg_readdir(struct open_file *f, struct dirent *d)
{
struct tarfile *tf;
tf = (struct tarfile *)f->f_fsdata;
if (tf != NULL)
return (EBADF);
tf = scan_tarfile(package, NULL);
if (tf == NULL)
return (ENOENT);
d->d_fileno = 0;
d->d_reclen = sizeof(*d);
d->d_type = DT_REG;
memcpy(d->d_name, tf->tf_hdr.ut_name, sizeof(d->d_name));
return (0);
}
/*
* Low-level support functions.
*/
static int
get_byte(struct package *pkg, off_t *op)
{
int c;
if (pkg->pkg_zs.avail_in == 0) {
c = read(pkg->pkg_fd, pkg->pkg_buf, PKG_BUFSIZE);
if (c <= 0)
return (-1);
pkg->pkg_zs.avail_in = c;
pkg->pkg_zs.next_in = pkg->pkg_buf;
}
c = *pkg->pkg_zs.next_in;
pkg->pkg_zs.next_in++;
pkg->pkg_zs.avail_in--;
(*op)++;
return (c);
}
static int
get_zipped(struct package *pkg, void *buf, size_t bufsz)
{
int c;
pkg->pkg_zs.next_out = buf;
pkg->pkg_zs.avail_out = bufsz;
while (pkg->pkg_zs.avail_out) {
if (pkg->pkg_zs.avail_in == 0) {
c = read(pkg->pkg_fd, pkg->pkg_buf, PKG_BUFSIZE);
if (c <= 0) {
errno = EIO;
return (-1);
}
pkg->pkg_zs.avail_in = c;
pkg->pkg_zs.next_in = pkg->pkg_buf;
}
c = inflate(&pkg->pkg_zs, Z_SYNC_FLUSH);
if (c != Z_OK && c != Z_STREAM_END) {
errno = EIO;
return (-1);
}
}
pkg->pkg_ofs += bufsz;
return (0);
}
/**
* @brief
* cache data of a tarfile
*
* @param[in] tf
* tarfile pointer
*
* @param[in] force
* If file size > PKG_MAXCACHESZ, cache that much
*
* @return 0, -1 (errno set to error value)
*/
static int
cache_data(struct tarfile *tf, int force)
{
struct package *pkg;
size_t sz;
if (tf == NULL) {
DBG(("%s: no file to cache data for?\n", __func__));
errno = EINVAL;
return (-1);
}
pkg = tf->tf_pkg;
if (pkg == NULL) {
DBG(("%s: no package associated with file?\n", __func__));
errno = EINVAL;
return (-1);
}
if (tf->tf_cachesz > 0) {
DBG(("%s: data already cached\n", __func__));
errno = EINVAL;
return (-1);
}
if (tf->tf_ofs != pkg->pkg_ofs) {
DBG(("%s: caching after force read of file %s?\n",
__func__, tf->tf_hdr.ut_name));
errno = EINVAL;
return (-1);
}
/* We don't cache everything... */
if (tf->tf_size > PKG_MAXCACHESZ && !force) {
errno = ENOBUFS;
return (-1);
}
sz = tf->tf_size < PKG_MAXCACHESZ ? tf->tf_size : PKG_MAXCACHESZ;
/* All files are padded to a multiple of 512 bytes. */
sz = (sz + 0x1ff) & ~0x1ff;
tf->tf_cache = malloc(sz);
if (tf->tf_cache == NULL) {
DBG(("%s: could not allocate %d bytes\n", __func__, (int)sz));
errno = ENOMEM;
return (-1);
}
tf->tf_cachesz = sz;
return (get_zipped(pkg, tf->tf_cache, sz));
}
/*
* Note that this implementation does not (and should not!) obey
* locale settings; you cannot simply substitute strtol here, since
* it does obey locale.
*/
static off_t
pkg_atol8(const char *p, unsigned char_cnt)
{
int64_t l, limit, last_digit_limit;
int digit, sign, base;
base = 8;
limit = INT64_MAX / base;
last_digit_limit = INT64_MAX % base;
while (*p == ' ' || *p == '\t')
p++;
if (*p == '-') {
sign = -1;
p++;
} else
sign = 1;
l = 0;
digit = *p - '0';
while (digit >= 0 && digit < base && char_cnt-- > 0) {
if (l>limit || (l == limit && digit > last_digit_limit)) {
l = UINT64_MAX; /* Truncate on overflow. */
break;
}
l = (l * base) + digit;
digit = *++p - '0';
}
return (sign < 0) ? -l : l;
}
/*
* Parse a base-256 integer. This is just a straight signed binary
* value in big-endian order, except that the high-order bit is
* ignored. Remember that "int64_t" may or may not be exactly 64
* bits; the implementation here tries to avoid making any assumptions
* about the actual size of an int64_t. It does assume we're using
* twos-complement arithmetic, though.
*/
static int64_t
pkg_atol256(const char *_p, unsigned char_cnt)
{
int64_t l, upper_limit, lower_limit;
const unsigned char *p = (const unsigned char *)_p;
upper_limit = INT64_MAX / 256;
lower_limit = INT64_MIN / 256;
/* Pad with 1 or 0 bits, depending on sign. */
if ((0x40 & *p) == 0x40)
l = (int64_t)-1;
else
l = 0;
l = (l << 6) | (0x3f & *p++);
while (--char_cnt > 0) {
if (l > upper_limit) {
l = INT64_MAX; /* Truncate on overflow */
break;
} else if (l < lower_limit) {
l = INT64_MIN;
break;
}
l = (l << 8) | (0xff & (int64_t)*p++);
}
return (l);
}
static off_t
pkg_atol(const char *p, unsigned char_cnt)
{
/*
* Technically, GNU pkg considers a field to be in base-256
* only if the first byte is 0xff or 0x80.
*/
if (*p & 0x80)
return (pkg_atol256(p, char_cnt));
return (pkg_atol8(p, char_cnt));
}
static int
get_mode(struct tarfile *tf)
{
return (pkg_atol(tf->tf_hdr.ut_mode, sizeof(tf->tf_hdr.ut_mode)));
}
/* GZip flag byte */
#define ASCII_FLAG 0x01 /* bit 0 set: file probably ascii text */
#define HEAD_CRC 0x02 /* bit 1 set: header CRC present */
#define EXTRA_FIELD 0x04 /* bit 2 set: extra field present */
#define ORIG_NAME 0x08 /* bit 3 set: original file name present */
#define COMMENT 0x10 /* bit 4 set: file comment present */
#define RESERVED 0xE0 /* bits 5..7: reserved */
static int
new_package(int fd, struct package **pp)
{
struct package *pkg;
off_t ofs;
int flags, i, error;
pkg = malloc(sizeof(*pkg));
if (pkg == NULL)
return (ENOMEM);
bzero(pkg, sizeof(*pkg));
pkg->pkg_fd = fd;
/*
* Parse the header.
*/
error = EFTYPE;
ofs = 0;
/* Check megic. */
if (get_byte(pkg, &ofs) != 0x1f || get_byte(pkg, &ofs) != 0x8b)
goto fail;
/* Check method. */
if (get_byte(pkg, &ofs) != Z_DEFLATED)
goto fail;
/* Check flags. */
flags = get_byte(pkg, &ofs);
if (flags & RESERVED)
goto fail;
/* Skip time, xflags and OS code. */
for (i = 0; i < 6; i++) {
if (get_byte(pkg, &ofs) == -1)
goto fail;
}
/* Skip extra field. */
if (flags & EXTRA_FIELD) {
i = (get_byte(pkg, &ofs) & 0xff) |
((get_byte(pkg, &ofs) << 8) & 0xff);
while (i-- > 0) {
if (get_byte(pkg, &ofs) == -1)
goto fail;
}
}
/* Skip original file name. */
if (flags & ORIG_NAME) {
do {
i = get_byte(pkg, &ofs);
} while (i != 0 && i != -1);
if (i == -1)
goto fail;
}
/* Print the comment if it's there. */
if (flags & COMMENT) {
while (1) {
i = get_byte(pkg, &ofs);
if (i == -1)
goto fail;
if (i == 0)
break;
putchar(i);
}
}
/* Skip the CRC. */
if (flags & HEAD_CRC) {
if (get_byte(pkg, &ofs) == -1)
goto fail;
if (get_byte(pkg, &ofs) == -1)
goto fail;
}
/*
* Done parsing the ZIP header. Spkgt the inflation engine.
*/
error = inflateInit2(&pkg->pkg_zs, -15);
if (error != Z_OK)
goto fail;
*pp = pkg;
return (0);
fail:
free(pkg);
return (error);
}
static struct tarfile *
scan_tarfile(struct package *pkg, struct tarfile *last)
{
char buf[512];
struct tarfile *cur;
off_t ofs;
size_t sz;
cur = (last != NULL) ? last->tf_next : pkg->pkg_first;
if (cur == NULL) {
ofs = (last != NULL) ? last->tf_ofs + last->tf_size :
pkg->pkg_ofs;
ofs = (ofs + 0x1ff) & ~0x1ff;
/* Check if we've reached EOF. */
if (ofs < pkg->pkg_ofs) {
errno = ENOSPC;
return (NULL);
}
if (ofs != pkg->pkg_ofs) {
if (last != NULL && pkg->pkg_ofs == last->tf_ofs) {
if (cache_data(last, 0) == -1)
return (NULL);
} else {
sz = ofs - pkg->pkg_ofs;
while (sz != 0) {
if (sz > sizeof(buf))
sz = sizeof(buf);
if (get_zipped(pkg, buf, sz) == -1)
return (NULL);
sz = ofs - pkg->pkg_ofs;
}
}
}
cur = malloc(sizeof(*cur));
if (cur == NULL)
return (NULL);
memset(cur, 0, sizeof(*cur));
cur->tf_pkg = pkg;
while (1) {
if (get_zipped(pkg, &cur->tf_hdr,
sizeof(cur->tf_hdr)) == -1) {
free(cur);
return (NULL);
}
/*
* There are always 2 empty blocks appended to
* a PKG. It marks the end of the archive.
*/
if (strncmp(cur->tf_hdr.ut_magic, "ustar", 5) != 0) {
free(cur);
errno = ENOSPC;
return (NULL);
}
cur->tf_ofs = pkg->pkg_ofs;
cur->tf_size = pkg_atol(cur->tf_hdr.ut_size,
sizeof(cur->tf_hdr.ut_size));
if (cur->tf_hdr.ut_name[0] != '+')
break;
/*
* Skip package meta-files.
*/
ofs = cur->tf_ofs + cur->tf_size;
ofs = (ofs + 0x1ff) & ~0x1ff;
while (pkg->pkg_ofs < ofs) {
if (get_zipped(pkg, buf, sizeof(buf)) == -1) {
free(cur);
return (NULL);
}
}
}
if (last != NULL)
last->tf_next = cur;
else
pkg->pkg_first = cur;
pkg->pkg_last = cur;
}
return (cur);
}