1/*-
2 * Copyright (c) 1999-2001 Robert N. M. Watson
3 * All rights reserved.
4 *
5 * This software was developed by Robert Watson for the TrustedBSD Project.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28
29#include <sys/cdefs.h>
30__FBSDID("$FreeBSD$");
31
32#include <sys/param.h>
33#include <sys/systm.h>
34#include <sys/capability.h>
35#include <sys/lock.h>
36#include <sys/mount.h>
37#include <sys/mutex.h>
38#include <sys/sysproto.h>
39#include <sys/fcntl.h>
40#include <sys/namei.h>
41#include <sys/filedesc.h>
42#include <sys/limits.h>
43#include <sys/vnode.h>
44#include <sys/proc.h>
45#include <sys/extattr.h>
46
47#include <security/audit/audit.h>
48#include <security/mac/mac_framework.h>
49
50/*
51 * Syscall to push extended attribute configuration information into the VFS.
52 * Accepts a path, which it converts to a mountpoint, as well as a command
53 * (int cmd), and attribute name and misc data.
54 *
55 * Currently this is used only by UFS1 extended attributes.
56 */
57int
58sys_extattrctl(td, uap)
59	struct thread *td;
60	struct extattrctl_args /* {
61		const char *path;
62		int cmd;
63		const char *filename;
64		int attrnamespace;
65		const char *attrname;
66	} */ *uap;
67{
68	struct vnode *filename_vp;
69	struct nameidata nd;
70	struct mount *mp, *mp_writable;
71	char attrname[EXTATTR_MAXNAMELEN];
72	int error;
73
74	AUDIT_ARG_CMD(uap->cmd);
75	AUDIT_ARG_VALUE(uap->attrnamespace);
76	/*
77	 * uap->attrname is not always defined.  We check again later when we
78	 * invoke the VFS call so as to pass in NULL there if needed.
79	 */
80	if (uap->attrname != NULL) {
81		error = copyinstr(uap->attrname, attrname, EXTATTR_MAXNAMELEN,
82		    NULL);
83		if (error)
84			return (error);
85	}
86	AUDIT_ARG_TEXT(attrname);
87
88	mp = NULL;
89	filename_vp = NULL;
90	if (uap->filename != NULL) {
91		NDINIT(&nd, LOOKUP, FOLLOW | AUDITVNODE2,
92		    UIO_USERSPACE, uap->filename, td);
93		error = namei(&nd);
94		if (error)
95			return (error);
96		filename_vp = nd.ni_vp;
97		NDFREE(&nd, NDF_NO_VP_RELE);
98	}
99
100	/* uap->path is always defined. */
101	NDINIT(&nd, LOOKUP, FOLLOW | LOCKLEAF | AUDITVNODE1,
102	    UIO_USERSPACE, uap->path, td);
103	error = namei(&nd);
104	if (error)
105		goto out;
106	mp = nd.ni_vp->v_mount;
107	error = vfs_busy(mp, 0);
108	if (error) {
109		NDFREE(&nd, 0);
110		mp = NULL;
111		goto out;
112	}
113	VOP_UNLOCK(nd.ni_vp, 0);
114	error = vn_start_write(nd.ni_vp, &mp_writable, V_WAIT | PCATCH);
115	NDFREE(&nd, NDF_NO_VP_UNLOCK);
116	if (error)
117		goto out;
118	if (filename_vp != NULL) {
119		/*
120		 * uap->filename is not always defined.  If it is,
121		 * grab a vnode lock, which VFS_EXTATTRCTL() will
122		 * later release.
123		 */
124		error = vn_lock(filename_vp, LK_EXCLUSIVE);
125		if (error) {
126			vn_finished_write(mp_writable);
127			goto out;
128		}
129	}
130
131	error = VFS_EXTATTRCTL(mp, uap->cmd, filename_vp, uap->attrnamespace,
132	    uap->attrname != NULL ? attrname : NULL);
133
134	vn_finished_write(mp_writable);
135out:
136	if (mp != NULL)
137		vfs_unbusy(mp);
138
139	/*
140	 * VFS_EXTATTRCTL will have unlocked, but not de-ref'd, filename_vp,
141	 * so vrele it if it is defined.
142	 */
143	if (filename_vp != NULL)
144		vrele(filename_vp);
145	return (error);
146}
147
148/*-
149 * Set a named extended attribute on a file or directory
150 *
151 * Arguments: unlocked vnode "vp", attribute namespace "attrnamespace",
152 *            kernelspace string pointer "attrname", userspace buffer
153 *            pointer "data", buffer length "nbytes", thread "td".
154 * Returns: 0 on success, an error number otherwise
155 * Locks: none
156 * References: vp must be a valid reference for the duration of the call
157 */
158static int
159extattr_set_vp(struct vnode *vp, int attrnamespace, const char *attrname,
160    void *data, size_t nbytes, struct thread *td)
161{
162	struct mount *mp;
163	struct uio auio;
164	struct iovec aiov;
165	ssize_t cnt;
166	int error;
167
168	error = vn_start_write(vp, &mp, V_WAIT | PCATCH);
169	if (error)
170		return (error);
171	vn_lock(vp, LK_EXCLUSIVE | LK_RETRY);
172
173	aiov.iov_base = data;
174	aiov.iov_len = nbytes;
175	auio.uio_iov = &aiov;
176	auio.uio_iovcnt = 1;
177	auio.uio_offset = 0;
178	if (nbytes > IOSIZE_MAX) {
179		error = EINVAL;
180		goto done;
181	}
182	auio.uio_resid = nbytes;
183	auio.uio_rw = UIO_WRITE;
184	auio.uio_segflg = UIO_USERSPACE;
185	auio.uio_td = td;
186	cnt = nbytes;
187
188#ifdef MAC
189	error = mac_vnode_check_setextattr(td->td_ucred, vp, attrnamespace,
190	    attrname);
191	if (error)
192		goto done;
193#endif
194
195	error = VOP_SETEXTATTR(vp, attrnamespace, attrname, &auio,
196	    td->td_ucred, td);
197	cnt -= auio.uio_resid;
198	td->td_retval[0] = cnt;
199
200done:
201	VOP_UNLOCK(vp, 0);
202	vn_finished_write(mp);
203	return (error);
204}
205
206int
207sys_extattr_set_fd(td, uap)
208	struct thread *td;
209	struct extattr_set_fd_args /* {
210		int fd;
211		int attrnamespace;
212		const char *attrname;
213		void *data;
214		size_t nbytes;
215	} */ *uap;
216{
217	struct file *fp;
218	char attrname[EXTATTR_MAXNAMELEN];
219	cap_rights_t rights;
220	int error;
221
222	AUDIT_ARG_FD(uap->fd);
223	AUDIT_ARG_VALUE(uap->attrnamespace);
224	error = copyinstr(uap->attrname, attrname, EXTATTR_MAXNAMELEN, NULL);
225	if (error)
226		return (error);
227	AUDIT_ARG_TEXT(attrname);
228
229	error = getvnode(td->td_proc->p_fd, uap->fd,
230	    cap_rights_init(&rights, CAP_EXTATTR_SET), &fp);
231	if (error)
232		return (error);
233
234	error = extattr_set_vp(fp->f_vnode, uap->attrnamespace,
235	    attrname, uap->data, uap->nbytes, td);
236	fdrop(fp, td);
237
238	return (error);
239}
240
241int
242sys_extattr_set_file(td, uap)
243	struct thread *td;
244	struct extattr_set_file_args /* {
245		const char *path;
246		int attrnamespace;
247		const char *attrname;
248		void *data;
249		size_t nbytes;
250	} */ *uap;
251{
252	struct nameidata nd;
253	char attrname[EXTATTR_MAXNAMELEN];
254	int error;
255
256	AUDIT_ARG_VALUE(uap->attrnamespace);
257	error = copyinstr(uap->attrname, attrname, EXTATTR_MAXNAMELEN, NULL);
258	if (error)
259		return (error);
260	AUDIT_ARG_TEXT(attrname);
261
262	NDINIT(&nd, LOOKUP, FOLLOW | AUDITVNODE1, UIO_USERSPACE,
263	    uap->path, td);
264	error = namei(&nd);
265	if (error)
266		return (error);
267	NDFREE(&nd, NDF_ONLY_PNBUF);
268
269	error = extattr_set_vp(nd.ni_vp, uap->attrnamespace, attrname,
270	    uap->data, uap->nbytes, td);
271
272	vrele(nd.ni_vp);
273	return (error);
274}
275
276int
277sys_extattr_set_link(td, uap)
278	struct thread *td;
279	struct extattr_set_link_args /* {
280		const char *path;
281		int attrnamespace;
282		const char *attrname;
283		void *data;
284		size_t nbytes;
285	} */ *uap;
286{
287	struct nameidata nd;
288	char attrname[EXTATTR_MAXNAMELEN];
289	int error;
290
291	AUDIT_ARG_VALUE(uap->attrnamespace);
292	error = copyinstr(uap->attrname, attrname, EXTATTR_MAXNAMELEN, NULL);
293	if (error)
294		return (error);
295	AUDIT_ARG_TEXT(attrname);
296
297	NDINIT(&nd, LOOKUP, NOFOLLOW | AUDITVNODE1, UIO_USERSPACE,
298	    uap->path, td);
299	error = namei(&nd);
300	if (error)
301		return (error);
302	NDFREE(&nd, NDF_ONLY_PNBUF);
303
304	error = extattr_set_vp(nd.ni_vp, uap->attrnamespace, attrname,
305	    uap->data, uap->nbytes, td);
306
307	vrele(nd.ni_vp);
308	return (error);
309}
310
311/*-
312 * Get a named extended attribute on a file or directory
313 *
314 * Arguments: unlocked vnode "vp", attribute namespace "attrnamespace",
315 *            kernelspace string pointer "attrname", userspace buffer
316 *            pointer "data", buffer length "nbytes", thread "td".
317 * Returns: 0 on success, an error number otherwise
318 * Locks: none
319 * References: vp must be a valid reference for the duration of the call
320 */
321static int
322extattr_get_vp(struct vnode *vp, int attrnamespace, const char *attrname,
323    void *data, size_t nbytes, struct thread *td)
324{
325	struct uio auio, *auiop;
326	struct iovec aiov;
327	ssize_t cnt;
328	size_t size, *sizep;
329	int error;
330
331	vn_lock(vp, LK_SHARED | LK_RETRY);
332
333	/*
334	 * Slightly unusual semantics: if the user provides a NULL data
335	 * pointer, they don't want to receive the data, just the maximum
336	 * read length.
337	 */
338	auiop = NULL;
339	sizep = NULL;
340	cnt = 0;
341	if (data != NULL) {
342		aiov.iov_base = data;
343		aiov.iov_len = nbytes;
344		auio.uio_iov = &aiov;
345		auio.uio_iovcnt = 1;
346		auio.uio_offset = 0;
347		if (nbytes > IOSIZE_MAX) {
348			error = EINVAL;
349			goto done;
350		}
351		auio.uio_resid = nbytes;
352		auio.uio_rw = UIO_READ;
353		auio.uio_segflg = UIO_USERSPACE;
354		auio.uio_td = td;
355		auiop = &auio;
356		cnt = nbytes;
357	} else
358		sizep = &size;
359
360#ifdef MAC
361	error = mac_vnode_check_getextattr(td->td_ucred, vp, attrnamespace,
362	    attrname);
363	if (error)
364		goto done;
365#endif
366
367	error = VOP_GETEXTATTR(vp, attrnamespace, attrname, auiop, sizep,
368	    td->td_ucred, td);
369
370	if (auiop != NULL) {
371		cnt -= auio.uio_resid;
372		td->td_retval[0] = cnt;
373	} else
374		td->td_retval[0] = size;
375
376done:
377	VOP_UNLOCK(vp, 0);
378	return (error);
379}
380
381int
382sys_extattr_get_fd(td, uap)
383	struct thread *td;
384	struct extattr_get_fd_args /* {
385		int fd;
386		int attrnamespace;
387		const char *attrname;
388		void *data;
389		size_t nbytes;
390	} */ *uap;
391{
392	struct file *fp;
393	char attrname[EXTATTR_MAXNAMELEN];
394	cap_rights_t rights;
395	int error;
396
397	AUDIT_ARG_FD(uap->fd);
398	AUDIT_ARG_VALUE(uap->attrnamespace);
399	error = copyinstr(uap->attrname, attrname, EXTATTR_MAXNAMELEN, NULL);
400	if (error)
401		return (error);
402	AUDIT_ARG_TEXT(attrname);
403
404	error = getvnode(td->td_proc->p_fd, uap->fd,
405	    cap_rights_init(&rights, CAP_EXTATTR_GET), &fp);
406	if (error)
407		return (error);
408
409	error = extattr_get_vp(fp->f_vnode, uap->attrnamespace,
410	    attrname, uap->data, uap->nbytes, td);
411
412	fdrop(fp, td);
413	return (error);
414}
415
416int
417sys_extattr_get_file(td, uap)
418	struct thread *td;
419	struct extattr_get_file_args /* {
420		const char *path;
421		int attrnamespace;
422		const char *attrname;
423		void *data;
424		size_t nbytes;
425	} */ *uap;
426{
427	struct nameidata nd;
428	char attrname[EXTATTR_MAXNAMELEN];
429	int error;
430
431	AUDIT_ARG_VALUE(uap->attrnamespace);
432	error = copyinstr(uap->attrname, attrname, EXTATTR_MAXNAMELEN, NULL);
433	if (error)
434		return (error);
435	AUDIT_ARG_TEXT(attrname);
436
437	NDINIT(&nd, LOOKUP, FOLLOW | AUDITVNODE1, UIO_USERSPACE, uap->path, td);
438	error = namei(&nd);
439	if (error)
440		return (error);
441	NDFREE(&nd, NDF_ONLY_PNBUF);
442
443	error = extattr_get_vp(nd.ni_vp, uap->attrnamespace, attrname,
444	    uap->data, uap->nbytes, td);
445
446	vrele(nd.ni_vp);
447	return (error);
448}
449
450int
451sys_extattr_get_link(td, uap)
452	struct thread *td;
453	struct extattr_get_link_args /* {
454		const char *path;
455		int attrnamespace;
456		const char *attrname;
457		void *data;
458		size_t nbytes;
459	} */ *uap;
460{
461	struct nameidata nd;
462	char attrname[EXTATTR_MAXNAMELEN];
463	int error;
464
465	AUDIT_ARG_VALUE(uap->attrnamespace);
466	error = copyinstr(uap->attrname, attrname, EXTATTR_MAXNAMELEN, NULL);
467	if (error)
468		return (error);
469	AUDIT_ARG_TEXT(attrname);
470
471	NDINIT(&nd, LOOKUP, NOFOLLOW | AUDITVNODE1, UIO_USERSPACE, uap->path,
472	    td);
473	error = namei(&nd);
474	if (error)
475		return (error);
476	NDFREE(&nd, NDF_ONLY_PNBUF);
477
478	error = extattr_get_vp(nd.ni_vp, uap->attrnamespace, attrname,
479	    uap->data, uap->nbytes, td);
480
481	vrele(nd.ni_vp);
482	return (error);
483}
484
485/*
486 * extattr_delete_vp(): Delete a named extended attribute on a file or
487 *                      directory
488 *
489 * Arguments: unlocked vnode "vp", attribute namespace "attrnamespace",
490 *            kernelspace string pointer "attrname", proc "p"
491 * Returns: 0 on success, an error number otherwise
492 * Locks: none
493 * References: vp must be a valid reference for the duration of the call
494 */
495static int
496extattr_delete_vp(struct vnode *vp, int attrnamespace, const char *attrname,
497    struct thread *td)
498{
499	struct mount *mp;
500	int error;
501
502	error = vn_start_write(vp, &mp, V_WAIT | PCATCH);
503	if (error)
504		return (error);
505	vn_lock(vp, LK_EXCLUSIVE | LK_RETRY);
506
507#ifdef MAC
508	error = mac_vnode_check_deleteextattr(td->td_ucred, vp, attrnamespace,
509	    attrname);
510	if (error)
511		goto done;
512#endif
513
514	error = VOP_DELETEEXTATTR(vp, attrnamespace, attrname, td->td_ucred,
515	    td);
516	if (error == EOPNOTSUPP)
517		error = VOP_SETEXTATTR(vp, attrnamespace, attrname, NULL,
518		    td->td_ucred, td);
519#ifdef MAC
520done:
521#endif
522	VOP_UNLOCK(vp, 0);
523	vn_finished_write(mp);
524	return (error);
525}
526
527int
528sys_extattr_delete_fd(td, uap)
529	struct thread *td;
530	struct extattr_delete_fd_args /* {
531		int fd;
532		int attrnamespace;
533		const char *attrname;
534	} */ *uap;
535{
536	struct file *fp;
537	char attrname[EXTATTR_MAXNAMELEN];
538	cap_rights_t rights;
539	int error;
540
541	AUDIT_ARG_FD(uap->fd);
542	AUDIT_ARG_VALUE(uap->attrnamespace);
543	error = copyinstr(uap->attrname, attrname, EXTATTR_MAXNAMELEN, NULL);
544	if (error)
545		return (error);
546	AUDIT_ARG_TEXT(attrname);
547
548	error = getvnode(td->td_proc->p_fd, uap->fd,
549	    cap_rights_init(&rights, CAP_EXTATTR_DELETE), &fp);
550	if (error)
551		return (error);
552
553	error = extattr_delete_vp(fp->f_vnode, uap->attrnamespace,
554	    attrname, td);
555	fdrop(fp, td);
556	return (error);
557}
558
559int
560sys_extattr_delete_file(td, uap)
561	struct thread *td;
562	struct extattr_delete_file_args /* {
563		const char *path;
564		int attrnamespace;
565		const char *attrname;
566	} */ *uap;
567{
568	struct nameidata nd;
569	char attrname[EXTATTR_MAXNAMELEN];
570	int error;
571
572	AUDIT_ARG_VALUE(uap->attrnamespace);
573	error = copyinstr(uap->attrname, attrname, EXTATTR_MAXNAMELEN, NULL);
574	if (error)
575		return(error);
576	AUDIT_ARG_TEXT(attrname);
577
578	NDINIT(&nd, LOOKUP, FOLLOW | AUDITVNODE1, UIO_USERSPACE, uap->path, td);
579	error = namei(&nd);
580	if (error)
581		return(error);
582	NDFREE(&nd, NDF_ONLY_PNBUF);
583
584	error = extattr_delete_vp(nd.ni_vp, uap->attrnamespace, attrname, td);
585	vrele(nd.ni_vp);
586	return(error);
587}
588
589int
590sys_extattr_delete_link(td, uap)
591	struct thread *td;
592	struct extattr_delete_link_args /* {
593		const char *path;
594		int attrnamespace;
595		const char *attrname;
596	} */ *uap;
597{
598	struct nameidata nd;
599	char attrname[EXTATTR_MAXNAMELEN];
600	int error;
601
602	AUDIT_ARG_VALUE(uap->attrnamespace);
603	error = copyinstr(uap->attrname, attrname, EXTATTR_MAXNAMELEN, NULL);
604	if (error)
605		return(error);
606	AUDIT_ARG_TEXT(attrname);
607
608	NDINIT(&nd, LOOKUP, NOFOLLOW | AUDITVNODE1, UIO_USERSPACE, uap->path, td);
609	error = namei(&nd);
610	if (error)
611		return(error);
612	NDFREE(&nd, NDF_ONLY_PNBUF);
613
614	error = extattr_delete_vp(nd.ni_vp, uap->attrnamespace, attrname, td);
615	vrele(nd.ni_vp);
616	return(error);
617}
618
619/*-
620 * Retrieve a list of extended attributes on a file or directory.
621 *
622 * Arguments: unlocked vnode "vp", attribute namespace 'attrnamespace",
623 *            userspace buffer pointer "data", buffer length "nbytes",
624 *            thread "td".
625 * Returns: 0 on success, an error number otherwise
626 * Locks: none
627 * References: vp must be a valid reference for the duration of the call
628 */
629static int
630extattr_list_vp(struct vnode *vp, int attrnamespace, void *data,
631    size_t nbytes, struct thread *td)
632{
633	struct uio auio, *auiop;
634	size_t size, *sizep;
635	struct iovec aiov;
636	ssize_t cnt;
637	int error;
638
639	vn_lock(vp, LK_EXCLUSIVE | LK_RETRY);
640
641	auiop = NULL;
642	sizep = NULL;
643	cnt = 0;
644	if (data != NULL) {
645		aiov.iov_base = data;
646		aiov.iov_len = nbytes;
647		auio.uio_iov = &aiov;
648		auio.uio_iovcnt = 1;
649		auio.uio_offset = 0;
650		if (nbytes > IOSIZE_MAX) {
651			error = EINVAL;
652			goto done;
653		}
654		auio.uio_resid = nbytes;
655		auio.uio_rw = UIO_READ;
656		auio.uio_segflg = UIO_USERSPACE;
657		auio.uio_td = td;
658		auiop = &auio;
659		cnt = nbytes;
660	} else
661		sizep = &size;
662
663#ifdef MAC
664	error = mac_vnode_check_listextattr(td->td_ucred, vp, attrnamespace);
665	if (error)
666		goto done;
667#endif
668
669	error = VOP_LISTEXTATTR(vp, attrnamespace, auiop, sizep,
670	    td->td_ucred, td);
671
672	if (auiop != NULL) {
673		cnt -= auio.uio_resid;
674		td->td_retval[0] = cnt;
675	} else
676		td->td_retval[0] = size;
677
678done:
679	VOP_UNLOCK(vp, 0);
680	return (error);
681}
682
683
684int
685sys_extattr_list_fd(td, uap)
686	struct thread *td;
687	struct extattr_list_fd_args /* {
688		int fd;
689		int attrnamespace;
690		void *data;
691		size_t nbytes;
692	} */ *uap;
693{
694	struct file *fp;
695	cap_rights_t rights;
696	int error;
697
698	AUDIT_ARG_FD(uap->fd);
699	AUDIT_ARG_VALUE(uap->attrnamespace);
700	error = getvnode(td->td_proc->p_fd, uap->fd,
701	    cap_rights_init(&rights, CAP_EXTATTR_LIST), &fp);
702	if (error)
703		return (error);
704
705	error = extattr_list_vp(fp->f_vnode, uap->attrnamespace, uap->data,
706	    uap->nbytes, td);
707
708	fdrop(fp, td);
709	return (error);
710}
711
712int
713sys_extattr_list_file(td, uap)
714	struct thread*td;
715	struct extattr_list_file_args /* {
716		const char *path;
717		int attrnamespace;
718		void *data;
719		size_t nbytes;
720	} */ *uap;
721{
722	struct nameidata nd;
723	int error;
724
725	AUDIT_ARG_VALUE(uap->attrnamespace);
726	NDINIT(&nd, LOOKUP, FOLLOW | AUDITVNODE1, UIO_USERSPACE, uap->path, td);
727	error = namei(&nd);
728	if (error)
729		return (error);
730	NDFREE(&nd, NDF_ONLY_PNBUF);
731
732	error = extattr_list_vp(nd.ni_vp, uap->attrnamespace, uap->data,
733	    uap->nbytes, td);
734
735	vrele(nd.ni_vp);
736	return (error);
737}
738
739int
740sys_extattr_list_link(td, uap)
741	struct thread*td;
742	struct extattr_list_link_args /* {
743		const char *path;
744		int attrnamespace;
745		void *data;
746		size_t nbytes;
747	} */ *uap;
748{
749	struct nameidata nd;
750	int error;
751
752	AUDIT_ARG_VALUE(uap->attrnamespace);
753	NDINIT(&nd, LOOKUP, NOFOLLOW | AUDITVNODE1, UIO_USERSPACE, uap->path,
754	    td);
755	error = namei(&nd);
756	if (error)
757		return (error);
758	NDFREE(&nd, NDF_ONLY_PNBUF);
759
760	error = extattr_list_vp(nd.ni_vp, uap->attrnamespace, uap->data,
761	    uap->nbytes, td);
762
763	vrele(nd.ni_vp);
764	return (error);
765}
766