linux_getcwd.c revision 133816
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 133816 2004-08-16 07:28:16Z tjr $");
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/mac.h>
58#include <sys/malloc.h>
59#include <sys/dirent.h>
60#include <ufs/ufs/dir.h>	/* XXX only for DIRBLKSIZ */
61
62#include "opt_compat.h"
63
64#if !COMPAT_LINUX32
65#include <machine/../linux/linux.h>
66#include <machine/../linux/linux_proto.h>
67#else
68#include <machine/../linux32/linux.h>
69#include <machine/../linux32/linux32_proto.h>
70#endif
71#include <compat/linux/linux_util.h>
72
73static int
74linux_getcwd_scandir(struct vnode **, struct vnode **,
75    char **, char *, struct thread *);
76static int
77linux_getcwd_common(struct vnode *, struct vnode *,
78		   char **, char *, int, int, struct thread *);
79
80#define DIRENT_MINSIZE (sizeof(struct dirent) - (MAXNAMLEN+1) + 4)
81
82/*
83 * Vnode variable naming conventions in this file:
84 *
85 * rvp: the current root we're aiming towards.
86 * lvp, *lvpp: the "lower" vnode
87 * uvp, *uvpp: the "upper" vnode.
88 *
89 * Since all the vnodes we're dealing with are directories, and the
90 * lookups are going *up* in the filesystem rather than *down*, the
91 * usual "pvp" (parent) or "dvp" (directory) naming conventions are
92 * too confusing.
93 */
94
95/*
96 * XXX Will infinite loop in certain cases if a directory read reliably
97 *	returns EINVAL on last block.
98 * XXX is EINVAL the right thing to return if a directory is malformed?
99 */
100
101/*
102 * XXX Untested vs. mount -o union; probably does the wrong thing.
103 */
104
105/*
106 * Find parent vnode of *lvpp, return in *uvpp
107 *
108 * If we care about the name, scan it looking for name of directory
109 * entry pointing at lvp.
110 *
111 * Place the name in the buffer which starts at bufp, immediately
112 * before *bpp, and move bpp backwards to point at the start of it.
113 *
114 * On entry, *lvpp is a locked vnode reference; on exit, it is vput and NULL'ed
115 * On exit, *uvpp is either NULL or is a locked vnode reference.
116 */
117static int
118linux_getcwd_scandir(lvpp, uvpp, bpp, bufp, td)
119	struct vnode **lvpp;
120	struct vnode **uvpp;
121	char **bpp;
122	char *bufp;
123	struct thread *td;
124{
125	int     error = 0;
126	int     eofflag;
127	off_t   off;
128	int     tries;
129	struct uio uio;
130	struct iovec iov;
131	char   *dirbuf = NULL;
132	int	dirbuflen;
133	ino_t   fileno;
134	struct vattr va;
135	struct vnode *uvp = NULL;
136	struct vnode *lvp = *lvpp;
137	struct componentname cn;
138	int len, reclen;
139	tries = 0;
140
141	/*
142	 * If we want the filename, get some info we need while the
143	 * current directory is still locked.
144	 */
145	if (bufp != NULL) {
146		error = VOP_GETATTR(lvp, &va, td->td_ucred, td);
147		if (error) {
148			vput(lvp);
149			*lvpp = NULL;
150			*uvpp = NULL;
151			return error;
152		}
153	}
154
155	/*
156	 * Ok, we have to do it the hard way..
157	 * Next, get parent vnode using lookup of ..
158	 */
159	cn.cn_nameiop = LOOKUP;
160	cn.cn_flags = ISLASTCN | ISDOTDOT | RDONLY;
161	cn.cn_thread = td;
162	cn.cn_cred = td->td_ucred;
163	cn.cn_pnbuf = NULL;
164	cn.cn_nameptr = "..";
165	cn.cn_namelen = 2;
166	cn.cn_consume = 0;
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_check_vnode_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		vrele(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_check_vnode_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	vrele(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, td);
329	if (error) {
330		vrele(lvp);
331		lvp = NULL;
332		goto out;
333	}
334	if (bufp)
335		bp = *bpp;
336	/*
337	 * this loop will terminate when one of the following happens:
338	 *	- we hit the root
339	 *	- getdirentries or lookup fails
340	 *	- we run out of space in the buffer.
341	 */
342	if (lvp == rvp) {
343		if (bp)
344			*(--bp) = '/';
345		goto out;
346	}
347	do {
348		if (lvp->v_type != VDIR) {
349			error = ENOTDIR;
350			goto out;
351		}
352
353		/*
354		 * access check here is optional, depending on
355		 * whether or not caller cares.
356		 */
357		if (flags & GETCWD_CHECK_ACCESS) {
358			error = VOP_ACCESS(lvp, perms, td->td_ucred, td);
359			if (error)
360				goto out;
361			perms = VEXEC|VREAD;
362		}
363
364		/*
365		 * step up if we're a covered vnode..
366		 */
367		while (lvp->v_vflag & VV_ROOT) {
368			struct vnode *tvp;
369
370			if (lvp == rvp)
371				goto out;
372
373			tvp = lvp;
374			lvp = lvp->v_mount->mnt_vnodecovered;
375			vput(tvp);
376			/*
377			 * hodie natus est radici frater
378			 */
379			if (lvp == NULL) {
380				error = ENOENT;
381				goto out;
382			}
383			VREF(lvp);
384			error = vn_lock(lvp, LK_EXCLUSIVE | LK_RETRY, td);
385			if (error != 0) {
386				vrele(lvp);
387				lvp = NULL;
388				goto out;
389			}
390		}
391		error = linux_getcwd_scandir(&lvp, &uvp, &bp, bufp, td);
392		if (error)
393			goto out;
394#if DIAGNOSTIC
395		if (lvp != NULL)
396			panic("getcwd: oops, forgot to null lvp");
397		if (bufp && (bp <= bufp)) {
398			panic("getcwd: oops, went back too far");
399		}
400#endif
401		if (bp)
402			*(--bp) = '/';
403		lvp = uvp;
404		uvp = NULL;
405		limit--;
406	} while ((lvp != rvp) && (limit > 0));
407
408out:
409	if (bpp)
410		*bpp = bp;
411	if (uvp)
412		vput(uvp);
413	if (lvp)
414		vput(lvp);
415	vrele(rvp);
416	return error;
417}
418
419
420/*
421 * Find pathname of process's current directory.
422 *
423 * Use vfs vnode-to-name reverse cache; if that fails, fall back
424 * to reading directory contents.
425 */
426
427int
428linux_getcwd(struct thread *td, struct linux_getcwd_args *args)
429{
430	caddr_t bp, bend, path;
431	int error, len, lenused;
432
433#ifdef DEBUG
434	printf("Linux-emul(%ld): getcwd(%p, %ld)\n", (long)td->td_proc->p_pid,
435	       args->buf, (long)args->bufsize);
436#endif
437
438	len = args->bufsize;
439
440	if (len > MAXPATHLEN*4)
441		len = MAXPATHLEN*4;
442	else if (len < 2)
443		return ERANGE;
444
445	path = (char *)malloc(len, M_TEMP, M_WAITOK);
446
447	error = kern___getcwd(td, path, UIO_SYSSPACE, len);
448	if (!error) {
449		lenused = strlen(path) + 1;
450		if (lenused <= args->bufsize) {
451			td->td_retval[0] = lenused;
452			error = copyout(path, args->buf, lenused);
453		}
454		else
455			error = ERANGE;
456	} else {
457		bp = &path[len];
458		bend = bp;
459		*(--bp) = '\0';
460
461		/*
462		 * 5th argument here is "max number of vnodes to traverse".
463		 * Since each entry takes up at least 2 bytes in the output buffer,
464		 * limit it to N/2 vnodes for an N byte buffer.
465		 */
466
467		error = linux_getcwd_common (td->td_proc->p_fd->fd_cdir, NULL,
468		    &bp, path, len/2, GETCWD_CHECK_ACCESS, td);
469
470		if (error)
471			goto out;
472		lenused = bend - bp;
473		td->td_retval[0] = lenused;
474		/* put the result into user buffer */
475		error = copyout(bp, args->buf, lenused);
476	}
477out:
478	free(path, M_TEMP);
479	return (error);
480}
481
482