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