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