aboutsummaryrefslogtreecommitdiffstats
path: root/builtins/cd.def
blob: f66e68c13de33013ff2bad5e379adbb16749d1c2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
This file is cd.def, from which is created cd.c.  It implements the
builtins "cd" and "pwd" in Bash.

Copyright (C) 1987-2013 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 3 of the License, 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.  If not, see <http://www.gnu.org/licenses/>.

$PRODUCES cd.c
#include <config.h>

#if defined (HAVE_UNISTD_H)
#  ifdef _MINIX
#    include <sys/types.h>
#  endif
#  include <unistd.h>
#endif

#include "../bashtypes.h"
#include "posixdir.h"
#include "posixstat.h"
#if defined (HAVE_SYS_PARAM_H)
#include <sys/param.h>
#endif
#include <fcntl.h>

#include <stdio.h>

#include "../bashansi.h"
#include "../bashintl.h"

#include <errno.h>
#include <tilde/tilde.h>

#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;
extern int array_needs_making;
extern const char * const bash_getcwd_errstr;

static int bindpwd __P((int));
static int setpwd __P((char *));
static char *resetpwd __P((char *));
static int change_to_directory __P((char *, int, int));

static int cdxattr __P((char *, char **));
static void resetxattr __P((void));

/* Change this to 1 to get cd spelling correction by default. */
int cdspelling = 0;

int cdable_vars;

static int eflag;	/* file scope so bindpwd() can see it */
static int xattrflag;	/* O_XATTR support for openat */
static int xattrfd = -1;

$BUILTIN cd
$FUNCTION cd_builtin
$SHORT_DOC cd [-L|[-P [-e]] [-@]] [dir]
Change the shell working directory.

Change the current directory to DIR.  The default DIR is the value of the
HOME shell variable.

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.  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,
the word is assumed to be  a variable name.  If that variable has a value,
its value is used for DIR.

Options:
    -L	force symbolic links to be followed: resolve symbolic links in
	DIR after processing instances of `..'
    -P	use the physical directory structure without following symbolic
	links: resolve symbolic links in DIR before processing instances
	of `..'
    -e	if the -P option is supplied, and the current working directory
	cannot be determined successfully, exit with a non-zero status
#if defined (O_XATTR)
    -@  on systems that support it, present a file with extended attributes
        as a directory containing the file attributes
#endif

The default is to follow symbolic links, as if `-L' were specified.
`..' is processed by removing the immediately previous pathname component
back to a slash or the beginning of DIR.

Exit Status:
Returns 0 if the directory is changed, and if $PWD is set successfully when
-P is used; non-zero otherwise.
$END

/* Just set $PWD, don't change OLDPWD.  Used by `pwd -P' in posix mode. */
static int
setpwd (dirname)
     char *dirname;
{
  int old_anm;
  SHELL_VAR *tvar;

  old_anm = array_needs_making;
  tvar = bind_variable ("PWD", dirname ? dirname : "", 0);
  if (tvar && readonly_p (tvar))
    return EXECUTION_FAILURE;
  if (tvar && old_anm == 0 && array_needs_making && exported_p (tvar))
    {
      update_export_env_inplace ("PWD=", 4, dirname ? dirname : "");
      array_needs_making = 0;
    }
  return EXECUTION_SUCCESS;
}

static int
bindpwd (no_symlinks)
     int no_symlinks;
{
  char *dirname, *pwdvar;
  int old_anm, r;
  SHELL_VAR *tvar;

  r = sh_chkwrite (EXECUTION_SUCCESS);

#define tcwd the_current_working_directory
  dirname = tcwd ? (no_symlinks ? sh_physpath (tcwd, 0) : tcwd)
  		 : get_working_directory ("cd");
#undef tcwd

  old_anm = array_needs_making;
  pwdvar = get_string_value ("PWD");

  tvar = bind_variable ("OLDPWD", pwdvar, 0);
  if (tvar && readonly_p (tvar))
    r = EXECUTION_FAILURE;

  if (old_anm == 0 && array_needs_making && exported_p (tvar))
    {
      update_export_env_inplace ("OLDPWD=", 7, pwdvar);
      array_needs_making = 0;
    }

  if (setpwd (dirname) == EXECUTION_FAILURE)
    r = EXECUTION_FAILURE;
  if (dirname == 0 && eflag)
    r = EXECUTION_FAILURE;

  if (dirname && dirname != the_current_working_directory)
    free (dirname);

  return (r);
}

