]> git.ipfire.org Git - thirdparty/bash.git/blob - builtins/pushd.def
Bash-5.2 patch 17: fix for optimizing forks when using the . builtin in a subshell
[thirdparty/bash.git] / builtins / pushd.def
1 This file is pushd.def, from which is created pushd.c. It implements the
2 builtins "pushd", "popd", and "dirs" in Bash.
3
4 Copyright (C) 1987-2020 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
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation, either version 3 of the License, or
11 (at your option) any later version.
12
13 Bash is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with Bash. If not, see <http://www.gnu.org/licenses/>.
20
21 $PRODUCES pushd.c
22
23 $BUILTIN pushd
24 $FUNCTION pushd_builtin
25 $DEPENDS_ON PUSHD_AND_POPD
26 $SHORT_DOC pushd [-n] [+N | -N | dir]
27 Add directories to stack.
28
29 Adds a directory to the top of the directory stack, or rotates
30 the stack, making the new top of the stack the current working
31 directory. With no arguments, exchanges the top two directories.
32
33 Options:
34 -n Suppresses the normal change of directory when adding
35 directories to the stack, so only the stack is manipulated.
36
37 Arguments:
38 +N Rotates the stack so that the Nth directory (counting
39 from the left of the list shown by `dirs', starting with
40 zero) is at the top.
41
42 -N Rotates the stack so that the Nth directory (counting
43 from the right of the list shown by `dirs', starting with
44 zero) is at the top.
45
46 dir Adds DIR to the directory stack at the top, making it the
47 new current working directory.
48
49 The `dirs' builtin displays the directory stack.
50
51 Exit Status:
52 Returns success unless an invalid argument is supplied or the directory
53 change fails.
54 $END
55
56 $BUILTIN popd
57 $FUNCTION popd_builtin
58 $DEPENDS_ON PUSHD_AND_POPD
59 $SHORT_DOC popd [-n] [+N | -N]
60 Remove directories from stack.
61
62 Removes entries from the directory stack. With no arguments, removes
63 the top directory from the stack, and changes to the new top directory.
64
65 Options:
66 -n Suppresses the normal change of directory when removing
67 directories from the stack, so only the stack is manipulated.
68
69 Arguments:
70 +N Removes the Nth entry counting from the left of the list
71 shown by `dirs', starting with zero. For example: `popd +0'
72 removes the first directory, `popd +1' the second.
73
74 -N Removes the Nth entry counting from the right of the list
75 shown by `dirs', starting with zero. For example: `popd -0'
76 removes the last directory, `popd -1' the next to last.
77
78 The `dirs' builtin displays the directory stack.
79
80 Exit Status:
81 Returns success unless an invalid argument is supplied or the directory
82 change fails.
83 $END
84
85 $BUILTIN dirs
86 $FUNCTION dirs_builtin
87 $DEPENDS_ON PUSHD_AND_POPD
88 $SHORT_DOC dirs [-clpv] [+N] [-N]
89 Display directory stack.
90
91 Display the list of currently remembered directories. Directories
92 find their way onto the list with the `pushd' command; you can get
93 back up through the list with the `popd' command.
94
95 Options:
96 -c clear the directory stack by deleting all of the elements
97 -l do not print tilde-prefixed versions of directories relative
98 to your home directory
99 -p print the directory stack with one entry per line
100 -v print the directory stack with one entry per line prefixed
101 with its position in the stack
102
103 Arguments:
104 +N Displays the Nth entry counting from the left of the list
105 shown by dirs when invoked without options, starting with
106 zero.
107
108 -N Displays the Nth entry counting from the right of the list
109 shown by dirs when invoked without options, starting with
110 zero.
111
112 Exit Status:
113 Returns success unless an invalid option is supplied or an error occurs.
114 $END
115
116 #include <config.h>
117
118 #if defined (PUSHD_AND_POPD)
119 #include <stdio.h>
120 #if defined (HAVE_SYS_PARAM_H)
121 # include <sys/param.h>
122 #endif
123
124 #if defined (HAVE_UNISTD_H)
125 # ifdef _MINIX
126 # include <sys/types.h>
127 # endif
128 # include <unistd.h>
129 #endif
130
131 #include "../bashansi.h"
132 #include "../bashintl.h"
133
134 #include <errno.h>
135
136 #include <tilde/tilde.h>
137
138 #include "../shell.h"
139 #include "maxpath.h"
140 #include "common.h"
141 #include "builtext.h"
142
143 #ifdef LOADABLE_BUILTIN
144 # include "builtins.h"
145 #endif
146
147 #if !defined (errno)
148 extern int errno;
149 #endif /* !errno */
150
151 /* The list of remembered directories. */
152 static char **pushd_directory_list = (char **)NULL;
153
154 /* Number of existing slots in this list. */
155 static int directory_list_size;
156
157 /* Offset to the end of the list. */
158 static int directory_list_offset;
159
160 static void pushd_error PARAMS((int, char *));
161 static void clear_directory_stack PARAMS((void));
162 static int cd_to_string PARAMS((char *));
163 static int change_to_temp PARAMS((char *));
164 static void add_dirstack_element PARAMS((char *));
165 static int get_dirstack_index PARAMS((intmax_t, int, int *));
166
167 #define NOCD 0x01
168 #define ROTATE 0x02
169 #define LONGFORM 0x04
170 #define CLEARSTAK 0x08
171
172 int
173 pushd_builtin (list)
174 WORD_LIST *list;
175 {
176 WORD_LIST *orig_list;
177 char *temp, *current_directory, *top;
178 int j, flags, skipopt;
179 intmax_t num;
180 char direction;
181
182 orig_list = list;
183
184 CHECK_HELPOPT (list);
185 if (list && list->word && ISOPTION (list->word->word, '-'))
186 {
187 list = list->next;
188 skipopt = 1;
189 }
190 else
191 skipopt = 0;
192
193 /* If there is no argument list then switch current and
194 top of list. */
195 if (list == 0)
196 {
197 if (directory_list_offset == 0)
198 {
199 builtin_error (_("no other directory"));
200 return (EXECUTION_FAILURE);
201 }
202
203 current_directory = get_working_directory ("pushd");
204 if (current_directory == 0)
205 return (EXECUTION_FAILURE);
206
207 j = directory_list_offset - 1;
208 temp = pushd_directory_list[j];
209 pushd_directory_list[j] = current_directory;
210 j = change_to_temp (temp);
211 free (temp);
212 return j;
213 }
214
215 for (flags = 0; skipopt == 0 && list; list = list->next)
216 {
217 if (ISOPTION (list->word->word, 'n'))
218 {
219 flags |= NOCD;
220 }
221 else if (ISOPTION (list->word->word, '-'))
222 {
223 list = list->next;
224 break;
225 }
226 else if (list->word->word[0] == '-' && list->word->word[1] == '\0')
227 /* Let `pushd -' work like it used to. */
228 break;
229 else if (((direction = list->word->word[0]) == '+') || direction == '-')
230 {
231 if (legal_number (list->word->word + 1, &num) == 0)
232 {
233 sh_invalidnum (list->word->word);
234 builtin_usage ();
235 return (EX_USAGE);
236 }
237
238 if (direction == '-')
239 num = directory_list_offset - num;
240
241 if (num > directory_list_offset || num < 0)
242 {
243 pushd_error (directory_list_offset, list->word->word);
244 return (EXECUTION_FAILURE);
245 }
246 flags |= ROTATE;
247 }
248 else if (*list->word->word == '-')
249 {
250 sh_invalidopt (list->word->word);
251 builtin_usage ();
252 return (EX_USAGE);
253 }
254 else
255 break;
256 }
257
258 if (flags & ROTATE)
259 {
260 /* Rotate the stack num times. Remember, the current
261 directory acts like it is part of the stack. */
262 temp = get_working_directory ("pushd");
263
264 if (num == 0)
265 {
266 j = ((flags & NOCD) == 0) ? change_to_temp (temp) : EXECUTION_SUCCESS;
267 free (temp);
268 return j;
269 }
270
271 do
272 {
273 top = pushd_directory_list[directory_list_offset - 1];
274
275 for (j = directory_list_offset - 2; j > -1; j--)
276 pushd_directory_list[j + 1] = pushd_directory_list[j];
277
278 pushd_directory_list[j + 1] = temp;
279
280 temp = top;
281 num--;
282 }
283 while (num);
284
285 j = ((flags & NOCD) == 0) ? change_to_temp (temp) : EXECUTION_SUCCESS;
286 free (temp);
287 return j;
288 }
289
290 if (list == 0)
291 return (EXECUTION_SUCCESS);
292
293 /* Change to the directory in list->word->word. Save the current
294 directory on the top of the stack. */
295 current_directory = get_working_directory ("pushd");
296 if (current_directory == 0)
297 return (EXECUTION_FAILURE);
298
299 j = ((flags & NOCD) == 0) ? cd_builtin (skipopt ? orig_list : list) : EXECUTION_SUCCESS;
300 if (j == EXECUTION_SUCCESS)
301 {
302 add_dirstack_element ((flags & NOCD) ? savestring (list->word->word) : current_directory);
303 dirs_builtin ((WORD_LIST *)NULL);
304 if (flags & NOCD)
305 free (current_directory);
306 return (EXECUTION_SUCCESS);
307 }
308 else
309 {
310 free (current_directory);
311 return (EXECUTION_FAILURE);
312 }
313 }
314
315 /* Pop the directory stack, and then change to the new top of the stack.
316 If LIST is non-null it should consist of a word +N or -N, which says
317 what element to delete from the stack. The default is the top one. */
318 int
319 popd_builtin (list)
320 WORD_LIST *list;
321 {
322 register int i;
323 intmax_t which;
324 int flags;
325 char direction;
326 char *which_word;
327
328 CHECK_HELPOPT (list);
329
330 which_word = (char *)NULL;
331 for (flags = 0, which = 0, direction = '+'; list; list = list->next)
332 {
333 if (ISOPTION (list->word->word, 'n'))
334 {
335 flags |= NOCD;
336 }
337 else if (ISOPTION (list->word->word, '-'))
338 {
339 list = list->next;
340 break;
341 }
342 else if (((direction = list->word->word[0]) == '+') || direction == '-')
343 {
344 if (legal_number (list->word->word + 1, &which) == 0)
345 {
346 sh_invalidnum (list->word->word);
347 builtin_usage ();
348 return (EX_USAGE);
349 }
350 which_word = list->word->word;
351 }
352 else if (*list->word->word == '-')
353 {
354 sh_invalidopt (list->word->word);
355 builtin_usage ();
356 return (EX_USAGE);
357 }
358 else if (*list->word->word)
359 {
360 builtin_error (_("%s: invalid argument"), list->word->word);
361 builtin_usage ();
362 return (EX_USAGE);
363 }
364 else
365 break;
366 }
367
368 if (which > directory_list_offset || (which < -directory_list_offset) || (directory_list_offset == 0 && which == 0))
369 {
370 pushd_error (directory_list_offset, which_word ? which_word : "");
371 return (EXECUTION_FAILURE);
372 }
373
374 /* Handle case of no specification, or top of stack specification. */
375 if ((direction == '+' && which == 0) ||
376 (direction == '-' && which == directory_list_offset))
377 {
378 i = ((flags & NOCD) == 0) ? cd_to_string (pushd_directory_list[directory_list_offset - 1])
379 : EXECUTION_SUCCESS;
380 if (i != EXECUTION_SUCCESS)
381 return (i);
382 free (pushd_directory_list[--directory_list_offset]);
383 }
384 else
385 {
386 /* Since an offset other than the top directory was specified,
387 remove that directory from the list and shift the remainder
388 of the list into place. */
389 i = (direction == '+') ? directory_list_offset - which : which;
390 if (i < 0 || i > directory_list_offset)
391 {
392 pushd_error (directory_list_offset, which_word ? which_word : "");
393 return (EXECUTION_FAILURE);
394 }
395 free (pushd_directory_list[i]);
396 directory_list_offset--;
397
398 /* Shift the remainder of the list into place. */
399 for (; i < directory_list_offset; i++)
400 pushd_directory_list[i] = pushd_directory_list[i + 1];
401 }
402
403 dirs_builtin ((WORD_LIST *)NULL);
404 return (EXECUTION_SUCCESS);
405 }
406
407 /* Print the current list of directories on the directory stack. */
408 int
409 dirs_builtin (list)
410 WORD_LIST *list;
411 {
412 int flags, desired_index, index_flag, vflag;
413 intmax_t i;
414 char *temp, *w;
415
416 CHECK_HELPOPT (list);
417 for (flags = vflag = index_flag = 0, desired_index = -1, w = ""; list; list = list->next)
418 {
419 if (ISOPTION (list->word->word, 'l'))
420 {
421 flags |= LONGFORM;
422 }
423 else if (ISOPTION (list->word->word, 'c'))
424 {
425 flags |= CLEARSTAK;
426 }
427 else if (ISOPTION (list->word->word, 'v'))
428 {
429 vflag |= 2;
430 }
431 else if (ISOPTION (list->word->word, 'p'))
432 {
433 vflag |= 1;
434 }
435 else if (ISOPTION (list->word->word, '-'))
436 {
437 list = list->next;
438 break;
439 }
440 else if (*list->word->word == '+' || *list->word->word == '-')
441 {
442 int sign;
443 if (legal_number (w = list->word->word + 1, &i) == 0)
444 {
445 sh_invalidnum (list->word->word);
446 builtin_usage ();
447 return (EX_USAGE);
448 }
449 sign = (*list->word->word == '+') ? 1 : -1;
450 desired_index = get_dirstack_index (i, sign, &index_flag);
451 }
452 else
453 {
454 sh_invalidopt (list->word->word);
455 builtin_usage ();
456 return (EX_USAGE);
457 }
458 }
459
460 if (flags & CLEARSTAK)
461 {
462 clear_directory_stack ();
463 return (EXECUTION_SUCCESS);
464 }
465
466 if (index_flag && (desired_index < 0 || desired_index > directory_list_offset))
467 {
468 pushd_error (directory_list_offset, w);
469 return (EXECUTION_FAILURE);
470 }
471
472 #define DIRSTACK_FORMAT(temp) \
473 (flags & LONGFORM) ? temp : polite_directory_format (temp)
474
475 /* The first directory printed is always the current working directory. */
476 if (index_flag == 0 || (index_flag == 1 && desired_index == 0))
477 {
478 temp = get_working_directory ("dirs");
479 if (temp == 0)
480 temp = savestring (_("<no current directory>"));
481 if (vflag & 2)
482 printf ("%2d %s", 0, DIRSTACK_FORMAT (temp));
483 else
484 printf ("%s", DIRSTACK_FORMAT (temp));
485 free (temp);
486 if (index_flag)
487 {
488 putchar ('\n');
489 return (sh_chkwrite (EXECUTION_SUCCESS));
490 }
491 }
492
493 #define DIRSTACK_ENTRY(i) \
494 (flags & LONGFORM) ? pushd_directory_list[i] \
495 : polite_directory_format (pushd_directory_list[i])
496
497 /* Now print the requested directory stack entries. */
498 if (index_flag)
499 {
500 if (vflag & 2)
501 printf ("%2d %s", directory_list_offset - desired_index,
502 DIRSTACK_ENTRY (desired_index));
503 else
504 printf ("%s", DIRSTACK_ENTRY (desired_index));
505 }
506 else
507 for (i = directory_list_offset - 1; i >= 0; i--)
508 if (vflag >= 2)
509 printf ("\n%2d %s", directory_list_offset - (int)i, DIRSTACK_ENTRY (i));
510 else
511 printf ("%s%s", (vflag & 1) ? "\n" : " ", DIRSTACK_ENTRY (i));
512
513 putchar ('\n');
514
515 return (sh_chkwrite (EXECUTION_SUCCESS));
516 }
517
518 static void
519 pushd_error (offset, arg)
520 int offset;
521 char *arg;
522 {
523 if (offset == 0)
524 builtin_error (_("directory stack empty"));
525 else
526 sh_erange (arg, _("directory stack index"));
527 }
528
529 static void
530 clear_directory_stack ()
531 {
532 register int i;
533
534 for (i = 0; i < directory_list_offset; i++)
535 free (pushd_directory_list[i]);
536 directory_list_offset = 0;
537 }
538
539 /* Switch to the directory in NAME. This uses the cd_builtin to do the work,
540 so if the result is EXECUTION_FAILURE then an error message has already
541 been printed. */
542 static int
543 cd_to_string (name)
544 char *name;
545 {
546 WORD_LIST *tlist;
547 WORD_LIST *dir;
548 int result;
549
550 dir = make_word_list (make_word (name), NULL);
551 tlist = make_word_list (make_word ("--"), dir);
552 result = cd_builtin (tlist);
553 dispose_words (tlist);
554 return (result);
555 }
556
557 static int
558 change_to_temp (temp)
559 char *temp;
560 {
561 int tt;
562
563 tt = temp ? cd_to_string (temp) : EXECUTION_FAILURE;
564
565 if (tt == EXECUTION_SUCCESS)
566 dirs_builtin ((WORD_LIST *)NULL);
567
568 return (tt);
569 }
570
571 static void
572 add_dirstack_element (dir)
573 char *dir;
574 {
575 if (directory_list_offset == directory_list_size)
576 pushd_directory_list = strvec_resize (pushd_directory_list, directory_list_size += 10);
577 pushd_directory_list[directory_list_offset++] = dir;
578 }
579
580 static int
581 get_dirstack_index (ind, sign, indexp)
582 intmax_t ind;
583 int sign, *indexp;
584 {
585 if (indexp)
586 *indexp = sign > 0 ? 1 : 2;
587
588 /* dirs +0 prints the current working directory. */
589 /* dirs -0 prints last element in directory stack */
590 if (ind == 0 && sign > 0)
591 return 0;
592 else if (ind == directory_list_offset)
593 {
594 if (indexp)
595 *indexp = sign > 0 ? 2 : 1;
596 return 0;
597 }
598 else if (ind >= 0 && ind <= directory_list_offset)
599 return (sign > 0 ? directory_list_offset - ind : ind);
600 else
601 return -1;
602 }
603
604 /* Used by the tilde expansion code. */
605 char *
606 get_dirstack_from_string (string)
607 char *string;
608 {
609 int ind, sign, index_flag;
610 intmax_t i;
611
612 sign = 1;
613 if (*string == '-' || *string == '+')
614 {
615 sign = (*string == '-') ? -1 : 1;
616 string++;
617 }
618 if (legal_number (string, &i) == 0)
619 return ((char *)NULL);
620
621 index_flag = 0;
622 ind = get_dirstack_index (i, sign, &index_flag);
623 if (index_flag && (ind < 0 || ind > directory_list_offset))
624 return ((char *)NULL);
625 if (index_flag == 0 || (index_flag == 1 && ind == 0))
626 return (get_string_value ("PWD"));
627 else
628 return (pushd_directory_list[ind]);
629 }
630
631 #ifdef INCLUDE_UNUSED
632 char *
633 get_dirstack_element (ind, sign)
634 intmax_t ind;
635 int sign;
636 {
637 int i;
638
639 i = get_dirstack_index (ind, sign, (int *)NULL);
640 return (i < 0 || i > directory_list_offset) ? (char *)NULL
641 : pushd_directory_list[i];
642 }
643 #endif
644
645 void
646 set_dirstack_element (ind, sign, value)
647 intmax_t ind;
648 int sign;
649 char *value;
650 {
651 int i;
652
653 i = get_dirstack_index (ind, sign, (int *)NULL);
654 if (ind == 0 || i < 0 || i > directory_list_offset)
655 return;
656 free (pushd_directory_list[i]);
657 pushd_directory_list[i] = savestring (value);
658 }
659
660 WORD_LIST *
661 get_directory_stack (flags)
662 int flags;
663 {
664 register int i;
665 WORD_LIST *ret;
666 char *d, *t;
667
668 for (ret = (WORD_LIST *)NULL, i = 0; i < directory_list_offset; i++)
669 {
670 d = (flags&1) ? polite_directory_format (pushd_directory_list[i])
671 : pushd_directory_list[i];
672 ret = make_word_list (make_word (d), ret);
673 }
674 /* Now the current directory. */
675 d = get_working_directory ("dirstack");
676 i = 0; /* sentinel to decide whether or not to free d */
677 if (d == 0)
678 d = ".";
679 else
680 {
681 t = (flags&1) ? polite_directory_format (d) : d;
682 /* polite_directory_format sometimes returns its argument unchanged.
683 If it does not, we can free d right away. If it does, we need to
684 mark d to be deleted later. */
685 if (t != d)
686 {
687 free (d);
688 d = t;
689 }
690 else /* t == d, so d is what we want */
691 i = 1;
692 }
693 ret = make_word_list (make_word (d), ret);
694 if (i)
695 free (d);
696 return ret; /* was (REVERSE_LIST (ret, (WORD_LIST *)); */
697 }
698
699 #ifdef LOADABLE_BUILTIN
700 char * const dirs_doc[] = {
701 N_("Display the list of currently remembered directories. Directories\n\
702 find their way onto the list with the `pushd' command; you can get\n\
703 back up through the list with the `popd' command.\n\
704 \n\
705 Options:\n\
706 -c clear the directory stack by deleting all of the elements\n\
707 -l do not print tilde-prefixed versions of directories relative\n\
708 to your home directory\n\
709 -p print the directory stack with one entry per line\n\
710 -v print the directory stack with one entry per line prefixed\n\
711 with its position in the stack\n\
712 \n\
713 Arguments:\n\
714 +N Displays the Nth entry counting from the left of the list shown by\n\
715 dirs when invoked without options, starting with zero.\n\
716 \n\
717 -N Displays the Nth entry counting from the right of the list shown by\n\
718 dirs when invoked without options, starting with zero."),
719 (char *)NULL
720 };
721
722 char * const pushd_doc[] = {
723 N_("Adds a directory to the top of the directory stack, or rotates\n\
724 the stack, making the new top of the stack the current working\n\
725 directory. With no arguments, exchanges the top two directories.\n\
726 \n\
727 Options:\n\
728 -n Suppresses the normal change of directory when adding\n\
729 directories to the stack, so only the stack is manipulated.\n\
730 \n\
731 Arguments:\n\
732 +N Rotates the stack so that the Nth directory (counting\n\
733 from the left of the list shown by `dirs', starting with\n\
734 zero) is at the top.\n\
735 \n\
736 -N Rotates the stack so that the Nth directory (counting\n\
737 from the right of the list shown by `dirs', starting with\n\
738 zero) is at the top.\n\
739 \n\
740 dir Adds DIR to the directory stack at the top, making it the\n\
741 new current working directory.\n\
742 \n\
743 The `dirs' builtin displays the directory stack."),
744 (char *)NULL
745 };
746
747 char * const popd_doc[] = {
748 N_("Removes entries from the directory stack. With no arguments, removes\n\
749 the top directory from the stack, and changes to the new top directory.\n\
750 \n\
751 Options:\n\
752 -n Suppresses the normal change of directory when removing\n\
753 directories from the stack, so only the stack is manipulated.\n\
754 \n\
755 Arguments:\n\
756 +N Removes the Nth entry counting from the left of the list\n\
757 shown by `dirs', starting with zero. For example: `popd +0'\n\
758 removes the first directory, `popd +1' the second.\n\
759 \n\
760 -N Removes the Nth entry counting from the right of the list\n\
761 shown by `dirs', starting with zero. For example: `popd -0'\n\
762 removes the last directory, `popd -1' the next to last.\n\
763 \n\
764 The `dirs' builtin displays the directory stack."),
765 (char *)NULL
766 };
767
768 struct builtin pushd_struct = {
769 "pushd",
770 pushd_builtin,
771 BUILTIN_ENABLED,
772 pushd_doc,
773 "pushd [+N | -N] [-n] [dir]",
774 0
775 };
776
777 struct builtin popd_struct = {
778 "popd",
779 popd_builtin,
780 BUILTIN_ENABLED,
781 popd_doc,
782 "popd [+N | -N] [-n]",
783 0
784 };
785
786 struct builtin dirs_struct = {
787 "dirs",
788 dirs_builtin,
789 BUILTIN_ENABLED,
790 dirs_doc,
791 "dirs [-clpv] [+N] [-N]",
792 0
793 };
794 #endif /* LOADABLE_BUILTIN */
795
796 #endif /* PUSHD_AND_POPD */