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