#include "stand.h"
#include <sys/stat.h>
#include <string.h>
#include <xz.h>
#define XZ_BUFSIZE 2048
struct xz_file
{
int xzf_rawfd;
struct xz_dec *xzf_strm;
struct xz_buf xzf_buf;
unsigned char xzf_inbuf[XZ_BUFSIZE];
int xzf_endseen;
off_t xzf_total_out;
};
static int xzf_fill(struct xz_file *xzf);
static int xzf_open(const char *path, struct open_file *f);
static int xzf_close(struct open_file *f);
static int xzf_read(struct open_file *f, void *buf, size_t size, size_t *resid);
static off_t xzf_seek(struct open_file *f, off_t offset, int where);
static int xzf_stat(struct open_file *f, struct stat *sb);
struct fs_ops xzfs_fsops = {
.fs_name = "xz",
.fs_flags = 0,
.fo_open = xzf_open,
.fo_close = xzf_close,
.fo_read = xzf_read,
.fo_write = null_write,
.fo_seek = xzf_seek,
.fo_stat = xzf_stat,
.fo_readdir = null_readdir,
};
static int
xzf_fill(struct xz_file *xzf)
{
int result;
int avail_in;
int req;
avail_in = xzf->xzf_buf.in_size - xzf->xzf_buf.in_pos;
req = XZ_BUFSIZE - avail_in;
result = 0;
if (req > 0) {
if (avail_in > 0)
bcopy(xzf->xzf_inbuf + xzf->xzf_buf.in_pos, xzf->xzf_inbuf, avail_in);
result = read(xzf->xzf_rawfd, xzf->xzf_inbuf + avail_in, req);
xzf->xzf_buf.in = xzf->xzf_inbuf;
xzf->xzf_buf.in_pos = 0;
xzf->xzf_buf.in_size = avail_in + (result >= 0 ? result : 0);
}
return (result);
}
static const unsigned char xz_magic[6] = {0xfd, '7', 'z', 'X', 'Z', 0x00};
static int
check_header(struct xz_file *xzf)
{
if (xzf->xzf_buf.in_size - xzf->xzf_buf.in_pos < (int)sizeof(xz_magic) &&
xzf_fill(xzf) == -1)
return (1);
if (xzf->xzf_buf.in_size - xzf->xzf_buf.in_pos < (int)sizeof(xz_magic))
return (1);
return (memcmp(xzf->xzf_buf.in + xzf->xzf_buf.in_pos, xz_magic,
sizeof(xz_magic)) != 0);
}
static int
xzf_open(const char *fname, struct open_file *f)
{
static char *xzfname;
int rawfd;
struct xz_file *xzf;
char *cp;
struct stat sb;
if (f->f_flags != F_READ)
return(EPERM);
if ((cp = strrchr(fname, '.')) && (!strcmp(cp, ".gz")
|| !strcmp(cp, ".bz2") || !strcmp(cp, ".xz")
|| !strcmp(cp, ".zst") || !strcmp(cp, ".split")))
return(ENOENT);
xzfname = malloc(strlen(fname) + 4);
if (xzfname == NULL)
return(ENOMEM);
sprintf(xzfname, "%s.xz", fname);
rawfd = open(xzfname, O_RDONLY);
free(xzfname);
if (rawfd == -1)
return(ENOENT);
if (fstat(rawfd, &sb) < 0) {
printf("xzf_open: stat failed\n");
close(rawfd);
return(ENOENT);
}
if (!S_ISREG(sb.st_mode)) {
printf("xzf_open: not a file\n");
close(rawfd);
return(EISDIR);
}
xzf = malloc(sizeof(struct xz_file));
if (xzf == NULL) {
close(rawfd);
return(ENOMEM);
}
bzero(xzf, sizeof(struct xz_file));
xzf->xzf_rawfd = rawfd;
xzf->xzf_buf.in = xzf->xzf_inbuf;
xz_crc32_init();
xz_crc64_init();
if (check_header(xzf)) {
close(xzf->xzf_rawfd);
free(xzf);
return(EFTYPE);
}
xzf->xzf_strm = xz_dec_init(XZ_DYNALLOC, (uint32_t)-1);
if (xzf->xzf_strm == NULL) {
close(xzf->xzf_rawfd);
free(xzf);
return(EIO);
}
f->f_fsdata = xzf;
return(0);
}
static int
xzf_close(struct open_file *f)
{
struct xz_file *xzf = (struct xz_file *)f->f_fsdata;
xz_dec_end(xzf->xzf_strm);
close(xzf->xzf_rawfd);
free(xzf);
return(0);
}
static int
xzf_read(struct open_file *f, void *buf, size_t size, size_t *resid)
{
struct xz_file *xzf = (struct xz_file *)f->f_fsdata;
enum xz_ret ret;
xzf->xzf_buf.out = buf;
xzf->xzf_buf.out_pos = 0;
xzf->xzf_buf.out_size = size;
while (xzf->xzf_buf.out_pos < xzf->xzf_buf.out_size && xzf->xzf_endseen == 0) {
if ((xzf->xzf_buf.in_pos == xzf->xzf_buf.in_size) && (xzf_fill(xzf) == -1)) {
printf("xzf_read: fill error\n");
return(EIO);
}
if (xzf->xzf_buf.in_pos == xzf->xzf_buf.in_size) {
printf("xzf_read: unexpected EOF\n");
if (xzf->xzf_buf.out_pos == 0)
return(EIO);
break;
}
ret = xz_dec_run(xzf->xzf_strm, &xzf->xzf_buf);
if (ret == XZ_STREAM_END) {
xzf->xzf_endseen = 1;
break;
}
if (ret != XZ_OK) {
printf("xzf_read: xz_dec_run returned %d\n", ret);
return(EIO);
}
}
xzf->xzf_total_out += xzf->xzf_buf.out_pos;
if (resid != NULL)
*resid = xzf->xzf_buf.out_size - xzf->xzf_buf.out_pos;
return(0);
}
static int
xzf_rewind(struct open_file *f)
{
struct xz_file *xzf = (struct xz_file *)f->f_fsdata;
if (lseek(xzf->xzf_rawfd, 0, SEEK_SET) == -1)
return(-1);
xzf->xzf_buf.in = xzf->xzf_inbuf;
xzf->xzf_buf.in_pos = 0;
xzf->xzf_buf.in_size = 0;
xzf->xzf_endseen = 0;
xzf->xzf_total_out = 0;
xz_dec_reset(xzf->xzf_strm);
return(0);
}
static off_t
xzf_seek(struct open_file *f, off_t offset, int where)
{
struct xz_file *xzf = (struct xz_file *)f->f_fsdata;
off_t target;
char discard[16];
switch (where) {
case SEEK_SET:
target = offset;
break;
case SEEK_CUR:
target = offset + xzf->xzf_total_out;
break;
default:
errno = EINVAL;
return(-1);
}
if (target < xzf->xzf_total_out && xzf_rewind(f) != 0)
return(-1);
while (target > xzf->xzf_total_out) {
errno = xzf_read(f, discard, min(sizeof(discard),
target - xzf->xzf_total_out), NULL);
if (errno)
return(-1);
if (xzf->xzf_endseen)
break;
}
return(xzf->xzf_total_out);
}
static int
xzf_stat(struct open_file *f, struct stat *sb)
{
struct xz_file *xzf = (struct xz_file *)f->f_fsdata;
int result;
if ((result = fstat(xzf->xzf_rawfd, sb)) == 0)
sb->st_size = -1;
return(result);
}