master xplshn/aruu / cmd / posix / sh / cd.c
  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}