1 /*
   2  * CDDL HEADER START
   3  *
   4  * The contents of this file are subject to the terms of the
   5  * Common Development and Distribution License (the "License").
   6  * You may not use this file except in compliance with the License.
   7  *
   8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
   9  * or http://www.opensolaris.org/os/licensing.
  10  * See the License for the specific language governing permissions
  11  * and limitations under the License.
  12  *
  13  * When distributing Covered Code, include this CDDL HEADER in each
  14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  15  * If applicable, add the following below this CDDL HEADER, with the
  16  * fields enclosed by brackets "[]" replaced with your own identifying
  17  * information: Portions Copyright [yyyy] [name of copyright owner]
  18  *
  19  * CDDL HEADER END
  20  */
  21 /*
  22  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
  23  * Use is subject to license terms.
  24  */
  25 
  26 #include <sys/param.h>
  27 #include <sys/types.h>
  28 #include <sys/systm.h>
  29 #include <sys/cred.h>
  30 #include <sys/proc.h>
  31 #include <sys/user.h>
  32 #include <sys/buf.h>
  33 #include <sys/vfs.h>
  34 #include <sys/vnode.h>
  35 #include <sys/pathname.h>
  36 #include <sys/uio.h>
  37 #include <sys/file.h>
  38 #include <sys/stat.h>
  39 #include <sys/errno.h>
  40 #include <sys/socket.h>
  41 #include <sys/sysmacros.h>
  42 #include <sys/siginfo.h>
  43 #include <sys/tiuser.h>
  44 #include <sys/statvfs.h>
  45 #include <sys/t_kuser.h>
  46 #include <sys/kmem.h>
  47 #include <sys/kstat.h>
  48 #include <sys/acl.h>
  49 #include <sys/dirent.h>
  50 #include <sys/cmn_err.h>
  51 #include <sys/debug.h>
  52 #include <sys/unistd.h>
  53 #include <sys/vtrace.h>
  54 #include <sys/mode.h>
  55 
  56 #include <rpc/types.h>
  57 #include <rpc/auth.h>
  58 #include <rpc/svc.h>
  59 #include <rpc/xdr.h>
  60 
  61 #include <nfs/nfs.h>
  62 #include <nfs/export.h>
  63 #include <nfs/nfssys.h>
  64 #include <nfs/nfs_clnt.h>
  65 #include <nfs/nfs_acl.h>
  66 
  67 #include <fs/fs_subr.h>
  68 
  69 /*
  70  * These are the interface routines for the server side of the
  71  * NFS ACL server.  See the NFS ACL protocol specification
  72  * for a description of this interface.
  73  */
  74 
  75 /* ARGSUSED */
  76 void
  77 acl2_getacl(GETACL2args *args, GETACL2res *resp, struct exportinfo *exi,
  78         struct svc_req *req, cred_t *cr)
  79 {
  80         int error;
  81         vnode_t *vp;
  82         vattr_t va;
  83 
  84         vp = nfs_fhtovp(&args->fh, exi);
  85         if (vp == NULL) {
  86                 resp->status = NFSERR_STALE;
  87                 return;
  88         }
  89 
  90         bzero((caddr_t)&resp->resok.acl, sizeof (resp->resok.acl));
  91 
  92         resp->resok.acl.vsa_mask = args->mask;
  93 
  94         error = VOP_GETSECATTR(vp, &resp->resok.acl, 0, cr, NULL);
  95 
  96         if ((error == ENOSYS) && !(exi->exi_export.ex_flags & EX_NOACLFAB)) {
  97                 /*
  98                  * If the underlying file system doesn't support
  99                  * aclent_t type acls, fabricate an acl.  This is
 100                  * required in order to to support existing clients
 101                  * that require the call to VOP_GETSECATTR to
 102                  * succeed while making the assumption that all
 103                  * file systems support aclent_t type acls.  This
 104                  * causes problems for servers exporting ZFS file
 105                  * systems because ZFS supports ace_t type acls,
 106                  * and fails (with ENOSYS) when asked for aclent_t
 107                  * type acls.
 108                  *
 109                  * Note: if the fs_fab_acl() fails, we have other problems.
 110                  * This error should be returned to the caller.
 111                  */
 112                 error = fs_fab_acl(vp, &resp->resok.acl, 0, cr, NULL);
 113         }
 114 
 115         if (error) {
 116                 VN_RELE(vp);
 117                 resp->status = puterrno(error);
 118                 return;
 119         }
 120 
 121         va.va_mask = AT_ALL;
 122         error = rfs4_delegated_getattr(vp, &va, 0, cr);
 123 
 124         VN_RELE(vp);
 125 
 126         /* check for overflowed values */
 127         if (!error) {
 128                 error = vattr_to_nattr(&va, &resp->resok.attr);
 129         }
 130         if (error) {
 131                 resp->status = puterrno(error);
 132                 if (resp->resok.acl.vsa_aclcnt > 0 &&
 133                     resp->resok.acl.vsa_aclentp != NULL) {
 134                         kmem_free((caddr_t)resp->resok.acl.vsa_aclentp,
 135                             resp->resok.acl.vsa_aclcnt * sizeof (aclent_t));
 136                 }
 137                 if (resp->resok.acl.vsa_dfaclcnt > 0 &&
 138                     resp->resok.acl.vsa_dfaclentp != NULL) {
 139                         kmem_free((caddr_t)resp->resok.acl.vsa_dfaclentp,
 140                             resp->resok.acl.vsa_dfaclcnt * sizeof (aclent_t));
 141                 }
 142                 return;
 143         }
 144 
 145         resp->status = NFS_OK;
 146         if (!(args->mask & NA_ACL)) {
 147                 if (resp->resok.acl.vsa_aclcnt > 0 &&
 148                     resp->resok.acl.vsa_aclentp != NULL) {
 149                         kmem_free((caddr_t)resp->resok.acl.vsa_aclentp,
 150                             resp->resok.acl.vsa_aclcnt * sizeof (aclent_t));
 151                 }
 152                 resp->resok.acl.vsa_aclentp = NULL;
 153         }
 154         if (!(args->mask & NA_DFACL)) {
 155                 if (resp->resok.acl.vsa_dfaclcnt > 0 &&
 156                     resp->resok.acl.vsa_dfaclentp != NULL) {
 157                         kmem_free((caddr_t)resp->resok.acl.vsa_dfaclentp,
 158                             resp->resok.acl.vsa_dfaclcnt * sizeof (aclent_t));
 159                 }
 160                 resp->resok.acl.vsa_dfaclentp = NULL;
 161         }
 162 }
 163 
 164 void *
 165 acl2_getacl_getfh(GETACL2args *args)
 166 {
 167 
 168         return (&args->fh);
 169 }
 170 
 171 void
 172 acl2_getacl_free(GETACL2res *resp)
 173 {
 174 
 175         if (resp->status == NFS_OK) {
 176                 if (resp->resok.acl.vsa_aclcnt > 0 &&
 177                     resp->resok.acl.vsa_aclentp != NULL) {
 178                         kmem_free((caddr_t)resp->resok.acl.vsa_aclentp,
 179                             resp->resok.acl.vsa_aclcnt * sizeof (aclent_t));
 180                 }
 181                 if (resp->resok.acl.vsa_dfaclcnt > 0 &&
 182                     resp->resok.acl.vsa_dfaclentp != NULL) {
 183                         kmem_free((caddr_t)resp->resok.acl.vsa_dfaclentp,
 184                             resp->resok.acl.vsa_dfaclcnt * sizeof (aclent_t));
 185                 }
 186         }
 187 }
 188 
 189 /* ARGSUSED */
 190 void
 191 acl2_setacl(SETACL2args *args, SETACL2res *resp, struct exportinfo *exi,
 192         struct svc_req *req, cred_t *cr)
 193 {
 194         int error;
 195         vnode_t *vp;
 196         vattr_t va;
 197 
 198         vp = nfs_fhtovp(&args->fh, exi);
 199         if (vp == NULL) {
 200                 resp->status = NFSERR_STALE;
 201                 return;
 202         }
 203 
 204         if (rdonly(exi, req) || vn_is_readonly(vp)) {
 205                 VN_RELE(vp);
 206                 resp->status = NFSERR_ROFS;
 207                 return;
 208         }
 209 
 210         (void) VOP_RWLOCK(vp, V_WRITELOCK_TRUE, NULL);
 211         error = VOP_SETSECATTR(vp, &args->acl, 0, cr, NULL);
 212         if (error) {
 213                 VOP_RWUNLOCK(vp, V_WRITELOCK_TRUE, NULL);
 214                 VN_RELE(vp);
 215                 resp->status = puterrno(error);
 216                 return;
 217         }
 218 
 219         va.va_mask = AT_ALL;
 220         error = rfs4_delegated_getattr(vp, &va, 0, cr);
 221 
 222         VOP_RWUNLOCK(vp, V_WRITELOCK_TRUE, NULL);
 223         VN_RELE(vp);
 224 
 225         /* check for overflowed values */
 226         if (!error) {
 227                 error = vattr_to_nattr(&va, &resp->resok.attr);
 228         }
 229         if (error) {
 230                 resp->status = puterrno(error);
 231                 return;
 232         }
 233 
 234         resp->status = NFS_OK;
 235 }
 236 
 237 void *
 238 acl2_setacl_getfh(SETACL2args *args)
 239 {
 240 
 241         return (&args->fh);
 242 }
 243 
 244 /* ARGSUSED */
 245 void
 246 acl2_getattr(GETATTR2args *args, GETATTR2res *resp, struct exportinfo *exi,
 247         struct svc_req *req, cred_t *cr)
 248 {
 249         int error;
 250         vnode_t *vp;
 251         vattr_t va;
 252 
 253         vp = nfs_fhtovp(&args->fh, exi);
 254         if (vp == NULL) {
 255                 resp->status = NFSERR_STALE;
 256                 return;
 257         }
 258 
 259         va.va_mask = AT_ALL;
 260         error = rfs4_delegated_getattr(vp, &va, 0, cr);
 261 
 262         VN_RELE(vp);
 263 
 264         /* check for overflowed values */
 265         if (!error) {
 266                 error = vattr_to_nattr(&va, &resp->resok.attr);
 267         }
 268         if (error) {
 269                 resp->status = puterrno(error);
 270                 return;
 271         }
 272 
 273         resp->status = NFS_OK;
 274 }
 275 
 276 void *
 277 acl2_getattr_getfh(GETATTR2args *args)
 278 {
 279 
 280         return (&args->fh);
 281 }
 282 
 283 /* ARGSUSED */
 284 void
 285 acl2_access(ACCESS2args *args, ACCESS2res *resp, struct exportinfo *exi,
 286         struct svc_req *req, cred_t *cr)
 287 {
 288         int error;
 289         vnode_t *vp;
 290         vattr_t va;
 291         int checkwriteperm;
 292 
 293         vp = nfs_fhtovp(&args->fh, exi);
 294         if (vp == NULL) {
 295                 resp->status = NFSERR_STALE;
 296                 return;
 297         }
 298 
 299         /*
 300          * If the file system is exported read only, it is not appropriate
 301          * to check write permissions for regular files and directories.
 302          * Special files are interpreted by the client, so the underlying
 303          * permissions are sent back to the client for interpretation.
 304          */
 305         if (rdonly(exi, req) && (vp->v_type == VREG || vp->v_type == VDIR))
 306                 checkwriteperm = 0;
 307         else
 308                 checkwriteperm = 1;
 309 
 310         /*
 311          * We need the mode so that we can correctly determine access
 312          * permissions relative to a mandatory lock file.  Access to
 313          * mandatory lock files is denied on the server, so it might
 314          * as well be reflected to the server during the open.
 315          */
 316         va.va_mask = AT_MODE;
 317         error = VOP_GETATTR(vp, &va, 0, cr, NULL);
 318         if (error) {
 319                 VN_RELE(vp);
 320                 resp->status = puterrno(error);
 321                 return;
 322         }
 323 
 324         resp->resok.access = 0;
 325 
 326         if (args->access & ACCESS2_READ) {
 327                 error = VOP_ACCESS(vp, VREAD, 0, cr, NULL);
 328                 if (!error && !MANDLOCK(vp, va.va_mode))
 329                         resp->resok.access |= ACCESS2_READ;
 330         }
 331         if ((args->access & ACCESS2_LOOKUP) && vp->v_type == VDIR) {
 332                 error = VOP_ACCESS(vp, VEXEC, 0, cr, NULL);
 333                 if (!error)
 334                         resp->resok.access |= ACCESS2_LOOKUP;
 335         }
 336         if (checkwriteperm &&
 337             (args->access & (ACCESS2_MODIFY|ACCESS2_EXTEND))) {
 338                 error = VOP_ACCESS(vp, VWRITE, 0, cr, NULL);
 339                 if (!error && !MANDLOCK(vp, va.va_mode))
 340                         resp->resok.access |=
 341                             (args->access & (ACCESS2_MODIFY|ACCESS2_EXTEND));
 342         }
 343         if (checkwriteperm &&
 344             (args->access & ACCESS2_DELETE) && (vp->v_type == VDIR)) {
 345                 error = VOP_ACCESS(vp, VWRITE, 0, cr, NULL);
 346                 if (!error)
 347                         resp->resok.access |= ACCESS2_DELETE;
 348         }
 349         if (args->access & ACCESS2_EXECUTE) {
 350                 error = VOP_ACCESS(vp, VEXEC, 0, cr, NULL);
 351                 if (!error && !MANDLOCK(vp, va.va_mode))
 352                         resp->resok.access |= ACCESS2_EXECUTE;
 353         }
 354 
 355         va.va_mask = AT_ALL;
 356         error = rfs4_delegated_getattr(vp, &va, 0, cr);
 357 
 358         VN_RELE(vp);
 359 
 360         /* check for overflowed values */
 361         if (!error) {
 362                 error = vattr_to_nattr(&va, &resp->resok.attr);
 363         }
 364         if (error) {
 365                 resp->status = puterrno(error);
 366                 return;
 367         }
 368 
 369         resp->status = NFS_OK;
 370 }
 371 
 372 void *
 373 acl2_access_getfh(ACCESS2args *args)
 374 {
 375 
 376         return (&args->fh);
 377 }
 378 
 379 /* ARGSUSED */
 380 void
 381 acl2_getxattrdir(GETXATTRDIR2args *args, GETXATTRDIR2res *resp,
 382         struct exportinfo *exi, struct svc_req *req, cred_t *cr)
 383 {
 384         int error;
 385         int flags;
 386         vnode_t *vp, *avp;
 387 
 388         vp = nfs_fhtovp(&args->fh, exi);
 389         if (vp == NULL) {
 390                 resp->status = NFSERR_STALE;
 391                 return;
 392         }
 393 
 394         flags = LOOKUP_XATTR;
 395         if (args->create)
 396                 flags |= CREATE_XATTR_DIR;
 397         else {
 398                 ulong_t val = 0;
 399                 error = VOP_PATHCONF(vp, _PC_SATTR_EXISTS, &val, cr, NULL);
 400                 if (!error && val == 0) {
 401                         error = VOP_PATHCONF(vp, _PC_XATTR_EXISTS,
 402                             &val, cr, NULL);
 403                         if (!error && val == 0) {
 404                                 VN_RELE(vp);
 405                                 resp->status = NFSERR_NOENT;
 406                                 return;
 407                         }
 408                 }
 409         }
 410 
 411         error = VOP_LOOKUP(vp, "", &avp, NULL, flags, NULL, cr,
 412             NULL, NULL, NULL);
 413         if (!error && avp == vp) {      /* lookup of "" on old FS? */
 414                 error = EINVAL;
 415                 VN_RELE(avp);
 416         }
 417         if (!error) {
 418                 struct vattr va;
 419                 va.va_mask = AT_ALL;
 420                 error = rfs4_delegated_getattr(avp, &va, 0, cr);
 421                 if (!error) {
 422                         error = vattr_to_nattr(&va, &resp->resok.attr);
 423                         if (!error)
 424                                 error = makefh(&resp->resok.fh, avp, exi);
 425                 }
 426                 VN_RELE(avp);
 427         }
 428 
 429         VN_RELE(vp);
 430 
 431         if (error) {
 432                 resp->status = puterrno(error);
 433                 return;
 434         }
 435         resp->status = NFS_OK;
 436 }
 437 
 438 void *
 439 acl2_getxattrdir_getfh(GETXATTRDIR2args *args)
 440 {
 441         return (&args->fh);
 442 }
 443 
 444 /* ARGSUSED */
 445 void
 446 acl3_getacl(GETACL3args *args, GETACL3res *resp, struct exportinfo *exi,
 447         struct svc_req *req, cred_t *cr)
 448 {
 449         int error;
 450         vnode_t *vp;
 451         vattr_t *vap;
 452         vattr_t va;
 453 
 454         vap = NULL;
 455 
 456         vp = nfs3_fhtovp(&args->fh, exi);
 457         if (vp == NULL) {
 458                 error = ESTALE;
 459                 goto out;
 460         }
 461 
 462         va.va_mask = AT_ALL;
 463         vap = rfs4_delegated_getattr(vp, &va, 0, cr) ? NULL : &va;
 464 
 465         bzero((caddr_t)&resp->resok.acl, sizeof (resp->resok.acl));
 466 
 467         resp->resok.acl.vsa_mask = args->mask;
 468 
 469         error = VOP_GETSECATTR(vp, &resp->resok.acl, 0, cr, NULL);
 470 
 471         if ((error == ENOSYS) && !(exi->exi_export.ex_flags & EX_NOACLFAB)) {
 472                 /*
 473                  * If the underlying file system doesn't support
 474                  * aclent_t type acls, fabricate an acl.  This is
 475                  * required in order to to support existing clients
 476                  * that require the call to VOP_GETSECATTR to
 477                  * succeed while making the assumption that all
 478                  * file systems support aclent_t type acls.  This
 479                  * causes problems for servers exporting ZFS file
 480                  * systems because ZFS supports ace_t type acls,
 481                  * and fails (with ENOSYS) when asked for aclent_t
 482                  * type acls.
 483                  *
 484                  * Note: if the fs_fab_acl() fails, we have other problems.
 485                  * This error should be returned to the caller.
 486                  */
 487                 error = fs_fab_acl(vp, &resp->resok.acl, 0, cr, NULL);
 488         }
 489 
 490         if (error)
 491                 goto out;
 492 
 493         va.va_mask = AT_ALL;
 494         vap = rfs4_delegated_getattr(vp, &va, 0, cr) ? NULL : &va;
 495 
 496         VN_RELE(vp);
 497 
 498         resp->status = NFS3_OK;
 499         vattr_to_post_op_attr(vap, &resp->resok.attr);
 500         if (!(args->mask & NA_ACL)) {
 501                 if (resp->resok.acl.vsa_aclcnt > 0 &&
 502                     resp->resok.acl.vsa_aclentp != NULL) {
 503                         kmem_free((caddr_t)resp->resok.acl.vsa_aclentp,
 504                             resp->resok.acl.vsa_aclcnt * sizeof (aclent_t));
 505                 }
 506                 resp->resok.acl.vsa_aclentp = NULL;
 507         }
 508         if (!(args->mask & NA_DFACL)) {
 509                 if (resp->resok.acl.vsa_dfaclcnt > 0 &&
 510                     resp->resok.acl.vsa_dfaclentp != NULL) {
 511                         kmem_free((caddr_t)resp->resok.acl.vsa_dfaclentp,
 512                             resp->resok.acl.vsa_dfaclcnt * sizeof (aclent_t));
 513                 }
 514                 resp->resok.acl.vsa_dfaclentp = NULL;
 515         }
 516         return;
 517 
 518 out:
 519         if (curthread->t_flag & T_WOULDBLOCK) {
 520                 curthread->t_flag &= ~T_WOULDBLOCK;
 521                 resp->status = NFS3ERR_JUKEBOX;
 522         } else
 523                 resp->status = puterrno3(error);
 524 out1:
 525         if (vp != NULL)
 526                 VN_RELE(vp);
 527         vattr_to_post_op_attr(vap, &resp->resfail.attr);
 528 }
 529 
 530 void *
 531 acl3_getacl_getfh(GETACL3args *args)
 532 {
 533 
 534         return (&args->fh);
 535 }
 536 
 537 void
 538 acl3_getacl_free(GETACL3res *resp)
 539 {
 540 
 541         if (resp->status == NFS3_OK) {
 542                 if (resp->resok.acl.vsa_aclcnt > 0 &&
 543                     resp->resok.acl.vsa_aclentp != NULL) {
 544                         kmem_free((caddr_t)resp->resok.acl.vsa_aclentp,
 545                             resp->resok.acl.vsa_aclcnt * sizeof (aclent_t));
 546                 }
 547                 if (resp->resok.acl.vsa_dfaclcnt > 0 &&
 548                     resp->resok.acl.vsa_dfaclentp != NULL) {
 549                         kmem_free((caddr_t)resp->resok.acl.vsa_dfaclentp,
 550                             resp->resok.acl.vsa_dfaclcnt * sizeof (aclent_t));
 551                 }
 552         }
 553 }
 554 
 555 /* ARGSUSED */
 556 void
 557 acl3_setacl(SETACL3args *args, SETACL3res *resp, struct exportinfo *exi,
 558         struct svc_req *req, cred_t *cr)
 559 {
 560         int error;
 561         vnode_t *vp;
 562         vattr_t *vap;
 563         vattr_t va;
 564 
 565         vap = NULL;
 566 
 567         vp = nfs3_fhtovp(&args->fh, exi);
 568         if (vp == NULL) {
 569                 error = ESTALE;
 570                 goto out1;
 571         }
 572 
 573         (void) VOP_RWLOCK(vp, V_WRITELOCK_TRUE, NULL);
 574 
 575         va.va_mask = AT_ALL;
 576         vap = rfs4_delegated_getattr(vp, &va, 0, cr) ? NULL : &va;
 577 
 578         if (rdonly(exi, req) || vn_is_readonly(vp)) {
 579                 resp->status = NFS3ERR_ROFS;
 580                 goto out1;
 581         }
 582 
 583         error = VOP_SETSECATTR(vp, &args->acl, 0, cr, NULL);
 584 
 585         va.va_mask = AT_ALL;
 586         vap = rfs4_delegated_getattr(vp, &va, 0, cr) ? NULL : &va;
 587 
 588         if (error)
 589                 goto out;
 590 
 591         VOP_RWUNLOCK(vp, V_WRITELOCK_TRUE, NULL);
 592         VN_RELE(vp);
 593 
 594         resp->status = NFS3_OK;
 595         vattr_to_post_op_attr(vap, &resp->resok.attr);
 596         return;
 597 
 598 out:
 599         if (curthread->t_flag & T_WOULDBLOCK) {
 600                 curthread->t_flag &= ~T_WOULDBLOCK;
 601                 resp->status = NFS3ERR_JUKEBOX;
 602         } else
 603                 resp->status = puterrno3(error);
 604 out1:
 605         if (vp != NULL) {
 606                 VOP_RWUNLOCK(vp, V_WRITELOCK_TRUE, NULL);
 607                 VN_RELE(vp);
 608         }
 609         vattr_to_post_op_attr(vap, &resp->resfail.attr);
 610 }
 611 
 612 void *
 613 acl3_setacl_getfh(SETACL3args *args)
 614 {
 615 
 616         return (&args->fh);
 617 }
 618 
 619 /* ARGSUSED */
 620 void
 621 acl3_getxattrdir(GETXATTRDIR3args *args, GETXATTRDIR3res *resp,
 622         struct exportinfo *exi, struct svc_req *req, cred_t *cr)
 623 {
 624         int error;
 625         int flags;
 626         vnode_t *vp, *avp;
 627 
 628         vp = nfs3_fhtovp(&args->fh, exi);
 629         if (vp == NULL) {
 630                 resp->status = NFS3ERR_STALE;
 631                 return;
 632         }
 633 
 634         flags = LOOKUP_XATTR;
 635         if (args->create)
 636                 flags |= CREATE_XATTR_DIR;
 637         else {
 638                 ulong_t val = 0;
 639 
 640                 error = VOP_PATHCONF(vp, _PC_SATTR_EXISTS, &val, cr, NULL);
 641                 if (!error && val == 0) {
 642                         error = VOP_PATHCONF(vp, _PC_XATTR_EXISTS,
 643                             &val, cr, NULL);
 644                         if (!error && val == 0) {
 645                                 VN_RELE(vp);
 646                                 resp->status = NFS3ERR_NOENT;
 647                                 return;
 648                         }
 649                 }
 650         }
 651 
 652         error = VOP_LOOKUP(vp, "", &avp, NULL, flags, NULL, cr,
 653             NULL, NULL, NULL);
 654         if (!error && avp == vp) {      /* lookup of "" on old FS? */
 655                 error = EINVAL;
 656                 VN_RELE(avp);
 657         }
 658         if (!error) {
 659                 struct vattr va;
 660                 va.va_mask = AT_ALL;
 661                 error = rfs4_delegated_getattr(avp, &va, 0, cr);
 662                 if (!error) {
 663                         vattr_to_post_op_attr(&va, &resp->resok.attr);
 664                         error = makefh3(&resp->resok.fh, avp, exi);
 665                 }
 666                 VN_RELE(avp);
 667         }
 668 
 669         VN_RELE(vp);
 670 
 671         if (error) {
 672                 resp->status = puterrno3(error);
 673                 return;
 674         }
 675         resp->status = NFS3_OK;
 676 }
 677 
 678 void *
 679 acl3_getxattrdir_getfh(GETXATTRDIR3args *args)
 680 {
 681         return (&args->fh);
 682 }