Imported from ../bash-2.0.tar.gz.
[platform/upstream/bash.git] / builtins / cd.def
1 This file is cd.def, from which is created cd.c.  It implements the
2 builtins "cd" and "pwd" in Bash.
3
4 Copyright (C) 1987, 1989, 1991 Free Software Foundation, Inc.
5
6 This file is part of GNU Bash, the Bourne Again SHell.
7
8 Bash is free software; you can redistribute it and/or modify it under
9 the terms of the GNU General Public License as published by the Free
10 Software Foundation; either version 1, or (at your option) any later
11 version.
12
13 Bash is distributed in the hope that it will be useful, but WITHOUT ANY
14 WARRANTY; without even the implied warranty of MERCHANTABILITY or
15 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
16 for more details.
17
18 You should have received a copy of the GNU General Public License along
19 with Bash; see the file COPYING.  If not, write to the Free Software
20 Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
21
22 $PRODUCES cd.c
23 #include <config.h>
24
25 #if defined (HAVE_UNISTD_H)
26 #  include <unistd.h>
27 #endif
28
29 #include "../bashtypes.h"
30 #include "../posixdir.h"
31 #include "../posixstat.h"
32 #include <sys/param.h>
33
34 #include <stdio.h>
35
36 #include "../bashansi.h"
37
38 #include <errno.h>
39 #include <tilde/tilde.h>
40
41 #include "../shell.h"
42 #include "../flags.h"
43 #include "../maxpath.h"
44 #include "common.h"
45 #include "bashgetopt.h"
46
47 #if !defined (errno)
48 extern int errno;
49 #endif /* !errno */
50
51 extern int posixly_correct, interactive;
52 extern char *bash_getcwd_errstr;
53
54 static int change_to_directory ();
55
56 static char *cdspell ();
57 static int spname (), mindist (), spdist ();
58 int cdspelling = 1;
59
60 int cdable_vars;
61
62 $BUILTIN cd
63 $FUNCTION cd_builtin
64 $SHORT_DOC cd [-PL] [dir]
65 Change the current directory to DIR.  The variable $HOME is the
66 default DIR.  The variable $CDPATH defines the search path for
67 the directory containing DIR.  Alternative directory names in CDPATH
68 are separated by a colon (:).  A null directory name is the same as
69 the current directory, i.e. `.'.  If DIR begins with a slash (/),
70 then $CDPATH is not used.  If the directory is not found, and the
71 shell option `cdable_vars' is set, then try the word as a variable
72 name.  If that variable has a value, then cd to the value of that
73 variable.  The -P option says to use the physical directory structure
74 instead of following symbolic links; the -L option forces symbolic links
75 to be followed.
76 $END
77
78 /* Take PATH, an element from $CDPATH, and DIR, a directory name, and paste
79    them together into PATH/DIR.  Tilde expansion is performed on PATH if
80    DOTILDE is non-zero.  If PATH is the empty string, it is converted to
81    `./', since a null element in $CDPATH means the current directory. */
82 static char *
83 mkpath (path, dir, dotilde)
84      char *path, *dir;
85      int dotilde;
86 {
87   int dirlen, pathlen;
88   char *ret, *xpath;
89
90   if (*path == '\0')
91     {
92       xpath = xmalloc (2);
93       xpath[0] = '.';
94       xpath[1] = '\0';
95       pathlen = 1;
96     }
97   else
98     {
99       xpath = (dotilde && *path == '~') ? bash_tilde_expand (path) : path;
100       pathlen = strlen (xpath);
101     }
102
103   dirlen = strlen (dir);
104   ret = xmalloc (2 + dirlen + pathlen);
105   strcpy (ret, xpath);
106   if (xpath[pathlen - 1] != '/')
107     {
108       ret[pathlen++] = '/';
109       ret[pathlen] = '\0';
110     }
111   strcpy (ret + pathlen, dir);
112   if (xpath != path)
113     free (xpath);
114   return (ret);
115 }
116
117 static int
118 bindpwd (no_symlinks)
119      int no_symlinks;
120 {
121   char *dirname;
122   int old_symlinks;
123
124   if (no_symlinks)
125     {
126       old_symlinks = no_symbolic_links;
127       no_symbolic_links = 1;
128       dirname = get_working_directory ("cd");
129       no_symbolic_links = old_symlinks;
130     }
131   else
132     dirname = get_working_directory ("cd");
133
134   bind_variable ("OLDPWD", get_string_value ("PWD"));
135   bind_variable ("PWD", dirname);
136
137   FREE (dirname);
138   return (EXECUTION_SUCCESS);
139 }
140
141 /* This builtin is ultimately the way that all user-visible commands should
142    change the current working directory.  It is called by cd_to_string (),
143    so the programming interface is simple, and it handles errors and
144    restrictions properly. */
145 int
146 cd_builtin (list)
147      WORD_LIST *list;
148 {
149   char *dirname, *cdpath, *path, *temp;
150   int path_index, no_symlinks, opt;
151   struct stat sb;
152
153 #if defined (RESTRICTED_SHELL)
154   if (restricted)
155     {
156       builtin_error ("restricted");
157       return (EXECUTION_FAILURE);
158     }
159 #endif /* RESTRICTED_SHELL */
160
161   no_symlinks = no_symbolic_links;
162   reset_internal_getopt ();
163   while ((opt = internal_getopt (list, "LP")) != -1)
164     {
165       switch (opt)
166         {
167         case 'P':
168           no_symlinks = 1;
169           break;
170         case 'L':
171           no_symlinks = 0;
172           break;
173         default:
174           builtin_usage ();
175           return (EXECUTION_FAILURE);
176         }
177     }
178   list = loptend;
179
180   if (list == 0)
181     {
182       /* `cd' without arguments is equivalent to `cd $HOME' */
183       dirname = get_string_value ("HOME");
184
185       if (dirname == 0)
186         {
187           builtin_error ("HOME not set");
188           return (EXECUTION_FAILURE);
189         }
190
191       if (change_to_directory (dirname, no_symlinks) == 0)
192         {
193           builtin_error ("%s: %s", dirname, strerror (errno));
194           return (EXECUTION_FAILURE);
195         }
196     }
197   else if (list->word->word[0] == '-' && list->word->word[1] == '\0')
198     {
199       /* This is `cd -', equivalent to `cd $OLDPWD' */
200       dirname = get_string_value ("OLDPWD");
201
202       if (dirname == 0 || change_to_directory (dirname, no_symlinks) == 0)
203         {
204           if (dirname == 0)
205             builtin_error ("OLDPWD not set");
206           else
207             builtin_error ("%s: %s", dirname, strerror (errno));
208           return (EXECUTION_FAILURE);
209         }
210     }
211   else
212     {
213       dirname = list->word->word;
214
215       if (absolute_pathname (dirname) == 0 && (cdpath = get_string_value ("CDPATH")))
216         {
217           /* Find directory in $CDPATH. */
218           path_index = 0;
219           while ((path = extract_colon_unit (cdpath, &path_index)))
220             {
221               temp = mkpath (path, dirname, 1);
222               free (path);
223
224               if (stat (temp, &sb) < 0 || S_ISDIR (sb.st_mode) == 0)
225                 {
226                   free (temp);
227                   continue;
228                 }
229
230               if (change_to_directory (temp, no_symlinks))
231                 {
232                   if (temp[0] != '.' || temp[1] != '/')
233                     printf ("%s\n", temp);
234
235                   free (temp);
236                   /* Posix.2 says that after using CDPATH, the resultant
237                      value of $PWD will not contain symlinks. */
238                   return (bindpwd (posixly_correct));
239                 }
240               else
241                 free (temp);
242             }
243         }
244
245       if (change_to_directory (dirname, no_symlinks))
246         return (bindpwd (no_symlinks));
247
248       /* If the user requests it, then perhaps this is the name of
249          a shell variable, whose value contains the directory to
250          change to.  If that is the case, then change to that
251          directory. */
252       if (cdable_vars)
253         {
254           temp = get_string_value (dirname);
255           if (temp && change_to_directory (temp, no_symlinks))
256             {
257               printf ("%s\n", temp);
258               return (bindpwd (no_symlinks));
259             }
260         }
261
262       /* If the user requests it, try to find a directory name similar in
263          spelling to the one requested, in case the user made a simple
264          typo.  This is similar to the UNIX 8th and 9th Edition shells. */
265       if (interactive && cdspelling)
266         {
267           temp = cdspell (dirname);
268           if (temp && change_to_directory (temp, no_symlinks))
269             {
270               printf ("%s\n", temp);
271               free (temp);
272               return (bindpwd (no_symlinks));
273             }
274           else
275             FREE (temp);
276         }
277
278       builtin_error ("%s: %s", dirname, strerror (errno));
279       return (EXECUTION_FAILURE);
280     }
281
282   return (bindpwd (no_symlinks));
283 }
284
285 $BUILTIN pwd
286 $FUNCTION pwd_builtin
287 $SHORT_DOC pwd [-PL]
288 Print the current working directory.  With the -P option, pwd prints
289 the physical directory, without any symbolic links; the -L option
290 makes pwd follow symbolic links.
291 $END
292
293 /* Non-zero means that pwd always prints the physical directory, without
294    symbolic links. */
295 static int verbatim_pwd;
296
297 /* Print the name of the current working directory. */
298 int
299 pwd_builtin (list)
300      WORD_LIST *list;
301 {
302   char *directory, *buffer;
303   int opt;
304
305   verbatim_pwd = no_symbolic_links;
306   reset_internal_getopt ();
307   while ((opt = internal_getopt (list, "LP")) != -1)
308     {
309       switch (opt)
310         {
311         case 'P':
312           verbatim_pwd = 1;
313           break;
314         case 'L':
315           verbatim_pwd = 0;
316           break;
317         default:
318           builtin_usage ();
319           return (EXECUTION_FAILURE);
320         }
321     }
322   list = loptend;
323
324   if (verbatim_pwd)
325     {
326       buffer = xmalloc (PATH_MAX);
327       directory = getcwd (buffer, PATH_MAX);
328
329       if (directory == 0)
330         {
331           builtin_error ("%s: %s", bash_getcwd_errstr, strerror (errno));
332           free (buffer);
333         }
334     }
335   else
336     directory = get_working_directory ("pwd");
337
338   if (directory)
339     {
340       printf ("%s\n", directory);
341       fflush (stdout);
342       free (directory);
343       return (EXECUTION_SUCCESS);
344     }
345   else
346     return (EXECUTION_FAILURE);
347 }
348
349 /* Do the work of changing to the directory NEWDIR.  Handle symbolic
350    link following, etc. */
351
352 static int
353 change_to_directory (newdir, nolinks)
354      char *newdir;
355      int nolinks;
356 {
357   char *t;
358
359   if (nolinks == 0)
360     {
361       int chdir_return = 0;
362       char *tdir = (char *)NULL;
363
364       if (the_current_working_directory == 0)
365         {
366           t = get_working_directory ("cd_links");
367           FREE (t);
368         }
369
370       if (the_current_working_directory)
371         t = make_absolute (newdir, the_current_working_directory);
372       else
373         t = savestring (newdir);
374
375       /* TDIR is the canonicalized absolute pathname of the NEWDIR. */
376       tdir = canonicalize_pathname (t);
377
378       /* Use the canonicalized version of NEWDIR, or, if canonicalization
379          failed, use the non-canonical form. */
380       if (tdir && *tdir)
381         free (t);
382       else
383         {
384           FREE (tdir);
385           tdir = t;
386         }
387
388       if (chdir (tdir) < 0)
389         {
390           int err;
391
392           chdir_return = 0;
393           free (tdir);
394
395           err = errno;
396
397           /* We failed changing to the canonicalized directory name.  Try
398              what the user passed verbatim.  If we succeed, reinitialize
399              the_current_working_directory. */
400           if (chdir (newdir) == 0)
401             {
402               chdir_return = 1;
403               if (the_current_working_directory)
404                 {
405                   free (the_current_working_directory);
406                   the_current_working_directory = (char *)NULL;
407                 }
408
409               tdir = get_working_directory ("cd");
410               FREE (tdir);
411             }
412           else
413             errno = err;
414         }
415       else
416         {
417           chdir_return = 1;
418
419           FREE (the_current_working_directory);
420           the_current_working_directory = tdir;
421         }
422
423       return (chdir_return);
424     }
425   else
426     return (chdir (newdir) == 0);
427 }
428
429 /* Code for cd spelling correction.  Original patch submitted by
430    Neil Russel (caret@c-side.com). */
431
432 static char *
433 cdspell (dirname)
434      char *dirname;
435 {
436   int n;
437   char *guess;
438
439   n = (strlen (dirname) * 3 + 1) / 2 + 1;
440   guess = xmalloc (n);
441
442   switch (spname (dirname, guess))
443     {
444     case -1:
445     default:
446       free (guess);
447       return (char *)NULL;
448     case 0:
449     case 1:
450       return guess;
451     }
452 }
453
454 /*
455  * `spname' and its helpers are inspired by the code in "The UNIX
456  * Programming Environment, Kernighan & Pike, Prentice-Hall 1984",
457  * pages 209 - 213.
458  */
459
460 /*
461  *      `spname' -- return a correctly spelled filename
462  *
463  *      int spname(char * oldname, char * newname)
464  *      Returns:  -1 if no reasonable match found
465  *                 0 if exact match found
466  *                 1 if corrected
467  *      Stores corrected name in `newname'.
468  */
469 static int
470 spname(oldname, newname)
471      char *oldname;
472      char *newname;
473 {
474   char *op, *np, *p;
475   char guess[PATH_MAX + 1], best[PATH_MAX + 1];
476
477   op = oldname;
478   np = newname;
479   for (;;)
480     {
481       while (*op == '/')    /* Skip slashes */
482         *np++ = *op++;
483       *np = '\0';
484
485       if (*op == '\0')    /* Exact or corrected */
486         {
487           /* `.' is rarely the right thing. */
488           if (oldname[1] == '\0' && newname[1] == '\0' &&
489                 oldname[0] != '.' && newname[0] == '.')
490             return -1;
491           return strcmp(oldname, newname) != 0;
492         }
493
494       /* Copy next component into guess */
495       for (p = guess; *op != '/' && *op != '\0'; op++)
496         if (p < guess + PATH_MAX)
497           *p++ = *op;
498       *p = '\0';
499
500       if (mindist(newname, guess, best) >= 3)
501         return -1;  /* Hopeless */
502
503       /*
504        *  Add to end of newname
505        */
506       for (p = best; *np = *p++; np++)
507         ;
508     }
509 }
510
511 /*
512  *  Search directory for a guess
513  */
514 static int
515 mindist(dir, guess, best)
516      char *dir;
517      char *guess;
518      char *best;
519 {
520   DIR *fd;
521   struct dirent *dp;
522   int dist, x;
523
524   dist = 3;    /* Worst distance */
525   if (*dir == '\0')
526     dir = ".";
527
528   if ((fd = opendir(dir)) == NULL)
529     return dist;
530
531   while ((dp = readdir(fd)) != NULL)
532     {
533       /*
534        *  Look for a better guess.  If the new guess is as
535        *  good as the current one, we take it.  This way,
536        *  any single character match will be a better match
537        *  than ".".
538        */
539       x = spdist(dp->d_name, guess);
540       if (x <= dist && x != 3)
541         {
542           strcpy(best, dp->d_name);
543           dist = x;
544           if (dist == 0)    /* Exact match */
545             break;
546         }
547     }
548   (void)closedir(fd);
549
550   return dist;
551 }
552
553 /*
554  *  `spdist' -- return the "distance" between two names.
555  *
556  *  int spname(char * oldname, char * newname)
557  *  Returns:  0 if strings are identical
558  *      1 if two characters are transposed
559  *      2 if one character is wrong, added or deleted
560  *      3 otherwise
561  */
562 static int
563 spdist(cur, new)
564      char *cur, *new;
565 {
566   while (*cur == *new)
567     {
568       if (*cur == '\0')
569         return 0;    /* Exact match */
570       cur++;
571       new++;
572     }
573
574   if (*cur)
575     {
576       if (*new)
577         {
578           if (cur[1] && new[1] && cur[0] == new[1] && cur[1] == new[0] && strcmp (cur + 2, new + 2) == 0)
579             return 1;  /* Transposition */
580
581           if (strcmp (cur + 1, new + 1) == 0)
582             return 2;  /* One character mismatch */
583         }
584
585       if (strcmp(&cur[1], &new[0]) == 0)
586         return 2;    /* Extra character */
587     }
588
589   if (*new && strcmp(cur, new + 1) == 0)
590     return 2;      /* Missing character */
591
592   return 3;
593 }