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