Line data Source code
1 : /* $OpenBSD: vfs_getcwd.c,v 1.31 2018/05/27 06:02:14 visa 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/kernel.h>
38 : #include <sys/stat.h>
39 : #include <sys/lock.h>
40 : #include <sys/vnode.h>
41 : #include <sys/mount.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
53 0 : vfs_getcwd_scandir(struct vnode **lvpp, struct vnode **uvpp, char **bpp,
54 : char *bufp, struct proc *p)
55 : {
56 0 : int eofflag, tries, dirbuflen = 0, len, reclen, error = 0;
57 : off_t off;
58 0 : struct uio uio;
59 0 : struct iovec iov;
60 : char *dirbuf = NULL;
61 : ino_t fileno;
62 0 : struct vattr va;
63 : struct vnode *uvp = NULL;
64 0 : struct vnode *lvp = *lvpp;
65 0 : 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 0 : if (bufp != NULL) {
74 0 : error = VOP_GETATTR(lvp, &va, p->p_ucred, p);
75 0 : if (error) {
76 0 : vput(lvp);
77 0 : *lvpp = NULL;
78 0 : *uvpp = NULL;
79 0 : return (error);
80 : }
81 : }
82 :
83 0 : cn.cn_nameiop = LOOKUP;
84 0 : cn.cn_flags = ISLASTCN | ISDOTDOT | RDONLY;
85 0 : cn.cn_proc = p;
86 0 : cn.cn_cred = p->p_ucred;
87 0 : cn.cn_pnbuf = NULL;
88 0 : cn.cn_nameptr = "..";
89 0 : cn.cn_namelen = 2;
90 0 : cn.cn_consume = 0;
91 :
92 : /* Get parent vnode using lookup of '..' */
93 0 : error = VOP_LOOKUP(lvp, uvpp, &cn);
94 0 : if (error) {
95 0 : vput(lvp);
96 0 : *lvpp = NULL;
97 0 : *uvpp = NULL;
98 0 : return (error);
99 : }
100 :
101 0 : uvp = *uvpp;
102 :
103 : /* If we don't care about the pathname, we're done */
104 0 : if (bufp == NULL) {
105 : error = 0;
106 0 : goto out;
107 : }
108 :
109 0 : fileno = va.va_fileid;
110 :
111 : dirbuflen = DIRBLKSIZ;
112 0 : if (dirbuflen < va.va_blocksize)
113 0 : dirbuflen = va.va_blocksize;
114 : /* XXX we need some limit for fuse, 1 MB should be enough */
115 0 : if (dirbuflen > 0xfffff) {
116 : error = EINVAL;
117 0 : goto out;
118 : }
119 0 : dirbuf = malloc(dirbuflen, M_TEMP, M_WAITOK);
120 :
121 : off = 0;
122 :
123 0 : do {
124 : char *cpos;
125 : struct dirent *dp;
126 :
127 0 : iov.iov_base = dirbuf;
128 0 : iov.iov_len = dirbuflen;
129 :
130 0 : uio.uio_iov = &iov;
131 0 : uio.uio_iovcnt = 1;
132 0 : uio.uio_offset = off;
133 0 : uio.uio_resid = dirbuflen;
134 0 : uio.uio_segflg = UIO_SYSSPACE;
135 0 : uio.uio_rw = UIO_READ;
136 0 : uio.uio_procp = p;
137 :
138 0 : eofflag = 0;
139 :
140 : /* Call VOP_READDIR of parent */
141 0 : error = VOP_READDIR(uvp, &uio, p->p_ucred, &eofflag);
142 :
143 0 : off = uio.uio_offset;
144 :
145 : /* Try again if NFS tosses its cookies */
146 0 : if (error == EINVAL && tries < 3) {
147 0 : tries++;
148 : off = 0;
149 0 : continue;
150 0 : } else if (error) {
151 0 : goto out; /* Old userland getcwd() behaviour */
152 : }
153 :
154 : cpos = dirbuf;
155 : tries = 0;
156 :
157 : /* Scan directory page looking for matching vnode */
158 0 : for (len = (dirbuflen - uio.uio_resid); len > 0;
159 0 : len -= reclen) {
160 0 : dp = (struct dirent *)cpos;
161 0 : reclen = dp->d_reclen;
162 :
163 : /* Check for malformed directory */
164 0 : if (reclen < DIRENT_RECSIZE(1) || reclen > len) {
165 : error = EINVAL;
166 0 : goto out;
167 : }
168 :
169 0 : if (dp->d_fileno == fileno) {
170 0 : char *bp = *bpp;
171 :
172 0 : if (offsetof(struct dirent, d_name) +
173 0 : dp->d_namlen > reclen) {
174 : error = EINVAL;
175 0 : goto out;
176 : }
177 0 : bp -= dp->d_namlen;
178 0 : if (bp <= bufp) {
179 : error = ERANGE;
180 0 : goto out;
181 : }
182 :
183 0 : memmove(bp, dp->d_name, dp->d_namlen);
184 : error = 0;
185 0 : *bpp = bp;
186 :
187 0 : goto out;
188 : }
189 :
190 0 : cpos += reclen;
191 : }
192 :
193 0 : } while (!eofflag);
194 :
195 0 : error = ENOENT;
196 :
197 : out:
198 :
199 0 : vrele(lvp);
200 0 : *lvpp = NULL;
201 :
202 0 : free(dirbuf, M_TEMP, dirbuflen);
203 :
204 0 : return (error);
205 0 : }
206 :
207 : /* Do a lookup in the vnode-to-name reverse */
208 : int
209 0 : 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 0 : lvp = *lvpp;
217 0 : obp = *bpp; /* Save original position to restore to on error */
218 :
219 0 : error = cache_revlookup(lvp, uvpp, bpp, bufp);
220 0 : if (error) {
221 0 : if (error != -1) {
222 0 : vput(lvp);
223 0 : *lvpp = NULL;
224 0 : *uvpp = NULL;
225 0 : }
226 :
227 0 : return (error);
228 : }
229 :
230 0 : uvp = *uvpp;
231 0 : vpid = uvp->v_id;
232 :
233 :
234 : /* Release current lock before acquiring the parent lock */
235 0 : VOP_UNLOCK(lvp);
236 :
237 0 : error = vget(uvp, LK_EXCLUSIVE | LK_RETRY);
238 0 : if (error)
239 0 : *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 0 : 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 0 : if (!error)
251 0 : vput(uvp);
252 :
253 0 : *uvpp = NULL;
254 :
255 0 : error = vn_lock(lvp, LK_EXCLUSIVE | LK_RETRY);
256 0 : if (!error) {
257 0 : *bpp = obp; /* restore the buffer */
258 0 : return (-1);
259 : }
260 : }
261 :
262 0 : vrele(lvp);
263 0 : *lvpp = NULL;
264 :
265 0 : return (error);
266 0 : }
267 :
268 : /* Common routine shared by sys___getcwd() and vn_isunder() */
269 : int
270 0 : vfs_getcwd_common(struct vnode *lvp, struct vnode *rvp, char **bpp, char *bufp,
271 : int limit, int flags, struct proc *p)
272 : {
273 0 : struct filedesc *fdp = p->p_fd;
274 0 : struct vnode *uvp = NULL;
275 0 : char *bp = NULL;
276 : int error, perms = VEXEC;
277 :
278 0 : if (rvp == NULL) {
279 0 : rvp = fdp->fd_rdir;
280 0 : if (rvp == NULL)
281 0 : rvp = rootvnode;
282 : }
283 :
284 0 : vref(rvp);
285 0 : vref(lvp);
286 :
287 0 : error = vn_lock(lvp, LK_EXCLUSIVE | LK_RETRY);
288 0 : if (error) {
289 0 : vrele(lvp);
290 0 : lvp = NULL;
291 0 : goto out;
292 : }
293 :
294 0 : if (bufp)
295 0 : bp = *bpp;
296 :
297 0 : if (lvp == rvp) {
298 0 : if (bp)
299 0 : *(--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 0 : do {
308 0 : if (lvp->v_type != VDIR) {
309 : error = ENOTDIR;
310 0 : goto out;
311 : }
312 :
313 : /* Check for access if caller cares */
314 0 : if (flags & GETCWD_CHECK_ACCESS) {
315 0 : error = VOP_ACCESS(lvp, perms, p->p_ucred, p);
316 0 : if (error)
317 : goto out;
318 : perms = VEXEC|VREAD;
319 0 : }
320 :
321 : /* Step up if we're a covered vnode */
322 0 : while (lvp->v_flag & VROOT) {
323 : struct vnode *tvp;
324 :
325 0 : if (lvp == rvp)
326 0 : goto out;
327 :
328 : tvp = lvp;
329 0 : lvp = lvp->v_mount->mnt_vnodecovered;
330 :
331 0 : vput(tvp);
332 :
333 0 : if (lvp == NULL) {
334 : error = ENOENT;
335 0 : goto out;
336 : }
337 :
338 0 : vref(lvp);
339 :
340 0 : error = vn_lock(lvp, LK_EXCLUSIVE | LK_RETRY);
341 0 : if (error) {
342 0 : vrele(lvp);
343 0 : lvp = NULL;
344 0 : goto out;
345 : }
346 0 : }
347 :
348 : /* Look in the name cache */
349 0 : error = vfs_getcwd_getcache(&lvp, &uvp, &bp, bufp);
350 :
351 0 : if (error == -1) {
352 : /* If that fails, look in the directory */
353 0 : error = vfs_getcwd_scandir(&lvp, &uvp, &bp, bufp, p);
354 0 : }
355 :
356 0 : if (error)
357 : goto out;
358 :
359 : #ifdef DIAGNOSTIC
360 0 : if (lvp != NULL)
361 0 : panic("getcwd: oops, forgot to null lvp");
362 0 : if (bufp && (bp <= bufp)) {
363 0 : panic("getcwd: oops, went back too far");
364 : }
365 : #endif
366 :
367 0 : if (bp)
368 0 : *(--bp) = '/';
369 :
370 0 : lvp = uvp;
371 0 : uvp = NULL;
372 0 : limit--;
373 :
374 0 : } while ((lvp != rvp) && (limit > 0));
375 :
376 : out:
377 :
378 0 : if (bpp)
379 0 : *bpp = bp;
380 :
381 0 : if (uvp)
382 0 : vput(uvp);
383 :
384 0 : if (lvp)
385 0 : vput(lvp);
386 :
387 0 : vrele(rvp);
388 :
389 0 : return (error);
390 0 : }
391 :
392 : /* Find pathname of a process's current directory */
393 : int
394 0 : sys___getcwd(struct proc *p, void *v, register_t *retval)
395 : {
396 0 : struct sys___getcwd_args *uap = v;
397 0 : int error, lenused, len = SCARG(uap, len);
398 0 : char *path, *bp, *bend;
399 :
400 0 : if (len > MAXPATHLEN * 4)
401 0 : len = MAXPATHLEN * 4;
402 0 : else if (len < 2)
403 0 : return (ERANGE);
404 :
405 0 : path = malloc(len, M_TEMP, M_WAITOK);
406 :
407 0 : bp = &path[len];
408 : bend = bp;
409 0 : *(--bp) = '\0';
410 :
411 : /*
412 : * 5th argument here is "max number of vnodes to traverse".
413 : * Since each entry takes up at least 2 bytes in the output
414 : * buffer, limit it to N/2 vnodes for an N byte buffer.
415 : */
416 0 : error = vfs_getcwd_common(p->p_fd->fd_cdir, NULL, &bp, path, len/2,
417 : GETCWD_CHECK_ACCESS, p);
418 :
419 0 : if (error)
420 : goto out;
421 :
422 0 : lenused = bend - bp;
423 0 : *retval = lenused;
424 :
425 : /* Put the result into user buffer */
426 0 : error = copyout(bp, SCARG(uap, buf), lenused);
427 :
428 : out:
429 0 : free(path, M_TEMP, len);
430 :
431 0 : return (error);
432 0 : }
|