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