/* Call get_working_directory to reset the value of
   the_current_working_directory () */
static char *
resetpwd (caller)
     char *caller;
{
  char *tdir;
      
  FREE (the_current_working_directory);
  the_current_working_directory = (char *)NULL;
  tdir = get_working_directory (caller);
  return (tdir);
}

static int
cdxattr (dir, ndirp)
     char *dir;		/* don't assume we can always free DIR */
     char **ndirp;	/* return new constructed directory name */
{
#if defined (O_XATTR)
  int apfd, fd, r, e;
  char buf[11+40+40];	/* construct new `fake' path for pwd */

  apfd = openat (AT_FDCWD, dir, O_RDONLY|O_NONBLOCK);
  if (apfd < 0)
    return -1;
  fd = openat (apfd, ".", O_XATTR);
  e = errno;
  close (apfd);		/* ignore close error for now */
  errno = e;
  if (fd < 0)
    return -1;
  r = fchdir (fd);	/* assume fchdir exists everywhere with O_XATTR */
  if (r < 0)
    {
      close (fd);
      return -1;
    }
  /* NFSv4 and ZFS extended attribute directories do not have names which are
     visible in the standard Unix directory tree structure.  To ensure we have
     a valid name for $PWD, we synthesize one under /proc, but to keep that
     path valid, we need to keep the file descriptor open as long as we are in
     this directory.  This imposes a certain structure on /proc. */
  if (ndirp)
    {
      sprintf (buf, "/proc/%d/fd/%d", getpid(), fd);
      *ndirp = savestring (buf);
    }

  if (xattrfd >= 0)
    close (xattrfd);
  xattrfd = fd;  

  return r;
#else
  return -1;
#endif
}

/* Clean up the O_XATTR baggage.  Currently only closes xattrfd */
static void
resetxattr ()
{
#if defined (O_XATTR)
  if (xattrfd >= 0)
    {
      close (xattrfd);
      xattrfd = -1;
    }
#else
  xattrfd = -1;		/* not strictly necessary */
#endif
}

#define LCD_DOVARS	0x001
#define LCD_DOSPELL	0x002
#define LCD_PRINTPATH	0x004
#define LCD_FREEDIRNAME	0x008

/* 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, lflag;

#if defined (RESTRICTED_SHELL)
  if (restricted)
    {
      sh_restricted ((char *)NULL);
      return (EXECUTION_FAILURE);
    }
#endif /* RESTRICTED_SHELL */

  eflag = 0;
  no_symlinks = no_symbolic_links;
  xattrflag = 0;
  reset_internal_getopt ();
#if defined (O_XATTR)
  while ((opt = internal_getopt (list, "eLP@")) != -1)
#else
  while ((opt = internal_getopt (list, "eLP")) != -1)
#endif
    {
      switch (opt)
	{
	case 'P':
	  no_symlinks = 1;
	  break;
	case 'L':
	  no_symlinks = 0;
	  break;
	case 'e':
	  eflag = 1;
	  break;
#if defined (O_XATTR)
	case '@':
	  xattrflag = 1;
	  break;
#endif
	default:
	  builtin_usage ();
	  return (EX_USAGE);
	}
    }
  list = loptend;

  lflag = (cdable_vars ? LCD_DOVARS : 0) |
	  ((interactive && cdspelling) ? LCD_DOSPELL : 0);
  if (eflag && no_symlinks == 0)
    eflag = 0;

  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);
	}
      lflag = 0;
    }
#if defined (CD_COMPLAINS)
  else if (list->next)
    {
      builtin_error (_("too many arguments"));
      return (EXECUTION_FAILURE);
    }
#endif
  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)
	{
	  builtin_error (_("OLDPWD not set"));
	  return (EXECUTION_FAILURE);
	}
#if 0
      lflag = interactive ? LCD_PRINTPATH : 0;
