linux_getcwd.c revision 182371
1/* $OpenBSD: linux_getcwd.c,v 1.2 2001/05/16 12:50:21 ho Exp $ */
2/* $NetBSD: vfs_getcwd.c,v 1.3.2.3 1999/07/11 10:24:09 sommerfeld Exp $ */
3/*-
4 * Copyright (c) 1999 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Bill Sommerfeld.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 *    notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 *    notice, this list of conditions and the following disclaimer in the
17 *    documentation and/or other materials provided with the distribution.
18 * 3. All advertising materials mentioning features or use of this software
19 *    must display the following acknowledgement:
20 *        This product includes software developed by the NetBSD
21 *        Foundation, Inc. and its contributors.
22 * 4. Neither the name of The NetBSD Foundation nor the names of its
23 *    contributors may be used to endorse or promote products derived
24 *    from this software without specific prior written permission.
25 *
26 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
27 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
28 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
29 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
30 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
31 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
32 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
33 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
34 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
36 * POSSIBILITY OF SUCH DAMAGE.
37 */
38
39#include <sys/cdefs.h>
40__FBSDID("$FreeBSD: head/sys/compat/linux/linux_getcwd.c 182371 2008-08-28 15:23:18Z attilio $");
41
42#include "opt_compat.h"
43#include "opt_mac.h"
44
45#include <sys/param.h>
46#include <sys/systm.h>
47#include <sys/namei.h>
48#include <sys/filedesc.h>
49#include <sys/kernel.h>
50#include <sys/file.h>
51#include <sys/stat.h>
52#include <sys/syscallsubr.h>
53#include <sys/vnode.h>
54#include <sys/mount.h>
55#include <sys/proc.h>
56#include <sys/uio.h>
57#include <sys/malloc.h>
58#include <sys/dirent.h>
59#include <ufs/ufs/dir.h>	/* XXX only for DIRBLKSIZ */
60
61#ifdef COMPAT_LINUX32
62#include <machine/../linux32/linux.h>
63#include <machine/../linux32/linux32_proto.h>
64#else
65#include <machine/../linux/linux.h>
66#include <machine/../linux/linux_proto.h>
67#endif
68#include <compat/linux/linux_util.h>
69
70#include <security/mac/mac_framework.h>
71
72static int
73linux_getcwd_scandir(struct vnode **, struct vnode **,
74    char **, char *, struct thread *);
75static int
76linux_getcwd_common(struct vnode *, struct vnode *,
77		   char **, char *, int, int, struct thread *);
78
79#define DIRENT_MINSIZE (sizeof(struct dirent) - (MAXNAMLEN+1) + 4)
80
81/*
82 * Vnode variable naming conventions in this file:
83 *
84 * rvp: the current root we're aiming towards.
85 * lvp, *lvpp: the "lower" vnode
86 * uvp, *uvpp: the "upper" vnode.
87 *
88 * Since all the vnodes we're dealing with are directories, and the
89 * lookups are going *up* in the filesystem rather than *down*, the
90 * usual "pvp" (parent) or "dvp" (directory) naming conventions are
91 * too confusing.
92 */
93
94/*
95 * XXX Will infinite loop in certain cases if a directory read reliably
96 *	returns EINVAL on last block.
97 * XXX is EINVAL the right thing to return if a directory is malformed?
98 */
99
100/*
101 * XXX Untested vs. mount -o union; probably does the wrong thing.
102 */
103
104/*
105 * Find parent vnode of *lvpp, return in *uvpp
106 *
107 * If we care about the name, scan it looking for name of directory
108 * entry pointing at lvp.
109 *
110 * Place the name in the buffer which starts at bufp, immediately
111 * before *bpp, and move bpp backwards to point at the start of it.
112 *
113 * On entry, *lvpp is a locked vnode reference; on exit, it is vput and NULL'ed
114 * On exit, *uvpp is either NULL or is a locked vnode reference.
115 */
116static int
117linux_getcwd_scandir(lvpp, uvpp, bpp, bufp, td)
118	struct vnode **lvpp;
119	struct vnode **uvpp;
120	char **bpp;
121	char *bufp;
122	struct thread *td;
123{
124	int     error = 0;
125	int     eofflag;
126	off_t   off;
127	int     tries;
128	struct uio uio;
129	struct iovec iov;
130	char   *dirbuf = NULL;
131	int	dirbuflen;
132	ino_t   fileno;
133	struct vattr va;
134	struct vnode *uvp = NULL;
135	struct vnode *lvp = *lvpp;
136	struct componentname cn;
137	int len, reclen;
138	tries = 0;
139
140	/*
141	 * If we want the filename, get some info we need while the
142	 * current directory is still locked.
143	 */
144	if (bufp != NULL) {
145		error = VOP_GETATTR(lvp, &va, td->td_ucred);
146		if (error) {
147			vput(lvp);
148			*lvpp = NULL;
149			*uvpp = NULL;
150			return error;
151		}
152	}
153
154	/*
155	 * Ok, we have to do it the hard way..
156	 * Next, get parent vnode using lookup of ..
157	 */
158	cn.cn_nameiop = LOOKUP;
159	cn.cn_flags = ISLASTCN | ISDOTDOT | RDONLY;
160	cn.cn_thread = td;
161	cn.cn_cred = td->td_ucred;
162	cn.cn_pnbuf = NULL;
163	cn.cn_nameptr = "..";
164	cn.cn_namelen = 2;
165	cn.cn_consume = 0;
166	cn.cn_lkflags = LK_EXCLUSIVE;
167
168	/*
169	 * At this point, lvp is locked and will be unlocked by the lookup.
170	 * On successful return, *uvpp will be locked
171	 */
172#ifdef MAC
173	error = mac_vnode_check_lookup(td->td_ucred, lvp, &cn);
174	if (error == 0)
175#endif
176		error = VOP_LOOKUP(lvp, uvpp, &cn);
177	if (error) {
178		vput(lvp);
179		*lvpp = NULL;
180		*uvpp = NULL;
181		return error;
182	}
183	uvp = *uvpp;
184
185	/* If we don't care about the pathname, we're done */
186	if (bufp == NULL) {
187		vput(lvp);
188		*lvpp = NULL;
189		return 0;
190	}
191
192	fileno = va.va_fileid;
193
194	dirbuflen = DIRBLKSIZ;
195	if (dirbuflen < va.va_blocksize)
196		dirbuflen = va.va_blocksize;
197	dirbuf = (char *)malloc(dirbuflen, M_TEMP, M_WAITOK);
198
199#if 0
200unionread:
201#endif
202	off = 0;
203	do {
204		/* call VOP_READDIR of parent */
205		iov.iov_base = dirbuf;
206		iov.iov_len = dirbuflen;
207
208		uio.uio_iov = &iov;
209		uio.uio_iovcnt = 1;
210		uio.uio_offset = off;
211		uio.uio_resid = dirbuflen;
212		uio.uio_segflg = UIO_SYSSPACE;
213		uio.uio_rw = UIO_READ;
214		uio.uio_td = td;
215
216		eofflag = 0;
217
218#ifdef MAC
219		error = mac_vnode_check_readdir(td->td_ucred, uvp);
220		if (error == 0)
221#endif /* MAC */
222			error = VOP_READDIR(uvp, &uio, td->td_ucred, &eofflag,
223			    0, 0);
224
225		off = uio.uio_offset;
226
227		/*
228		 * Try again if NFS tosses its cookies.
229		 * XXX this can still loop forever if the directory is busted
230		 * such that the second or subsequent page of it always
231		 * returns EINVAL
232		 */
233		if ((error == EINVAL) && (tries < 3)) {
234			off = 0;
235			tries++;
236			continue;	/* once more, with feeling */
237		}
238
239		if (!error) {
240			char   *cpos;
241			struct dirent *dp;
242
243			cpos = dirbuf;
244			tries = 0;
245
246			/* scan directory page looking for matching vnode */
247			for (len = (dirbuflen - uio.uio_resid); len > 0; len -= reclen) {
248				dp = (struct dirent *) cpos;
249				reclen = dp->d_reclen;
250
251				/* check for malformed directory.. */
252				if (reclen < DIRENT_MINSIZE) {
253					error = EINVAL;
254					goto out;
255				}
256				/*
257				 * XXX should perhaps do VOP_LOOKUP to
258				 * check that we got back to the right place,
259				 * but getting the locking games for that
260				 * right would be heinous.
261				 */
262				if ((dp->d_type != DT_WHT) &&
263				    (dp->d_fileno == fileno)) {
264					char *bp = *bpp;
265					bp -= dp->d_namlen;
266
267					if (bp <= bufp) {
268						error = ERANGE;
269						goto out;
270					}
271					bcopy(dp->d_name, bp, dp->d_namlen);
272					error = 0;
273					*bpp = bp;
274					goto out;
275				}
276				cpos += reclen;
277			}
278		}
279	} while (!eofflag);
280	error = ENOENT;
281
282out:
283	vput(lvp);
284	*lvpp = NULL;
285	free(dirbuf, M_TEMP);
286	return error;
287}
288
289
290/*
291 * common routine shared by sys___getcwd() and linux_vn_isunder()
292 */
293
294#define GETCWD_CHECK_ACCESS 0x0001
295
296static int
297linux_getcwd_common (lvp, rvp, bpp, bufp, limit, flags, td)
298	struct vnode *lvp;
299	struct vnode *rvp;
300	char **bpp;
301	char *bufp;
302	int limit;
303	int flags;
304	struct thread *td;
305{
306	struct filedesc *fdp = td->td_proc->p_fd;
307	struct vnode *uvp = NULL;
308	char *bp = NULL;
309	int error;
310	int perms = VEXEC;
311
312	if (rvp == NULL) {
313		rvp = fdp->fd_rdir;
314		if (rvp == NULL)
315			rvp = rootvnode;
316	}
317
318	VREF(rvp);
319	VREF(lvp);
320
321	/*
322	 * Error handling invariant:
323	 * Before a `goto out':
324	 *	lvp is either NULL, or locked and held.
325	 *	uvp is either NULL, or locked and held.
326	 */
327
328	error = vn_lock(lvp, LK_EXCLUSIVE | LK_RETRY);
329	if (error != 0)
330		panic("vn_lock LK_RETRY returned error %d", error);
331	if (bufp)
332		bp = *bpp;
333	/*
334	 * this loop will terminate when one of the following happens:
335	 *	- we hit the root
336	 *	- getdirentries or lookup fails
337	 *	- we run out of space in the buffer.
338	 */
339	if (lvp == rvp) {
340		if (bp)
341			*(--bp) = '/';
342		goto out;
343	}
344	do {
345		if (lvp->v_type != VDIR) {
346			error = ENOTDIR;
347			goto out;
348		}
349
350		/*
351		 * access check here is optional, depending on
352		 * whether or not caller cares.
353		 */
354		if (flags & GETCWD_CHECK_ACCESS) {
355			error = VOP_ACCESS(lvp, perms, td->td_ucred, td);
356			if (error)
357				goto out;
358			perms = VEXEC|VREAD;
359		}
360
361		/*
362		 * step up if we're a covered vnode..
363		 */
364		while (lvp->v_vflag & VV_ROOT) {
365			struct vnode *tvp;
366
367			if (lvp == rvp)
368				goto out;
369
370			tvp = lvp;
371			lvp = lvp->v_mount->mnt_vnodecovered;
372			vput(tvp);
373			/*
374			 * hodie natus est radici frater
375			 */
376			if (lvp == NULL) {
377				error = ENOENT;
378				goto out;
379			}
380			VREF(lvp);
381			error = vn_lock(lvp, LK_EXCLUSIVE | LK_RETRY);
382			if (error != 0)
383				panic("vn_lock LK_RETRY returned %d", error);
384		}
385		error = linux_getcwd_scandir(&lvp, &uvp, &bp, bufp, td);
386		if (error)
387			goto out;
388#ifdef DIAGNOSTIC
389		if (lvp != NULL)
390			panic("getcwd: oops, forgot to null lvp");
391		if (bufp && (bp <= bufp)) {
392			panic("getcwd: oops, went back too far");
393		}
394#endif
395		if (bp)
396			*(--bp) = '/';
397		lvp = uvp;
398		uvp = NULL;
399		limit--;
400	} while ((lvp != rvp) && (limit > 0));
401
402out:
403	if (bpp)
404		*bpp = bp;
405	if (uvp)
406		vput(uvp);
407	if (lvp)
408		vput(lvp);
409	vrele(rvp);
410	return error;
411}
412
413
414/*
415 * Find pathname of process's current directory.
416 *
417 * Use vfs vnode-to-name reverse cache; if that fails, fall back
418 * to reading directory contents.
419 */
420
421int
422linux_getcwd(struct thread *td, struct linux_getcwd_args *args)
423{
424	caddr_t bp, bend, path;
425	int error, len, lenused;
426
427#ifdef DEBUG
428	if (ldebug(getcwd))
429		printf(ARGS(getcwd, "%p, %ld"), args->buf, (long)args->bufsize);
430#endif
431
432	len = args->bufsize;
433
434	if (len > MAXPATHLEN*4)
435		len = MAXPATHLEN*4;
436	else if (len < 2)
437		return ERANGE;
438
439	path = (char *)malloc(len, M_TEMP, M_WAITOK);
440
441	error = kern___getcwd(td, path, UIO_SYSSPACE, len);
442	if (!error) {
443		lenused = strlen(path) + 1;
444		if (lenused <= args->bufsize) {
445			td->td_retval[0] = lenused;
446			error = copyout(path, args->buf, lenused);
447		}
448		else
449			error = ERANGE;
450	} else {
451		bp = &path[len];
452		bend = bp;
453		*(--bp) = '\0';
454
455		/*
456		 * 5th argument here is "max number of vnodes to traverse".
457		 * Since each entry takes up at least 2 bytes in the output buffer,
458		 * limit it to N/2 vnodes for an N byte buffer.
459		 */
460
461		mtx_lock(&Giant);
462		error = linux_getcwd_common (td->td_proc->p_fd->fd_cdir, NULL,
463		    &bp, path, len/2, GETCWD_CHECK_ACCESS, td);
464		mtx_unlock(&Giant);
465
466		if (error)
467			goto out;
468		lenused = bend - bp;
469		td->td_retval[0] = lenused;
470		/* put the result into user buffer */
471		error = copyout(bp, args->buf, lenused);
472	}
473out:
474	free(path, M_TEMP);
475	return (error);
476}
477
478