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