xref: /illumos-gate/usr/src/uts/common/fs/nfs/nfs_acl_srv.c (revision fbd1c0dae6f4a2ccc2ce0527c7f19d3dd5ea90b8)
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 2006 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);
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);
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);
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);
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);
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);
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);
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);
349 		if (!error)
350 			resp->resok.access |= ACCESS2_DELETE;
351 	}
352 	if (args->access & ACCESS2_EXECUTE) {
353 		error = VOP_ACCESS(vp, VEXEC, 0, cr);
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);
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 	if (!error && avp == vp) {	/* lookup of "" on old FS? */
412 		error = EINVAL;
413 		VN_RELE(avp);
414 	}
415 	if (!error) {
416 		struct vattr va;
417 		va.va_mask = AT_ALL;
418 		error = rfs4_delegated_getattr(avp, &va, 0, cr);
419 		if (!error) {
420 			error = vattr_to_nattr(&va, &resp->resok.attr);
421 			if (!error)
422 				error = makefh(&resp->resok.fh, avp, exi);
423 		}
424 		VN_RELE(avp);
425 	}
426 
427 	VN_RELE(vp);
428 
429 	if (error) {
430 		resp->status = puterrno(error);
431 		return;
432 	}
433 	resp->status = NFS_OK;
434 }
435 
436 void *
437 acl2_getxattrdir_getfh(GETXATTRDIR2args *args)
438 {
439 	return (&args->fh);
440 }
441 
442 /* ARGSUSED */
443 void
444 acl3_getacl(GETACL3args *args, GETACL3res *resp, struct exportinfo *exi,
445 	struct svc_req *req, cred_t *cr)
446 {
447 	int error;
448 	vnode_t *vp;
449 	vattr_t *vap;
450 	vattr_t va;
451 
452 	vap = NULL;
453 
454 	vp = nfs3_fhtovp(&args->fh, exi);
455 	if (vp == NULL) {
456 		error = ESTALE;
457 		goto out;
458 	}
459 
460 #ifdef DEBUG
461 	if (rfs3_do_post_op_attr) {
462 		va.va_mask = AT_ALL;
463 		vap = rfs4_delegated_getattr(vp, &va, 0, cr) ? NULL : &va;
464 	} else
465 		vap = NULL;
466 #else
467 	va.va_mask = AT_ALL;
468 	vap = rfs4_delegated_getattr(vp, &va, 0, cr) ? NULL : &va;
469 #endif
470 
471 	bzero((caddr_t)&resp->resok.acl, sizeof (resp->resok.acl));
472 
473 	resp->resok.acl.vsa_mask = args->mask;
474 
475 	error = VOP_GETSECATTR(vp, &resp->resok.acl, 0, cr);
476 
477 	if (error == ENOSYS) {
478 		/*
479 		 * If the underlying file system doesn't support
480 		 * aclent_t type acls, fabricate an acl.  This is
481 		 * required in order to to support existing clients
482 		 * that require the call to VOP_GETSECATTR to
483 		 * succeed while making the assumption that all
484 		 * file systems support aclent_t type acls.  This
485 		 * causes problems for servers exporting ZFS file
486 		 * systems because ZFS supports ace_t type acls,
487 		 * and fails (with ENOSYS) when asked for aclent_t
488 		 * type acls.
489 		 *
490 		 * Note: if the fs_fab_acl() fails, we have other problems.
491 		 * This error should be returned to the caller.
492 		 */
493 		error = fs_fab_acl(vp, &resp->resok.acl, 0, cr);
494 	}
495 
496 	if (error)
497 		goto out;
498 
499 #ifdef DEBUG
500 	if (rfs3_do_post_op_attr) {
501 		va.va_mask = AT_ALL;
502 		vap = rfs4_delegated_getattr(vp, &va, 0, cr) ? NULL : &va;
503 	} else
504 		vap = NULL;
505 #else
506 	va.va_mask = AT_ALL;
507 	vap = rfs4_delegated_getattr(vp, &va, 0, cr) ? NULL : &va;
508 #endif
509 
510 	VN_RELE(vp);
511 
512 	resp->status = NFS3_OK;
513 	vattr_to_post_op_attr(vap, &resp->resok.attr);
514 	if (!(args->mask & NA_ACL)) {
515 		if (resp->resok.acl.vsa_aclcnt > 0 &&
516 		    resp->resok.acl.vsa_aclentp != NULL) {
517 			kmem_free((caddr_t)resp->resok.acl.vsa_aclentp,
518 			    resp->resok.acl.vsa_aclcnt * sizeof (aclent_t));
519 		}
520 		resp->resok.acl.vsa_aclentp = NULL;
521 	}
522 	if (!(args->mask & NA_DFACL)) {
523 		if (resp->resok.acl.vsa_dfaclcnt > 0 &&
524 		    resp->resok.acl.vsa_dfaclentp != NULL) {
525 			kmem_free((caddr_t)resp->resok.acl.vsa_dfaclentp,
526 			    resp->resok.acl.vsa_dfaclcnt * sizeof (aclent_t));
527 		}
528 		resp->resok.acl.vsa_dfaclentp = NULL;
529 	}
530 	return;
531 
532 out:
533 	if (curthread->t_flag & T_WOULDBLOCK) {
534 		curthread->t_flag &= ~T_WOULDBLOCK;
535 		resp->status = NFS3ERR_JUKEBOX;
536 	} else
537 		resp->status = puterrno3(error);
538 out1:
539 	if (vp != NULL)
540 		VN_RELE(vp);
541 	vattr_to_post_op_attr(vap, &resp->resfail.attr);
542 }
543 
544 void *
545 acl3_getacl_getfh(GETACL3args *args)
546 {
547 
548 	return (&args->fh);
549 }
550 
551 void
552 acl3_getacl_free(GETACL3res *resp)
553 {
554 
555 	if (resp->status == NFS3_OK) {
556 		if (resp->resok.acl.vsa_aclcnt > 0 &&
557 		    resp->resok.acl.vsa_aclentp != NULL) {
558 			kmem_free((caddr_t)resp->resok.acl.vsa_aclentp,
559 			    resp->resok.acl.vsa_aclcnt * sizeof (aclent_t));
560 		}
561 		if (resp->resok.acl.vsa_dfaclcnt > 0 &&
562 		    resp->resok.acl.vsa_dfaclentp != NULL) {
563 			kmem_free((caddr_t)resp->resok.acl.vsa_dfaclentp,
564 			    resp->resok.acl.vsa_dfaclcnt * sizeof (aclent_t));
565 		}
566 	}
567 }
568 
569 /* ARGSUSED */
570 void
571 acl3_setacl(SETACL3args *args, SETACL3res *resp, struct exportinfo *exi,
572 	struct svc_req *req, cred_t *cr)
573 {
574 	int error;
575 	vnode_t *vp;
576 	vattr_t *vap;
577 	vattr_t va;
578 
579 	vap = NULL;
580 
581 	vp = nfs3_fhtovp(&args->fh, exi);
582 	if (vp == NULL) {
583 		error = ESTALE;
584 		goto out1;
585 	}
586 
587 	(void) VOP_RWLOCK(vp, V_WRITELOCK_TRUE, NULL);
588 
589 #ifdef DEBUG
590 	if (rfs3_do_post_op_attr) {
591 		va.va_mask = AT_ALL;
592 		vap = rfs4_delegated_getattr(vp, &va, 0, cr) ? NULL : &va;
593 	} else
594 		vap = NULL;
595 #else
596 	va.va_mask = AT_ALL;
597 	vap = rfs4_delegated_getattr(vp, &va, 0, cr) ? NULL : &va;
598 #endif
599 
600 	if (rdonly(exi, req) || vn_is_readonly(vp)) {
601 		resp->status = NFS3ERR_ROFS;
602 		goto out1;
603 	}
604 
605 	error = VOP_SETSECATTR(vp, &args->acl, 0, cr);
606 
607 #ifdef DEBUG
608 	if (rfs3_do_post_op_attr) {
609 		va.va_mask = AT_ALL;
610 		vap = rfs4_delegated_getattr(vp, &va, 0, cr) ? NULL : &va;
611 	} else
612 		vap = NULL;
613 #else
614 	va.va_mask = AT_ALL;
615 	vap = rfs4_delegated_getattr(vp, &va, 0, cr) ? NULL : &va;
616 #endif
617 
618 	if (error)
619 		goto out;
620 
621 	VOP_RWUNLOCK(vp, V_WRITELOCK_TRUE, NULL);
622 	VN_RELE(vp);
623 
624 	resp->status = NFS3_OK;
625 	vattr_to_post_op_attr(vap, &resp->resok.attr);
626 	return;
627 
628 out:
629 	if (curthread->t_flag & T_WOULDBLOCK) {
630 		curthread->t_flag &= ~T_WOULDBLOCK;
631 		resp->status = NFS3ERR_JUKEBOX;
632 	} else
633 		resp->status = puterrno3(error);
634 out1:
635 	if (vp != NULL) {
636 		VOP_RWUNLOCK(vp, V_WRITELOCK_TRUE, NULL);
637 		VN_RELE(vp);
638 	}
639 	vattr_to_post_op_attr(vap, &resp->resfail.attr);
640 }
641 
642 void *
643 acl3_setacl_getfh(SETACL3args *args)
644 {
645 
646 	return (&args->fh);
647 }
648 
649 /* ARGSUSED */
650 void
651 acl3_getxattrdir(GETXATTRDIR3args *args, GETXATTRDIR3res *resp,
652 	struct exportinfo *exi, struct svc_req *req, cred_t *cr)
653 {
654 	int error;
655 	int flags;
656 	vnode_t *vp, *avp;
657 
658 	vp = nfs3_fhtovp(&args->fh, exi);
659 	if (vp == NULL) {
660 		resp->status = NFS3ERR_STALE;
661 		return;
662 	}
663 
664 	flags = LOOKUP_XATTR;
665 	if (args->create)
666 		flags |= CREATE_XATTR_DIR;
667 	else {
668 		ulong_t val = 0;
669 		error = VOP_PATHCONF(vp, _PC_XATTR_EXISTS, &val, cr);
670 		if (!error && val == 0) {
671 			VN_RELE(vp);
672 			resp->status = NFS3ERR_NOENT;
673 			return;
674 		}
675 	}
676 
677 	error = VOP_LOOKUP(vp, "", &avp, NULL, flags, NULL, cr);
678 	if (!error && avp == vp) {	/* lookup of "" on old FS? */
679 		error = EINVAL;
680 		VN_RELE(avp);
681 	}
682 	if (!error) {
683 		struct vattr va;
684 		va.va_mask = AT_ALL;
685 		error = rfs4_delegated_getattr(avp, &va, 0, cr);
686 		if (!error) {
687 			vattr_to_post_op_attr(&va, &resp->resok.attr);
688 			error = makefh3(&resp->resok.fh, avp, exi);
689 		}
690 		VN_RELE(avp);
691 	}
692 
693 	VN_RELE(vp);
694 
695 	if (error) {
696 		resp->status = puterrno3(error);
697 		return;
698 	}
699 	resp->status = NFS3_OK;
700 }
701 
702 void *
703 acl3_getxattrdir_getfh(GETXATTRDIR3args *args)
704 {
705 	return (&args->fh);
706 }
707