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