Changeset View
Standalone View
sys/fs/tarfs/tarfs_io.c
- This file was added.
/*- | |||||
* Copyright (c) 2013 Juniper Networks, Inc. | |||||
kib: New files should come with SPDX-License-Identifier there. No idea where to look for the… | |||||
Done Inline Actionspauamma_gundo.com: https://www.freebsd.org/internal/software-license/ | |||||
Done Inline ActionsCan you please be more specific? des: Can you please be more specific? | |||||
* Copyright (c) 2022 Klara, Inc. | |||||
* | |||||
* 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$"); | |||||
Done Inline ActionsI believe the convention now is to not add $FreeBSD$ tags (and sys/cdefs.h) for new files. kib: I believe the convention now is to not add $FreeBSD$ tags (and sys/cdefs.h) for new files. | |||||
#include "opt_tarfs.h" | |||||
#include "opt_zstdio.h" | |||||
#include <sys/param.h> | |||||
#include <sys/systm.h> | |||||
#include <sys/counter.h> | |||||
#include <sys/bio.h> | |||||
#include <sys/buf.h> | |||||
#include <sys/malloc.h> | |||||
#include <sys/mount.h> | |||||
#include <sys/sysctl.h> | |||||
#include <sys/uio.h> | |||||
#include <sys/vnode.h> | |||||
#ifdef ZSTDIO | |||||
#define ZSTD_STATIC_LINKING_ONLY | |||||
#include <contrib/zstd/lib/zstd.h> | |||||
#endif | |||||
#include <fs/tarfs/tarfs.h> | |||||
#include <fs/tarfs/tarfs_dbg.h> | |||||
#ifdef TARFS_DEBUG | |||||
SYSCTL_NODE(_vfs_tarfs, OID_AUTO, zio, CTLFLAG_RD, 0, | |||||
"Tar filesystem decompression layer"); | |||||
COUNTER_U64_DEFINE_EARLY(tarfs_zio_inflated); | |||||
SYSCTL_COUNTER_U64(_vfs_tarfs_zio, OID_AUTO, inflated, CTLFLAG_RD, | |||||
&tarfs_zio_inflated, "Amount of compressed data inflated."); | |||||
COUNTER_U64_DEFINE_EARLY(tarfs_zio_consumed); | |||||
SYSCTL_COUNTER_U64(_vfs_tarfs_zio, OID_AUTO, consumed, CTLFLAG_RD, | |||||
&tarfs_zio_consumed, "Amount of compressed data consumed."); | |||||
static int | |||||
tarfs_sysctl_handle_zio_reset(SYSCTL_HANDLER_ARGS) | |||||
{ | |||||
unsigned int tmp; | |||||
int error; | |||||
Done Inline ActionsIt's OK to consistently either initialize in declaration or to refrain from it, here you do the latter and somewhere else you do the former. pstef: It's OK to consistently either initialize in declaration or to refrain from it, here you do the… | |||||
tmp = 0; | |||||
if ((error = SYSCTL_OUT(req, &tmp, sizeof(tmp))) != 0) | |||||
return (error); | |||||
if (req->newptr != NULL) { | |||||
if ((error = SYSCTL_IN(req, &tmp, sizeof(tmp))) != 0) | |||||
return (error); | |||||
counter_u64_zero(tarfs_zio_inflated); | |||||
counter_u64_zero(tarfs_zio_consumed); | |||||
} | |||||
return (0); | |||||
} | |||||
Done Inline Actionsreturn (0); pstef: return (0); | |||||
SYSCTL_PROC(_vfs_tarfs_zio, OID_AUTO, reset, | |||||
CTLTYPE_INT | CTLFLAG_MPSAFE | CTLFLAG_RW, | |||||
NULL, 0, tarfs_sysctl_handle_zio_reset, "IU", | |||||
"Reset compression counters."); | |||||
#endif | |||||
MALLOC_DEFINE(M_TARFSZSTATE, "tarfs zstate", "tarfs decompression state"); | |||||
MALLOC_DEFINE(M_TARFSZBUF, "tarfs zbuf", "tarfs decompression buffers"); | |||||
#define XZ_MAGIC (uint8_t[]){ 0xfd, 0x37, 0x7a, 0x58, 0x5a } | |||||
#define ZLIB_MAGIC (uint8_t[]){ 0x1f, 0x8b, 0x08 } | |||||
#define ZSTD_MAGIC (uint8_t[]){ 0x28, 0xb5, 0x2f, 0xfd } | |||||
#ifdef ZSTDIO | |||||
struct tarfs_zstd { | |||||
ZSTD_DStream *zds; | |||||
}; | |||||
#endif | |||||
/* XXX review use of curthread / uio_td / td_cred */ | |||||
/* | |||||
* Reads from the tar file according to the provided uio. If the archive | |||||
* is compressed and raw is false, reads the decompressed stream; | |||||
* otherwise, reads directly from the original file. Returns 0 on success | |||||
* and a positive errno value on failure. | |||||
*/ | |||||
int | |||||
tarfs_io_read(struct tarfs_mount *tmp, bool raw, struct uio *uiop) | |||||
{ | |||||
void *rl = NULL; | |||||
off_t off = uiop->uio_offset; | |||||
size_t len = uiop->uio_resid; | |||||
int error; | |||||
if (raw || tmp->znode == NULL) { | |||||
error = vn_lock(tmp->vp, LK_EXCLUSIVE); | |||||
if (error == 0) { | |||||
Done Inline ActionsLK_SHARED is enough for VOP_READ kib: LK_SHARED is enough for VOP_READ | |||||
rl = vn_rangelock_rlock(tmp->vp, off, off + len); | |||||
error = VOP_READ(tmp->vp, uiop, IO_DIRECT, | |||||
Done Inline ActionsThis is wrong lock order, range lock is before vnode lock. kib: This is wrong lock order, range lock is before vnode lock. | |||||
uiop->uio_td->td_ucred); | |||||
vn_rangelock_unlock(tmp->vp, rl); | |||||
VOP_UNLOCK(tmp->vp); | |||||
} | |||||
} else { | |||||
error = vn_lock(tmp->znode, LK_EXCLUSIVE); | |||||
if (error == 0) { | |||||
error = VOP_READ(tmp->znode, uiop, IO_DIRECT, | |||||
uiop->uio_td->td_ucred); | |||||
VOP_UNLOCK(tmp->znode); | |||||
} | |||||
} | |||||
Done Inline Actionsreturn (error); pstef: return (error);
Many other return operators are missing the parens. I'll leave the rest without… | |||||
TARFS_DPF(IO, "%s(%zu, %zu) = %d (resid %zd)\n", __func__, | |||||
(size_t)off, len, error, uiop->uio_resid); | |||||
return (error); | |||||
} | |||||
/* | |||||
* Reads from the tar file into the provided buffer. If the archive is | |||||
* compressed and raw is false, reads the decompressed stream; otherwise, | |||||
* reads directly from the original file. Returns the number of bytes | |||||
* read on success, 0 on EOF, and a negative errno value on failure. | |||||
*/ | |||||
ssize_t | |||||
tarfs_io_read_buf(struct tarfs_mount *tmp, bool raw, | |||||
void *buf, off_t off, size_t len) | |||||
{ | |||||
struct uio auio; | |||||
struct iovec aiov; | |||||
ssize_t res; | |||||
int error; | |||||
if (len == 0) { | |||||
TARFS_DPF(IO, "%s(%zu, %zu) null\n", __func__, | |||||
(size_t)off, len); | |||||
return (0); | |||||
} | |||||
aiov.iov_base = buf; | |||||
aiov.iov_len = len; | |||||
auio.uio_iov = &aiov; | |||||
auio.uio_iovcnt = 1; | |||||
auio.uio_offset = off; | |||||
auio.uio_segflg = UIO_SYSSPACE; | |||||
auio.uio_rw = UIO_READ; | |||||
auio.uio_resid = len; | |||||
auio.uio_td = curthread; | |||||
error = tarfs_io_read(tmp, raw, &auio); | |||||
if (error != 0) { | |||||
TARFS_DPF(IO, "%s(%zu, %zu) error %d\n", __func__, | |||||
(size_t)off, len, error); | |||||
return (-error); | |||||
} | |||||
res = len - auio.uio_resid; | |||||
if (res == 0 && len != 0) { | |||||
TARFS_DPF(IO, "%s(%zu, %zu) eof\n", __func__, | |||||
(size_t)off, len); | |||||
} else { | |||||
TARFS_DPF(IO, "%s(%zu, %zu) read %zd | %*D\n", __func__, | |||||
(size_t)off, len, res, | |||||
(int)(res > 8 ? 8 : res), (uint8_t *)buf, " "); | |||||
} | |||||
return (res); | |||||
} | |||||
#ifdef ZSTDIO | |||||
static void * | |||||
tarfs_zstate_alloc(void *opaque, size_t size) | |||||
{ | |||||
(void)opaque; | |||||
return (malloc(size, M_TARFSZSTATE, M_WAITOK)); | |||||
} | |||||
#endif | |||||
#ifdef ZSTDIO | |||||
static void | |||||
tarfs_zstate_free(void *opaque, void *address) | |||||
{ | |||||
(void)opaque; | |||||
free(address, M_TARFSZSTATE); | |||||
} | |||||
#endif | |||||
#ifdef ZSTDIO | |||||
static ZSTD_customMem tarfs_zstd_mem = { | |||||
tarfs_zstate_alloc, | |||||
tarfs_zstate_free, | |||||
NULL, | |||||
}; | |||||
#endif | |||||
/* | |||||
* Updates the decompression frame index, recording the current input and | |||||
* output offsets in a new index entry, and growing the index if | |||||
* necessary. | |||||
*/ | |||||
static void | |||||
tarfs_zio_update_index(struct tarfs_zio *zio, off_t i, off_t o) | |||||
{ | |||||
if (++zio->curidx >= zio->nidx) { | |||||
if (++zio->nidx > zio->szidx) { | |||||
zio->szidx *= 2; | |||||
zio->idx = realloc(zio->idx, | |||||
zio->szidx * sizeof(*zio->idx), | |||||
M_TARFSZSTATE, M_ZERO | M_WAITOK); | |||||
TARFS_DPF(ALLOC, "%s: resized zio index\n", __func__); | |||||
} | |||||
zio->idx[zio->curidx].i = i; | |||||
zio->idx[zio->curidx].o = o; | |||||
TARFS_DPF(ZIDX, "%s: index %u = i %zu o %zu\n", __func__, | |||||
zio->curidx, (size_t)zio->idx[zio->curidx].i, | |||||
(size_t)zio->idx[zio->curidx].o); | |||||
} | |||||
MPASS(zio->idx[zio->curidx].i == i); | |||||
MPASS(zio->idx[zio->curidx].o == o); | |||||
} | |||||
/* | |||||
* VOP_ACCESS for zio node. | |||||
*/ | |||||
static int | |||||
tarfs_zaccess(struct vop_access_args *ap) | |||||
{ | |||||
struct vnode *vp = ap->a_vp; | |||||
struct tarfs_zio *zio = vp->v_data; | |||||
struct tarfs_mount *tmp = zio->tmp; | |||||
accmode_t accmode = ap->a_accmode; | |||||
int error = EPERM; | |||||
if (accmode == VREAD) | |||||
error = VOP_ACCESS(tmp->vp, accmode, ap->a_cred, ap->a_td); | |||||
TARFS_DPF(ZIO, "%s(%d) = %d\n", __func__, accmode, error); | |||||
Done Inline ActionsLK_SHARED is enough for VOP_ACCESS kib: LK_SHARED is enough for VOP_ACCESS | |||||
Done Inline ActionsThis is not handled despite marked as such. kib: This is not handled despite marked as such. | |||||
return (error); | |||||
} | |||||
/* | |||||
* VOP_GETATTR for zio node. | |||||
*/ | |||||
static int | |||||
tarfs_zgetattr(struct vop_getattr_args *ap) | |||||
{ | |||||
struct vattr va; | |||||
struct vnode *vp = ap->a_vp; | |||||
struct tarfs_zio *zio = vp->v_data; | |||||
struct tarfs_mount *tmp = zio->tmp; | |||||
struct vattr *vap = ap->a_vap; | |||||
int error = 0; | |||||
VATTR_NULL(vap); | |||||
error = VOP_GETATTR(tmp->vp, &va, ap->a_cred); | |||||
if (error == 0) { | |||||
vap->va_type = VREG; | |||||
vap->va_mode = va.va_mode; | |||||
vap->va_nlink = 1; | |||||
vap->va_gid = va.va_gid; | |||||
vap->va_uid = va.va_uid; | |||||
Done Inline ActionsLK_SHARED is enough for VOP_GETATTR kib: LK_SHARED is enough for VOP_GETATTR | |||||
Done Inline ActionsSame. kib: Same. | |||||
vap->va_fsid = vp->v_mount->mnt_stat.f_fsid.val[0]; | |||||
vap->va_fileid = TARFS_ZIOINO; | |||||
vap->va_size = zio->idx[zio->nidx - 1].o; | |||||
Done Inline ActionsIt is better to unlock the vnode right after VOP_GETATTR(), there is no point of copying data from local va under the vnode lock. Don't you leak the vnode lock on VOP_GETATTR() error? kib: It is better to unlock the vnode right after VOP_GETATTR(), there is no point of copying data… | |||||
Done Inline ActionsAgain not handled. kib: Again not handled. | |||||
vap->va_blocksize = vp->v_mount->mnt_stat.f_iosize; | |||||
vap->va_atime = va.va_atime; | |||||
vap->va_ctime = va.va_ctime; | |||||
vap->va_mtime = va.va_mtime; | |||||
vap->va_birthtime = tmp->root->birthtime; | |||||
vap->va_bytes = va.va_bytes; | |||||
} | |||||
TARFS_DPF(ZIO, "%s() = %d\n", __func__, error); | |||||
return (error); | |||||
} | |||||
/* | |||||
* VOP_READ for zio node. | |||||
*/ | |||||
static int | |||||
tarfs_zread(struct vop_read_args *ap) | |||||
{ | |||||
struct vnode *vp = ap->a_vp; | |||||
struct tarfs_zio *zio = vp->v_data; | |||||
struct tarfs_mount *tmp = zio->tmp; | |||||
struct uio *uiop = ap->a_uio; | |||||
struct buf *bp; | |||||
off_t off = uiop->uio_offset; | |||||
size_t len = uiop->uio_resid; | |||||
int error; | |||||
Done Inline ActionsI haven't checked all the types; isn't this missing a cast? pstef: I haven't checked all the types; isn't this missing a cast?
Similarly in other places where… | |||||
error = bread(vp, off / tmp->iosize, | |||||
(off + len + tmp->iosize - 1) / tmp->iosize - off / tmp->iosize, | |||||
uiop->uio_td->td_ucred, &bp); | |||||
if (error == 0) { | |||||
if (off % tmp->iosize + len > bp->b_bufsize) | |||||
len = bp->b_bufsize - off % tmp->iosize; | |||||
error = uiomove(bp->b_data + off % tmp->iosize, len, uiop); | |||||
brelse(bp); | |||||
} | |||||
TARFS_DPF(ZIO, "%s(%zu, %zu) = %d (resid %zd)\n", __func__, | |||||
(size_t)off, len, error, uiop->uio_resid); | |||||
return (error); | |||||
} | |||||
/* | |||||
* VOP_RECLAIM for zio node. | |||||
*/ | |||||
Done Inline Actionsuiomove() under the vnode lock is potential deadlock, when e.g. user sets up the buffer as backed by the vnode itself. Can zio node be mapped in the userspace? Is it writeable? It seems that it is accessible by userspace? kib: uiomove() under the vnode lock is potential deadlock, when e.g. user sets up the buffer as… | |||||
Done Inline ActionsIt's read-only, accessible by userspace for debugging purposes. I can remove that if it causes trouble. des: It's read-only, accessible by userspace for debugging purposes. I can remove that if it causes… | |||||
static int | |||||
tarfs_zreclaim(struct vop_reclaim_args *ap) | |||||
{ | |||||
struct vnode *vp = ap->a_vp; | |||||
TARFS_DPF(ZIO, "%s(%p)\n", __func__, vp); | |||||
vp->v_data = NULL; | |||||
vnode_destroy_vobject(vp); | |||||
cache_purge(vp); | |||||
return (0); | |||||
} | |||||
#ifdef ZSTDIO | |||||
/* | |||||
* VOP_STRATEGY for zio node, zstd edition. | |||||
*/ | |||||
static int | |||||
tarfs_zstrategy_zstd(struct tarfs_zio *zio, struct buf *bp) | |||||
{ | |||||
void *buf = NULL, *rl = NULL; | |||||
struct uio auio; | |||||
struct iovec aiov; | |||||
struct tarfs_mount *tmp = zio->tmp; | |||||
struct tarfs_zstd *zstd = zio->zstd; | |||||
struct vattr va; | |||||
ZSTD_inBuffer zib; | |||||
ZSTD_outBuffer zob; | |||||
off_t ipos, opos; | |||||
size_t ilen, olen; | |||||
size_t zerror; | |||||
off_t off = bp->b_blkno * tmp->iosize; | |||||
size_t len = bp->b_bufsize; | |||||
size_t bsize; | |||||
int error; | |||||
bool reset = false; | |||||
TARFS_DPF(ZIO, "%s: bufsize %ld bcount %ld resid %ld\n", __func__, | |||||
bp->b_bufsize, bp->b_bcount, bp->b_resid); | |||||
/* lock tarball */ | |||||
error = vn_lock(tmp->vp, LK_EXCLUSIVE); | |||||
if (error != 0) { | |||||
goto fail_unlocked; | |||||
} | |||||
/* check size */ | |||||
error = VOP_GETATTR(tmp->vp, &va, bp->b_rcred); | |||||
if (error != 0) { | |||||
goto fail; | |||||
} | |||||
/* do we have to rewind? */ | |||||
if (off < zio->opos) { | |||||
while (zio->curidx > 0 && off < zio->idx[zio->curidx].o) | |||||
zio->curidx--; | |||||
reset = true; | |||||
} | |||||
/* advance to the nearest index entry */ | |||||
if (off > zio->opos) { | |||||
// XXX maybe do a binary search instead | |||||
while (zio->curidx < zio->nidx - 1 && | |||||
off >= zio->idx[zio->curidx + 1].o) { | |||||
zio->curidx++; | |||||
reset = true; | |||||
} | |||||
} | |||||
/* reset the decompression stream if needed */ | |||||
if (reset) { | |||||
zio->ipos = zio->idx[zio->curidx].i; | |||||
zio->opos = zio->idx[zio->curidx].o; | |||||
ZSTD_resetDStream(zstd->zds); | |||||
TARFS_DPF(ZIDX, "%s: skipping to index %u = i %zu o %zu\n", __func__, | |||||
zio->curidx, (size_t)zio->ipos, (size_t)zio->opos); | |||||
} else { | |||||
TARFS_DPF(ZIDX, "%s: continuing at i %zu o %zu\n", __func__, | |||||
(size_t)zio->ipos, (size_t)zio->opos); | |||||
} | |||||
if (zio->ipos >= va.va_size) { | |||||
error = EIO; | |||||
goto fail; | |||||
} | |||||
MPASS(zio->opos <= off); | |||||
bsize = MAXBSIZE; // XXX should probably use ZSTD_CStreamOutSize() | |||||
buf = malloc(bsize, M_TEMP, M_WAITOK); | |||||
zib.src = NULL; | |||||
zib.size = 0; | |||||
zib.pos = 0; | |||||
zob.dst = bp->b_data; | |||||
zob.size = bp->b_bufsize; | |||||
zob.pos = 0; | |||||
bp->b_resid = len; | |||||
error = 0; | |||||
while (bp->b_resid > 0) { | |||||
if (zib.pos == zib.size) { | |||||
Done Inline Actionsmalloc() under the vnode lock is potential deadlock in pagedaemon, which might need to write out or free pages owned by the locked vnode, to satisfy the allocation. BTW, why do you need the exclusive lock there? kib: malloc() under the vnode lock is potential deadlock in pagedaemon, which might need to write… | |||||
/* request data from the underlying file */ | |||||
aiov.iov_base = buf; | |||||
aiov.iov_len = bsize; | |||||
auio.uio_iov = &aiov; | |||||
Done Inline ActionsRangelock should be taken over the whole read operation, otherwise taking it for each partial read defeats the purpose or rangelocking. In other words, rangelock should be moved out of b_resid loop, at least. kib: Rangelock should be taken over the whole read operation, otherwise taking it for each partial… | |||||
Done Inline ActionsWe have no idea how much we're going to read. Do you want me to lock the entire file? des: We have no idea how much we're going to read. Do you want me to lock the entire file? | |||||
Done Inline ActionsYes, you need to rangelock the whole file. Or at least, from the point where you start reading, to the MAX_OFF_T. kib: Yes, you need to rangelock the whole file. Or at least, from the point where you start reading… | |||||
auio.uio_iovcnt = 1; | |||||
auio.uio_offset = zio->ipos; | |||||
Done Inline ActionsDid you tested code with DEBUG_VFS_LOCKS enabled? VOP_READ() requires locked vnode. kib: Did you tested code with DEBUG_VFS_LOCKS enabled? VOP_READ() requires locked vnode. | |||||
Done Inline ActionsI think the vnode is locked at this point, but I may be mistaken... des: I think the vnode is locked at this point, but I may be mistaken... | |||||
auio.uio_segflg = UIO_SYSSPACE; | |||||
auio.uio_rw = UIO_READ; | |||||
auio.uio_resid = aiov.iov_len; | |||||
Done Inline ActionsThis is LoR, rangelock should be taken before the vnode lock. kib: This is LoR, rangelock should be taken before the vnode lock. | |||||
auio.uio_td = curthread; | |||||
rl = vn_rangelock_rlock(tmp->vp, auio.uio_offset, | |||||
auio.uio_offset + auio.uio_resid); | |||||
error = VOP_READ(tmp->vp, &auio, IO_DIRECT, bp->b_rcred); | |||||
vn_rangelock_unlock(tmp->vp, rl); | |||||
if (error != 0) | |||||
goto fail; | |||||
TARFS_DPF(ZIO, "%s: req %zu+%zu got %zu+%zu\n", __func__, | |||||
(size_t)zio->ipos, bsize, | |||||
(size_t)zio->ipos, bsize - auio.uio_resid); | |||||
zib.src = buf; | |||||
zib.size = bsize - auio.uio_resid; | |||||
zib.pos = 0; | |||||
} | |||||
MPASS(zib.pos <= zib.size); | |||||
if (zib.pos == zib.size) { | |||||
TARFS_DPF(ZIO, "%s: end of file after i %zu o %zu\n", __func__, | |||||
(size_t)zio->ipos, (size_t)zio->opos); | |||||
goto fail; | |||||
} | |||||
if (zio->opos < off) { | |||||
/* to be discarded */ | |||||
zob.size = min(off - zio->opos, bp->b_bufsize); | |||||
zob.pos = 0; | |||||
} else { | |||||
zob.size = bp->b_bufsize; | |||||
zob.pos = zio->opos - off; | |||||
Done Inline ActionsNot sure what this is, I assume this wouldn't be committed. pstef: Not sure what this is, I assume this wouldn't be committed. | |||||
Done Inline ActionsIt's very useful when you need it, but too verbose (and a huge performance hit) when you don't. des: It's very useful when you need it, but too verbose (and a huge performance hit) when you don't. | |||||
Done Inline ActionsIf it's useful enough to be committed but disabled, maybe it would be better to enclose it with an #if 0. If you still want to commit it as a comment, at least use the only comment style that is allowed. pstef: If it's useful enough to be committed but disabled, maybe it would be better to enclose it with… | |||||
if (zob.size > zob.pos + bp->b_resid) | |||||
zob.size = zob.pos + bp->b_resid; | |||||
} | |||||
ipos = zib.pos; | |||||
opos = zob.pos; | |||||
/* decompress as much as possible */ | |||||
zerror = ZSTD_decompressStream(zstd->zds, &zob, &zib); | |||||
zio->ipos += ilen = zib.pos - ipos; | |||||
zio->opos += olen = zob.pos - opos; | |||||
if (zio->opos > off) | |||||
bp->b_resid -= olen; | |||||
if (ZSTD_isError(zerror)) { | |||||
TARFS_DPF(ZIO, "%s: inflate failed after i %zu o %zu: %s\n", __func__, | |||||
(size_t)zio->ipos, (size_t)zio->opos, ZSTD_getErrorName(zerror)); | |||||
error = EIO; | |||||
goto fail; | |||||
} | |||||
if (zerror == 0 && olen == 0) { | |||||
TARFS_DPF(ZIO, "%s: end of stream after i %zu o %zu\n", __func__, | |||||
(size_t)zio->ipos, (size_t)zio->opos); | |||||
break; | |||||
} | |||||
if (zerror == 0) { | |||||
TARFS_DPF(ZIO, "%s: end of frame after i %zu o %zu\n", __func__, | |||||
(size_t)zio->ipos, (size_t)zio->opos); | |||||
tarfs_zio_update_index(zio, zio->ipos, zio->opos); | |||||
} | |||||
TARFS_DPF(ZIO, "%s: inflated %zu\n", __func__, olen); | |||||
#ifdef TARFS_DEBUG | |||||
counter_u64_add(tarfs_zio_inflated, olen); | |||||
#endif | |||||
} | |||||
fail: | |||||
VOP_UNLOCK(tmp->vp); | |||||
fail_unlocked: | |||||
if (buf != NULL) | |||||
free(buf, M_TEMP); | |||||
TARFS_DPF(ZIO, "%s(%zu, %zu) = %d (resid %zd)\n", __func__, | |||||
(size_t)off, len, error, bp->b_resid); | |||||
#ifdef TARFS_DEBUG | |||||
counter_u64_add(tarfs_zio_consumed, len - bp->b_resid); | |||||
#endif | |||||
bp->b_flags |= B_DONE; | |||||
bp->b_error = error; | |||||
if (error != 0) { | |||||
bp->b_ioflags |= BIO_ERROR; | |||||
zio->curidx = 0; | |||||
zio->ipos = zio->idx[0].i; | |||||
zio->opos = zio->idx[0].o; | |||||
ZSTD_resetDStream(zstd->zds); | |||||
} | |||||
return (0); | |||||
} | |||||
#endif | |||||
/* | |||||
* VOP_STRATEGY for zio node. | |||||
*/ | |||||
static int | |||||
tarfs_zstrategy(struct vop_strategy_args *ap) | |||||
{ | |||||
struct vnode *vp = ap->a_vp; | |||||
struct buf *bp = ap->a_bp; | |||||
struct tarfs_zio *zio = vp->v_data; | |||||
#ifdef ZSTDIO | |||||
if (zio->zstd != NULL) { | |||||
return (tarfs_zstrategy_zstd(zio, bp)); | |||||
} | |||||
#endif | |||||
bp->b_flags |= B_DONE; | |||||
bp->b_ioflags |= BIO_ERROR; | |||||
bp->b_error = EFTYPE; | |||||
return (0); | |||||
} | |||||
static struct vop_vector tarfs_znodeops = { | |||||
.vop_default = &default_vnodeops, | |||||
.vop_access = tarfs_zaccess, | |||||
.vop_getattr = tarfs_zgetattr, | |||||
.vop_read = tarfs_zread, | |||||
.vop_reclaim = tarfs_zreclaim, | |||||
.vop_strategy = tarfs_zstrategy, | |||||
}; | |||||
VFS_VOP_VECTOR_REGISTER(tarfs_znodeops); | |||||
/* | |||||
* Initializes the decompression layer. | |||||
*/ | |||||
static struct tarfs_zio * | |||||
tarfs_zio_init(struct tarfs_mount *tmp, off_t i, off_t o) | |||||
{ | |||||
struct tarfs_zio *zio; | |||||
struct vnode *zvp; | |||||
zio = malloc(sizeof(*zio), M_TARFSZSTATE, M_ZERO | M_WAITOK); | |||||
TARFS_DPF(ALLOC, "%s: allocated zio\n", __func__); | |||||
zio->tmp = tmp; | |||||
zio->szidx = 128; | |||||
zio->idx = malloc(zio->szidx * sizeof(*zio->idx), M_TARFSZSTATE, | |||||
M_ZERO | M_WAITOK); | |||||
zio->curidx = 0; | |||||
zio->nidx = 1; | |||||
zio->idx[zio->curidx].i = zio->ipos = i; | |||||
zio->idx[zio->curidx].o = zio->opos = o; | |||||
tmp->zio = zio; | |||||
TARFS_DPF(ALLOC, "%s: allocated zio index\n", __func__); | |||||
getnewvnode("tarfs", tmp->vfs, &tarfs_znodeops, &zvp); | |||||
zvp->v_data = zio; | |||||
zvp->v_type = VREG; | |||||
zvp->v_mount = tmp->vfs; | |||||
tmp->znode = zvp; | |||||
TARFS_DPF(ZIO, "%s: created zio node\n", __func__); | |||||
return (zio); | |||||
} | |||||
/* | |||||
* Initializes the I/O layer, including decompression if the signature of | |||||
* a supported compression format is detected. Returns 0 on success and a | |||||
* positive errno value on failure. | |||||
Done Inline Actionsuint8_t? pstef: uint8_t? | |||||
Done Inline ActionsI missed this the first time, but this is a VLA. pstef: I missed this the first time, but this is a VLA. | |||||
*/ | |||||
int | |||||
tarfs_io_init(struct tarfs_mount *tmp) | |||||
{ | |||||
uint8_t *block; | |||||
struct tarfs_zio *zio = NULL; | |||||
ssize_t res; | |||||
int error = 0; | |||||
block = malloc(tmp->iosize, M_TEMP, M_ZERO | M_WAITOK); | |||||
res = tarfs_io_read_buf(tmp, true, block, 0, tmp->iosize); | |||||
if (res < 0) { | |||||
return (-res); | |||||
} | |||||
if (memcmp(block, XZ_MAGIC, sizeof(XZ_MAGIC)) == 0) { | |||||
printf("xz compression not supported\n"); | |||||
error = EOPNOTSUPP; | |||||
goto bad; | |||||
} else if (memcmp(block, ZLIB_MAGIC, sizeof(ZLIB_MAGIC)) == 0) { | |||||
printf("zlib compression not supported\n"); | |||||
error = EOPNOTSUPP; | |||||
goto bad; | |||||
} else if (memcmp(block, ZSTD_MAGIC, sizeof(ZSTD_MAGIC)) == 0) { | |||||
#ifdef ZSTDIO | |||||
zio = tarfs_zio_init(tmp, 0, 0); | |||||
zio->zstd = malloc(sizeof(*zio->zstd), M_TARFSZSTATE, M_WAITOK); | |||||
zio->zstd->zds = ZSTD_createDStream_advanced(tarfs_zstd_mem); | |||||
(void)ZSTD_initDStream(zio->zstd->zds); | |||||
#else | |||||
printf("zstd compression not supported\n"); | |||||
Done Inline ActionsShouldn't we return an error if we don't recognize the magic string? markj: Shouldn't we return an error if we don't recognize the magic string? | |||||
Done Inline ActionsNo. It could be uncompressed. des: No. It could be uncompressed. | |||||
error = EOPNOTSUPP; | |||||
goto bad; | |||||
#endif | |||||
} | |||||
bad: | |||||
free(block, M_TEMP); | |||||
return (error); | |||||
} | |||||
/* | |||||
* Tears down the decompression layer. | |||||
*/ | |||||
static int | |||||
tarfs_zio_fini(struct tarfs_mount *tmp) | |||||
{ | |||||
struct tarfs_zio *zio = tmp->zio; | |||||
int error = 0; | |||||
if (tmp->znode != NULL) { | |||||
error = vn_lock(tmp->znode, LK_EXCLUSIVE); | |||||
if (error != 0) { | |||||
TARFS_DPF(ALLOC, "%s: failed to lock znode", __func__); | |||||
return (error); | |||||
} | |||||
tmp->znode->v_mount = NULL; | |||||
vgone(tmp->znode); | |||||
desAuthorUnsubmitted Done Inline ActionsThere is now a LOR here which will have to be addressed... des: There is now a LOR here which will have to be addressed... | |||||
vunref(tmp->znode); | |||||
VOP_UNLOCK(tmp->znode); | |||||
kibUnsubmitted Done Inline ActionsThese two lines can be written as vput() kib: These two lines can be written as vput() | |||||
tmp->znode = NULL; | |||||
} | |||||
#ifdef ZSTDIO | |||||
if (zio->zstd != NULL) { | |||||
TARFS_DPF(ALLOC, "%s: freeing zstd state\n", __func__); | |||||
ZSTD_freeDStream(zio->zstd->zds); | |||||
free(zio->zstd, M_TARFSZSTATE); | |||||
} | |||||
#endif | |||||
if (zio->idx != NULL) { | |||||
TARFS_DPF(ALLOC, "%s: freeing index\n", __func__); | |||||
free(zio->idx, M_TARFSZSTATE); | |||||
} | |||||
TARFS_DPF(ALLOC, "%s: freeing zio\n", __func__); | |||||
free(zio, M_TARFSZSTATE); | |||||
tmp->zio = NULL; | |||||
return (error); | |||||
} | |||||
/* | |||||
* Tears down the I/O layer, including the decompression layer if | |||||
* applicable. | |||||
*/ | |||||
int | |||||
tarfs_io_fini(struct tarfs_mount *tmp) | |||||
{ | |||||
int error = 0; | |||||
if (tmp->zio != NULL) { | |||||
error = tarfs_zio_fini(tmp); | |||||
} | |||||
return (error); | |||||
} |
New files should come with SPDX-License-Identifier there. No idea where to look for the guidance, might be the committers guide has some info.