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