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