#else
      lflag = LCD_PRINTPATH;		/* According to SUSv3 */
#endif
    }
  else if (absolute_pathname (list->word->word))
    dirname = list->word->word;
  else if (privileged_mode == 0 && (cdpath = get_string_value ("CDPATH")))
    {
      dirname = list->word->word;

      /* Find directory in $CDPATH. */
      path_index = 0;
      while (path = extract_colon_unit (cdpath, &path_index))
	{
	  /* OPT is 1 if the path element is non-empty */
	  opt = path[0] != '\0';
	  temp = sh_makepath (path, dirname, MP_DOTILDE);
	  free (path);

	  if (change_to_directory (temp, no_symlinks, xattrflag))
	    {
	      /* POSIX.2 says that if a nonempty directory from CDPATH
		 is used to find the directory to change to, the new
		 directory name is echoed to stdout, whether or not
		 the shell is interactive. */
	      if (opt && (path = no_symlinks ? temp : the_current_working_directory))
		printf ("%s\n", path);

	      free (temp);
#if 0
	      /* Posix.2 says that after using CDPATH, the resultant
		 value of $PWD will not contain `.' or `..'. */
	      return (bindpwd (posixly_correct || no_symlinks));
#else
	      return (bindpwd (no_symlinks));
#endif
	    }
	  else
	    free (temp);
	}

#if 0
      /* changed for bash-4.2 Posix cd description steps 5-6 */
      /* POSIX.2 says that if `.' does not appear in $CDPATH, we don't
	 try the current directory, so we just punt now with an error
	 message if POSIXLY_CORRECT is non-zero.  The check for cdpath[0]
	 is so we don't mistakenly treat a CDPATH value of "" as not
	 specifying the current directory. */
      if (posixly_correct && cdpath[0])
	{
	  builtin_error ("%s: %s", dirname, strerror (ENOENT));
	  return (EXECUTION_FAILURE);
	}
#endif
    }
  else
    dirname = list->word->word;

  /* When we get here, DIRNAME is the directory to change to.  If we
     chdir successfully, just return. */
  if (change_to_directory (dirname, no_symlinks, xattrflag))
    {
      if (lflag & LCD_PRINTPATH)
	printf ("%s\n", dirname);
      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 (lflag & LCD_DOVARS)
    {
      temp = get_string_value (dirname);
      if (temp && change_to_directory (temp, no_symlinks, xattrflag))
	{
	  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 (lflag & LCD_DOSPELL)
    {
      temp = dirspell (dirname);
      if (temp && change_to_directory (temp, no_symlinks, xattrflag))
	{
	  printf ("%s\n", temp);
	  free (temp);
	  return (bindpwd (no_symlinks));
	}
      else
	FREE (temp);
    }

  builtin_error ("%s: %s", dirname, strerror (errno));
  return (EXECUTION_FAILURE);
}

$BUILTIN pwd
$FUNCTION pwd_builtin
$SHORT_DOC pwd [-LP]
Print the name of the current working directory.

Options:
  -L	print the value of $PWD if it names the current working
	directory
  -P	print the physical directory, without any symbolic links

By default, `pwd' behaves as if `-L' were specified.

Exit Status:
Returns 0 unless an invalid option is given or the current directory
cannot be read.
$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;
  int opt, pflag;

  verbatim_pwd = no_symbolic_links;
  pflag = 0;
  reset_internal_getopt ();
  while ((opt = internal_getopt (list, "LP")) != -1)
    {
      switch (opt)
	{
	case 'P':
	  verbatim_pwd = pflag = 1;
	  break;
	case 'L':
	  verbatim_pwd = 0;
	  break;
	default:
	  builtin_usage ();
	  return (EX_USAGE);
	}
    }
  list = loptend;

#define tcwd the_current_working_directory

  directory = tcwd ? (verbatim_pwd ? sh_physpath (tcwd, 0) : tcwd)
		   : get_working_directory ("pwd");

  /* Try again using getcwd() if canonicalization fails (for instance, if
     the file system has changed state underneath bash). */
  if ((tcwd && directory == 0) ||
      (posixly_correct && same_file (".", tcwd, (struct stat *)0, (struct stat *)0) == 0))
    {
      if (directory && directory != tcwd)
        free (directory);
      directory = resetpwd ("pwd");
    }

#undef tcwd

  if (directory)
    {
      opt = EXECUTION_SUCCESS;
      printf ("%s\n", directory);
      /* This is dumb but posix-mandated. */
      if (posixly_correct && pflag)
	opt = setpwd (directory);
      if (directory != the_current_working_directory)
	free (directory);
      return (sh_chkwrite (opt));
    }
  else
    return (EXECUTION_FAILURE);
}

/* Do the work of changing to the directory NEWDIR.  Handle symbolic
   link following, etc.  This function *must* return with
   the_current_working_directory either set to NULL (in which case
   getcwd() will eventually be called), or set to a string corresponding
   to the working directory.  Return 1 on success, 0 on failure. */

static int
change_to_directory (newdir, nolinks, xattr)
     char *newdir;
     int nolinks, xattr;
{
  char *t, *tdir, *ndir;
  int err, canon_failed, r, ndlen, dlen;

  tdir = (char *)NULL;

  if (the_current_working_directory == 0)
    {
      t = get_working_directory ("chdir");
      FREE (t);
    }

  t = make_absolute (newdir, the_current_working_directory);

  /* TDIR is either the canonicalized absolute pathname of NEWDIR
     (nolinks == 0) or the absolute physical pathname of NEWDIR
     (nolinks != 0). */
  tdir = nolinks ? sh_physpath (t, 0)
		 : sh_canonpath (t, PATH_CHECKDOTDOT|PATH_CHECKEXISTS);

  ndlen = strlen (newdir);
  dlen = strlen (t);

  /* Use the canonicalized version of NEWDIR, or, if canonicalization
     failed, use the non-canonical form. */
  canon_failed = 0;
  if (tdir && *tdir)
    free (t);
  else
    {
      FREE (tdir);
      tdir = t;
      canon_failed = 1;
    }

  /* In POSIX mode, if we're resolving symlinks logically and sh_canonpath
     returns NULL (because it checks the path, it will return NULL if the
     resolved path doesn't exist), fail immediately. */
  if (posixly_correct && nolinks == 0 && canon_failed && (errno != ENAMETOOLONG || ndlen > PATH_MAX))
    {
#if defined ENAMETOOLONG
      if (errno != ENOENT && errno != ENAMETOOLONG)
#else
      if (errno != ENOENT)
#endif
	errno = ENOTDIR;
      free (tdir);
      return (0);
    }

#if defined (O_XATTR)
  if (xattrflag)
    {
      r = cdxattr (nolinks ? newdir : tdir, &ndir);
      if (r >= 0)
	{
	  canon_failed = 0;
	  free (tdir);
	  tdir = ndir;
	}
      else
	{
	  err = errno;
	  free (tdir);
	  errno = err;
	  return (0);		/* no xattr */
	}
    }
  else
#endif
    {
      r = chdir (nolinks ? newdir : tdir);
      if (r >= 0)
	resetxattr ();
    }

  /* If the chdir succeeds, update the_current_working_directory. */
  if (r == 0)
    {
      /* If canonicalization failed, but the chdir succeeded, reset the
	 shell's idea of the_current_working_directory. */
      if (canon_failed)
	{
	  t = resetpwd ("cd");
	  if (t == 0)
	    set_working_directory (tdir);
	  else
	    free (t);
	}
      else
	set_working_directory (tdir);

      free (tdir);
      return (1);
    }

  /* We failed to change to the appropriate directory name.  If we tried
     what the user passed (nolinks != 0), punt now. */
  if (nolinks)
    {
      free (tdir);
      return (0);
    }

  err = errno;

  /* We're not in physical mode (nolinks == 0), but we failed to change to
     the canonicalized directory name (TDIR).  Try what the user passed
     verbatim. If we succeed, reinitialize the_current_working_directory. */
  if (chdir (newdir) == 0)
    {
      t = resetpwd ("cd");
      if (t == 0)
	set_working_directory (tdir);
      else
	free (t);

      r = 1;
    }
  else
    {
      errno = err;
      r = 0;
    }

  free (tdir);
  return r;
}