This file is cd.def, from which is created cd.c. It implements the builtins "cd" and "pwd" in Bash. Copyright (C) 1987, 1989, 1991 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 1, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. $PRODUCES cd.c #include #if defined (HAVE_UNISTD_H) # include #endif #include "../bashtypes.h" #include "../posixdir.h" #include "../posixstat.h" #include #include #include "../bashansi.h" #include #include #include "../shell.h" #include "../flags.h" #include "../maxpath.h" #include "common.h" #include "bashgetopt.h" #if !defined (errno) extern int errno; #endif /* !errno */ extern int posixly_correct, interactive; extern char *bash_getcwd_errstr; static int change_to_directory (); static char *cdspell (); static int spname (), mindist (), spdist (); int cdspelling = 1; int cdable_vars; $BUILTIN cd $FUNCTION cd_builtin $SHORT_DOC cd [-PL] [dir] Change the current directory to DIR. The variable $HOME is the default DIR. The variable $CDPATH defines the search path for the directory containing DIR. Alternative directory names in CDPATH are separated by a colon (:). A null directory name is the same as the current directory, i.e. `.'. If DIR begins with a slash (/), then $CDPATH is not used. If the directory is not found, and the shell option `cdable_vars' is set, then try the word as a variable name. If that variable has a value, then cd to the value of that variable. The -P option says to use the physical directory structure instead of following symbolic links; the -L option forces symbolic links to be followed. $END /* Take PATH, an element from $CDPATH, and DIR, a directory name, and paste them together into PATH/DIR. Tilde expansion is performed on PATH if DOTILDE is non-zero. If PATH is the empty string, it is converted to `./', since a null element in $CDPATH means the current directory. */ static char * mkpath (path, dir, dotilde) char *path, *dir; int dotilde; { int dirlen, pathlen; char *ret, *xpath; if (*path == '\0') { xpath = xmalloc (2); xpath[0] = '.'; xpath[1] = '\0'; pathlen = 1; } else { xpath = (dotilde && *path == '~') ? bash_tilde_expand (path) : path; pathlen = strlen (xpath); } dirlen = strlen (dir); ret = xmalloc (2 + dirlen + pathlen); strcpy (ret, xpath); if (xpath[pathlen - 1] != '/') { ret[pathlen++] = '/'; ret[pathlen] = '\0'; } strcpy (ret + pathlen, dir); if (xpath != path) free (xpath); return (ret); } static int bindpwd (no_symlinks) int no_symlinks; { char *dirname; int old_symlinks; if (no_symlinks) { old_symlinks = no_symbolic_links; no_symbolic_links = 1; dirname = get_working_directory ("cd"); no_symbolic_links = old_symlinks; } else dirname = get_working_directory ("cd"); bind_variable ("OLDPWD", get_string_value ("PWD")); bind_variable ("PWD", dirname); FREE (dirname); return (EXECUTION_SUCCESS); } /* This builtin is ultimately the way that all user-visible commands should change the current working directory. It is called by cd_to_string (), so the programming interface is simple, and it handles errors and restrictions properly. */ int cd_builtin (list) WORD_LIST *list; { char *dirname, *cdpath, *path, *temp; int path_index, no_symlinks, opt; struct stat sb; #if defined (RESTRICTED_SHELL) if (restricted) { builtin_error ("restricted"); return (EXECUTION_FAILURE); } #endif /* RESTRICTED_SHELL */ no_symlinks = no_symbolic_links; reset_internal_getopt (); while ((opt = internal_getopt (list, "LP")) != -1) { switch (opt) { case 'P': no_symlinks = 1; break; case 'L': no_symlinks = 0; break; default: builtin_usage (); return (EXECUTION_FAILURE); } } list = loptend; if (list == 0) { /* `cd' without arguments is equivalent to `cd $HOME' */ dirname = get_string_value ("HOME"); if (dirname == 0) { builtin_error ("HOME not set"); return (EXECUTION_FAILURE); } if (change_to_directory (dirname, no_symlinks) == 0) { builtin_error ("%s: %s", dirname, strerror (errno)); return (EXECUTION_FAILURE); } } else if (list->word->word[0] == '-' && list->word->word[1] == '\0') { /* This is `cd -', equivalent to `cd $OLDPWD' */ dirname = get_string_value ("OLDPWD"); if (dirname == 0 || change_to_directory (dirname, no_symlinks) == 0) { if (dirname == 0) builtin_error ("OLDPWD not set"); else builtin_error ("%s: %s", dirname, strerror (errno)); return (EXECUTION_FAILURE); } } else { dirname = list->word->word; if (absolute_pathname (dirname) == 0 && (cdpath = get_string_value ("CDPATH"))) { /* Find directory in $CDPATH. */ path_index = 0; while ((path = extract_colon_unit (cdpath, &path_index))) { temp = mkpath (path, dirname, 1); free (path); if (stat (temp, &sb) < 0 || S_ISDIR (sb.st_mode) == 0) { free (temp); continue; } if (change_to_directory (temp, no_symlinks)) { if (temp[0] != '.' || temp[1] != '/') printf ("%s\n", temp); free (temp); /* Posix.2 says that after using CDPATH, the resultant value of $PWD will not contain symlinks. */ return (bindpwd (posixly_correct)); } else free (temp); } } if (change_to_directory (dirname, no_symlinks)) return (bindpwd (no_symlinks)); /* If the user requests it, then perhaps this is the name of a shell variable, whose value contains the directory to change to. If that is the case, then change to that directory. */ if (cdable_vars) { temp = get_string_value (dirname); if (temp && change_to_directory (temp, no_symlinks)) { printf ("%s\n", temp); return (bindpwd (no_symlinks)); } } /* If the user requests it, try to find a directory name similar in spelling to the one requested, in case the user made a simple typo. This is similar to the UNIX 8th and 9th Edition shells. */ if (interactive && cdspelling) { temp = cdspell (dirname); if (temp && change_to_directory (temp, no_symlinks)) { printf ("%s\n", temp); free (temp); return (bindpwd (no_symlinks)); } else FREE (temp); } builtin_error ("%s: %s", dirname, strerror (errno)); return (EXECUTION_FAILURE); } return (bindpwd (no_symlinks)); } $BUILTIN pwd $FUNCTION pwd_builtin $SHORT_DOC pwd [-PL] Print the current working directory. With the -P option, pwd prints the physical directory, without any symbolic links; the -L option makes pwd follow symbolic links. $END /* Non-zero means that pwd always prints the physical directory, without symbolic links. */ static int verbatim_pwd; /* Print the name of the current working directory. */ int pwd_builtin (list) WORD_LIST *list; { char *directory, *buffer; int opt; verbatim_pwd = no_symbolic_links; reset_internal_getopt (); while ((opt = internal_getopt (list, "LP")) != -1) { switch (opt) { case 'P': verbatim_pwd = 1; break; case 'L': verbatim_pwd = 0; break; default: builtin_usage (); return (EXECUTION_FAILURE); } } list = loptend; if (verbatim_pwd) { buffer = xmalloc (PATH_MAX); directory = getcwd (buffer, PATH_MAX); if (directory == 0) { builtin_error ("%s: %s", bash_getcwd_errstr, strerror (errno)); free (buffer); } } else directory = get_working_directory ("pwd"); if (directory) { printf ("%s\n", directory); fflush (stdout); free (directory); return (EXECUTION_SUCCESS); } else return (EXECUTION_FAILURE); } /* Do the work of changing to the directory NEWDIR. Handle symbolic link following, etc. */ static int change_to_directory (newdir, nolinks) char *newdir; int nolinks; { char *t; if (nolinks == 0) { int chdir_return = 0; char *tdir = (char *)NULL; if (the_current_working_directory == 0) { t = get_working_directory ("cd_links"); FREE (t); } if (the_current_working_directory) t = make_absolute (newdir, the_current_working_directory); else t = savestring (newdir); /* TDIR is the canonicalized absolute pathname of the NEWDIR. */ tdir = canonicalize_pathname (t); /* Use the canonicalized version of NEWDIR, or, if canonicalization failed, use the non-canonical form. */ if (tdir && *tdir) free (t); else { FREE (tdir); tdir = t; } if (chdir (tdir) < 0) { int err; chdir_return = 0; free (tdir); err = errno; /* We failed changing to the canonicalized directory name. Try what the user passed verbatim. If we succeed, reinitialize the_current_working_directory. */ if (chdir (newdir) == 0) { chdir_return = 1; if (the_current_working_directory) { free (the_current_working_directory); the_current_working_directory = (char *)NULL; } tdir = get_working_directory ("cd"); FREE (tdir); } else errno = err; } else { chdir_return = 1; FREE (the_current_working_directory); the_current_working_directory = tdir; } return (chdir_return); } else return (chdir (newdir) == 0); } /* Code for cd spelling correction. Original patch submitted by Neil Russel (caret@c-side.com). */ static char * cdspell (dirname) char *dirname; { int n; char *guess; n = (strlen (dirname) * 3 + 1) / 2 + 1; guess = xmalloc (n); switch (spname (dirname, guess)) { case -1: default: free (guess); return (char *)NULL; case 0: case 1: return guess; } } /* * `spname' and its helpers are inspired by the code in "The UNIX * Programming Environment, Kernighan & Pike, Prentice-Hall 1984", * pages 209 - 213. */ /* * `spname' -- return a correctly spelled filename * * int spname(char * oldname, char * newname) * Returns: -1 if no reasonable match found * 0 if exact match found * 1 if corrected * Stores corrected name in `newname'. */ static int spname(oldname, newname) char *oldname; char *newname; { char *op, *np, *p; char guess[PATH_MAX + 1], best[PATH_MAX + 1]; op = oldname; np = newname; for (;;) { while (*op == '/') /* Skip slashes */ *np++ = *op++; *np = '\0'; if (*op == '\0') /* Exact or corrected */ { /* `.' is rarely the right thing. */ if (oldname[1] == '\0' && newname[1] == '\0' && oldname[0] != '.' && newname[0] == '.') return -1; return strcmp(oldname, newname) != 0; } /* Copy next component into guess */ for (p = guess; *op != '/' && *op != '\0'; op++) if (p < guess + PATH_MAX) *p++ = *op; *p = '\0'; if (mindist(newname, guess, best) >= 3) return -1; /* Hopeless */ /* * Add to end of newname */ for (p = best; *np = *p++; np++) ; } } /* * Search directory for a guess */ static int mindist(dir, guess, best) char *dir; char *guess; char *best; { DIR *fd; struct dirent *dp; int dist, x; dist = 3; /* Worst distance */ if (*dir == '\0') dir = "."; if ((fd = opendir(dir)) == NULL) return dist; while ((dp = readdir(fd)) != NULL) { /* * Look for a better guess. If the new guess is as * good as the current one, we take it. This way, * any single character match will be a better match * than ".". */ x = spdist(dp->d_name, guess); if (x <= dist && x != 3) { strcpy(best, dp->d_name); dist = x; if (dist == 0) /* Exact match */ break; } } (void)closedir(fd); return dist; } /* * `spdist' -- return the "distance" between two names. * * int spname(char * oldname, char * newname) * Returns: 0 if strings are identical * 1 if two characters are transposed * 2 if one character is wrong, added or deleted * 3 otherwise */ static int spdist(cur, new) char *cur, *new; { while (*cur == *new) { if (*cur == '\0') return 0; /* Exact match */ cur++; new++; } if (*cur) { if (*new) { if (cur[1] && new[1] && cur[0] == new[1] && cur[1] == new[0] && strcmp (cur + 2, new + 2) == 0) return 1; /* Transposition */ if (strcmp (cur + 1, new + 1) == 0) return 2; /* One character mismatch */ } if (strcmp(&cur[1], &new[0]) == 0) return 2; /* Extra character */ } if (*new && strcmp(cur, new + 1) == 0) return 2; /* Missing character */ return 3; }