pseudofs_vnops.c revision 84386
161560Sn_hibma/*-
261560Sn_hibma * Copyright (c) 2001 Dag-Erling Co�dan Sm�rgrav
361560Sn_hibma * All rights reserved.
461560Sn_hibma *
561560Sn_hibma * Redistribution and use in source and binary forms, with or without
661560Sn_hibma * modification, are permitted provided that the following conditions
761560Sn_hibma * are met:
861560Sn_hibma * 1. Redistributions of source code must retain the above copyright
961560Sn_hibma *    notice, this list of conditions and the following disclaimer
1061560Sn_hibma *    in this position and unchanged.
1161560Sn_hibma * 2. Redistributions in binary form must reproduce the above copyright
1261560Sn_hibma *    notice, this list of conditions and the following disclaimer in the
1361560Sn_hibma *    documentation and/or other materials provided with the distribution.
1461560Sn_hibma * 3. The name of the author may not be used to endorse or promote products
1561560Sn_hibma *    derived from this software without specific prior written permission.
1661560Sn_hibma *
1761560Sn_hibma * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
1861560Sn_hibma * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
1961560Sn_hibma * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
2061560Sn_hibma * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
2161560Sn_hibma * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
2261560Sn_hibma * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
2361560Sn_hibma * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
2461560Sn_hibma * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 *
28 *	$FreeBSD: head/sys/fs/pseudofs/pseudofs_vnops.c 84386 2001-10-02 22:22:42Z des $
29 */
30
31#include <sys/param.h>
32#include <sys/kernel.h>
33#include <sys/systm.h>
34#include <sys/ctype.h>
35#include <sys/dirent.h>
36#include <sys/fcntl.h>
37#include <sys/lock.h>
38#include <sys/mount.h>
39#include <sys/mutex.h>
40#include <sys/namei.h>
41#include <sys/proc.h>
42#include <sys/sbuf.h>
43#include <sys/sx.h>
44#include <sys/sysctl.h>
45#include <sys/vnode.h>
46
47#include <fs/pseudofs/pseudofs.h>
48#include <fs/pseudofs/pseudofs_internal.h>
49
50#if 0
51#define PFS_TRACE(foo) \
52	do { \
53		printf("pseudofs: %s(): ", __FUNCTION__); \
54		printf foo ; \
55		printf("\n"); \
56	} while (0)
57#define PFS_RETURN(err) \
58	do { \
59		printf("pseudofs: %s(): returning %d\n", __FUNCTION__, err); \
60		return (err); \
61	} while (0)
62#else
63#define PFS_TRACE(foo) \
64	do { /* nothing */ } while (0)
65#define PFS_RETURN(err) \
66	return (err)
67#endif
68
69/*
70 * Returns non-zero if given file is visible to given process
71 */
72static int
73pfs_visible(struct thread *td, struct pfs_node *pn, pid_t pid)
74{
75	struct proc *proc;
76	int r;
77
78	PFS_TRACE(("%s (pid: %d, req: %d)",
79	    pn->pn_name, pid, td->td_proc->p_pid));
80
81	if (pn->pn_flags & PFS_DISABLED)
82		PFS_RETURN (0);
83
84	r = 1;
85	if (pid != NO_PID) {
86		if ((proc = pfind(pid)) == NULL)
87			PFS_RETURN (0);
88		/* XXX should lock td->td_proc? */
89		if (p_cansee(td->td_proc, proc) != 0 ||
90		    (pn->pn_vis != NULL && !(pn->pn_vis)(td, proc, pn)))
91			r = 0;
92		PROC_UNLOCK(proc);
93	}
94	PFS_RETURN (r);
95}
96
97/*
98 * Verify permissions
99 */
100static int
101pfs_access(struct vop_access_args *va)
102{
103	struct vnode *vn = va->a_vp;
104	struct pfs_vdata *pvd = (struct pfs_vdata *)vn->v_data;
105	struct pfs_node *pn = pvd->pvd_pn;
106	struct vattr vattr;
107	int error;
108
109	PFS_TRACE((pn->pn_name));
110
111	error = VOP_GETATTR(vn, &vattr, va->a_cred, va->a_td);
112	if (error)
113		PFS_RETURN (error);
114	error = vaccess(vn->v_type, vattr.va_mode, vattr.va_uid,
115	    vattr.va_gid, va->a_mode, va->a_cred, NULL);
116	PFS_RETURN (error);
117}
118
119/*
120 * Close a file or directory
121 */
122static int
123pfs_close(struct vop_close_args *va)
124{
125	PFS_RETURN (0);
126}
127
128/*
129 * Get file attributes
130 */
131static int
132pfs_getattr(struct vop_getattr_args *va)
133{
134	struct vnode *vn = va->a_vp;
135	struct pfs_vdata *pvd = (struct pfs_vdata *)vn->v_data;
136	struct pfs_node *pn = pvd->pvd_pn;
137	struct vattr *vap = va->a_vap;
138	struct proc *proc;
139	int error = 0;
140
141	PFS_TRACE((pn->pn_name));
142
143	VATTR_NULL(vap);
144	vap->va_type = vn->v_type;
145	vap->va_fileid = pn->pn_fileno;
146	vap->va_flags = 0;
147	vap->va_blocksize = PAGE_SIZE;
148	vap->va_bytes = vap->va_size = 0;
149	vap->va_fsid = vn->v_mount->mnt_stat.f_fsid.val[0];
150	vap->va_nlink = 1;
151	nanotime(&vap->va_ctime);
152	vap->va_atime = vap->va_mtime = vap->va_ctime;
153
154	switch (pn->pn_type) {
155	case pfstype_procdir:
156	case pfstype_root:
157	case pfstype_dir:
158		vap->va_mode = 0555;
159		break;
160	case pfstype_file:
161	case pfstype_symlink:
162		vap->va_mode = 0444;
163		break;
164	default:
165		printf("shouldn't be here!\n");
166		vap->va_mode = 0;
167		break;
168	}
169
170	if (pvd->pvd_pid != NO_PID) {
171		if ((proc = pfind(pvd->pvd_pid)) == NULL)
172			PFS_RETURN (ENOENT);
173		vap->va_uid = proc->p_ucred->cr_ruid;
174		vap->va_gid = proc->p_ucred->cr_rgid;
175		if (pn->pn_attr != NULL)
176			error = (pn->pn_attr)(va->a_td, proc, pn, vap);
177		PROC_UNLOCK(proc);
178	} else {
179		vap->va_uid = 0;
180		vap->va_gid = 0;
181	}
182
183	PFS_RETURN (error);
184}
185
186/*
187 * Look up a file or directory
188 */
189static int
190pfs_lookup(struct vop_lookup_args *va)
191{
192	struct vnode *vn = va->a_dvp;
193	struct vnode **vpp = va->a_vpp;
194	struct componentname *cnp = va->a_cnp;
195	struct pfs_vdata *pvd = (struct pfs_vdata *)vn->v_data;
196	struct pfs_node *pd = pvd->pvd_pn;
197	struct pfs_node *pn, *pdn = NULL;
198	pid_t pid = pvd->pvd_pid;
199	char *pname;
200	int error, i, namelen;
201
202	PFS_TRACE(("%.*s", (int)cnp->cn_namelen, cnp->cn_nameptr));
203
204	if (vn->v_type != VDIR)
205		PFS_RETURN (ENOTDIR);
206
207	/*
208	 * Don't support DELETE or RENAME.  CREATE is supported so
209	 * that O_CREAT will work, but the lookup will still fail if
210	 * the file does not exist.
211	 */
212	if (cnp->cn_nameiop == DELETE || cnp->cn_nameiop == RENAME)
213		PFS_RETURN (EOPNOTSUPP);
214
215	/* shortcut: check if the name is too long */
216	if (cnp->cn_namelen >= PFS_NAMELEN)
217		PFS_RETURN (ENOENT);
218
219	/* check that parent directory is visisble... */
220	if (!pfs_visible(curthread, pd, pvd->pvd_pid))
221		PFS_RETURN (ENOENT);
222
223	/* self */
224	namelen = cnp->cn_namelen;
225	pname = cnp->cn_nameptr;
226	if (namelen == 1 && *pname == '.') {
227		pn = pd;
228		*vpp = vn;
229		VREF(vn);
230		PFS_RETURN (0);
231	}
232
233	/* parent */
234	if (cnp->cn_flags & ISDOTDOT) {
235		if (pd->pn_type == pfstype_root)
236			PFS_RETURN (EIO);
237		KASSERT(pd->pn_parent, ("non-root directory has no parent"));
238		/*
239		 * This one is tricky.  Descendents of procdir nodes
240		 * inherit their parent's process affinity, but
241		 * there's no easy reverse mapping.  For simplicity,
242		 * we assume that if this node is a procdir, its
243		 * parent isn't (which is correct as long as
244		 * descendents of procdir nodes are never procdir
245		 * nodes themselves)
246		 */
247		if (pd->pn_type == pfstype_procdir)
248			pid = NO_PID;
249		pn = pd->pn_parent;
250		goto got_pnode;
251	}
252
253	/* named node */
254	for (pn = pd->pn_nodes; pn->pn_type; ++pn)
255		if (pn->pn_type == pfstype_procdir)
256			pdn = pn;
257		else if (pn->pn_name[namelen] == '\0'
258		    && bcmp(pname, pn->pn_name, namelen) == 0)
259			goto got_pnode;
260
261	/* process dependent node */
262	if ((pn = pdn) != NULL) {
263		pid = 0;
264		for (pid = 0, i = 0; i < namelen && isdigit(pname[i]); ++i)
265			if ((pid = pid * 10 + pname[i] - '0') > PID_MAX)
266				break;
267		if (i == cnp->cn_namelen)
268			goto got_pnode;
269	}
270
271	PFS_RETURN (ENOENT);
272 got_pnode:
273	if (pn != pd->pn_parent && !pn->pn_parent)
274		pn->pn_parent = pd;
275	if (!pfs_visible(curthread, pn, pvd->pvd_pid))
276		PFS_RETURN (ENOENT);
277	error = pfs_vncache_alloc(vn->v_mount, vpp, pn, pid);
278	if (error)
279		PFS_RETURN (error);
280	if (cnp->cn_flags & MAKEENTRY)
281		cache_enter(vn, *vpp, cnp);
282	PFS_RETURN (0);
283}
284
285/*
286 * Open a file or directory.
287 */
288static int
289pfs_open(struct vop_open_args *va)
290{
291	struct vnode *vn = va->a_vp;
292	struct pfs_vdata *pvd = (struct pfs_vdata *)vn->v_data;
293	struct pfs_node *pn = pvd->pvd_pn;
294	int mode = va->a_mode;
295
296	PFS_TRACE(("%s (mode 0x%x)", pn->pn_name, mode));
297
298	/*
299	 * check if the file is visible to the caller
300	 *
301	 * XXX Not sure if this is necessary, as the VFS system calls
302         * XXX pfs_lookup() and pfs_access() first, and pfs_lookup()
303         * XXX calls pfs_visible().  There's a race condition here, but
304	 * XXX calling pfs_visible() from here doesn't really close it,
305	 * XXX and the only consequence of that race is an EIO further
306	 * XXX down the line.
307	 */
308	if (!pfs_visible(va->a_td, pn, pvd->pvd_pid))
309		PFS_RETURN (ENOENT);
310
311	/* check if the requested mode is permitted */
312	if (((mode & FREAD) && !(mode & PFS_RD)) ||
313	    ((mode & FWRITE) && !(mode & PFS_WR)))
314		PFS_RETURN (EPERM);
315
316	/* we don't support locking */
317	if ((mode & O_SHLOCK) || (mode & O_EXLOCK))
318		PFS_RETURN (EOPNOTSUPP);
319
320	PFS_RETURN (0);
321}
322
323/*
324 * Read from a file
325 */
326static int
327pfs_read(struct vop_read_args *va)
328{
329	struct vnode *vn = va->a_vp;
330	struct pfs_vdata *pvd = (struct pfs_vdata *)vn->v_data;
331	struct pfs_node *pn = pvd->pvd_pn;
332	struct uio *uio = va->a_uio;
333	struct proc *proc = NULL;
334	struct sbuf *sb = NULL;
335	char *ps;
336	int error, xlen;
337
338	PFS_TRACE((pn->pn_name));
339
340	if (vn->v_type != VREG)
341		PFS_RETURN (EINVAL);
342
343	if (!(pn->pn_flags & PFS_RD))
344		PFS_RETURN (EBADF);
345
346	/*
347	 * This is necessary because either process' privileges may
348	 * have changed since the open() call.
349	 */
350	if (!pfs_visible(curthread, pn, pvd->pvd_pid))
351		PFS_RETURN (EIO);
352
353	/* XXX duplicates bits of pfs_visible() */
354	if (pvd->pvd_pid != NO_PID) {
355		if ((proc = pfind(pvd->pvd_pid)) == NULL)
356			PFS_RETURN (EIO);
357		_PHOLD(proc);
358		PROC_UNLOCK(proc);
359	}
360
361	if (pn->pn_flags & PFS_RAWRD) {
362		error = (pn->pn_func)(curthread, proc, pn, NULL, uio);
363		if (proc != NULL)
364			PRELE(proc);
365		PFS_RETURN (error);
366	}
367
368	sb = sbuf_new(sb, NULL, uio->uio_offset + uio->uio_resid, 0);
369	if (sb == NULL) {
370		if (proc != NULL)
371			PRELE(proc);
372		PFS_RETURN (EIO);
373	}
374
375	error = (pn->pn_func)(curthread, proc, pn, sb, uio);
376
377	if (proc != NULL)
378		PRELE(proc);
379
380	if (error) {
381		sbuf_delete(sb);
382		PFS_RETURN (error);
383	}
384
385	/* XXX we should possibly detect and handle overflows */
386	sbuf_finish(sb);
387	ps = sbuf_data(sb) + uio->uio_offset;
388	xlen = sbuf_len(sb) - uio->uio_offset;
389	xlen = imin(xlen, uio->uio_resid);
390	error = (xlen <= 0 ? 0 : uiomove(ps, xlen, uio));
391	sbuf_delete(sb);
392	PFS_RETURN (error);
393}
394
395/*
396 * Iterate through directory entries
397 */
398static int
399pfs_iterate(struct thread *td, pid_t pid, struct pfs_node **pn, struct proc **p)
400{
401	if ((*pn)->pn_type == pfstype_none)
402		return (-1);
403
404 again:
405	if ((*pn)->pn_type != pfstype_procdir)
406		++*pn;
407
408	while ((*pn)->pn_type == pfstype_procdir) {
409		if (*p == NULL)
410			*p = LIST_FIRST(&allproc);
411		else
412			*p = LIST_NEXT(*p, p_list);
413		if (*p != NULL)
414			break;
415		++*pn;
416	}
417
418	if ((*pn)->pn_type == pfstype_none)
419		return (-1);
420
421	if (!pfs_visible(td, *pn, *p ? (*p)->p_pid : pid))
422		goto again;
423
424	return (0);
425}
426
427/*
428 * Return directory entries.
429 */
430static int
431pfs_readdir(struct vop_readdir_args *va)
432{
433	struct vnode *vn = va->a_vp;
434	struct pfs_info *pi = (struct pfs_info *)vn->v_mount->mnt_data;
435	struct pfs_vdata *pvd = (struct pfs_vdata *)vn->v_data;
436	struct pfs_node *pd = pvd->pvd_pn;
437	pid_t pid = pvd->pvd_pid;
438	struct pfs_node *pn;
439	struct dirent entry;
440	struct uio *uio;
441	struct proc *p;
442	off_t offset;
443	int error, i, resid;
444
445	PFS_TRACE((pd->pn_name));
446
447	if (vn->v_type != VDIR)
448		PFS_RETURN (ENOTDIR);
449	uio = va->a_uio;
450
451	/* check if the directory is visible to the caller */
452	if (!pfs_visible(curthread, pd, pid))
453		PFS_RETURN (ENOENT);
454
455	/* only allow reading entire entries */
456	offset = uio->uio_offset;
457	resid = uio->uio_resid;
458	if (offset < 0 || offset % PFS_DELEN != 0 || resid < PFS_DELEN)
459		PFS_RETURN (EINVAL);
460
461	/* skip unwanted entries */
462	sx_slock(&allproc_lock);
463	for (pn = pd->pn_nodes, p = NULL; offset > 0; offset -= PFS_DELEN)
464		if (pfs_iterate(curthread, pid, &pn, &p) == -1)
465			break;
466
467	/* fill in entries */
468	entry.d_reclen = PFS_DELEN;
469	while (pfs_iterate(curthread, pid, &pn, &p) != -1 && resid > 0) {
470		if (!pn->pn_parent)
471			pn->pn_parent = pd;
472		if (!pn->pn_fileno)
473			pfs_fileno_alloc(pi, pn);
474		if (pid != NO_PID)
475			entry.d_fileno = pn->pn_fileno * NO_PID + pid;
476		else
477			entry.d_fileno = pn->pn_fileno;
478		/* PFS_DELEN was picked to fit PFS_NAMLEN */
479		for (i = 0; i < PFS_NAMELEN - 1 && pn->pn_name[i] != '\0'; ++i)
480			entry.d_name[i] = pn->pn_name[i];
481		entry.d_name[i] = 0;
482		entry.d_namlen = i;
483		switch (pn->pn_type) {
484		case pfstype_procdir:
485			KASSERT(p != NULL,
486			    ("reached procdir node with p == NULL"));
487			entry.d_fileno = pn->pn_fileno * NO_PID + p->p_pid;
488			entry.d_namlen = snprintf(entry.d_name,
489			    PFS_NAMELEN, "%d", p->p_pid);
490			/* fall through */
491		case pfstype_root:
492		case pfstype_dir:
493		case pfstype_this:
494		case pfstype_parent:
495			entry.d_type = DT_DIR;
496			break;
497		case pfstype_file:
498			entry.d_type = DT_REG;
499			break;
500		case pfstype_symlink:
501			entry.d_type = DT_LNK;
502			break;
503		default:
504			sx_sunlock(&allproc_lock);
505			panic("%s has unexpected node type: %d", pn->pn_name, pn->pn_type);
506		}
507		if ((error = uiomove((caddr_t)&entry, PFS_DELEN, uio))) {
508			sx_sunlock(&allproc_lock);
509			PFS_RETURN (error);
510		}
511		offset += PFS_DELEN;
512		resid -= PFS_DELEN;
513	}
514
515	sx_sunlock(&allproc_lock);
516	uio->uio_offset += offset;
517	PFS_RETURN (0);
518}
519
520/*
521 * Read a symbolic link
522 */
523static int
524pfs_readlink(struct vop_readlink_args *va)
525{
526	struct vnode *vn = va->a_vp;
527	struct pfs_vdata *pvd = (struct pfs_vdata *)vn->v_data;
528	struct pfs_node *pn = pvd->pvd_pn;
529	struct uio *uio = va->a_uio;
530	struct proc *proc = NULL;
531	char buf[MAXPATHLEN], *ps;
532	struct sbuf sb;
533	int error, xlen;
534
535	PFS_TRACE((pn->pn_name));
536
537	if (vn->v_type != VLNK)
538		PFS_RETURN (EINVAL);
539
540	if (pvd->pvd_pid != NO_PID) {
541		if ((proc = pfind(pvd->pvd_pid)) == NULL)
542			PFS_RETURN (EIO);
543		_PHOLD(proc);
544		PROC_UNLOCK(proc);
545	}
546
547	/* sbuf_new() can't fail with a static buffer */
548	sbuf_new(&sb, buf, sizeof buf, 0);
549
550	error = (pn->pn_func)(curthread, proc, pn, &sb, NULL);
551
552	if (proc != NULL)
553		PRELE(proc);
554
555	if (error) {
556		sbuf_delete(&sb);
557		PFS_RETURN (error);
558	}
559
560	/* XXX we should detect and handle overflows */
561	sbuf_finish(&sb);
562	ps = sbuf_data(&sb) + uio->uio_offset;
563	xlen = sbuf_len(&sb) - uio->uio_offset;
564	xlen = imin(xlen, uio->uio_resid);
565	error = (xlen <= 0 ? 0 : uiomove(ps, xlen, uio));
566	sbuf_delete(&sb);
567	PFS_RETURN (error);
568}
569
570/*
571 * Reclaim a vnode
572 */
573static int
574pfs_reclaim(struct vop_reclaim_args *va)
575{
576	struct vnode *vn = va->a_vp;
577	struct pfs_vdata *pvd = (struct pfs_vdata *)vn->v_data;
578	struct pfs_node *pn = pvd->pvd_pn;
579
580	PFS_TRACE((pn->pn_name));
581
582	return (pfs_vncache_free(va->a_vp));
583}
584
585/*
586 * Set attributes
587 */
588static int
589pfs_setattr(struct vop_setattr_args *va)
590{
591	struct vnode *vn = va->a_vp;
592	struct pfs_vdata *pvd = (struct pfs_vdata *)vn->v_data;
593	struct pfs_node *pn = pvd->pvd_pn;
594
595	PFS_TRACE((pn->pn_name));
596
597	if (va->a_vap->va_flags != (u_long)VNOVAL)
598		PFS_RETURN (EOPNOTSUPP);
599	/* XXX it's a bit more complex than that, really... */
600	PFS_RETURN (0);
601}
602
603/*
604 * Read from a file
605 */
606static int
607pfs_write(struct vop_read_args *va)
608{
609	struct vnode *vn = va->a_vp;
610	struct pfs_vdata *pvd = (struct pfs_vdata *)vn->v_data;
611	struct pfs_node *pn = pvd->pvd_pn;
612	struct uio *uio = va->a_uio;
613	struct proc *proc = NULL;
614	struct sbuf sb;
615	int error;
616
617	PFS_TRACE((pn->pn_name));
618
619	if (vn->v_type != VREG)
620		PFS_RETURN (EINVAL);
621
622	if (!(pn->pn_flags & PFS_WR))
623		PFS_RETURN (EBADF);
624
625	/*
626	 * This is necessary because either process' privileges may
627	 * have changed since the open() call.
628	 */
629	if (!pfs_visible(curthread, pn, pvd->pvd_pid))
630		PFS_RETURN (EIO);
631
632	/* XXX duplicates bits of pfs_visible() */
633	if (pvd->pvd_pid != NO_PID) {
634		if ((proc = pfind(pvd->pvd_pid)) == NULL)
635			PFS_RETURN (EIO);
636		_PHOLD(proc);
637		PROC_UNLOCK(proc);
638	}
639
640	if (pn->pn_flags & PFS_RAWWR) {
641		error = (pn->pn_func)(curthread, proc, pn, NULL, uio);
642		if (proc != NULL)
643			PRELE(proc);
644		PFS_RETURN (error);
645	}
646
647	sbuf_uionew(&sb, uio, &error);
648	if (error)
649		PFS_RETURN (error);
650
651	error = (pn->pn_func)(curthread, proc, pn, &sb, uio);
652
653	if (proc != NULL)
654		PRELE(proc);
655
656	sbuf_delete(&sb);
657	PFS_RETURN (error);
658}
659
660/*
661 * Dummy operations */
662static int pfs_badop(void *va)		{ return (EOPNOTSUPP); }
663#if 0
664static int pfs_erofs(void *va)		{ return (EROFS); }
665static int pfs_null(void *va)		{ return (0); }
666#endif
667
668/*
669 * Vnode operations
670 */
671vop_t **pfs_vnodeop_p;
672static struct vnodeopv_entry_desc pfs_vnodeop_entries[] = {
673	{ &vop_default_desc,		(vop_t *)vop_defaultop	},
674	{ &vop_access_desc,		(vop_t *)pfs_access	},
675	{ &vop_close_desc,		(vop_t *)pfs_close	},
676	{ &vop_create_desc,		(vop_t *)pfs_badop	},
677	{ &vop_getattr_desc,		(vop_t *)pfs_getattr	},
678	{ &vop_link_desc,		(vop_t *)pfs_badop	},
679	{ &vop_lookup_desc,		(vop_t *)pfs_lookup	},
680	{ &vop_mkdir_desc,		(vop_t *)pfs_badop	},
681	{ &vop_mknod_desc,		(vop_t *)pfs_badop	},
682	{ &vop_open_desc,		(vop_t *)pfs_open	},
683	{ &vop_read_desc,		(vop_t *)pfs_read	},
684	{ &vop_readdir_desc,		(vop_t *)pfs_readdir	},
685	{ &vop_readlink_desc,		(vop_t *)pfs_readlink	},
686	{ &vop_reclaim_desc,		(vop_t *)pfs_reclaim	},
687	{ &vop_remove_desc,		(vop_t *)pfs_badop	},
688	{ &vop_rename_desc,		(vop_t *)pfs_badop	},
689	{ &vop_rmdir_desc,		(vop_t *)pfs_badop	},
690	{ &vop_setattr_desc,		(vop_t *)pfs_setattr	},
691	{ &vop_symlink_desc,		(vop_t *)pfs_badop	},
692	{ &vop_write_desc,		(vop_t *)pfs_write	},
693	/* XXX I've probably forgotten a few that need pfs_badop */
694	{ NULL,				(vop_t *)NULL		}
695};
696
697static struct vnodeopv_desc pfs_vnodeop_opv_desc =
698	{ &pfs_vnodeop_p, pfs_vnodeop_entries };
699
700VNODEOP_SET(pfs_vnodeop_opv_desc);
701