Merge branch 'vendor/GREP'
[dragonfly.git] / bin / sh / cd.c
1 /*-
2  * Copyright (c) 1991, 1993
3  *      The Regents of the University of California.  All rights reserved.
4  *
5  * This code is derived from software contributed to Berkeley by
6  * Kenneth Almquist.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  * 3. Neither the name of the University nor the names of its contributors
17  *    may be used to endorse or promote products derived from this software
18  *    without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
21  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
24  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGE.
31  *
32  * @(#)cd.c     8.2 (Berkeley) 5/4/95
33  * $FreeBSD: head/bin/sh/cd.c 240541 2012-09-15 21:56:30Z jilles $
34  */
35
36 #include <sys/types.h>
37 #include <sys/stat.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <unistd.h>
41 #include <errno.h>
42 #include <limits.h>
43
44 /*
45  * The cd and pwd commands.
46  */
47
48 #include "shell.h"
49 #include "var.h"
50 #include "nodes.h"      /* for jobs.h */
51 #include "jobs.h"
52 #include "options.h"
53 #include "output.h"
54 #include "memalloc.h"
55 #include "error.h"
56 #include "exec.h"
57 #include "redir.h"
58 #include "mystring.h"
59 #include "show.h"
60 #include "cd.h"
61 #include "builtins.h"
62
63 static int cdlogical(char *);
64 static int cdphysical(char *);
65 static int docd(char *, int, int);
66 static char *getcomponent(void);
67 static char *findcwd(char *);
68 static void updatepwd(char *);
69 static char *getpwd(void);
70 static char *getpwd2(void);
71
72 static char *curdir = NULL;     /* current working directory */
73 static char *prevdir;           /* previous working directory */
74 static char *cdcomppath;
75
76 int
77 cdcmd(int argc __unused, char **argv __unused)
78 {
79         const char *dest;
80         const char *path;
81         char *p;
82         struct stat statb;
83         int ch, phys, print = 0, getcwderr = 0;
84         int rc;
85         int errno1 = ENOENT;
86
87         phys = Pflag;
88         while ((ch = nextopt("eLP")) != '\0') {
89                 switch (ch) {
90                 case 'e':
91                         getcwderr = 1;
92                         break;
93                 case 'L':
94                         phys = 0;
95                         break;
96                 case 'P':
97                         phys = 1;
98                         break;
99                 }
100         }
101
102         if (*argptr != NULL && argptr[1] != NULL)
103                 error("too many arguments");
104
105         if ((dest = *argptr) == NULL && (dest = bltinlookup("HOME", 1)) == NULL)
106                 error("HOME not set");
107         if (*dest == '\0')
108                 dest = ".";
109         if (dest[0] == '-' && dest[1] == '\0') {
110                 dest = prevdir ? prevdir : curdir;
111                 if (dest)
112                         print = 1;
113                 else
114                         dest = ".";
115         }
116         if (dest[0] == '/' ||
117             (dest[0] == '.' && (dest[1] == '/' || dest[1] == '\0')) ||
118             (dest[0] == '.' && dest[1] == '.' && (dest[2] == '/' || dest[2] == '\0')) ||
119             (path = bltinlookup("CDPATH", 1)) == NULL)
120                 path = nullstr;
121         while ((p = padvance(&path, dest)) != NULL) {
122                 if (stat(p, &statb) < 0) {
123                         if (errno != ENOENT)
124                                 errno1 = errno;
125                 } else if (!S_ISDIR(statb.st_mode))
126                         errno1 = ENOTDIR;
127                 else {
128                         if (!print) {
129                                 /*
130                                  * XXX - rethink
131                                  */
132                                 if (p[0] == '.' && p[1] == '/' && p[2] != '\0')
133                                         print = strcmp(p + 2, dest);
134                                 else
135                                         print = strcmp(p, dest);
136                         }
137                         rc = docd(p, print, phys);
138                         if (rc >= 0)
139                                 return getcwderr ? rc : 0;
140                         if (errno != ENOENT)
141                                 errno1 = errno;
142                 }
143         }
144         error("%s: %s", dest, strerror(errno1));
145         /*NOTREACHED*/
146         return 0;
147 }
148
149
150 /*
151  * Actually change the directory.  In an interactive shell, print the
152  * directory name if "print" is nonzero.
153  */
154 static int
155 docd(char *dest, int print, int phys)
156 {
157         int rc;
158
159         TRACE(("docd(\"%s\", %d, %d) called\n", dest, print, phys));
160
161         /* If logical cd fails, fall back to physical. */
162         if ((phys || (rc = cdlogical(dest)) < 0) && (rc = cdphysical(dest)) < 0)
163                 return (-1);
164
165         if (print && iflag && curdir)
166                 out1fmt("%s\n", curdir);
167
168         return (rc);
169 }
170
171 static int
172 cdlogical(char *dest)
173 {
174         char *p;
175         char *q;
176         char *component;
177         struct stat statb;
178         int first;
179         int badstat;
180
181         /*
182          *  Check each component of the path. If we find a symlink or
183          *  something we can't stat, clear curdir to force a getcwd()
184          *  next time we get the value of the current directory.
185          */
186         badstat = 0;
187         cdcomppath = stalloc(strlen(dest) + 1);
188         scopy(dest, cdcomppath);
189         STARTSTACKSTR(p);
190         if (*dest == '/') {
191                 STPUTC('/', p);
192                 cdcomppath++;
193         }
194         first = 1;
195         while ((q = getcomponent()) != NULL) {
196                 if (q[0] == '\0' || (q[0] == '.' && q[1] == '\0'))
197                         continue;
198                 if (! first)
199                         STPUTC('/', p);
200                 first = 0;
201                 component = q;
202                 STPUTS(q, p);
203                 if (equal(component, ".."))
204                         continue;
205                 STACKSTRNUL(p);
206                 if (lstat(stackblock(), &statb) < 0) {
207                         badstat = 1;
208                         break;
209                 }
210         }
211
212         INTOFF;
213         if ((p = findcwd(badstat ? NULL : dest)) == NULL || chdir(p) < 0) {
214                 INTON;
215                 return (-1);
216         }
217         updatepwd(p);
218         INTON;
219         return (0);
220 }
221
222 static int
223 cdphysical(char *dest)
224 {
225         char *p;
226         int rc = 0;
227
228         INTOFF;
229         if (chdir(dest) < 0) {
230                 INTON;
231                 return (-1);
232         }
233         p = findcwd(NULL);
234         if (p == NULL) {
235                 warning("warning: failed to get name of current directory");
236                 rc = 1;
237         }
238         updatepwd(p);
239         INTON;
240         return (rc);
241 }
242
243 /*
244  * Get the next component of the path name pointed to by cdcomppath.
245  * This routine overwrites the string pointed to by cdcomppath.
246  */
247 static char *
248 getcomponent(void)
249 {
250         char *p;
251         char *start;
252
253         if ((p = cdcomppath) == NULL)
254                 return NULL;
255         start = cdcomppath;
256         while (*p != '/' && *p != '\0')
257                 p++;
258         if (*p == '\0') {
259                 cdcomppath = NULL;
260         } else {
261                 *p++ = '\0';
262                 cdcomppath = p;
263         }
264         return start;
265 }
266
267
268 static char *
269 findcwd(char *dir)
270 {
271         char *new;
272         char *p;
273
274         /*
275          * If our argument is NULL, we don't know the current directory
276          * any more because we traversed a symbolic link or something
277          * we couldn't stat().
278          */
279         if (dir == NULL || curdir == NULL)
280                 return getpwd2();
281         cdcomppath = stalloc(strlen(dir) + 1);
282         scopy(dir, cdcomppath);
283         STARTSTACKSTR(new);
284         if (*dir != '/') {
285                 STPUTS(curdir, new);
286                 if (STTOPC(new) == '/')
287                         STUNPUTC(new);
288         }
289         while ((p = getcomponent()) != NULL) {
290                 if (equal(p, "..")) {
291                         while (new > stackblock() && (STUNPUTC(new), *new) != '/');
292                 } else if (*p != '\0' && ! equal(p, ".")) {
293                         STPUTC('/', new);
294                         STPUTS(p, new);
295                 }
296         }
297         if (new == stackblock())
298                 STPUTC('/', new);
299         STACKSTRNUL(new);
300         return stackblock();
301 }
302
303 /*
304  * Update curdir (the name of the current directory) in response to a
305  * cd command.  We also call hashcd to let the routines in exec.c know
306  * that the current directory has changed.
307  */
308 static void
309 updatepwd(char *dir)
310 {
311         hashcd();                               /* update command hash table */
312
313         if (prevdir)
314                 ckfree(prevdir);
315         prevdir = curdir;
316         curdir = dir ? savestr(dir) : NULL;
317         setvar("PWD", curdir, VEXPORT);
318         setvar("OLDPWD", prevdir, VEXPORT);
319 }
320
321 int
322 pwdcmd(int argc __unused, char **argv __unused)
323 {
324         char *p;
325         int ch, phys;
326
327         phys = Pflag;
328         while ((ch = nextopt("LP")) != '\0') {
329                 switch (ch) {
330                 case 'L':
331                         phys = 0;
332                         break;
333                 case 'P':
334                         phys = 1;
335                         break;
336                 }
337         }
338
339         if (*argptr != NULL)
340                 error("too many arguments");
341
342         if (!phys && getpwd()) {
343                 out1str(curdir);
344                 out1c('\n');
345         } else {
346                 if ((p = getpwd2()) == NULL)
347                         error(".: %s", strerror(errno));
348                 out1str(p);
349                 out1c('\n');
350         }
351
352         return 0;
353 }
354
355 /*
356  * Get the current directory and cache the result in curdir.
357  */
358 static char *
359 getpwd(void)
360 {
361         char *p;
362
363         if (curdir)
364                 return curdir;
365
366         p = getpwd2();
367         if (p != NULL)
368                 curdir = savestr(p);
369
370         return curdir;
371 }
372
373 #define MAXPWD 256
374
375 /*
376  * Return the current directory.
377  */
378 static char *
379 getpwd2(void)
380 {
381         char *pwd;
382         int i;
383
384         for (i = MAXPWD;; i *= 2) {
385                 pwd = stalloc(i);
386                 if (getcwd(pwd, i) != NULL)
387                         return pwd;
388                 stunalloc(pwd);
389                 if (errno != ERANGE)
390                         break;
391         }
392
393         return NULL;
394 }
395
396 /*
397  * Initialize PWD in a new shell.
398  * If the shell is interactive, we need to warn if this fails.
399  */
400 void
401 pwd_init(int warn)
402 {
403         char *pwd;
404         struct stat stdot, stpwd;
405
406         pwd = lookupvar("PWD");
407         if (pwd && *pwd == '/' && stat(".", &stdot) != -1 &&
408             stat(pwd, &stpwd) != -1 &&
409             stdot.st_dev == stpwd.st_dev &&
410             stdot.st_ino == stpwd.st_ino) {
411                 if (curdir)
412                         ckfree(curdir);
413                 curdir = savestr(pwd);
414         }
415         if (getpwd() == NULL && warn)
416                 out2fmt_flush("sh: cannot determine working directory\n");
417         setvar("PWD", curdir, VEXPORT);
418 }