1/*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2013 Juniper Networks, Inc.
5 * Copyright (c) 2022-2023 Klara, Inc.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28
29#include "opt_tarfs.h"
30
31#include <sys/param.h>
32#include <sys/systm.h>
33#include <sys/bio.h>
34#include <sys/buf.h>
35#include <sys/dirent.h>
36#include <sys/fcntl.h>
37#include <sys/limits.h>
38#include <sys/mount.h>
39#include <sys/namei.h>
40#include <sys/proc.h>
41#include <sys/vnode.h>
42
43#include <fs/tarfs/tarfs.h>
44#include <fs/tarfs/tarfs_dbg.h>
45
46static int
47tarfs_open(struct vop_open_args *ap)
48{
49	struct tarfs_node *tnp;
50	struct vnode *vp;
51
52	vp = ap->a_vp;
53	MPASS(VOP_ISLOCKED(vp));
54	tnp = VP_TO_TARFS_NODE(vp);
55
56	TARFS_DPF(VNODE, "%s(%p=%s, %o)\n", __func__,
57	    tnp, tnp->name, ap->a_mode);
58
59	if (vp->v_type != VREG && vp->v_type != VDIR)
60		return (EOPNOTSUPP);
61
62	vnode_create_vobject(vp, tnp->size, ap->a_td);
63	return (0);
64}
65
66static int
67tarfs_close(struct vop_close_args *ap)
68{
69#ifdef TARFS_DEBUG
70	struct tarfs_node *tnp;
71	struct vnode *vp;
72
73	vp = ap->a_vp;
74
75	MPASS(VOP_ISLOCKED(vp));
76	tnp = VP_TO_TARFS_NODE(vp);
77
78	TARFS_DPF(VNODE, "%s(%p=%s)\n", __func__,
79	    tnp, tnp->name);
80#else
81	(void)ap;
82#endif
83	return (0);
84}
85
86static int
87tarfs_access(struct vop_access_args *ap)
88{
89	struct tarfs_node *tnp;
90	struct vnode *vp;
91	accmode_t accmode;
92	struct ucred *cred;
93	int error;
94
95	vp = ap->a_vp;
96	accmode = ap->a_accmode;
97	cred = ap->a_cred;
98
99	MPASS(VOP_ISLOCKED(vp));
100	tnp = VP_TO_TARFS_NODE(vp);
101
102	TARFS_DPF(VNODE, "%s(%p=%s, %o)\n", __func__,
103	    tnp, tnp->name, accmode);
104
105	switch (vp->v_type) {
106	case VDIR:
107	case VLNK:
108	case VREG:
109		if ((accmode & VWRITE) != 0)
110			return (EROFS);
111		break;
112	case VBLK:
113	case VCHR:
114	case VFIFO:
115		break;
116	default:
117		return (EINVAL);
118	}
119
120	if ((accmode & VWRITE) != 0)
121		return (EPERM);
122
123	error = vaccess(vp->v_type, tnp->mode, tnp->uid,
124	    tnp->gid, accmode, cred);
125	return (error);
126}
127
128static int
129tarfs_bmap(struct vop_bmap_args *ap)
130{
131	struct tarfs_node *tnp;
132	struct vnode *vp;
133	off_t off;
134	uint64_t iosize;
135	int ra, rb, rmax;
136
137	vp = ap->a_vp;
138	iosize = vp->v_mount->mnt_stat.f_iosize;
139
140	if (ap->a_bop != NULL)
141		*ap->a_bop = &vp->v_bufobj;
142	if (ap->a_bnp != NULL)
143		*ap->a_bnp = ap->a_bn * btodb(iosize);
144	if (ap->a_runp == NULL)
145		return (0);
146
147	tnp = VP_TO_TARFS_NODE(vp);
148	off = ap->a_bn * iosize;
149
150	ra = rb = 0;
151	for (u_int i = 0; i < tnp->nblk; i++) {
152		off_t bs, be;
153
154		bs = tnp->blk[i].o;
155		be = tnp->blk[i].o + tnp->blk[i].l;
156		if (off > be)
157			continue;
158		else if (off < bs) {
159			/* We're in a hole. */
160			ra = bs - off < iosize ?
161			    0 : howmany(bs - (off + iosize), iosize);
162			rb = howmany(off - (i == 0 ?
163			    0 : tnp->blk[i - 1].o + tnp->blk[i - 1].l),
164			    iosize);
165			break;
166		} else {
167			/* We'll be reading from the backing file. */
168			ra = be - off < iosize ?
169			    0 : howmany(be - (off + iosize), iosize);
170			rb = howmany(off - bs, iosize);
171			break;
172		}
173	}
174
175	rmax = vp->v_mount->mnt_iosize_max / iosize - 1;
176	*ap->a_runp = imin(ra, rmax);
177	if (ap->a_runb != NULL)
178		*ap->a_runb = imin(rb, rmax);
179	return (0);
180}
181
182static int
183tarfs_getattr(struct vop_getattr_args *ap)
184{
185	struct tarfs_node *tnp;
186	struct vnode *vp;
187	struct vattr *vap;
188
189	vp = ap->a_vp;
190	vap = ap->a_vap;
191	tnp = VP_TO_TARFS_NODE(vp);
192
193	TARFS_DPF(VNODE, "%s(%p=%s)\n", __func__,
194	    tnp, tnp->name);
195
196	vap->va_type = vp->v_type;
197	vap->va_mode = tnp->mode;
198	vap->va_nlink = tnp->nlink;
199	vap->va_gid = tnp->gid;
200	vap->va_uid = tnp->uid;
201	vap->va_fsid = vp->v_mount->mnt_stat.f_fsid.val[0];
202	vap->va_fileid = tnp->ino;
203	vap->va_size = tnp->size;
204	vap->va_blocksize = vp->v_mount->mnt_stat.f_iosize;
205	vap->va_atime = tnp->atime;
206	vap->va_ctime = tnp->ctime;
207	vap->va_mtime = tnp->mtime;
208	vap->va_birthtime = tnp->birthtime;
209	vap->va_gen = tnp->gen;
210	vap->va_flags = tnp->flags;
211	vap->va_rdev = (vp->v_type == VBLK || vp->v_type == VCHR) ?
212	    tnp->rdev : NODEV;
213	vap->va_bytes = round_page(tnp->physize);
214	vap->va_filerev = 0;
215
216	return (0);
217}
218
219static int
220tarfs_lookup(struct vop_cachedlookup_args *ap)
221{
222	struct tarfs_mount *tmp;
223	struct tarfs_node *dirnode, *parent, *tnp;
224	struct componentname *cnp;
225	struct vnode *dvp, **vpp;
226#ifdef TARFS_DEBUG
227	struct vnode *vp;
228#endif
229	int error;
230
231	dvp = ap->a_dvp;
232	vpp = ap->a_vpp;
233	cnp = ap->a_cnp;
234
235	*vpp = NULLVP;
236	dirnode = VP_TO_TARFS_NODE(dvp);
237	parent = dirnode->parent;
238	tmp = dirnode->tmp;
239	tnp = NULL;
240
241	TARFS_DPF(LOOKUP, "%s(%p=%s, %.*s)\n", __func__,
242	    dirnode, dirnode->name,
243	    (int)cnp->cn_namelen, cnp->cn_nameptr);
244
245	error = VOP_ACCESS(dvp, VEXEC, cnp->cn_cred, curthread);
246	if (error != 0)
247		return (error);
248
249	if (cnp->cn_flags & ISDOTDOT) {
250		/* Do not allow .. on the root node */
251		if (parent == NULL || parent == dirnode)
252			return (ENOENT);
253
254		/* Allocate a new vnode on the matching entry */
255		error = vn_vget_ino(dvp, parent->ino, cnp->cn_lkflags,
256		    vpp);
257		if (error != 0)
258			return (error);
259	} else if (cnp->cn_namelen == 1 && cnp->cn_nameptr[0] == '.') {
260		VREF(dvp);
261		*vpp = dvp;
262#ifdef TARFS_DEBUG
263	} else if (dirnode == dirnode->tmp->root &&
264	    (vp = dirnode->tmp->znode) != NULL &&
265	    cnp->cn_namelen == TARFS_ZIO_NAMELEN &&
266	    memcmp(cnp->cn_nameptr, TARFS_ZIO_NAME, TARFS_ZIO_NAMELEN) == 0) {
267		error = vn_lock(vp, cnp->cn_lkflags);
268		if (error != 0)
269			return (error);
270		vref(vp);
271		*vpp = vp;
272		return (0);
273#endif
274	} else {
275		tnp = tarfs_lookup_node(dirnode, NULL, cnp);
276		if (tnp == NULL) {
277			TARFS_DPF(LOOKUP, "%s(%p=%s, %.*s): file not found\n", __func__,
278			    dirnode, dirnode->name,
279			    (int)cnp->cn_namelen, cnp->cn_nameptr);
280			return (ENOENT);
281		}
282
283		if ((cnp->cn_flags & ISLASTCN) == 0 &&
284		    (tnp->type != VDIR && tnp->type != VLNK))
285			return (ENOTDIR);
286
287		error = VFS_VGET(tmp->vfs, tnp->ino, cnp->cn_lkflags, vpp);
288		if (error != 0)
289			return (error);
290	}
291
292#ifdef	TARFS_DEBUG
293	if (tnp == NULL)
294		tnp = VP_TO_TARFS_NODE(*vpp);
295	TARFS_DPF(LOOKUP, "%s: found vnode %p, tarfs_node %p\n", __func__,
296	    *vpp, tnp);
297#endif	/* TARFS_DEBUG */
298
299	/* Store the result of the cache if MAKEENTRY is specified in flags */
300	if ((cnp->cn_flags & MAKEENTRY) != 0 && cnp->cn_nameiop != CREATE)
301		cache_enter(dvp, *vpp, cnp);
302
303	return (error);
304}
305
306static int
307tarfs_readdir(struct vop_readdir_args *ap)
308{
309	struct dirent cde = { };
310	struct tarfs_node *current, *tnp;
311	struct vnode *vp;
312	struct uio *uio;
313	int *eofflag;
314	uint64_t **cookies;
315	int *ncookies;
316	off_t off;
317	u_int idx, ndirents;
318	int error;
319
320	vp = ap->a_vp;
321	uio = ap->a_uio;
322	eofflag = ap->a_eofflag;
323	cookies = ap->a_cookies;
324	ncookies = ap->a_ncookies;
325
326	if (vp->v_type != VDIR)
327		return (ENOTDIR);
328
329	tnp = VP_TO_TARFS_NODE(vp);
330	off = uio->uio_offset;
331	current = NULL;
332	ndirents = 0;
333
334	TARFS_DPF(VNODE, "%s(%p=%s, %zu, %zd)\n", __func__,
335	    tnp, tnp->name, uio->uio_offset, uio->uio_resid);
336
337	if (uio->uio_offset == TARFS_COOKIE_EOF) {
338		TARFS_DPF(VNODE, "%s: EOF\n", __func__);
339		return (0);
340	}
341
342	if (uio->uio_offset == TARFS_COOKIE_DOT) {
343		TARFS_DPF(VNODE, "%s: Generating . entry\n", __func__);
344		/* fake . entry */
345		cde.d_fileno = tnp->ino;
346		cde.d_type = DT_DIR;
347		cde.d_namlen = 1;
348		cde.d_name[0] = '.';
349		cde.d_name[1] = '\0';
350		cde.d_reclen = GENERIC_DIRSIZ(&cde);
351		if (cde.d_reclen > uio->uio_resid)
352			goto full;
353		dirent_terminate(&cde);
354		error = uiomove(&cde, cde.d_reclen, uio);
355		if (error)
356			return (error);
357		/* next is .. */
358		uio->uio_offset = TARFS_COOKIE_DOTDOT;
359		ndirents++;
360	}
361
362	if (uio->uio_offset == TARFS_COOKIE_DOTDOT) {
363		TARFS_DPF(VNODE, "%s: Generating .. entry\n", __func__);
364		/* fake .. entry */
365		MPASS(tnp->parent != NULL);
366		TARFS_NODE_LOCK(tnp->parent);
367		cde.d_fileno = tnp->parent->ino;
368		TARFS_NODE_UNLOCK(tnp->parent);
369		cde.d_type = DT_DIR;
370		cde.d_namlen = 2;
371		cde.d_name[0] = '.';
372		cde.d_name[1] = '.';
373		cde.d_name[2] = '\0';
374		cde.d_reclen = GENERIC_DIRSIZ(&cde);
375		if (cde.d_reclen > uio->uio_resid)
376			goto full;
377		dirent_terminate(&cde);
378		error = uiomove(&cde, cde.d_reclen, uio);
379		if (error)
380			return (error);
381		/* next is first child */
382		current = TAILQ_FIRST(&tnp->dir.dirhead);
383		if (current == NULL)
384			goto done;
385		uio->uio_offset = current->ino;
386		TARFS_DPF(VNODE, "%s: [%u] setting current node to %p=%s\n",
387		    __func__, ndirents, current, current->name);
388		ndirents++;
389	}
390
391	/* resuming previous call */
392	if (current == NULL) {
393		current = tarfs_lookup_dir(tnp, uio->uio_offset);
394		if (current == NULL) {
395			error = EINVAL;
396			goto done;
397		}
398		uio->uio_offset = current->ino;
399		TARFS_DPF(VNODE, "%s: [%u] setting current node to %p=%s\n",
400		    __func__, ndirents, current, current->name);
401	}
402
403	for (;;) {
404		cde.d_fileno = current->ino;
405		switch (current->type) {
406		case VBLK:
407			cde.d_type = DT_BLK;
408			break;
409		case VCHR:
410			cde.d_type = DT_CHR;
411			break;
412		case VDIR:
413			cde.d_type = DT_DIR;
414			break;
415		case VFIFO:
416			cde.d_type = DT_FIFO;
417			break;
418		case VLNK:
419			cde.d_type = DT_LNK;
420			break;
421		case VREG:
422			cde.d_type = DT_REG;
423			break;
424		default:
425			panic("%s: tarfs_node %p, type %d\n", __func__,
426			    current, current->type);
427		}
428		cde.d_namlen = current->namelen;
429		MPASS(tnp->namelen < sizeof(cde.d_name));
430		(void)memcpy(cde.d_name, current->name, current->namelen);
431		cde.d_name[current->namelen] = '\0';
432		cde.d_reclen = GENERIC_DIRSIZ(&cde);
433		if (cde.d_reclen > uio->uio_resid)
434			goto full;
435		dirent_terminate(&cde);
436		error = uiomove(&cde, cde.d_reclen, uio);
437		if (error != 0)
438			goto done;
439		ndirents++;
440		/* next sibling */
441		current = TAILQ_NEXT(current, dirents);
442		if (current == NULL)
443			goto done;
444		uio->uio_offset = current->ino;
445		TARFS_DPF(VNODE, "%s: [%u] setting current node to %p=%s\n",
446		    __func__, ndirents, current, current->name);
447	}
448full:
449	if (cde.d_reclen > uio->uio_resid) {
450		TARFS_DPF(VNODE, "%s: out of space, returning\n",
451		    __func__);
452		error = (ndirents == 0) ? EINVAL : 0;
453	}
454done:
455	TARFS_DPF(VNODE, "%s: %u entries written\n", __func__, ndirents);
456	TARFS_DPF(VNODE, "%s: saving cache information\n", __func__);
457	if (current == NULL) {
458		uio->uio_offset = TARFS_COOKIE_EOF;
459		tnp->dir.lastcookie = 0;
460		tnp->dir.lastnode = NULL;
461	} else {
462		tnp->dir.lastcookie = current->ino;
463		tnp->dir.lastnode = current;
464	}
465
466	if (eofflag != NULL) {
467		TARFS_DPF(VNODE, "%s: Setting EOF flag\n", __func__);
468		*eofflag = (error == 0 && current == NULL);
469	}
470
471	/* Update for NFS */
472	if (error == 0 && cookies != NULL && ncookies != NULL) {
473		TARFS_DPF(VNODE, "%s: Updating NFS cookies\n", __func__);
474		current = NULL;
475		*cookies = malloc(ndirents * sizeof(off_t), M_TEMP, M_WAITOK);
476		*ncookies = ndirents;
477		for (idx = 0; idx < ndirents; idx++) {
478			if (off == TARFS_COOKIE_DOT)
479				off = TARFS_COOKIE_DOTDOT;
480			else {
481				if (off == TARFS_COOKIE_DOTDOT) {
482					current = TAILQ_FIRST(&tnp->dir.dirhead);
483				} else if (current != NULL) {
484					current = TAILQ_NEXT(current, dirents);
485				} else {
486					current = tarfs_lookup_dir(tnp, off);
487					current = TAILQ_NEXT(current, dirents);
488				}
489				if (current == NULL)
490					off = TARFS_COOKIE_EOF;
491				else
492					off = current->ino;
493			}
494
495			TARFS_DPF(VNODE, "%s: [%u] offset %zu\n", __func__,
496			    idx, off);
497			(*cookies)[idx] = off;
498		}
499		MPASS(uio->uio_offset == off);
500	}
501
502	return (error);
503}
504
505static int
506tarfs_read(struct vop_read_args *ap)
507{
508	struct tarfs_node *tnp;
509	struct uio *uiop;
510	struct vnode *vp;
511	size_t len;
512	off_t resid;
513	int error;
514
515	uiop = ap->a_uio;
516	vp = ap->a_vp;
517
518	if (vp->v_type == VCHR || vp->v_type == VBLK)
519		return (EOPNOTSUPP);
520
521	if (vp->v_type != VREG)
522		return (EISDIR);
523
524	if (uiop->uio_offset < 0)
525		return (EINVAL);
526
527	tnp = VP_TO_TARFS_NODE(vp);
528	error = 0;
529
530	TARFS_DPF(VNODE, "%s(%p=%s, %zu, %zd)\n", __func__,
531	    tnp, tnp->name, uiop->uio_offset, uiop->uio_resid);
532
533	while ((resid = uiop->uio_resid) > 0) {
534		if (tnp->size <= uiop->uio_offset)
535			break;
536		len = MIN(tnp->size - uiop->uio_offset, resid);
537		if (len == 0)
538			break;
539
540		error = tarfs_read_file(tnp, len, uiop);
541		if (error != 0 || resid == uiop->uio_resid)
542			break;
543	}
544
545	return (error);
546}
547
548static int
549tarfs_readlink(struct vop_readlink_args *ap)
550{
551	struct tarfs_node *tnp;
552	struct uio *uiop;
553	struct vnode *vp;
554	int error;
555
556	uiop = ap->a_uio;
557	vp = ap->a_vp;
558
559	MPASS(uiop->uio_offset == 0);
560	MPASS(vp->v_type == VLNK);
561
562	tnp = VP_TO_TARFS_NODE(vp);
563
564	TARFS_DPF(VNODE, "%s(%p=%s)\n", __func__,
565	    tnp, tnp->name);
566
567	error = uiomove(tnp->link.name,
568	    MIN(tnp->size, uiop->uio_resid), uiop);
569
570	return (error);
571}
572
573static int
574tarfs_reclaim(struct vop_reclaim_args *ap)
575{
576	struct tarfs_node *tnp;
577	struct vnode *vp;
578
579	vp = ap->a_vp;
580	tnp = VP_TO_TARFS_NODE(vp);
581
582	vfs_hash_remove(vp);
583
584	TARFS_NODE_LOCK(tnp);
585	tnp->vnode = NULLVP;
586	vp->v_data = NULL;
587	TARFS_NODE_UNLOCK(tnp);
588
589	return (0);
590}
591
592static int
593tarfs_print(struct vop_print_args *ap)
594{
595	struct tarfs_node *tnp;
596	struct vnode *vp;
597
598	vp = ap->a_vp;
599	tnp = VP_TO_TARFS_NODE(vp);
600
601	printf("tag tarfs, tarfs_node %p, links %lu\n",
602	    tnp, (unsigned long)tnp->nlink);
603	printf("\tmode 0%o, owner %d, group %d, size %zd\n",
604	    tnp->mode, tnp->uid, tnp->gid,
605	    tnp->size);
606
607	if (vp->v_type == VFIFO)
608		fifo_printinfo(vp);
609
610	printf("\n");
611
612	return (0);
613}
614
615static int
616tarfs_strategy(struct vop_strategy_args *ap)
617{
618	struct uio auio;
619	struct iovec iov;
620	struct tarfs_node *tnp;
621	struct buf *bp;
622	off_t off;
623	size_t len;
624	int error;
625
626	tnp = VP_TO_TARFS_NODE(ap->a_vp);
627	bp = ap->a_bp;
628	MPASS(bp->b_iocmd == BIO_READ);
629	MPASS(bp->b_iooffset >= 0);
630	MPASS(bp->b_bcount > 0);
631	MPASS(bp->b_bufsize >= bp->b_bcount);
632	TARFS_DPF(VNODE, "%s(%p=%s, %zu, %ld/%ld)\n", __func__, tnp,
633	    tnp->name, (size_t)bp->b_iooffset, bp->b_bcount, bp->b_bufsize);
634	iov.iov_base = bp->b_data;
635	iov.iov_len = bp->b_bcount;
636	off = bp->b_iooffset;
637	len = bp->b_bcount;
638	bp->b_resid = len;
639	if (off > tnp->size) {
640		/* XXX read beyond EOF - figure out correct handling */
641		error = EIO;
642		goto out;
643	}
644	if (off + len > tnp->size) {
645		/* clip to file length */
646		len = tnp->size - off;
647	}
648	auio.uio_iov = &iov;
649	auio.uio_iovcnt = 1;
650	auio.uio_offset = off;
651	auio.uio_resid = len;
652	auio.uio_segflg = UIO_SYSSPACE;
653	auio.uio_rw = UIO_READ;
654	auio.uio_td = curthread;
655	error = tarfs_read_file(tnp, len, &auio);
656	bp->b_resid -= len - auio.uio_resid;
657out:
658	if (error != 0) {
659		bp->b_ioflags |= BIO_ERROR;
660		bp->b_error = error;
661	}
662	bp->b_flags |= B_DONE;
663	return (0);
664}
665
666static int
667tarfs_vptofh(struct vop_vptofh_args *ap)
668{
669	struct tarfs_fid *tfp;
670	struct tarfs_node *tnp;
671
672	tfp = (struct tarfs_fid *)ap->a_fhp;
673	tnp = VP_TO_TARFS_NODE(ap->a_vp);
674
675	tfp->len = sizeof(struct tarfs_fid);
676	tfp->ino = tnp->ino;
677	tfp->gen = tnp->gen;
678
679	return (0);
680}
681
682struct vop_vector tarfs_vnodeops = {
683	.vop_default =		&default_vnodeops,
684
685	.vop_access =		tarfs_access,
686	.vop_bmap =		tarfs_bmap,
687	.vop_cachedlookup =	tarfs_lookup,
688	.vop_close =		tarfs_close,
689	.vop_getattr =		tarfs_getattr,
690	.vop_lookup =		vfs_cache_lookup,
691	.vop_open =		tarfs_open,
692	.vop_print =		tarfs_print,
693	.vop_read =		tarfs_read,
694	.vop_readdir =		tarfs_readdir,
695	.vop_readlink =		tarfs_readlink,
696	.vop_reclaim =		tarfs_reclaim,
697	.vop_strategy =		tarfs_strategy,
698	.vop_vptofh =		tarfs_vptofh,
699};
700VFS_VOP_VECTOR_REGISTER(tarfs_vnodeops);
701