1 /* $OpenBSD: vfs_getcwd.c,v 1.12 2007/08/07 07:41:59 thib Exp $ */
2 /* $NetBSD: vfs_getcwd.c,v 1.3.2.3 1999/07/11 10:24:09 sommerfeld Exp $ */
3
4 /*
5 * Copyright (c) 1999 The NetBSD Foundation, Inc.
6 * All rights reserved.
7 *
8 * This code is derived from software contributed to The NetBSD Foundation
9 * by Bill Sommerfeld.
10 *
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. All advertising materials mentioning features or use of this software
20 * must display the following acknowledgement:
21 * This product includes software developed by the NetBSD
22 * Foundation, Inc. and its contributors.
23 * 4. Neither the name of The NetBSD Foundation nor the names of its
24 * contributors may be used to endorse or promote products derived
25 * from this software without specific prior written permission.
26 *
27 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
28 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
29 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
30 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
31 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
32 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
33 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
34 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
35 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
36 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
37 * POSSIBILITY OF SUCH DAMAGE.
38 */
39
40 #include <sys/param.h>
41 #include <sys/systm.h>
42 #include <sys/namei.h>
43 #include <sys/filedesc.h>
44 #include <sys/kernel.h>
45 #include <sys/file.h>
46 #include <sys/stat.h>
47 #include <sys/vnode.h>
48 #include <sys/mount.h>
49 #include <sys/proc.h>
50 #include <sys/uio.h>
51 #include <sys/malloc.h>
52 #include <sys/dirent.h>
53 #include <ufs/ufs/dir.h> /* only for DIRBLKSIZ */
54
55 #include <sys/syscallargs.h>
56
57 #define DIRENT_MINSIZE (sizeof(struct dirent) - (MAXNAMLEN + 1) + 4)
58
59 /* Find parent vnode of *lvpp, return in *uvpp */
60 int
61 vfs_getcwd_scandir(struct vnode **lvpp, struct vnode **uvpp, char **bpp,
62 char *bufp, struct proc *p)
63 {
64 int eofflag, tries, dirbuflen, len, reclen, error = 0;
65 off_t off;
66 struct uio uio;
67 struct iovec iov;
68 char *dirbuf = NULL;
69 ino_t fileno;
70 struct vattr va;
71 struct vnode *uvp = NULL;
72 struct vnode *lvp = *lvpp;
73 struct componentname cn;
74
75 tries = 0;
76
77 /*
78 * If we want the filename, get some info we need while the
79 * current directory is still locked.
80 */
81 if (bufp != NULL) {
82 error = VOP_GETATTR(lvp, &va, p->p_ucred, p);
83 if (error) {
84 vput(lvp);
85 *lvpp = NULL;
86 *uvpp = NULL;
87 return (error);
88 }
89 }
90
91 cn.cn_nameiop = LOOKUP;
92 cn.cn_flags = ISLASTCN | ISDOTDOT | RDONLY;
93 cn.cn_proc = p;
94 cn.cn_cred = p->p_ucred;
95 cn.cn_pnbuf = NULL;
96 cn.cn_nameptr = "..";
97 cn.cn_namelen = 2;
98 cn.cn_hash = 0;
99 cn.cn_consume = 0;
100
101 /* Get parent vnode using lookup of '..' */
102 error = VOP_LOOKUP(lvp, uvpp, &cn);
103 if (error) {
104 vput(lvp);
105 *lvpp = NULL;
106 *uvpp = NULL;
107 return (error);
108 }
109
110 uvp = *uvpp;
111
112 /* If we don't care about the pathname, we're done */
113 if (bufp == NULL) {
114 vrele(lvp);
115 *lvpp = NULL;
116 return (0);
117 }
118
119 fileno = va.va_fileid;
120
121 dirbuflen = DIRBLKSIZ;
122
123 if (dirbuflen < va.va_blocksize)
124 dirbuflen = va.va_blocksize;
125
126 dirbuf = malloc(dirbuflen, M_TEMP, M_WAITOK);
127
128 off = 0;
129
130 do {
131 char *cpos;
132 struct dirent *dp;
133
134 iov.iov_base = dirbuf;
135 iov.iov_len = dirbuflen;
136
137 uio.uio_iov = &iov;
138 uio.uio_iovcnt = 1;
139 uio.uio_offset = off;
140 uio.uio_resid = dirbuflen;
141 uio.uio_segflg = UIO_SYSSPACE;
142 uio.uio_rw = UIO_READ;
143 uio.uio_procp = p;
144
145 eofflag = 0;
146
147 /* Call VOP_READDIR of parent */
148 error = VOP_READDIR(uvp, &uio, p->p_ucred, &eofflag, 0, 0);
149
150 off = uio.uio_offset;
151
152 /* Try again if NFS tosses its cookies */
153 if (error == EINVAL && tries < 3) {
154 tries++;
155 off = 0;
156 continue;
157 } else if (error) {
158 goto out; /* Old userland getcwd() behaviour */
159 }
160
161 cpos = dirbuf;
162 tries = 0;
163
164 /* Scan directory page looking for matching vnode */
165 for (len = (dirbuflen - uio.uio_resid); len > 0;
166 len -= reclen) {
167 dp = (struct dirent *)cpos;
168 reclen = dp->d_reclen;
169
170 /* Check for malformed directory */
171 if (reclen < DIRENT_MINSIZE) {
172 error = EINVAL;
173 goto out;
174 }
175
176 if (dp->d_fileno == fileno) {
177 char *bp = *bpp;
178 bp -= dp->d_namlen;
179
180 if (bp <= bufp) {
181 error = ERANGE;
182 goto out;
183 }
184
185 bcopy(dp->d_name, bp, dp->d_namlen);
186 error = 0;
187 *bpp = bp;
188
189 goto out;
190 }
191
192 cpos += reclen;
193 }
194
195 } while (!eofflag);
196
197 error = ENOENT;
198
199 out:
200
201 vrele(lvp);
202 *lvpp = NULL;
203
204 free(dirbuf, M_TEMP);
205
206 return (error);
207 }
208
209 /* Do a lookup in the vnode-to-name reverse */
210 int
211 vfs_getcwd_getcache(struct vnode **lvpp, struct vnode **uvpp, char **bpp,
212 char *bufp)
213 {
214 struct vnode *lvp, *uvp = NULL;
215 struct proc *p = curproc;
216 char *obp;
217 int error, vpid;
218
219 lvp = *lvpp;
220 obp = *bpp; /* Save orginal position to restore to on error */
221
222 error = cache_revlookup(lvp, uvpp, bpp, bufp);
223 if (error) {
224 if (error != -1) {
225 vput(lvp);
226 *lvpp = NULL;
227 *uvpp = NULL;
228 }
229
230 return (error);
231 }
232
233 uvp = *uvpp;
234 vpid = uvp->v_id;
235
236
237 /* Release current lock before acquiring the parent lock */
238 VOP_UNLOCK(lvp, 0, p);
239
240 error = vget(uvp, LK_EXCLUSIVE | LK_RETRY, p);
241 if (error)
242 *uvpp = NULL;
243
244 /*
245 * Verify that vget() succeeded, and check that vnode capability
246 * didn't change while we were waiting for the lock.
247 */
248 if (error || (vpid != uvp->v_id)) {
249 /*
250 * Try to get our lock back. If that works, tell the caller to
251 * try things the hard way, otherwise give up.
252 */
253 if (!error)
254 vput(uvp);
255
256 *uvpp = NULL;
257
258 error = vn_lock(lvp, LK_EXCLUSIVE | LK_RETRY, p);
259 if (!error) {
260 *bpp = obp; /* restore the buffer */
261 return (-1);
262 }
263 }
264
265 vrele(lvp);
266 *lvpp = NULL;
267
268 return (error);
269 }
270
271 #define GETCWD_CHECK_ACCESS 0x0001
272
273 /* Common routine shared by sys___getcwd() and vn_isunder() */
274 int
275 vfs_getcwd_common(struct vnode *lvp, struct vnode *rvp, char **bpp, char *bufp,
276 int limit, int flags, struct proc *p)
277 {
278 struct filedesc *fdp = p->p_fd;
279 struct vnode *uvp = NULL;
280 char *bp = NULL;
281 int error, perms = VEXEC;
282
283 if (rvp == NULL) {
284 rvp = fdp->fd_rdir;
285 if (rvp == NULL)
286 rvp = rootvnode;
287 }
288
289 VREF(rvp);
290 VREF(lvp);
291
292 error = vn_lock(lvp, LK_EXCLUSIVE | LK_RETRY, p);
293 if (error) {
294 vrele(lvp);
295 lvp = NULL;
296 goto out;
297 }
298
299 if (bufp)
300 bp = *bpp;
301
302 if (lvp == rvp) {
303 if (bp)
304 *(--bp) = '/';
305 goto out;
306 }
307
308 /*
309 * This loop will terminate when we hit the root, VOP_READDIR() or
310 * VOP_LOOKUP() fails, or we run out of space in the user buffer.
311 */
312 do {
313 if (lvp->v_type != VDIR) {
314 error = ENOTDIR;
315 goto out;
316 }
317
318 /* Check for access if caller cares */
319 if (flags & GETCWD_CHECK_ACCESS) {
320 error = VOP_ACCESS(lvp, perms, p->p_ucred, p);
321 if (error)
322 goto out;
323 perms = VEXEC|VREAD;
324 }
325
326 /* Step up if we're a covered vnode */
327 while (lvp->v_flag & VROOT) {
328 struct vnode *tvp;
329
330 if (lvp == rvp)
331 goto out;
332
333 tvp = lvp;
334 lvp = lvp->v_mount->mnt_vnodecovered;
335
336 vput(tvp);
337
338 if (lvp == NULL) {
339 error = ENOENT;
340 goto out;
341 }
342
343 VREF(lvp);
344
345 error = vn_lock(lvp, LK_EXCLUSIVE | LK_RETRY, p);
346 if (error) {
347 vrele(lvp);
348 lvp = NULL;
349 goto out;
350 }
351 }
352
353 /* Look in the name cache */
354 error = vfs_getcwd_getcache(&lvp, &uvp, &bp, bufp);
355
356 if (error == -1) {
357 /* If that fails, look in the directory */
358 error = vfs_getcwd_scandir(&lvp, &uvp, &bp, bufp, p);
359 }
360
361 if (error)
362 goto out;
363
364 #ifdef DIAGNOSTIC
365 if (lvp != NULL)
366 panic("getcwd: oops, forgot to null lvp");
367 if (bufp && (bp <= bufp)) {
368 panic("getcwd: oops, went back too far");
369 }
370 #endif
371
372 if (bp)
373 *(--bp) = '/';
374
375 lvp = uvp;
376 uvp = NULL;
377 limit--;
378
379 } while ((lvp != rvp) && (limit > 0));
380
381 out:
382
383 if (bpp)
384 *bpp = bp;
385
386 if (uvp)
387 vput(uvp);
388
389 if (lvp)
390 vput(lvp);
391
392 vrele(rvp);
393
394 return (error);
395 }
396
397 /* True if p1's root directory is equal to or under p2's root directory */
398 int
399 proc_isunder(struct proc *p1, struct proc *p2)
400 {
401 struct vnode *r1 = p1->p_fd->fd_rdir;
402 struct vnode *r2 = p2->p_fd->fd_rdir;
403
404 if (r1 == NULL)
405 return (r2 == NULL);
406
407 if (r2 == NULL)
408 return (1);
409
410 return (vn_isunder(r1, r2, p2));
411 }
412
413 /* Find pathname of a process's current directory */
414 int
415 sys___getcwd(struct proc *p, void *v, register_t *retval)
416 {
417 struct sys___getcwd_args *uap = v;
418 int error, lenused, len = SCARG(uap, len);
419 char *path, *bp, *bend;
420
421 if (len > MAXPATHLEN * 4)
422 len = MAXPATHLEN * 4;
423 else if (len < 2)
424 return (ERANGE);
425
426 path = malloc(len, M_TEMP, M_WAITOK);
427
428 bp = &path[len];
429 bend = bp;
430 *(--bp) = '\0';
431
432 /*
433 * 5th argument here is "max number of vnodes to traverse".
434 * Since each entry takes up at least 2 bytes in the output
435 * buffer, limit it to N/2 vnodes for an N byte buffer.
436 */
437 error = vfs_getcwd_common(p->p_fd->fd_cdir, NULL, &bp, path, len/2,
438 GETCWD_CHECK_ACCESS, p);
439
440 if (error)
441 goto out;
442
443 lenused = bend - bp;
444 *retval = lenused;
445
446 /* Put the result into user buffer */
447 error = copyout(bp, SCARG(uap, buf), lenused);
448
449 out:
450 free(path, M_TEMP);
451
452 return (error);
453 }