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