| /*- |
| * Copyright (c) 1991, 1993 |
| * The Regents of the University of California. All rights reserved. |
| * Copyright (c) 1997-2005 |
| * Herbert Xu <herbert@gondor.apana.org.au>. All rights reserved. |
| * |
| * This code is derived from software contributed to Berkeley by |
| * Kenneth Almquist. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * 3. Neither the name of the University nor the names of its contributors |
| * may be used to endorse or promote products derived from this software |
| * without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND |
| * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE |
| * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
| * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
| * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
| * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
| * SUCH DAMAGE. |
| */ |
| |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <limits.h> |
| #ifdef __CYGWIN__ |
| #include <sys/cygwin.h> |
| #endif |
| |
| /* |
| * The cd and pwd commands. |
| */ |
| |
| #include "shell.h" |
| #include "var.h" |
| #include "nodes.h" /* for jobs.h */ |
| #include "jobs.h" |
| #include "options.h" |
| #include "output.h" |
| #include "memalloc.h" |
| #include "error.h" |
| #include "exec.h" |
| #include "redir.h" |
| #include "main.h" |
| #include "mystring.h" |
| #include "show.h" |
| #include "cd.h" |
| |
| #define CD_PHYSICAL 1 |
| #define CD_PRINT 2 |
| |
| STATIC int docd(const char *, int); |
| STATIC const char *updatepwd(const char *); |
| STATIC char *getpwd(void); |
| STATIC int cdopt(void); |
| |
| STATIC char *curdir = nullstr; /* current working directory */ |
| STATIC char *physdir = nullstr; /* physical working directory */ |
| |
| STATIC int |
| cdopt() |
| { |
| int flags = 0; |
| int i, j; |
| |
| j = 'L'; |
| while ((i = nextopt("LP"))) { |
| if (i != j) { |
| flags ^= CD_PHYSICAL; |
| j = i; |
| } |
| } |
| |
| return flags; |
| } |
| |
| int |
| cdcmd(int argc, char **argv) |
| { |
| const char *dest; |
| const char *path; |
| const char *p; |
| char c; |
| struct stat statb; |
| int flags; |
| |
| flags = cdopt(); |
| dest = *argptr; |
| if (!dest) |
| dest = bltinlookup(homestr); |
| else if (dest[0] == '-' && dest[1] == '\0') { |
| dest = bltinlookup("OLDPWD"); |
| flags |= CD_PRINT; |
| } |
| if (!dest) |
| dest = nullstr; |
| if (*dest == '/') |
| goto step6; |
| if (*dest == '.') { |
| c = dest[1]; |
| dotdot: |
| switch (c) { |
| case '\0': |
| case '/': |
| goto step6; |
| case '.': |
| c = dest[2]; |
| if (c != '.') |
| goto dotdot; |
| } |
| } |
| if (!*dest) |
| dest = "."; |
| path = bltinlookup("CDPATH"); |
| while (path) { |
| c = *path; |
| p = padvance(&path, dest); |
| if (stat(p, &statb) >= 0 && S_ISDIR(statb.st_mode)) { |
| if (c && c != ':') |
| flags |= CD_PRINT; |
| docd: |
| if (!docd(p, flags)) |
| goto out; |
| goto err; |
| } |
| } |
| |
| step6: |
| p = dest; |
| goto docd; |
| |
| err: |
| sh_error("can't cd to %s", dest); |
| /* NOTREACHED */ |
| out: |
| if (flags & CD_PRINT) |
| out1fmt(snlfmt, curdir); |
| return 0; |
| } |
| |
| |
| /* |
| * Actually do the chdir. We also call hashcd to let the routines in exec.c |
| * know that the current directory has changed. |
| */ |
| |
| STATIC int |
| docd(const char *dest, int flags) |
| { |
| const char *dir = 0; |
| int err; |
| |
| TRACE(("docd(\"%s\", %d) called\n", dest, flags)); |
| |
| INTOFF; |
| if (!(flags & CD_PHYSICAL)) { |
| dir = updatepwd(dest); |
| if (dir) |
| dest = dir; |
| } |
| err = chdir(dest); |
| if (err) |
| goto out; |
| setpwd(dir, 1); |
| hashcd(); |
| out: |
| INTON; |
| return err; |
| } |
| |
| |
| /* |
| * Update curdir (the name of the current directory) in response to a |
| * cd command. |
| */ |
| |
| STATIC const char * |
| updatepwd(const char *dir) |
| { |
| char *new; |
| char *p; |
| char *cdcomppath; |
| const char *lim; |
| |
| #ifdef __CYGWIN__ |
| /* On cygwin, thanks to drive letters, some absolute paths do |
| not begin with slash; but cygwin includes a function that |
| forces normalization to the posix form */ |
| char pathbuf[PATH_MAX]; |
| if (cygwin_conv_path(CCP_WIN_A_TO_POSIX | CCP_RELATIVE, dir, pathbuf, |
| sizeof(pathbuf)) < 0) |
| sh_error("can't normalize %s", dir); |
| dir = pathbuf; |
| #endif |
| |
| cdcomppath = sstrdup(dir); |
| STARTSTACKSTR(new); |
| if (*dir != '/') { |
| if (curdir == nullstr) |
| return 0; |
| new = stputs(curdir, new); |
| } |
| new = makestrspace(strlen(dir) + 2, new); |
| lim = stackblock() + 1; |
| if (*dir != '/') { |
| if (new[-1] != '/') |
| USTPUTC('/', new); |
| if (new > lim && *lim == '/') |
| lim++; |
| } else { |
| USTPUTC('/', new); |
| cdcomppath++; |
| if (dir[1] == '/' && dir[2] != '/') { |
| USTPUTC('/', new); |
| cdcomppath++; |
| lim++; |
| } |
| } |
| p = strtok(cdcomppath, "/"); |
| while (p) { |
| switch(*p) { |
| case '.': |
| if (p[1] == '.' && p[2] == '\0') { |
| while (new > lim) { |
| STUNPUTC(new); |
| if (new[-1] == '/') |
| break; |
| } |
| break; |
| } else if (p[1] == '\0') |
| break; |
| /* fall through */ |
| default: |
| new = stputs(p, new); |
| USTPUTC('/', new); |
| } |
| p = strtok(0, "/"); |
| } |
| if (new > lim) |
| STUNPUTC(new); |
| *new = 0; |
| return stackblock(); |
| } |
| |
| |
| /* |
| * Find out what the current directory is. If we already know the current |
| * directory, this routine returns immediately. |
| */ |
| inline |
| STATIC char * |
| getpwd() |
| { |
| #ifdef __GLIBC__ |
| char *dir = getcwd(0, 0); |
| |
| if (dir) |
| return dir; |
| #else |
| char buf[PATH_MAX]; |
| |
| if (getcwd(buf, sizeof(buf))) |
| return savestr(buf); |
| #endif |
| |
| sh_warnx("getcwd() failed: %s", strerror(errno)); |
| return nullstr; |
| } |
| |
| int |
| pwdcmd(int argc, char **argv) |
| { |
| int flags; |
| const char *dir = curdir; |
| |
| flags = cdopt(); |
| if (flags) { |
| if (physdir == nullstr) |
| setpwd(dir, 0); |
| dir = physdir; |
| } |
| out1fmt(snlfmt, dir); |
| return 0; |
| } |
| |
| void |
| setpwd(const char *val, int setold) |
| { |
| char *oldcur, *dir; |
| |
| oldcur = dir = curdir; |
| |
| if (setold) { |
| setvar("OLDPWD", oldcur, VEXPORT); |
| } |
| INTOFF; |
| if (physdir != nullstr) { |
| if (physdir != oldcur) |
| free(physdir); |
| physdir = nullstr; |
| } |
| if (oldcur == val || !val) { |
| char *s = getpwd(); |
| physdir = s; |
| if (!val) |
| dir = s; |
| } else |
| dir = savestr(val); |
| if (oldcur != dir && oldcur != nullstr) { |
| free(oldcur); |
| } |
| curdir = dir; |
| INTON; |
| setvar("PWD", dir, VEXPORT); |
| } |