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