ufs_extattr.c revision 90448
1/*- 2 * Copyright (c) 1999-2001 Robert N. M. Watson 3 * All rights reserved. 4 * 5 * This software was developed by Robert Watson for the TrustedBSD Project. 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 * $FreeBSD: head/sys/ufs/ufs/ufs_extattr.c 90448 2002-02-10 04:43:22Z rwatson $ 29 */ 30/* 31 * Developed by the TrustedBSD Project. 32 * Support for file system extended attribute: UFS-specific support functions. 33 */ 34 35#include <sys/param.h> 36#include <sys/systm.h> 37#include <sys/kernel.h> 38#include <sys/namei.h> 39#include <sys/malloc.h> 40#include <sys/fcntl.h> 41#include <sys/proc.h> 42#include <sys/vnode.h> 43#include <sys/mount.h> 44#include <sys/lock.h> 45#include <sys/dirent.h> 46#include <sys/extattr.h> 47#include <sys/sysctl.h> 48 49#include <vm/vm_zone.h> 50 51#include <ufs/ufs/dir.h> 52#include <ufs/ufs/extattr.h> 53#include <ufs/ufs/quota.h> 54#include <ufs/ufs/ufsmount.h> 55#include <ufs/ufs/inode.h> 56#include <ufs/ufs/ufs_extern.h> 57 58#include "opt_ufs.h" 59 60#ifdef UFS_EXTATTR 61 62#define MIN(a,b) (((a)<(b))?(a):(b)) 63 64static MALLOC_DEFINE(M_UFS_EXTATTR, "ufs_extattr", "ufs extended attribute"); 65 66static int ufs_extattr_sync = 0; 67SYSCTL_INT(_debug, OID_AUTO, ufs_extattr_sync, CTLFLAG_RW, &ufs_extattr_sync, 68 0, ""); 69 70static int ufs_extattr_valid_attrname(int attrnamespace, 71 const char *attrname); 72static int ufs_extattr_credcheck(struct vnode *vp, 73 struct ufs_extattr_list_entry *uele, struct ucred *cred, 74 struct thread *td, int access); 75static int ufs_extattr_enable_with_open(struct ufsmount *ump, 76 struct vnode *vp, int attrnamespace, const char *attrname, 77 struct thread *td); 78static int ufs_extattr_enable(struct ufsmount *ump, int attrnamespace, 79 const char *attrname, struct vnode *backing_vnode, struct thread *td); 80static int ufs_extattr_disable(struct ufsmount *ump, int attrnamespace, 81 const char *attrname, struct thread *td); 82static int ufs_extattr_get(struct vnode *vp, int attrnamespace, 83 const char *name, struct uio *uio, size_t *size, struct ucred *cred, 84 struct thread *td); 85static int ufs_extattr_set(struct vnode *vp, int attrnamespace, 86 const char *name, struct uio *uio, struct ucred *cred, struct thread *td); 87static int ufs_extattr_rm(struct vnode *vp, int attrnamespace, 88 const char *name, struct ucred *cred, struct thread *td); 89 90/* 91 * Per-FS attribute lock protecting attribute operations. 92 * XXX Right now there is a lot of lock contention due to having a single 93 * lock per-FS; really, this should be far more fine-grained. 94 */ 95static void 96ufs_extattr_uepm_lock(struct ufsmount *ump, struct thread *td) 97{ 98 99 /* Ideally, LK_CANRECURSE would not be used, here. */ 100 lockmgr(&ump->um_extattr.uepm_lock, LK_EXCLUSIVE | LK_RETRY | 101 LK_CANRECURSE, 0, td); 102} 103 104static void 105ufs_extattr_uepm_unlock(struct ufsmount *ump, struct thread *td) 106{ 107 108 lockmgr(&ump->um_extattr.uepm_lock, LK_RELEASE, 0, td); 109} 110 111/* 112 * Determine whether the name passed is a valid name for an actual 113 * attribute. 114 * 115 * Invalid currently consists of: 116 * NULL pointer for attrname 117 * zero-length attrname (used to retrieve application attribute list) 118 */ 119static int 120ufs_extattr_valid_attrname(int attrnamespace, const char *attrname) 121{ 122 123 if (attrname == NULL) 124 return (0); 125 if (strlen(attrname) == 0) 126 return (0); 127 return (1); 128} 129 130/* 131 * Locate an attribute given a name and mountpoint. 132 * Must be holding uepm lock for the mount point. 133 */ 134static struct ufs_extattr_list_entry * 135ufs_extattr_find_attr(struct ufsmount *ump, int attrnamespace, 136 const char *attrname) 137{ 138 struct ufs_extattr_list_entry *search_attribute; 139 140 for (search_attribute = LIST_FIRST(&ump->um_extattr.uepm_list); 141 search_attribute; 142 search_attribute = LIST_NEXT(search_attribute, uele_entries)) { 143 if (!(strncmp(attrname, search_attribute->uele_attrname, 144 UFS_EXTATTR_MAXEXTATTRNAME)) && 145 (attrnamespace == search_attribute->uele_attrnamespace)) { 146 return (search_attribute); 147 } 148 } 149 150 return (0); 151} 152 153/* 154 * Initialize per-FS structures supporting extended attributes. Do not 155 * start extended attributes yet. 156 */ 157void 158ufs_extattr_uepm_init(struct ufs_extattr_per_mount *uepm) 159{ 160 161 uepm->uepm_flags = 0; 162 163 LIST_INIT(&uepm->uepm_list); 164 /* XXX is PVFS right, here? */ 165 lockinit(&uepm->uepm_lock, PVFS, "extattr", 0, 0); 166 uepm->uepm_flags |= UFS_EXTATTR_UEPM_INITIALIZED; 167} 168 169/* 170 * Destroy per-FS structures supporting extended attributes. Assumes 171 * that EAs have already been stopped, and will panic if not. 172 */ 173void 174ufs_extattr_uepm_destroy(struct ufs_extattr_per_mount *uepm) 175{ 176 177 if (!(uepm->uepm_flags & UFS_EXTATTR_UEPM_INITIALIZED)) 178 panic("ufs_extattr_uepm_destroy: not initialized"); 179 180 if ((uepm->uepm_flags & UFS_EXTATTR_UEPM_STARTED)) 181 panic("ufs_extattr_uepm_destroy: called while still started"); 182 183 /* 184 * It's not clear that either order for the next two lines is 185 * ideal, and it should never be a problem if this is only called 186 * during unmount, and with vfs_busy(). 187 */ 188 uepm->uepm_flags &= ~UFS_EXTATTR_UEPM_INITIALIZED; 189 lockdestroy(&uepm->uepm_lock); 190} 191 192/* 193 * Start extended attribute support on an FS. 194 */ 195int 196ufs_extattr_start(struct mount *mp, struct thread *td) 197{ 198 struct ufsmount *ump; 199 int error = 0; 200 201 ump = VFSTOUFS(mp); 202 203 ufs_extattr_uepm_lock(ump, td); 204 205 if (!(ump->um_extattr.uepm_flags & UFS_EXTATTR_UEPM_INITIALIZED)) { 206 error = EOPNOTSUPP; 207 goto unlock; 208 } 209 if (ump->um_extattr.uepm_flags & UFS_EXTATTR_UEPM_STARTED) { 210 error = EBUSY; 211 goto unlock; 212 } 213 214 ump->um_extattr.uepm_flags |= UFS_EXTATTR_UEPM_STARTED; 215 216 ump->um_extattr.uepm_ucred = crhold(td->td_proc->p_ucred); 217 218unlock: 219 ufs_extattr_uepm_unlock(ump, td); 220 221 return (error); 222} 223 224#ifdef UFS_EXTATTR_AUTOSTART 225/* 226 * Helper routine: given a locked parent directory and filename, return 227 * the locked vnode of the inode associated with the name. Will not 228 * follow symlinks, may return any type of vnode. Lock on parent will 229 * be released even in the event of a failure. In the event that the 230 * target is the parent (i.e., "."), there will be two references and 231 * one lock, requiring the caller to possibly special-case. 232 */ 233#define UE_GETDIR_LOCKPARENT 1 234#define UE_GETDIR_LOCKPARENT_DONT 2 235static int 236ufs_extattr_lookup(struct vnode *start_dvp, int lockparent, char *dirname, 237 struct vnode **vp, struct thread *td) 238{ 239 struct vop_cachedlookup_args vargs; 240 struct componentname cnp; 241 struct vnode *target_vp; 242 int error; 243 244 bzero(&cnp, sizeof(cnp)); 245 cnp.cn_nameiop = LOOKUP; 246 cnp.cn_flags = ISLASTCN; 247 if (lockparent == UE_GETDIR_LOCKPARENT) 248 cnp.cn_flags |= LOCKPARENT; 249 cnp.cn_thread = td; 250 cnp.cn_cred = td->td_proc->p_ucred; 251 cnp.cn_pnbuf = zalloc(namei_zone); 252 cnp.cn_nameptr = cnp.cn_pnbuf; 253 error = copystr(dirname, cnp.cn_pnbuf, MAXPATHLEN, 254 (size_t *) &cnp.cn_namelen); 255 if (error) { 256 if (lockparent == UE_GETDIR_LOCKPARENT_DONT) { 257 VOP_UNLOCK(start_dvp, 0, td); 258 } 259 zfree(namei_zone, cnp.cn_pnbuf); 260 printf("ufs_extattr_lookup: copystr failed\n"); 261 return (error); 262 } 263 cnp.cn_namelen--; /* trim nul termination */ 264 vargs.a_desc = NULL; 265 vargs.a_dvp = start_dvp; 266 vargs.a_vpp = &target_vp; 267 vargs.a_cnp = &cnp; 268 error = ufs_lookup(&vargs); 269 zfree(namei_zone, cnp.cn_pnbuf); 270 if (error) { 271 /* 272 * Error condition, may have to release the lock on the parent 273 * if ufs_lookup() didn't. 274 */ 275 if (!(cnp.cn_flags & PDIRUNLOCK) && 276 (lockparent == UE_GETDIR_LOCKPARENT_DONT)) 277 VOP_UNLOCK(start_dvp, 0, td); 278 279 /* 280 * Check that ufs_lookup() didn't release the lock when we 281 * didn't want it to. 282 */ 283 if ((cnp.cn_flags & PDIRUNLOCK) && 284 (lockparent == UE_GETDIR_LOCKPARENT)) 285 panic("ufs_extattr_lookup: lockparent but PDIRUNLOCK"); 286 287 return (error); 288 } 289/* 290 if (target_vp == start_dvp) 291 panic("ufs_extattr_lookup: target_vp == start_dvp"); 292*/ 293 294 if (target_vp != start_dvp && 295 !(cnp.cn_flags & PDIRUNLOCK) && 296 (lockparent == UE_GETDIR_LOCKPARENT_DONT)) 297 panic("ufs_extattr_lookup: !lockparent but !PDIRUNLOCK"); 298 299 if ((cnp.cn_flags & PDIRUNLOCK) && 300 (lockparent == UE_GETDIR_LOCKPARENT)) 301 panic("ufs_extattr_lookup: lockparent but PDIRUNLOCK"); 302 303 /* printf("ufs_extattr_lookup: success\n"); */ 304 *vp = target_vp; 305 return (0); 306} 307#endif /* !UFS_EXTATTR_AUTOSTART */ 308 309/* 310 * Enable an EA using the passed file system, backing vnode, attribute name, 311 * namespace, and proc. Will perform a VOP_OPEN() on the vp, so expects vp 312 * to be locked when passed in. The vnode will be returned unlocked, 313 * regardless of success/failure of the function. As a result, the caller 314 * will always need to vrele(), but not vput(). 315 */ 316static int 317ufs_extattr_enable_with_open(struct ufsmount *ump, struct vnode *vp, 318 int attrnamespace, const char *attrname, struct thread *td) 319{ 320 int error; 321 322 error = VOP_OPEN(vp, FREAD|FWRITE, td->td_proc->p_ucred, td); 323 if (error) { 324 printf("ufs_extattr_enable_with_open.VOP_OPEN(): failed " 325 "with %d\n", error); 326 VOP_UNLOCK(vp, 0, td); 327 return (error); 328 } 329 330 /* 331 * XXX: Note, should VOP_CLOSE() if vfs_object_create() fails, but due 332 * to a similar piece of code in vn_open(), we don't. 333 */ 334 if (vn_canvmio(vp) == TRUE) 335 if ((error = vfs_object_create(vp, td, 336 td->td_proc->p_ucred)) != 0) { 337 /* 338 * XXX: bug replicated from vn_open(): should 339 * VOP_CLOSE() here. 340 */ 341 VOP_UNLOCK(vp, 0, td); 342 return (error); 343 } 344 345 vp->v_writecount++; 346 347 vref(vp); 348 349 VOP_UNLOCK(vp, 0, td); 350 351 error = ufs_extattr_enable(ump, attrnamespace, attrname, vp, td); 352 if (error != 0) 353 vn_close(vp, FREAD|FWRITE, td->td_proc->p_ucred, td); 354 return (error); 355} 356 357#ifdef UFS_EXTATTR_AUTOSTART 358/* 359 * Given a locked directory vnode, iterate over the names in the directory 360 * and use ufs_extattr_lookup() to retrieve locked vnodes of potential 361 * attribute files. Then invoke ufs_extattr_enable_with_open() on each 362 * to attempt to start the attribute. Leaves the directory locked on 363 * exit. 364 */ 365static int 366ufs_extattr_iterate_directory(struct ufsmount *ump, struct vnode *dvp, 367 int attrnamespace, struct thread *td) 368{ 369 struct vop_readdir_args vargs; 370 struct dirent *dp, *edp; 371 struct vnode *attr_vp; 372 struct uio auio; 373 struct iovec aiov; 374 char *dirbuf; 375 int error, eofflag = 0; 376 377 if (dvp->v_type != VDIR) 378 return (ENOTDIR); 379 380 MALLOC(dirbuf, char *, DIRBLKSIZ, M_TEMP, M_WAITOK); 381 382 auio.uio_iov = &aiov; 383 auio.uio_iovcnt = 1; 384 auio.uio_rw = UIO_READ; 385 auio.uio_segflg = UIO_SYSSPACE; 386 auio.uio_td = td; 387 auio.uio_offset = 0; 388 389 vargs.a_desc = NULL; 390 vargs.a_vp = dvp; 391 vargs.a_uio = &auio; 392 vargs.a_cred = td->td_proc->p_ucred; 393 vargs.a_eofflag = &eofflag; 394 vargs.a_ncookies = NULL; 395 vargs.a_cookies = NULL; 396 397 while (!eofflag) { 398 auio.uio_resid = DIRBLKSIZ; 399 aiov.iov_base = dirbuf; 400 aiov.iov_len = DIRBLKSIZ; 401 error = ufs_readdir(&vargs); 402 if (error) { 403 printf("ufs_extattr_iterate_directory: ufs_readdir " 404 "%d\n", error); 405 return (error); 406 } 407 408 edp = (struct dirent *)&dirbuf[DIRBLKSIZ]; 409 for (dp = (struct dirent *)dirbuf; dp < edp; ) { 410#if (BYTE_ORDER == LITTLE_ENDIAN) 411 dp->d_type = dp->d_namlen; 412 dp->d_namlen = 0; 413#else 414 dp->d_type = 0; 415#endif 416 if (dp->d_reclen == 0) 417 break; 418 error = ufs_extattr_lookup(dvp, UE_GETDIR_LOCKPARENT, 419 dp->d_name, &attr_vp, td); 420 if (error) { 421 printf("ufs_extattr_iterate_directory: lookup " 422 "%s %d\n", dp->d_name, error); 423 } else if (attr_vp == dvp) { 424 vrele(attr_vp); 425 } else if (attr_vp->v_type != VREG) { 426/* 427 * Eventually, this will be uncommented, but in the mean time, the ".." 428 * entry causes unnecessary console warnings. 429 printf("ufs_extattr_iterate_directory: " 430 "%s not VREG\n", dp->d_name); 431*/ 432 vput(attr_vp); 433 } else { 434 error = ufs_extattr_enable_with_open(ump, 435 attr_vp, attrnamespace, dp->d_name, td); 436 vrele(attr_vp); 437 if (error) { 438 printf("ufs_extattr_iterate_directory: " 439 "enable %s %d\n", dp->d_name, 440 error); 441 } else if (bootverbose) { 442 printf("UFS autostarted EA %s\n", 443 dp->d_name); 444 } 445 } 446 dp = (struct dirent *) ((char *)dp + dp->d_reclen); 447 if (dp >= edp) 448 break; 449 } 450 } 451 FREE(dirbuf, M_TEMP); 452 453 return (0); 454} 455 456/* 457 * Auto-start of extended attributes, to be executed (optionally) at 458 * mount-time. 459 */ 460int 461ufs_extattr_autostart(struct mount *mp, struct thread *td) 462{ 463 struct vnode *rvp, *attr_dvp, *attr_system_dvp, *attr_user_dvp; 464 int error; 465 466 /* 467 * Does UFS_EXTATTR_FSROOTSUBDIR exist off the file system root? 468 * If so, automatically start EA's. 469 */ 470 error = VFS_ROOT(mp, &rvp); 471 if (error) { 472 printf("ufs_extattr_autostart.VFS_ROOT() returned %d\n", 473 error); 474 return (error); 475 } 476 477 error = ufs_extattr_lookup(rvp, UE_GETDIR_LOCKPARENT_DONT, 478 UFS_EXTATTR_FSROOTSUBDIR, &attr_dvp, td); 479 if (error) { 480 /* rvp ref'd but now unlocked */ 481 vrele(rvp); 482 return (error); 483 } 484 if (rvp == attr_dvp) { 485 /* Should never happen. */ 486 vrele(attr_dvp); 487 vput(rvp); 488 return (EINVAL); 489 } 490 vrele(rvp); 491 492 if (attr_dvp->v_type != VDIR) { 493 printf("ufs_extattr_autostart: %s != VDIR\n", 494 UFS_EXTATTR_FSROOTSUBDIR); 495 goto return_vput_attr_dvp; 496 } 497 498 error = ufs_extattr_start(mp, td); 499 if (error) { 500 printf("ufs_extattr_autostart: ufs_extattr_start failed (%d)\n", 501 error); 502 goto return_vput_attr_dvp; 503 } 504 505 /* 506 * Look for two subdirectories: UFS_EXTATTR_SUBDIR_SYSTEM, 507 * UFS_EXTATTR_SUBDIR_USER. For each, iterate over the sub-directory, 508 * and start with appropriate type. Failures in either don't 509 * result in an over-all failure. attr_dvp is left locked to 510 * be cleaned up on exit. 511 */ 512 error = ufs_extattr_lookup(attr_dvp, UE_GETDIR_LOCKPARENT, 513 UFS_EXTATTR_SUBDIR_SYSTEM, &attr_system_dvp, td); 514 if (!error) { 515 error = ufs_extattr_iterate_directory(VFSTOUFS(mp), 516 attr_system_dvp, EXTATTR_NAMESPACE_SYSTEM, td); 517 if (error) 518 printf("ufs_extattr_iterate_directory returned %d\n", 519 error); 520 vput(attr_system_dvp); 521 } 522 523 error = ufs_extattr_lookup(attr_dvp, UE_GETDIR_LOCKPARENT, 524 UFS_EXTATTR_SUBDIR_USER, &attr_user_dvp, td); 525 if (!error) { 526 error = ufs_extattr_iterate_directory(VFSTOUFS(mp), 527 attr_user_dvp, EXTATTR_NAMESPACE_USER, td); 528 if (error) 529 printf("ufs_extattr_iterate_directory returned %d\n", 530 error); 531 vput(attr_user_dvp); 532 } 533 534 /* Mask startup failures in sub-directories. */ 535 error = 0; 536 537return_vput_attr_dvp: 538 vput(attr_dvp); 539 540 return (error); 541} 542#endif /* !UFS_EXTATTR_AUTOSTART */ 543 544/* 545 * Stop extended attribute support on an FS. 546 */ 547int 548ufs_extattr_stop(struct mount *mp, struct thread *td) 549{ 550 struct ufs_extattr_list_entry *uele; 551 struct ufsmount *ump = VFSTOUFS(mp); 552 int error = 0; 553 554 ufs_extattr_uepm_lock(ump, td); 555 556 if (!(ump->um_extattr.uepm_flags & UFS_EXTATTR_UEPM_STARTED)) { 557 error = EOPNOTSUPP; 558 goto unlock; 559 } 560 561 while (LIST_FIRST(&ump->um_extattr.uepm_list) != NULL) { 562 uele = LIST_FIRST(&ump->um_extattr.uepm_list); 563 ufs_extattr_disable(ump, uele->uele_attrnamespace, 564 uele->uele_attrname, td); 565 } 566 567 ump->um_extattr.uepm_flags &= ~UFS_EXTATTR_UEPM_STARTED; 568 569 crfree(ump->um_extattr.uepm_ucred); 570 ump->um_extattr.uepm_ucred = NULL; 571 572unlock: 573 ufs_extattr_uepm_unlock(ump, td); 574 575 return (error); 576} 577 578/* 579 * Enable a named attribute on the specified file system; provide an 580 * unlocked backing vnode to hold the attribute data. 581 */ 582static int 583ufs_extattr_enable(struct ufsmount *ump, int attrnamespace, 584 const char *attrname, struct vnode *backing_vnode, struct thread *td) 585{ 586 struct ufs_extattr_list_entry *attribute; 587 struct iovec aiov; 588 struct uio auio; 589 int error = 0; 590 591 if (!ufs_extattr_valid_attrname(attrnamespace, attrname)) 592 return (EINVAL); 593 if (backing_vnode->v_type != VREG) 594 return (EINVAL); 595 596 MALLOC(attribute, struct ufs_extattr_list_entry *, 597 sizeof(struct ufs_extattr_list_entry), M_UFS_EXTATTR, M_WAITOK); 598 if (attribute == NULL) 599 return (ENOMEM); 600 601 if (!(ump->um_extattr.uepm_flags & UFS_EXTATTR_UEPM_STARTED)) { 602 error = EOPNOTSUPP; 603 goto free_exit; 604 } 605 606 if (ufs_extattr_find_attr(ump, attrnamespace, attrname)) { 607 error = EEXIST; 608 goto free_exit; 609 } 610 611 strncpy(attribute->uele_attrname, attrname, 612 UFS_EXTATTR_MAXEXTATTRNAME); 613 attribute->uele_attrnamespace = attrnamespace; 614 bzero(&attribute->uele_fileheader, 615 sizeof(struct ufs_extattr_fileheader)); 616 617 attribute->uele_backing_vnode = backing_vnode; 618 619 auio.uio_iov = &aiov; 620 auio.uio_iovcnt = 1; 621 aiov.iov_base = (caddr_t) &attribute->uele_fileheader; 622 aiov.iov_len = sizeof(struct ufs_extattr_fileheader); 623 auio.uio_resid = sizeof(struct ufs_extattr_fileheader); 624 auio.uio_offset = (off_t) 0; 625 auio.uio_segflg = UIO_SYSSPACE; 626 auio.uio_rw = UIO_READ; 627 auio.uio_td = td; 628 629 VOP_LEASE(backing_vnode, td, td->td_proc->p_ucred, LEASE_WRITE); 630 vn_lock(backing_vnode, LK_SHARED | LK_NOPAUSE | LK_RETRY, td); 631 error = VOP_READ(backing_vnode, &auio, IO_NODELOCKED, 632 ump->um_extattr.uepm_ucred); 633 VOP_UNLOCK(backing_vnode, 0, td); 634 635 if (error) 636 goto free_exit; 637 638 if (auio.uio_resid != 0) { 639 printf("ufs_extattr_enable: malformed attribute header\n"); 640 error = EINVAL; 641 goto free_exit; 642 } 643 644 if (attribute->uele_fileheader.uef_magic != UFS_EXTATTR_MAGIC) { 645 printf("ufs_extattr_enable: invalid attribute header magic\n"); 646 error = EINVAL; 647 goto free_exit; 648 } 649 650 if (attribute->uele_fileheader.uef_version != UFS_EXTATTR_VERSION) { 651 printf("ufs_extattr_enable: incorrect attribute header " 652 "version\n"); 653 error = EINVAL; 654 goto free_exit; 655 } 656 657 backing_vnode->v_flag |= VSYSTEM; 658 LIST_INSERT_HEAD(&ump->um_extattr.uepm_list, attribute, 659 uele_entries); 660 661 return (0); 662 663free_exit: 664 FREE(attribute, M_UFS_EXTATTR); 665 return (error); 666} 667 668/* 669 * Disable extended attribute support on an FS. 670 */ 671static int 672ufs_extattr_disable(struct ufsmount *ump, int attrnamespace, 673 const char *attrname, struct thread *td) 674{ 675 struct ufs_extattr_list_entry *uele; 676 int error = 0; 677 678 if (!ufs_extattr_valid_attrname(attrnamespace, attrname)) 679 return (EINVAL); 680 681 uele = ufs_extattr_find_attr(ump, attrnamespace, attrname); 682 if (!uele) 683 /* XXX: ENOENT here will eventually be ENOATTR. */ 684 return (ENOENT); 685 686 LIST_REMOVE(uele, uele_entries); 687 688 uele->uele_backing_vnode->v_flag &= ~VSYSTEM; 689 error = vn_close(uele->uele_backing_vnode, FREAD|FWRITE, 690 td->td_proc->p_ucred, td); 691 692 FREE(uele, M_UFS_EXTATTR); 693 694 return (error); 695} 696 697/* 698 * VFS call to manage extended attributes in UFS. If filename_vp is 699 * non-NULL, it must be passed in locked, and regardless of errors in 700 * processing, will be unlocked. 701 */ 702int 703ufs_extattrctl(struct mount *mp, int cmd, struct vnode *filename_vp, 704 int attrnamespace, const char *attrname, struct thread *td) 705{ 706 struct ufsmount *ump = VFSTOUFS(mp); 707 int error; 708 709 /* 710 * Processes with privilege, but in jail, are not allowed to 711 * configure extended attributes. 712 */ 713 if ((error = suser_xxx(td->td_proc->p_ucred, td->td_proc, 0))) { 714 if (filename_vp != NULL) 715 VOP_UNLOCK(filename_vp, 0, td); 716 return (error); 717 } 718 719 switch(cmd) { 720 case UFS_EXTATTR_CMD_START: 721 if (filename_vp != NULL) { 722 VOP_UNLOCK(filename_vp, 0, td); 723 return (EINVAL); 724 } 725 if (attrname != NULL) 726 return (EINVAL); 727 728 error = ufs_extattr_start(mp, td); 729 730 return (error); 731 732 case UFS_EXTATTR_CMD_STOP: 733 if (filename_vp != NULL) { 734 VOP_UNLOCK(filename_vp, 0, td); 735 return (EINVAL); 736 } 737 if (attrname != NULL) 738 return (EINVAL); 739 740 error = ufs_extattr_stop(mp, td); 741 742 return (error); 743 744 case UFS_EXTATTR_CMD_ENABLE: 745 746 if (filename_vp == NULL) 747 return (EINVAL); 748 if (attrname == NULL) { 749 VOP_UNLOCK(filename_vp, 0, td); 750 return (EINVAL); 751 } 752 753 /* 754 * ufs_extattr_enable_with_open() will always unlock the 755 * vnode, regardless of failure. 756 */ 757 ufs_extattr_uepm_lock(ump, td); 758 error = ufs_extattr_enable_with_open(ump, filename_vp, 759 attrnamespace, attrname, td); 760 ufs_extattr_uepm_unlock(ump, td); 761 762 return (error); 763 764 case UFS_EXTATTR_CMD_DISABLE: 765 766 if (filename_vp != NULL) { 767 VOP_UNLOCK(filename_vp, 0, td); 768 return (EINVAL); 769 } 770 if (attrname == NULL) 771 return (EINVAL); 772 773 ufs_extattr_uepm_lock(ump, td); 774 error = ufs_extattr_disable(ump, attrnamespace, attrname, 775 td); 776 ufs_extattr_uepm_unlock(ump, td); 777 778 return (error); 779 780 default: 781 return (EINVAL); 782 } 783} 784 785/* 786 * Credential check based on process requesting service, and per-attribute 787 * permissions. 788 */ 789static int 790ufs_extattr_credcheck(struct vnode *vp, struct ufs_extattr_list_entry *uele, 791 struct ucred *cred, struct thread *td, int access) 792{ 793 794 /* 795 * Kernel-invoked always succeeds. 796 */ 797 if (cred == NULL) 798 return (0); 799 800 /* 801 * Do not allow privileged processes in jail to directly 802 * manipulate system attributes. 803 * 804 * XXX What capability should apply here? 805 * Probably CAP_SYS_SETFFLAG. 806 */ 807 switch (uele->uele_attrnamespace) { 808 case EXTATTR_NAMESPACE_SYSTEM: 809 /* Potentially should be: return (EPERM); */ 810 return (suser_xxx(cred, td->td_proc, 0)); 811 case EXTATTR_NAMESPACE_USER: 812 return (VOP_ACCESS(vp, access, cred, td)); 813 default: 814 return (EPERM); 815 } 816} 817 818/* 819 * Vnode operating to retrieve a named extended attribute. 820 */ 821int 822ufs_vop_getextattr(struct vop_getextattr_args *ap) 823/* 824vop_getextattr { 825 IN struct vnode *a_vp; 826 IN int a_attrnamespace; 827 IN const char *a_name; 828 INOUT struct uio *a_uio; 829 OUT struct size_t *a_size; 830 IN struct ucred *a_cred; 831 IN struct thread *a_td; 832}; 833*/ 834{ 835 struct mount *mp = ap->a_vp->v_mount; 836 struct ufsmount *ump = VFSTOUFS(mp); 837 int error; 838 839 ufs_extattr_uepm_lock(ump, ap->a_td); 840 841 error = ufs_extattr_get(ap->a_vp, ap->a_attrnamespace, ap->a_name, 842 ap->a_uio, ap->a_size, ap->a_cred, ap->a_td); 843 844 ufs_extattr_uepm_unlock(ump, ap->a_td); 845 846 return (error); 847} 848 849/* 850 * Real work associated with retrieving a named attribute--assumes that 851 * the attribute lock has already been grabbed. 852 */ 853static int 854ufs_extattr_get(struct vnode *vp, int attrnamespace, const char *name, 855 struct uio *uio, size_t *size, struct ucred *cred, struct thread *td) 856{ 857 struct ufs_extattr_list_entry *attribute; 858 struct ufs_extattr_header ueh; 859 struct iovec local_aiov; 860 struct uio local_aio; 861 struct mount *mp = vp->v_mount; 862 struct ufsmount *ump = VFSTOUFS(mp); 863 struct inode *ip = VTOI(vp); 864 off_t base_offset; 865 size_t len, old_len; 866 int error = 0; 867 868 if (!(ump->um_extattr.uepm_flags & UFS_EXTATTR_UEPM_STARTED)) 869 return (EOPNOTSUPP); 870 871 if (strlen(name) == 0) { 872 /* XXX retrieve attribute lists. */ 873 /* XXX should probably be checking for name == NULL? */ 874 return (EINVAL); 875 } 876 877 attribute = ufs_extattr_find_attr(ump, attrnamespace, name); 878 if (!attribute) 879 /* XXX: ENOENT here will eventually be ENOATTR. */ 880 return (ENOENT); 881 882 if ((error = ufs_extattr_credcheck(vp, attribute, cred, td, IREAD))) 883 return (error); 884 885 /* 886 * Allow only offsets of zero to encourage the read/replace 887 * extended attribute semantic. Otherwise we can't guarantee 888 * atomicity, as we don't provide locks for extended attributes. 889 */ 890 if (uio != NULL && uio->uio_offset != 0) 891 return (ENXIO); 892 893 /* 894 * Find base offset of header in file based on file header size, and 895 * data header size + maximum data size, indexed by inode number. 896 */ 897 base_offset = sizeof(struct ufs_extattr_fileheader) + 898 ip->i_number * (sizeof(struct ufs_extattr_header) + 899 attribute->uele_fileheader.uef_size); 900 901 /* 902 * Read in the data header to see if the data is defined, and if so 903 * how much. 904 */ 905 bzero(&ueh, sizeof(struct ufs_extattr_header)); 906 local_aiov.iov_base = (caddr_t) &ueh; 907 local_aiov.iov_len = sizeof(struct ufs_extattr_header); 908 local_aio.uio_iov = &local_aiov; 909 local_aio.uio_iovcnt = 1; 910 local_aio.uio_rw = UIO_READ; 911 local_aio.uio_segflg = UIO_SYSSPACE; 912 local_aio.uio_td = td; 913 local_aio.uio_offset = base_offset; 914 local_aio.uio_resid = sizeof(struct ufs_extattr_header); 915 916 /* 917 * Acquire locks. 918 */ 919 VOP_LEASE(attribute->uele_backing_vnode, td, cred, LEASE_READ); 920 /* 921 * Don't need to get a lock on the backing file if the getattr is 922 * being applied to the backing file, as the lock is already held. 923 */ 924 if (attribute->uele_backing_vnode != vp) 925 vn_lock(attribute->uele_backing_vnode, LK_SHARED | 926 LK_NOPAUSE | LK_RETRY, td); 927 928 error = VOP_READ(attribute->uele_backing_vnode, &local_aio, 929 IO_NODELOCKED, ump->um_extattr.uepm_ucred); 930 if (error) 931 goto vopunlock_exit; 932 933 /* Defined? */ 934 if ((ueh.ueh_flags & UFS_EXTATTR_ATTR_FLAG_INUSE) == 0) { 935 /* XXX: ENOENT here will eventually be ENOATTR. */ 936 error = ENOENT; 937 goto vopunlock_exit; 938 } 939 940 /* Valid for the current inode generation? */ 941 if (ueh.ueh_i_gen != ip->i_gen) { 942 /* 943 * The inode itself has a different generation number 944 * than the attribute data. For now, the best solution 945 * is to coerce this to undefined, and let it get cleaned 946 * up by the next write or extattrctl clean. 947 */ 948 printf("ufs_extattr_get: inode number inconsistency (%d, %d)\n", 949 ueh.ueh_i_gen, ip->i_gen); 950 /* XXX: ENOENT here will eventually be ENOATTR. */ 951 error = ENOENT; 952 goto vopunlock_exit; 953 } 954 955 /* Local size consistency check. */ 956 if (ueh.ueh_len > attribute->uele_fileheader.uef_size) { 957 error = ENXIO; 958 goto vopunlock_exit; 959 } 960 961 /* Return full data size if caller requested it. */ 962 if (size != NULL) 963 *size = ueh.ueh_len; 964 965 /* Return data if the caller requested it. */ 966 if (uio != NULL) { 967 /* Allow for offset into the attribute data. */ 968 uio->uio_offset = base_offset + sizeof(struct 969 ufs_extattr_header); 970 971 /* 972 * Figure out maximum to transfer -- use buffer size and 973 * local data limit. 974 */ 975 len = MIN(uio->uio_resid, ueh.ueh_len); 976 old_len = uio->uio_resid; 977 uio->uio_resid = len; 978 979 error = VOP_READ(attribute->uele_backing_vnode, uio, 980 IO_NODELOCKED, ump->um_extattr.uepm_ucred); 981 if (error) 982 goto vopunlock_exit; 983 984 uio->uio_resid = old_len - (len - uio->uio_resid); 985 } 986 987vopunlock_exit: 988 989 if (uio != NULL) 990 uio->uio_offset = 0; 991 992 if (attribute->uele_backing_vnode != vp) 993 VOP_UNLOCK(attribute->uele_backing_vnode, 0, td); 994 995 return (error); 996} 997 998/* 999 * Vnode operation to set a named attribute. 1000 */ 1001int 1002ufs_vop_setextattr(struct vop_setextattr_args *ap) 1003/* 1004vop_setextattr { 1005 IN struct vnode *a_vp; 1006 IN int a_attrnamespace; 1007 IN const char *a_name; 1008 INOUT struct uio *a_uio; 1009 IN struct ucred *a_cred; 1010 IN struct thread *a_td; 1011}; 1012*/ 1013{ 1014 struct mount *mp = ap->a_vp->v_mount; 1015 struct ufsmount *ump = VFSTOUFS(mp); 1016 1017 int error; 1018 1019 ufs_extattr_uepm_lock(ump, ap->a_td); 1020 1021 if (ap->a_uio != NULL) 1022 error = ufs_extattr_set(ap->a_vp, ap->a_attrnamespace, 1023 ap->a_name, ap->a_uio, ap->a_cred, ap->a_td); 1024 else 1025 error = ufs_extattr_rm(ap->a_vp, ap->a_attrnamespace, 1026 ap->a_name, ap->a_cred, ap->a_td); 1027 1028 ufs_extattr_uepm_unlock(ump, ap->a_td); 1029 1030 return (error); 1031} 1032 1033/* 1034 * Real work associated with setting a vnode's extended attributes; 1035 * assumes that the attribute lock has already been grabbed. 1036 */ 1037static int 1038ufs_extattr_set(struct vnode *vp, int attrnamespace, const char *name, 1039 struct uio *uio, struct ucred *cred, struct thread *td) 1040{ 1041 struct ufs_extattr_list_entry *attribute; 1042 struct ufs_extattr_header ueh; 1043 struct iovec local_aiov; 1044 struct uio local_aio; 1045 struct mount *mp = vp->v_mount; 1046 struct ufsmount *ump = VFSTOUFS(mp); 1047 struct inode *ip = VTOI(vp); 1048 off_t base_offset; 1049 int error = 0, ioflag; 1050 1051 if (vp->v_mount->mnt_flag & MNT_RDONLY) 1052 return (EROFS); 1053 if (!(ump->um_extattr.uepm_flags & UFS_EXTATTR_UEPM_STARTED)) 1054 return (EOPNOTSUPP); 1055 if (!ufs_extattr_valid_attrname(attrnamespace, name)) 1056 return (EINVAL); 1057 1058 attribute = ufs_extattr_find_attr(ump, attrnamespace, name); 1059 if (!attribute) 1060 /* XXX: ENOENT here will eventually be ENOATTR. */ 1061 return (ENOENT); 1062 1063 if ((error = ufs_extattr_credcheck(vp, attribute, cred, td, IWRITE))) 1064 return (error); 1065 1066 /* 1067 * Early rejection of invalid offsets/length. 1068 * Reject: any offset but 0 (replace) 1069 * Any size greater than attribute size limit 1070 */ 1071 if (uio->uio_offset != 0 || 1072 uio->uio_resid > attribute->uele_fileheader.uef_size) 1073 return (ENXIO); 1074 1075 /* 1076 * Find base offset of header in file based on file header size, and 1077 * data header size + maximum data size, indexed by inode number. 1078 */ 1079 base_offset = sizeof(struct ufs_extattr_fileheader) + 1080 ip->i_number * (sizeof(struct ufs_extattr_header) + 1081 attribute->uele_fileheader.uef_size); 1082 1083 /* 1084 * Write out a data header for the data. 1085 */ 1086 ueh.ueh_len = uio->uio_resid; 1087 ueh.ueh_flags = UFS_EXTATTR_ATTR_FLAG_INUSE; 1088 ueh.ueh_i_gen = ip->i_gen; 1089 local_aiov.iov_base = (caddr_t) &ueh; 1090 local_aiov.iov_len = sizeof(struct ufs_extattr_header); 1091 local_aio.uio_iov = &local_aiov; 1092 local_aio.uio_iovcnt = 1; 1093 local_aio.uio_rw = UIO_WRITE; 1094 local_aio.uio_segflg = UIO_SYSSPACE; 1095 local_aio.uio_td = td; 1096 local_aio.uio_offset = base_offset; 1097 local_aio.uio_resid = sizeof(struct ufs_extattr_header); 1098 1099 /* 1100 * Acquire locks. 1101 */ 1102 VOP_LEASE(attribute->uele_backing_vnode, td, cred, LEASE_WRITE); 1103 1104 /* 1105 * Don't need to get a lock on the backing file if the setattr is 1106 * being applied to the backing file, as the lock is already held. 1107 */ 1108 if (attribute->uele_backing_vnode != vp) 1109 vn_lock(attribute->uele_backing_vnode, 1110 LK_EXCLUSIVE | LK_NOPAUSE | LK_RETRY, td); 1111 1112 ioflag = IO_NODELOCKED; 1113 if (ufs_extattr_sync) 1114 ioflag |= IO_SYNC; 1115 error = VOP_WRITE(attribute->uele_backing_vnode, &local_aio, ioflag, 1116 ump->um_extattr.uepm_ucred); 1117 if (error) 1118 goto vopunlock_exit; 1119 1120 if (local_aio.uio_resid != 0) { 1121 error = ENXIO; 1122 goto vopunlock_exit; 1123 } 1124 1125 /* 1126 * Write out user data. 1127 */ 1128 uio->uio_offset = base_offset + sizeof(struct ufs_extattr_header); 1129 1130 ioflag = IO_NODELOCKED; 1131 if (ufs_extattr_sync) 1132 ioflag |= IO_SYNC; 1133 error = VOP_WRITE(attribute->uele_backing_vnode, uio, ioflag, 1134 ump->um_extattr.uepm_ucred); 1135 1136vopunlock_exit: 1137 uio->uio_offset = 0; 1138 1139 if (attribute->uele_backing_vnode != vp) 1140 VOP_UNLOCK(attribute->uele_backing_vnode, 0, td); 1141 1142 return (error); 1143} 1144 1145/* 1146 * Real work associated with removing an extended attribute from a vnode. 1147 * Assumes the attribute lock has already been grabbed. 1148 */ 1149static int 1150ufs_extattr_rm(struct vnode *vp, int attrnamespace, const char *name, 1151 struct ucred *cred, struct thread *td) 1152{ 1153 struct ufs_extattr_list_entry *attribute; 1154 struct ufs_extattr_header ueh; 1155 struct iovec local_aiov; 1156 struct uio local_aio; 1157 struct mount *mp = vp->v_mount; 1158 struct ufsmount *ump = VFSTOUFS(mp); 1159 struct inode *ip = VTOI(vp); 1160 off_t base_offset; 1161 int error = 0, ioflag; 1162 1163 if (vp->v_mount->mnt_flag & MNT_RDONLY) 1164 return (EROFS); 1165 if (!(ump->um_extattr.uepm_flags & UFS_EXTATTR_UEPM_STARTED)) 1166 return (EOPNOTSUPP); 1167 if (!ufs_extattr_valid_attrname(attrnamespace, name)) 1168 return (EINVAL); 1169 1170 attribute = ufs_extattr_find_attr(ump, attrnamespace, name); 1171 if (!attribute) 1172 /* XXX: ENOENT here will eventually be ENOATTR. */ 1173 return (ENOENT); 1174 1175 if ((error = ufs_extattr_credcheck(vp, attribute, cred, td, IWRITE))) 1176 return (error); 1177 1178 /* 1179 * Find base offset of header in file based on file header size, and 1180 * data header size + maximum data size, indexed by inode number. 1181 */ 1182 base_offset = sizeof(struct ufs_extattr_fileheader) + 1183 ip->i_number * (sizeof(struct ufs_extattr_header) + 1184 attribute->uele_fileheader.uef_size); 1185 1186 /* 1187 * Check to see if currently defined. 1188 */ 1189 bzero(&ueh, sizeof(struct ufs_extattr_header)); 1190 1191 local_aiov.iov_base = (caddr_t) &ueh; 1192 local_aiov.iov_len = sizeof(struct ufs_extattr_header); 1193 local_aio.uio_iov = &local_aiov; 1194 local_aio.uio_iovcnt = 1; 1195 local_aio.uio_rw = UIO_READ; 1196 local_aio.uio_segflg = UIO_SYSSPACE; 1197 local_aio.uio_td = td; 1198 local_aio.uio_offset = base_offset; 1199 local_aio.uio_resid = sizeof(struct ufs_extattr_header); 1200 1201 VOP_LEASE(attribute->uele_backing_vnode, td, cred, LEASE_WRITE); 1202 1203 /* 1204 * Don't need to get the lock on the backing vnode if the vnode we're 1205 * modifying is it, as we already hold the lock. 1206 */ 1207 if (attribute->uele_backing_vnode != vp) 1208 vn_lock(attribute->uele_backing_vnode, 1209 LK_EXCLUSIVE | LK_NOPAUSE | LK_RETRY, td); 1210 1211 error = VOP_READ(attribute->uele_backing_vnode, &local_aio, 1212 IO_NODELOCKED, ump->um_extattr.uepm_ucred); 1213 if (error) 1214 goto vopunlock_exit; 1215 1216 /* Defined? */ 1217 if ((ueh.ueh_flags & UFS_EXTATTR_ATTR_FLAG_INUSE) == 0) { 1218 /* XXX: ENOENT here will eventually be ENOATTR. */ 1219 error = ENOENT; 1220 goto vopunlock_exit; 1221 } 1222 1223 /* Valid for the current inode generation? */ 1224 if (ueh.ueh_i_gen != ip->i_gen) { 1225 /* 1226 * The inode itself has a different generation number than 1227 * the attribute data. For now, the best solution is to 1228 * coerce this to undefined, and let it get cleaned up by 1229 * the next write or extattrctl clean. 1230 */ 1231 printf("ufs_extattr_rm: inode number inconsistency (%d, %d)\n", 1232 ueh.ueh_i_gen, ip->i_gen); 1233 /* XXX: ENOENT here will eventually be ENOATTR. */ 1234 error = ENOENT; 1235 goto vopunlock_exit; 1236 } 1237 1238 /* Flag it as not in use. */ 1239 ueh.ueh_flags = 0; 1240 ueh.ueh_len = 0; 1241 1242 local_aiov.iov_base = (caddr_t) &ueh; 1243 local_aiov.iov_len = sizeof(struct ufs_extattr_header); 1244 local_aio.uio_iov = &local_aiov; 1245 local_aio.uio_iovcnt = 1; 1246 local_aio.uio_rw = UIO_WRITE; 1247 local_aio.uio_segflg = UIO_SYSSPACE; 1248 local_aio.uio_td = td; 1249 local_aio.uio_offset = base_offset; 1250 local_aio.uio_resid = sizeof(struct ufs_extattr_header); 1251 1252 ioflag = IO_NODELOCKED; 1253 if (ufs_extattr_sync) 1254 ioflag |= IO_SYNC; 1255 error = VOP_WRITE(attribute->uele_backing_vnode, &local_aio, ioflag, 1256 ump->um_extattr.uepm_ucred); 1257 if (error) 1258 goto vopunlock_exit; 1259 1260 if (local_aio.uio_resid != 0) 1261 error = ENXIO; 1262 1263vopunlock_exit: 1264 VOP_UNLOCK(attribute->uele_backing_vnode, 0, td); 1265 1266 return (error); 1267} 1268 1269/* 1270 * Called by UFS when an inode is no longer active and should have its 1271 * attributes stripped. 1272 */ 1273void 1274ufs_extattr_vnode_inactive(struct vnode *vp, struct thread *td) 1275{ 1276 struct ufs_extattr_list_entry *uele; 1277 struct mount *mp = vp->v_mount; 1278 struct ufsmount *ump = VFSTOUFS(mp); 1279 1280 /* 1281 * In that case, we cannot lock. We should not have any active vnodes 1282 * on the fs if this is not yet initialized but is going to be, so 1283 * this can go unlocked. 1284 */ 1285 if (!(ump->um_extattr.uepm_flags & UFS_EXTATTR_UEPM_INITIALIZED)) 1286 return; 1287 1288 ufs_extattr_uepm_lock(ump, td); 1289 1290 if (!(ump->um_extattr.uepm_flags & UFS_EXTATTR_UEPM_STARTED)) { 1291 ufs_extattr_uepm_unlock(ump, td); 1292 return; 1293 } 1294 1295 LIST_FOREACH(uele, &ump->um_extattr.uepm_list, uele_entries) 1296 ufs_extattr_rm(vp, uele->uele_attrnamespace, 1297 uele->uele_attrname, NULL, td); 1298 1299 ufs_extattr_uepm_unlock(ump, td); 1300} 1301 1302#endif /* !UFS_EXTATTR */ 1303