#include "stand.h"
#include <sys/stat.h>
#include <string.h>
#include <zstd.h>
#define ZSTD_BUFSIZE 2048
struct zstd_file
{
int zstdf_rawfd;
ZSTD_DStream *zstdf_strm;
ZSTD_inBuffer zstdf_in;
unsigned char zstdf_inbuf[ZSTD_BUFSIZE];
int zstdf_endseen;
off_t zstdf_total_out;
};
static int zstdf_fill(struct zstd_file *zstdf);
static int zstdf_open(const char *path, struct open_file *f);
static int zstdf_close(struct open_file *f);
static int zstdf_read(struct open_file *f, void *buf, size_t size, size_t *resid);
static off_t zstdf_seek(struct open_file *f, off_t offset, int where);
static int zstdf_stat(struct open_file *f, struct stat *sb);
struct fs_ops zstdfs_fsops = {
.fs_name = "zstd",
.fs_flags = 0,
.fo_open = zstdf_open,
.fo_close = zstdf_close,
.fo_read = zstdf_read,
.fo_write = null_write,
.fo_seek = zstdf_seek,
.fo_stat = zstdf_stat,
.fo_readdir = null_readdir,
};
static int
zstdf_fill(struct zstd_file *zstdf)
{
int result;
int avail_in;
int req;
avail_in = zstdf->zstdf_in.size - zstdf->zstdf_in.pos;
req = ZSTD_BUFSIZE - avail_in;
result = 0;
if (req > 0) {
if (avail_in > 0)
bcopy(zstdf->zstdf_inbuf + zstdf->zstdf_in.pos, zstdf->zstdf_inbuf, avail_in);
result = read(zstdf->zstdf_rawfd, zstdf->zstdf_inbuf + avail_in, req);
zstdf->zstdf_in.src = zstdf->zstdf_inbuf;
zstdf->zstdf_in.pos = 0;
zstdf->zstdf_in.size = avail_in + (result >= 0 ? result : 0);
}
return (result);
}
static const unsigned char zstd_magic[4] = {0x28, 0xb5, 0x2f, 0xfd};
static int
check_header(struct zstd_file *zstdf)
{
if (zstdf->zstdf_in.size - zstdf->zstdf_in.pos < sizeof(zstd_magic) &&
zstdf_fill(zstdf) == -1)
return (1);
if (zstdf->zstdf_in.size - zstdf->zstdf_in.pos < sizeof(zstd_magic))
return (1);
return (memcmp((const unsigned char *)zstdf->zstdf_in.src + zstdf->zstdf_in.pos,
zstd_magic, sizeof(zstd_magic)) != 0);
}
static int
zstdf_open(const char *fname, struct open_file *f)
{
static char *zstdfname;
int rawfd;
struct zstd_file *zstdf;
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);
zstdfname = malloc(strlen(fname) + 5);
if (zstdfname == NULL)
return(ENOMEM);
sprintf(zstdfname, "%s.zst", fname);
rawfd = open(zstdfname, O_RDONLY);
free(zstdfname);
if (rawfd == -1)
return(ENOENT);
if (fstat(rawfd, &sb) < 0) {
printf("zstdf_open: stat failed\n");
close(rawfd);
return(ENOENT);
}
if (!S_ISREG(sb.st_mode)) {
printf("zstdf_open: not a file\n");
close(rawfd);
return(EISDIR);
}
zstdf = malloc(sizeof(struct zstd_file));
if (zstdf == NULL) {
close(rawfd);
return(ENOMEM);
}
bzero(zstdf, sizeof(struct zstd_file));
zstdf->zstdf_rawfd = rawfd;
zstdf->zstdf_in.src = zstdf->zstdf_inbuf;
if (check_header(zstdf)) {
close(zstdf->zstdf_rawfd);
free(zstdf);
return(EFTYPE);
}
zstdf->zstdf_strm = ZSTD_createDStream();
if (zstdf->zstdf_strm == NULL) {
close(zstdf->zstdf_rawfd);
free(zstdf);
return(ENOMEM);
}
if (ZSTD_isError(ZSTD_initDStream(zstdf->zstdf_strm))) {
ZSTD_freeDStream(zstdf->zstdf_strm);
close(zstdf->zstdf_rawfd);
free(zstdf);
return(EIO);
}
f->f_fsdata = zstdf;
return(0);
}
static int
zstdf_close(struct open_file *f)
{
struct zstd_file *zstdf = (struct zstd_file *)f->f_fsdata;
ZSTD_freeDStream(zstdf->zstdf_strm);
close(zstdf->zstdf_rawfd);
free(zstdf);
return(0);
}
static int
zstdf_read(struct open_file *f, void *buf, size_t size, size_t *resid)
{
struct zstd_file *zstdf = (struct zstd_file *)f->f_fsdata;
ZSTD_outBuffer out = { buf, size, 0 };
size_t ret;
while (out.pos < out.size && zstdf->zstdf_endseen == 0) {
if ((zstdf->zstdf_in.pos == zstdf->zstdf_in.size) && (zstdf_fill(zstdf) == -1)) {
printf("zstdf_read: fill error\n");
return(EIO);
}
if (zstdf->zstdf_in.pos == zstdf->zstdf_in.size) {
printf("zstdf_read: unexpected EOF\n");
if (out.pos == 0)
return(EIO);
break;
}
ret = ZSTD_decompressStream(zstdf->zstdf_strm, &out, &zstdf->zstdf_in);
if (ZSTD_isError(ret)) {
printf("zstdf_read: %s\n", ZSTD_getErrorName(ret));
return(EIO);
}
if (ret == 0) {
zstdf->zstdf_endseen = 1;
break;
}
}
zstdf->zstdf_total_out += out.pos;
if (resid != NULL)
*resid = out.size - out.pos;
return(0);
}
static int
zstdf_rewind(struct open_file *f)
{
struct zstd_file *zstdf = (struct zstd_file *)f->f_fsdata;
if (lseek(zstdf->zstdf_rawfd, 0, SEEK_SET) == -1)
return(-1);
zstdf->zstdf_in.src = zstdf->zstdf_inbuf;
zstdf->zstdf_in.pos = 0;
zstdf->zstdf_in.size = 0;
zstdf->zstdf_endseen = 0;
zstdf->zstdf_total_out = 0;
if (ZSTD_isError(ZSTD_initDStream(zstdf->zstdf_strm)))
return(-1);
return(0);
}
static off_t
zstdf_seek(struct open_file *f, off_t offset, int where)
{
struct zstd_file *zstdf = (struct zstd_file *)f->f_fsdata;
off_t target;
char discard[16];
switch (where) {
case SEEK_SET:
target = offset;
break;
case SEEK_CUR:
target = offset + zstdf->zstdf_total_out;
break;
default:
errno = EINVAL;
return(-1);
}
if (target < zstdf->zstdf_total_out && zstdf_rewind(f) != 0)
return(-1);
while (target > zstdf->zstdf_total_out) {
errno = zstdf_read(f, discard, min(sizeof(discard),
target - zstdf->zstdf_total_out), NULL);
if (errno)
return(-1);
if (zstdf->zstdf_endseen)
break;
}
return(zstdf->zstdf_total_out);
}
static int
zstdf_stat(struct open_file *f, struct stat *sb)
{
struct zstd_file *zstdf = (struct zstd_file *)f->f_fsdata;
int result;
if ((result = fstat(zstdf->zstdf_rawfd, sb)) == 0)
sb->st_size = -1;
return(result);
}