xref: /illumos-gate/usr/src/uts/common/fs/nfs/nfs_acl_srv.c (revision a07094369b21309434206d9b3601d162693466fc)
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, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2005 Sun Microsystems, Inc.
24  * All rights reserved.
25  * Use is subject to license terms.
26  */
27 
28 #pragma ident	"%Z%%M%	%I%	%E% SMI"
29 
30 #include <sys/param.h>
31 #include <sys/types.h>
32 #include <sys/systm.h>
33 #include <sys/cred.h>
34 #include <sys/proc.h>
35 #include <sys/user.h>
36 #include <sys/buf.h>
37 #include <sys/vfs.h>
38 #include <sys/vnode.h>
39 #include <sys/pathname.h>
40 #include <sys/uio.h>
41 #include <sys/file.h>
42 #include <sys/stat.h>
43 #include <sys/errno.h>
44 #include <sys/socket.h>
45 #include <sys/sysmacros.h>
46 #include <sys/siginfo.h>
47 #include <sys/tiuser.h>
48 #include <sys/statvfs.h>
49 #include <sys/t_kuser.h>
50 #include <sys/kmem.h>
51 #include <sys/kstat.h>
52 #include <sys/acl.h>
53 #include <sys/dirent.h>
54 #include <sys/cmn_err.h>
55 #include <sys/debug.h>
56 #include <sys/unistd.h>
57 #include <sys/vtrace.h>
58 #include <sys/mode.h>
59 
60 #include <rpc/types.h>
61 #include <rpc/auth.h>
62 #include <rpc/svc.h>
63 #include <rpc/xdr.h>
64 
65 #include <nfs/nfs.h>
66 #include <nfs/export.h>
67 #include <nfs/nfssys.h>
68 #include <nfs/nfs_clnt.h>
69 #include <nfs/nfs_acl.h>
70 
71 #include <fs/fs_subr.h>
72 
73 /*
74  * These are the interface routines for the server side of the
75  * NFS ACL server.  See the NFS ACL protocol specification
76  * for a description of this interface.
77  */
78 
79 /* ARGSUSED */
80 void
81 acl2_getacl(GETACL2args *args, GETACL2res *resp, struct exportinfo *exi,
82 	struct svc_req *req, cred_t *cr)
83 {
84 	int error;
85 	vnode_t *vp;
86 	vattr_t va;
87 
88 	vp = nfs_fhtovp(&args->fh, exi);
89 	if (vp == NULL) {
90 		resp->status = NFSERR_STALE;
91 		return;
92 	}
93 
94 	bzero((caddr_t)&resp->resok.acl, sizeof (resp->resok.acl));
95 
96 	resp->resok.acl.vsa_mask = args->mask;
97 
98 	error = VOP_GETSECATTR(vp, &resp->resok.acl, 0, cr);
99 
100 	if (error == ENOSYS) {
101 		/*
102 		 * If the underlying file system doesn't support
103 		 * aclent_t type acls, fabricate an acl.  This is
104 		 * required in order to to support existing clients
105 		 * that require the call to VOP_GETSECATTR to
106 		 * succeed while making the assumption that all
107 		 * file systems support aclent_t type acls.  This
108 		 * causes problems for servers exporting ZFS file
109 		 * systems because ZFS supports ace_t type acls,
110 		 * and fails (with ENOSYS) when asked for aclent_t
111 		 * type acls.
112 		 *
113 		 * Note: if the fs_fab_acl() fails, we have other problems.
114 		 * This error should be returned to the caller.
115 		 */
116 		error = fs_fab_acl(vp, &resp->resok.acl, 0, cr);
117 	}
118 
119 	if (error) {
120 		VN_RELE(vp);
121 		resp->status = puterrno(error);
122 		return;
123 	}
124 
125 	va.va_mask = AT_ALL;
126 	error = rfs4_delegated_getattr(vp, &va, 0, cr);
127 
128 	VN_RELE(vp);
129 
130 	/* check for overflowed values */
131 	if (!error) {
132 		error = vattr_to_nattr(&va, &resp->resok.attr);
133 	}
134 	if (error) {
135 		resp->status = puterrno(error);
136 		if (resp->resok.acl.vsa_aclcnt > 0 &&
137 		    resp->resok.acl.vsa_aclentp != NULL) {
138 			kmem_free((caddr_t)resp->resok.acl.vsa_aclentp,
139 			    resp->resok.acl.vsa_aclcnt * sizeof (aclent_t));
140 		}
141 		if (resp->resok.acl.vsa_dfaclcnt > 0 &&
142 		    resp->resok.acl.vsa_dfaclentp != NULL) {
143 			kmem_free((caddr_t)resp->resok.acl.vsa_dfaclentp,
144 			    resp->resok.acl.vsa_dfaclcnt * sizeof (aclent_t));
145 		}
146 		return;
147 	}
148 
149 	resp->status = NFS_OK;
150 	if (!(args->mask & NA_ACL)) {
151 		if (resp->resok.acl.vsa_aclcnt > 0 &&
152 		    resp->resok.acl.vsa_aclentp != NULL) {
153 			kmem_free((caddr_t)resp->resok.acl.vsa_aclentp,
154 			    resp->resok.acl.vsa_aclcnt * sizeof (aclent_t));
155 		}
156 		resp->resok.acl.vsa_aclentp = NULL;
157 	}
158 	if (!(args->mask & NA_DFACL)) {
159 		if (resp->resok.acl.vsa_dfaclcnt > 0 &&
160 		    resp->resok.acl.vsa_dfaclentp != NULL) {
161 			kmem_free((caddr_t)resp->resok.acl.vsa_dfaclentp,
162 			    resp->resok.acl.vsa_dfaclcnt * sizeof (aclent_t));
163 		}
164 		resp->resok.acl.vsa_dfaclentp = NULL;
165 	}
166 }
167 
168 fhandle_t *
169 acl2_getacl_getfh(GETACL2args *args)
170 {
171 
172 	return (&args->fh);
173 }
174 
175 void
176 acl2_getacl_free(GETACL2res *resp)
177 {
178 
179 	if (resp->status == NFS_OK) {
180 		if (resp->resok.acl.vsa_aclcnt > 0 &&
181 		    resp->resok.acl.vsa_aclentp != NULL) {
182 			kmem_free((caddr_t)resp->resok.acl.vsa_aclentp,
183 			    resp->resok.acl.vsa_aclcnt * sizeof (aclent_t));
184 		}
185 		if (resp->resok.acl.vsa_dfaclcnt > 0 &&
186 		    resp->resok.acl.vsa_dfaclentp != NULL) {
187 			kmem_free((caddr_t)resp->resok.acl.vsa_dfaclentp,
188 			    resp->resok.acl.vsa_dfaclcnt * sizeof (aclent_t));
189 		}
190 	}
191 }
192 
193 /* ARGSUSED */
194 void
195 acl2_setacl(SETACL2args *args, SETACL2res *resp, struct exportinfo *exi,
196 	struct svc_req *req, cred_t *cr)
197 {
198 	int error;
199 	vnode_t *vp;
200 	vattr_t va;
201 
202 	vp = nfs_fhtovp(&args->fh, exi);
203 	if (vp == NULL) {
204 		resp->status = NFSERR_STALE;
205 		return;
206 	}
207 
208 	if (rdonly(exi, req) || vn_is_readonly(vp)) {
209 		VN_RELE(vp);
210 		resp->status = NFSERR_ROFS;
211 		return;
212 	}
213 
214 	(void) VOP_RWLOCK(vp, V_WRITELOCK_TRUE, NULL);
215 	error = VOP_SETSECATTR(vp, &args->acl, 0, cr);
216 	if (error) {
217 		VOP_RWUNLOCK(vp, V_WRITELOCK_TRUE, NULL);
218 		VN_RELE(vp);
219 		resp->status = puterrno(error);
220 		return;
221 	}
222 
223 	va.va_mask = AT_ALL;
224 	error = rfs4_delegated_getattr(vp, &va, 0, cr);
225 
226 	VOP_RWUNLOCK(vp, V_WRITELOCK_TRUE, NULL);
227 	VN_RELE(vp);
228 
229 	/* check for overflowed values */
230 	if (!error) {
231 		error = vattr_to_nattr(&va, &resp->resok.attr);
232 	}
233 	if (error) {
234 		resp->status = puterrno(error);
235 		return;
236 	}
237 
238 	resp->status = NFS_OK;
239 }
240 
241 fhandle_t *
242 acl2_setacl_getfh(SETACL2args *args)
243 {
244 
245 	return (&args->fh);
246 }
247 
248 /* ARGSUSED */
249 void
250 acl2_getattr(GETATTR2args *args, GETATTR2res *resp, struct exportinfo *exi,
251 	struct svc_req *req, cred_t *cr)
252 {
253 	int error;
254 	vnode_t *vp;
255 	vattr_t va;
256 
257 	vp = nfs_fhtovp(&args->fh, exi);
258 	if (vp == NULL) {
259 		resp->status = NFSERR_STALE;
260 		return;
261 	}
262 
263 	va.va_mask = AT_ALL;
264 	error = rfs4_delegated_getattr(vp, &va, 0, cr);
265 
266 	VN_RELE(vp);
267 
268 	/* check for overflowed values */
269 	if (!error) {
270 		error = vattr_to_nattr(&va, &resp->resok.attr);
271 	}
272 	if (error) {
273 		resp->status = puterrno(error);
274 		return;
275 	}
276 
277 	resp->status = NFS_OK;
278 }
279 
280 fhandle_t *
281 acl2_getattr_getfh(GETATTR2args *args)
282 {
283 
284 	return (&args->fh);
285 }
286 
287 /* ARGSUSED */
288 void
289 acl2_access(ACCESS2args *args, ACCESS2res *resp, struct exportinfo *exi,
290 	struct svc_req *req, cred_t *cr)
291 {
292 	int error;
293 	vnode_t *vp;
294 	vattr_t va;
295 	int checkwriteperm;
296 
297 	vp = nfs_fhtovp(&args->fh, exi);
298 	if (vp == NULL) {
299 		resp->status = NFSERR_STALE;
300 		return;
301 	}
302 
303 	/*
304 	 * If the file system is exported read only, it is not appropriate
305 	 * to check write permissions for regular files and directories.
306 	 * Special files are interpreted by the client, so the underlying
307 	 * permissions are sent back to the client for interpretation.
308 	 */
309 	if (rdonly(exi, req) && (vp->v_type == VREG || vp->v_type == VDIR))
310 		checkwriteperm = 0;
311 	else
312 		checkwriteperm = 1;
313 
314 	/*
315 	 * We need the mode so that we can correctly determine access
316 	 * permissions relative to a mandatory lock file.  Access to
317 	 * mandatory lock files is denied on the server, so it might
318 	 * as well be reflected to the server during the open.
319 	 */
320 	va.va_mask = AT_MODE;
321 	error = VOP_GETATTR(vp, &va, 0, cr);
322 	if (error) {
323 		VN_RELE(vp);
324 		resp->status = puterrno(error);
325 		return;
326 	}
327 
328 	resp->resok.access = 0;
329 
330 	if (args->access & ACCESS2_READ) {
331 		error = VOP_ACCESS(vp, VREAD, 0, cr);
332 		if (!error && !MANDLOCK(vp, va.va_mode))
333 			resp->resok.access |= ACCESS2_READ;
334 	}
335 	if ((args->access & ACCESS2_LOOKUP) && vp->v_type == VDIR) {
336 		error = VOP_ACCESS(vp, VEXEC, 0, cr);
337 		if (!error)
338 			resp->resok.access |= ACCESS2_LOOKUP;
339 	}
340 	if (checkwriteperm &&
341 	    (args->access & (ACCESS2_MODIFY|ACCESS2_EXTEND))) {
342 		error = VOP_ACCESS(vp, VWRITE, 0, cr);
343 		if (!error && !MANDLOCK(vp, va.va_mode))
344 			resp->resok.access |=
345 			    (args->access & (ACCESS2_MODIFY|ACCESS2_EXTEND));
346 	}
347 	if (checkwriteperm &&
348 	    (args->access & ACCESS2_DELETE) && (vp->v_type == VDIR)) {
349 		error = VOP_ACCESS(vp, VWRITE, 0, cr);
350 		if (!error)
351 			resp->resok.access |= ACCESS2_DELETE;
352 	}
353 	if (args->access & ACCESS2_EXECUTE) {
354 		error = VOP_ACCESS(vp, VEXEC, 0, cr);
355 		if (!error && !MANDLOCK(vp, va.va_mode))
356 			resp->resok.access |= ACCESS2_EXECUTE;
357 	}
358 
359 	va.va_mask = AT_ALL;
360 	error = rfs4_delegated_getattr(vp, &va, 0, cr);
361 
362 	VN_RELE(vp);
363 
364 	/* check for overflowed values */
365 	if (!error) {
366 		error = vattr_to_nattr(&va, &resp->resok.attr);
367 	}
368 	if (error) {
369 		resp->status = puterrno(error);
370 		return;
371 	}
372 
373 	resp->status = NFS_OK;
374 }
375 
376 fhandle_t *
377 acl2_access_getfh(ACCESS2args *args)
378 {
379 
380 	return (&args->fh);
381 }
382 
383 /* ARGSUSED */
384 void
385 acl2_getxattrdir(GETXATTRDIR2args *args, GETXATTRDIR2res *resp,
386 	struct exportinfo *exi, struct svc_req *req, cred_t *cr)
387 {
388 	int error;
389 	int flags;
390 	vnode_t *vp, *avp;
391 
392 	vp = nfs_fhtovp(&args->fh, exi);
393 	if (vp == NULL) {
394 		resp->status = NFSERR_STALE;
395 		return;
396 	}
397 
398 	flags = LOOKUP_XATTR;
399 	if (args->create)
400 		flags |= CREATE_XATTR_DIR;
401 	else {
402 		ulong_t val = 0;
403 		error = VOP_PATHCONF(vp, _PC_XATTR_EXISTS, &val, cr);
404 		if (!error && val == 0) {
405 			VN_RELE(vp);
406 			resp->status = NFSERR_NOENT;
407 			return;
408 		}
409 	}
410 
411 	error = VOP_LOOKUP(vp, "", &avp, NULL, flags, NULL, cr);
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 fhandle_t *
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);
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);
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 fhandle_t *
546 acl3_getacl_getfh(GETACL3args *args)
547 {
548 
549 	return ((fhandle_t *)&args->fh.fh3_u.nfs_fh3_i.fh3_i);
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);
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 fhandle_t *
644 acl3_setacl_getfh(SETACL3args *args)
645 {
646 
647 	return ((fhandle_t *)&args->fh.fh3_u.nfs_fh3_i.fh3_i);
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);
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 	if (!error && avp == vp) {	/* lookup of "" on old FS? */
680 		error = EINVAL;
681 		VN_RELE(avp);
682 	}
683 	if (!error) {
684 		struct vattr va;
685 		va.va_mask = AT_ALL;
686 		error = rfs4_delegated_getattr(avp, &va, 0, cr);
687 		if (!error) {
688 			vattr_to_post_op_attr(&va, &resp->resok.attr);
689 			error = makefh3(&resp->resok.fh, avp, exi);
690 		}
691 		VN_RELE(avp);
692 	}
693 
694 	VN_RELE(vp);
695 
696 	if (error) {
697 		resp->status = puterrno3(error);
698 		return;
699 	}
700 	resp->status = NFS3_OK;
701 }
702 
703 fhandle_t *
704 acl3_getxattrdir_getfh(GETXATTRDIR3args *args)
705 {
706 	return ((fhandle_t *)&args->fh.fh3_u.nfs_fh3_i.fh3_i);
707 }
708