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