1/* cd.c, created from cd.def. */
2#line 23 "cd.def"
3#include <config.h>
4
5#if defined (HAVE_UNISTD_H)
6#  ifdef _MINIX
7#    include <sys/types.h>
8#  endif
9#  include <unistd.h>
10#endif
11
12#include "../bashtypes.h"
13#include "posixdir.h"
14#include "posixstat.h"
15#ifndef _MINIX
16#include <sys/param.h>
17#endif
18
19#include <stdio.h>
20
21#include "../bashansi.h"
22#include "../bashintl.h"
23
24#include <errno.h>
25#include <tilde/tilde.h>
26
27#include "../shell.h"
28#include "../flags.h"
29#include "maxpath.h"
30#include "common.h"
31#include "bashgetopt.h"
32
33#if !defined (errno)
34extern int errno;
35#endif /* !errno */
36
37extern int posixly_correct;
38extern int array_needs_making;
39extern char *bash_getcwd_errstr;
40
41static int bindpwd __P((int));
42static void setpwd __P((char *));
43static char *resetpwd __P((char *));
44static int change_to_directory __P((char *, int));
45
46static char *cdspell __P((char *));
47
48/* Change this to 1 to get cd spelling correction by default. */
49int cdspelling = 0;
50
51int cdable_vars;
52
53#line 88 "cd.def"
54
55/* Just set $PWD, don't change OLDPWD.  Used by `pwd -P' in posix mode. */
56static void
57setpwd (dirname)
58     char *dirname;
59{
60  int old_anm;
61  SHELL_VAR *tvar;
62
63  old_anm = array_needs_making;
64  tvar = bind_variable ("PWD", dirname ? dirname : "", 0);
65  if (old_anm == 0 && array_needs_making && exported_p (tvar))
66    {
67      update_export_env_inplace ("PWD=", 4, dirname ? dirname : "");
68      array_needs_making = 0;
69    }
70}
71
72static int
73bindpwd (no_symlinks)
74     int no_symlinks;
75{
76  char *dirname, *pwdvar;
77  int old_anm;
78  SHELL_VAR *tvar;
79
80#define tcwd the_current_working_directory
81  dirname = tcwd ? (no_symlinks ? sh_physpath (tcwd, 0) : tcwd)
82  		 : get_working_directory ("cd");
83#undef tcwd
84
85  old_anm = array_needs_making;
86  pwdvar = get_string_value ("PWD");
87
88  tvar = bind_variable ("OLDPWD", pwdvar, 0);
89  if (old_anm == 0 && array_needs_making && exported_p (tvar))
90    {
91      update_export_env_inplace ("OLDPWD=", 7, pwdvar);
92      array_needs_making = 0;
93    }
94
95  setpwd (dirname);
96
97  if (dirname && dirname != the_current_working_directory)
98    free (dirname);
99
100  return (EXECUTION_SUCCESS);
101}
102
103/* Call get_working_directory to reset the value of
104   the_current_working_directory () */
105static char *
106resetpwd (caller)
107     char *caller;
108{
109  char *tdir;
110
111  FREE (the_current_working_directory);
112  the_current_working_directory = (char *)NULL;
113  tdir = get_working_directory (caller);
114  return (tdir);
115}
116
117#define LCD_DOVARS	0x001
118#define LCD_DOSPELL	0x002
119#define LCD_PRINTPATH	0x004
120#define LCD_FREEDIRNAME	0x010
121
122/* This builtin is ultimately the way that all user-visible commands should
123   change the current working directory.  It is called by cd_to_string (),
124   so the programming interface is simple, and it handles errors and
125   restrictions properly. */
126int
127cd_builtin (list)
128     WORD_LIST *list;
129{
130  char *dirname, *cdpath, *path, *temp;
131  int path_index, no_symlinks, opt, lflag;
132
133#if defined (RESTRICTED_SHELL)
134  if (restricted)
135    {
136      sh_restricted ((char *)NULL);
137      return (EXECUTION_FAILURE);
138    }
139#endif /* RESTRICTED_SHELL */
140
141  no_symlinks = no_symbolic_links;
142  reset_internal_getopt ();
143  while ((opt = internal_getopt (list, "LP")) != -1)
144    {
145      switch (opt)
146	{
147	case 'P':
148	  no_symlinks = 1;
149	  break;
150	case 'L':
151	  no_symlinks = 0;
152	  break;
153	default:
154	  builtin_usage ();
155	  return (EXECUTION_FAILURE);
156	}
157    }
158  list = loptend;
159
160  lflag = (cdable_vars ? LCD_DOVARS : 0) |
161	  ((interactive && cdspelling) ? LCD_DOSPELL : 0);
162
163  if (list == 0)
164    {
165      /* `cd' without arguments is equivalent to `cd $HOME' */
166      dirname = get_string_value ("HOME");
167
168      if (dirname == 0)
169	{
170	  builtin_error (_("HOME not set"));
171	  return (EXECUTION_FAILURE);
172	}
173      lflag = 0;
174    }
175  else if (list->word->word[0] == '-' && list->word->word[1] == '\0')
176    {
177      /* This is `cd -', equivalent to `cd $OLDPWD' */
178      dirname = get_string_value ("OLDPWD");
179
180      if (dirname == 0)
181	{
182	  builtin_error (_("OLDPWD not set"));
183	  return (EXECUTION_FAILURE);
184	}
185#if 0
186      lflag = interactive ? LCD_PRINTPATH : 0;
187#else
188      lflag = LCD_PRINTPATH;		/* According to SUSv3 */
189#endif
190    }
191  else if (absolute_pathname (list->word->word))
192    dirname = list->word->word;
193  else if (cdpath = get_string_value ("CDPATH"))
194    {
195      dirname = list->word->word;
196
197      /* Find directory in $CDPATH. */
198      path_index = 0;
199      while (path = extract_colon_unit (cdpath, &path_index))
200	{
201	  /* OPT is 1 if the path element is non-empty */
202	  opt = path[0] != '\0';
203	  temp = sh_makepath (path, dirname, MP_DOTILDE);
204	  free (path);
205
206	  if (change_to_directory (temp, no_symlinks))
207	    {
208	      /* POSIX.2 says that if a nonempty directory from CDPATH
209		 is used to find the directory to change to, the new
210		 directory name is echoed to stdout, whether or not
211		 the shell is interactive. */
212	      if (opt && (path = no_symlinks ? temp : the_current_working_directory))
213		printf ("%s\n", path);
214
215	      free (temp);
216#if 0
217	      /* Posix.2 says that after using CDPATH, the resultant
218		 value of $PWD will not contain `.' or `..'. */
219	      return (bindpwd (posixly_correct || no_symlinks));
220#else
221	      return (bindpwd (no_symlinks));
222#endif
223	    }
224	  else
225	    free (temp);
226	}
227
228      /* POSIX.2 says that if `.' does not appear in $CDPATH, we don't
229	 try the current directory, so we just punt now with an error
230	 message if POSIXLY_CORRECT is non-zero.  The check for cdpath[0]
231	 is so we don't mistakenly treat a CDPATH value of "" as not
232	 specifying the current directory. */
233      if (posixly_correct && cdpath[0])
234	{
235	  builtin_error ("%s: %s", dirname, strerror (ENOENT));
236	  return (EXECUTION_FAILURE);
237	}
238    }
239  else
240    dirname = list->word->word;
241
242  /* When we get here, DIRNAME is the directory to change to.  If we
243     chdir successfully, just return. */
244  if (change_to_directory (dirname, no_symlinks))
245    {
246      if (lflag & LCD_PRINTPATH)
247	printf ("%s\n", dirname);
248      return (bindpwd (no_symlinks));
249    }
250
251  /* If the user requests it, then perhaps this is the name of
252     a shell variable, whose value contains the directory to
253     change to. */
254  if (lflag & LCD_DOVARS)
255    {
256      temp = get_string_value (dirname);
257      if (temp && change_to_directory (temp, no_symlinks))
258	{
259	  printf ("%s\n", temp);
260	  return (bindpwd (no_symlinks));
261	}
262    }
263
264  /* If the user requests it, try to find a directory name similar in
265     spelling to the one requested, in case the user made a simple
266     typo.  This is similar to the UNIX 8th and 9th Edition shells. */
267  if (lflag & LCD_DOSPELL)
268    {
269      temp = cdspell (dirname);
270      if (temp && change_to_directory (temp, no_symlinks))
271	{
272	  printf ("%s\n", temp);
273	  return (bindpwd (no_symlinks));
274	}
275      else
276	FREE (temp);
277    }
278
279  builtin_error ("%s: %s", dirname, strerror (errno));
280  return (EXECUTION_FAILURE);
281}
282
283#line 324 "cd.def"
284
285/* Non-zero means that pwd always prints the physical directory, without
286   symbolic links. */
287static int verbatim_pwd;
288
289/* Print the name of the current working directory. */
290int
291pwd_builtin (list)
292     WORD_LIST *list;
293{
294  char *directory;
295  int opt, pflag;
296
297  verbatim_pwd = no_symbolic_links;
298  pflag = 0;
299  reset_internal_getopt ();
300  while ((opt = internal_getopt (list, "LP")) != -1)
301    {
302      switch (opt)
303	{
304	case 'P':
305	  verbatim_pwd = pflag = 1;
306	  break;
307	case 'L':
308	  verbatim_pwd = 0;
309	  break;
310	default:
311	  builtin_usage ();
312	  return (EXECUTION_FAILURE);
313	}
314    }
315  list = loptend;
316
317#define tcwd the_current_working_directory
318
319  directory = tcwd ? (verbatim_pwd ? sh_physpath (tcwd, 0) : tcwd)
320		   : get_working_directory ("pwd");
321
322  /* Try again using getcwd() if canonicalization fails (for instance, if
323     the file system has changed state underneath bash). */
324  if ((tcwd && directory == 0) ||
325      (posixly_correct && same_file (".", tcwd, (struct stat *)0, (struct stat *)0) == 0))
326    directory = resetpwd ("pwd");
327
328#undef tcwd
329
330  if (directory)
331    {
332      printf ("%s\n", directory);
333      /* This is dumb but posix-mandated. */
334      if (posixly_correct && pflag)
335	setpwd (directory);
336      if (directory != the_current_working_directory)
337	free (directory);
338      fflush (stdout);
339      if (ferror (stdout))
340	{
341	  sh_wrerror ();
342	  clearerr (stdout);
343	  return (EXECUTION_FAILURE);
344	}
345
346      return (EXECUTION_SUCCESS);
347    }
348  else
349    return (EXECUTION_FAILURE);
350}
351
352/* Do the work of changing to the directory NEWDIR.  Handle symbolic
353   link following, etc.  This function *must* return with
354   the_current_working_directory either set to NULL (in which case
355   getcwd() will eventually be called), or set to a string corresponding
356   to the working directory.  Return 1 on success, 0 on failure. */
357
358static int
359change_to_directory (newdir, nolinks)
360     char *newdir;
361     int nolinks;
362{
363  char *t, *tdir;
364  int err, canon_failed, r, ndlen, dlen;
365
366  tdir = (char *)NULL;
367
368  if (the_current_working_directory == 0)
369    {
370      t = get_working_directory ("chdir");
371      FREE (t);
372    }
373
374  t = make_absolute (newdir, the_current_working_directory);
375
376  /* TDIR is either the canonicalized absolute pathname of NEWDIR
377     (nolinks == 0) or the absolute physical pathname of NEWDIR
378     (nolinks != 0). */
379  tdir = nolinks ? sh_physpath (t, 0)
380		 : sh_canonpath (t, PATH_CHECKDOTDOT|PATH_CHECKEXISTS);
381
382  ndlen = strlen (newdir);
383  dlen = strlen (t);
384
385  /* Use the canonicalized version of NEWDIR, or, if canonicalization
386     failed, use the non-canonical form. */
387  canon_failed = 0;
388  if (tdir && *tdir)
389    free (t);
390  else
391    {
392      FREE (tdir);
393      tdir = t;
394      canon_failed = 1;
395    }
396
397  /* In POSIX mode, if we're resolving symlinks logically and sh_canonpath
398     returns NULL (because it checks the path, it will return NULL if the
399     resolved path doesn't exist), fail immediately. */
400  if (posixly_correct && nolinks == 0 && canon_failed && (errno != ENAMETOOLONG || ndlen > PATH_MAX))
401    {
402#if defined ENAMETOOLONG
403      if (errno != ENOENT && errno != ENAMETOOLONG)
404#else
405      if (errno != ENOENT)
406#endif
407	errno = ENOTDIR;
408      free (tdir);
409      return (0);
410    }
411
412  /* If the chdir succeeds, update the_current_working_directory. */
413  if (chdir (nolinks ? newdir : tdir) == 0)
414    {
415      /* If canonicalization failed, but the chdir succeeded, reset the
416	 shell's idea of the_current_working_directory. */
417      if (canon_failed)
418	{
419	  t = resetpwd ("cd");
420	  if (t == 0)
421	    set_working_directory (tdir);
422	}
423      else
424	set_working_directory (tdir);
425
426      free (tdir);
427      return (1);
428    }
429
430  /* We failed to change to the appropriate directory name.  If we tried
431     what the user passed (nolinks != 0), punt now. */
432  if (nolinks)
433    {
434      free (tdir);
435      return (0);
436    }
437
438  err = errno;
439
440  /* We're not in physical mode (nolinks == 0), but we failed to change to
441     the canonicalized directory name (TDIR).  Try what the user passed
442     verbatim. If we succeed, reinitialize the_current_working_directory. */
443  if (chdir (newdir) == 0)
444    {
445      t = resetpwd ("cd");
446      if (t == 0)
447	set_working_directory (tdir);
448      else
449	free (t);
450
451      r = 1;
452    }
453  else
454    {
455      errno = err;
456      r = 0;
457    }
458
459  free (tdir);
460  return r;
461}
462
463/* Code for cd spelling correction.  Original patch submitted by
464   Neil Russel (caret@c-side.com). */
465
466static char *
467cdspell (dirname)
468     char *dirname;
469{
470  int n;
471  char *guess;
472
473  n = (strlen (dirname) * 3 + 1) / 2 + 1;
474  guess = (char *)xmalloc (n);
475
476  switch (spname (dirname, guess))
477    {
478    case -1:
479    default:
480      free (guess);
481      return (char *)NULL;
482    case 0:
483    case 1:
484      return guess;
485    }
486}
487