1/* history.c, created from history.def. */
2#line 23 "history.def"
3
4#line 50 "history.def"
5
6#include <config.h>
7
8#if defined (HISTORY)
9#include "../bashtypes.h"
10#if ! defined(_MINIX) && defined (HAVE_SYS_FILE_H)
11#  include <sys/file.h>
12#endif
13#include "posixstat.h"
14#include "filecntl.h"
15#include <errno.h>
16#include <stdio.h>
17#if defined (HAVE_UNISTD_H)
18#  include <unistd.h>
19#endif
20
21#include "../bashansi.h"
22#include "../bashintl.h"
23
24#include "../shell.h"
25#include "../bashhist.h"
26#include <readline/history.h>
27#include "bashgetopt.h"
28#include "common.h"
29
30#if !defined (errno)
31extern int errno;
32#endif
33
34extern int current_command_line_count;
35extern int force_append_history;	/* shopt -s histappend */
36
37int delete_last_history __P((void));
38
39static char *histtime __P((HIST_ENTRY *, const char *));
40static void display_history __P((WORD_LIST *));
41static int delete_histent __P((int));
42static void push_history __P((WORD_LIST *));
43static int expand_and_print_history __P((WORD_LIST *));
44
45#define AFLAG	0x01
46#define RFLAG	0x02
47#define WFLAG	0x04
48#define NFLAG	0x08
49#define SFLAG	0x10
50#define PFLAG	0x20
51#define CFLAG	0x40
52#define DFLAG	0x80
53
54int
55history_builtin (list)
56     WORD_LIST *list;
57{
58  int flags, opt, result, old_history_lines, obase;
59  char *filename, *delete_arg;
60  intmax_t delete_offset;
61
62  flags = 0;
63  reset_internal_getopt ();
64  while ((opt = internal_getopt (list, "acd:npsrw")) != -1)
65    {
66      switch (opt)
67	{
68	case 'a':
69	  flags |= AFLAG;
70	  break;
71	case 'c':
72	  flags |= CFLAG;
73	  break;
74	case 'n':
75	  flags |= NFLAG;
76	  break;
77	case 'r':
78	  flags |= RFLAG;
79	  break;
80	case 'w':
81	  flags |= WFLAG;
82	  break;
83	case 's':
84	  flags |= SFLAG;
85	  break;
86	case 'd':
87	  flags |= DFLAG;
88	  delete_arg = list_optarg;
89	  break;
90	case 'p':
91#if defined (BANG_HISTORY)
92	  flags |= PFLAG;
93#endif
94	  break;
95	default:
96	  builtin_usage ();
97	  return (EX_USAGE);
98	}
99    }
100  list = loptend;
101
102  opt = flags & (AFLAG|RFLAG|WFLAG|NFLAG);
103  if (opt && opt != AFLAG && opt != RFLAG && opt != WFLAG && opt != NFLAG)
104    {
105      builtin_error (_("cannot use more than one of -anrw"));
106      return (EXECUTION_FAILURE);
107    }
108
109  /* clear the history, but allow other arguments to add to it again. */
110  if (flags & CFLAG)
111    {
112      clear_history ();
113      if (list == 0)
114	return (EXECUTION_SUCCESS);
115    }
116
117  if (flags & SFLAG)
118    {
119      if (list)
120	push_history (list);
121      return (EXECUTION_SUCCESS);
122    }
123#if defined (BANG_HISTORY)
124  else if (flags & PFLAG)
125    {
126      if (list)
127	return (expand_and_print_history (list));
128      return (EXECUTION_SUCCESS);
129    }
130#endif
131  else if (flags & DFLAG)
132    {
133      if ((legal_number (delete_arg, &delete_offset) == 0)
134	  || (delete_offset < history_base)
135	  || (delete_offset > (history_base + history_length)))
136	{
137	  sh_erange (delete_arg, _("history position"));
138	  return (EXECUTION_FAILURE);
139	}
140      opt = delete_offset;
141      result = delete_histent (opt - history_base);
142      /* Since remove_history changes history_length, this can happen if
143	 we delete the last history entry. */
144      if (where_history () > history_length)
145	history_set_pos (history_length);
146      return (result ? EXECUTION_SUCCESS : EXECUTION_FAILURE);
147    }
148  else if ((flags & (AFLAG|RFLAG|NFLAG|WFLAG|CFLAG)) == 0)
149    {
150      display_history (list);
151      return (EXECUTION_SUCCESS);
152    }
153
154  filename = list ? list->word->word : get_string_value ("HISTFILE");
155  result = EXECUTION_SUCCESS;
156
157  if (flags & AFLAG)		/* Append session's history to file. */
158    result = maybe_append_history (filename);
159  else if (flags & WFLAG)	/* Write entire history. */
160    result = write_history (filename);
161  else if (flags & RFLAG)	/* Read entire file. */
162    result = read_history (filename);
163  else if (flags & NFLAG)	/* Read `new' history from file. */
164    {
165      /* Read all of the lines in the file that we haven't already read. */
166      old_history_lines = history_lines_in_file;
167      obase = history_base;
168
169      using_history ();
170      result = read_history_range (filename, history_lines_in_file, -1);
171      using_history ();
172
173      history_lines_in_file = where_history ();
174
175      /* If we're rewriting the history file at shell exit rather than just
176	 appending the lines from this session to it, the question is whether
177	 we reset history_lines_this_session to 0, losing any history entries
178	 we had before we read the new entries from the history file, or
179	 whether we count the new entries we just read from the file as
180	 history lines added during this session.
181	 Right now, we do the latter.  This will cause these history entries
182	 to be written to the history file along with any intermediate entries
183	 we add when we do a `history -a', but the alternative is losing
184	 them altogether. */
185      if (force_append_history == 0)
186	history_lines_this_session += history_lines_in_file - old_history_lines +
187				    history_base - obase;
188    }
189
190  return (result ? EXECUTION_FAILURE : EXECUTION_SUCCESS);
191}
192
193/* Accessors for HIST_ENTRY lists that are called HLIST. */
194#define histline(i) (hlist[(i)]->line)
195#define histdata(i) (hlist[(i)]->data)
196
197static char *
198histtime (hlist, histtimefmt)
199     HIST_ENTRY *hlist;
200     const char *histtimefmt;
201{
202  static char timestr[128];
203  time_t t;
204
205  t = history_get_time (hlist);
206  if (t)
207    strftime (timestr, sizeof (timestr), histtimefmt, localtime (&t));
208  else
209    strcpy (timestr, "??");
210  return timestr;
211}
212
213static void
214display_history (list)
215     WORD_LIST *list;
216{
217  register int i;
218  intmax_t limit;
219  HIST_ENTRY **hlist;
220  char *histtimefmt, *timestr;
221
222  if (list)
223    {
224      limit = get_numeric_arg (list, 0);
225      if (limit < 0)
226	limit = -limit;
227    }
228  else
229    limit = -1;
230
231  hlist = history_list ();
232
233  if (hlist)
234    {
235      for (i = 0;  hlist[i]; i++)
236	;
237
238      if (0 <= limit && limit < i)
239	i -= limit;
240      else
241	i = 0;
242
243
244      histtimefmt = get_string_value ("HISTTIMEFORMAT");
245
246      while (hlist[i])
247	{
248	  QUIT;
249
250	  timestr = (histtimefmt && *histtimefmt) ? histtime (hlist[i], histtimefmt) : (char *)NULL;
251	  printf ("%5d%c %s%s\n", i + history_base,
252		  histdata(i) ? '*' : ' ',
253		  ((timestr && *timestr) ? timestr : ""),
254		  histline(i));
255	  i++;
256	}
257    }
258}
259
260/* Delete and free the history list entry at offset I. */
261static int
262delete_histent (i)
263     int i;
264{
265  HIST_ENTRY *discard;
266
267  discard = remove_history (i);
268  if (discard)
269    free_history_entry (discard);
270
271  return 1;
272}
273
274int
275delete_last_history ()
276{
277  register int i;
278  HIST_ENTRY **hlist, *histent;
279  int r;
280
281  hlist = history_list ();
282  if (hlist == NULL)
283    return 0;
284
285  for (i = 0; hlist[i]; i++)
286    ;
287  i--;
288
289  /* History_get () takes a parameter that must be offset by history_base. */
290  histent = history_get (history_base + i);	/* Don't free this */
291  if (histent == NULL)
292    return 0;
293
294  r = delete_histent (i);
295
296  if (where_history () > history_length)
297    history_set_pos (history_length);
298
299  return r;
300}
301
302/* Remove the last entry in the history list and add each argument in
303   LIST to the history. */
304static void
305push_history (list)
306     WORD_LIST *list;
307{
308  char *s;
309
310  /* Delete the last history entry if it was a single entry added to the
311     history list (generally the `history -s' itself), or if `history -s'
312     is being used in a compound command and the compound command was
313     added to the history as a single element (command-oriented history).
314     If you don't want history -s to remove the compound command from the
315     history, change #if 0 to #if 1 below. */
316#if 0
317  if (hist_last_line_pushed == 0 && hist_last_line_added && delete_last_history () == 0)
318#else
319  if (hist_last_line_pushed == 0 &&
320	(hist_last_line_added ||
321	  (current_command_line_count > 0 && current_command_first_line_saved && command_oriented_history))
322      && delete_last_history () == 0)
323#endif
324      return;
325
326  s = string_list (list);
327  /* Call check_add_history with FORCE set to 1 to skip the check against
328     current_command_line_count.  If history -s is used in a compound
329     command, the above code will delete the compound command's history
330     entry and this call will add the line to the history as a separate
331     entry.  Without FORCE=1, if current_command_line_count were > 1, the
332     line would be appended to the entry before the just-deleted entry. */
333  check_add_history (s, 1);	/* obeys HISTCONTROL, HISTIGNORE */
334
335  hist_last_line_pushed = 1;	/* XXX */
336  free (s);
337}
338
339#if defined (BANG_HISTORY)
340static int
341expand_and_print_history (list)
342     WORD_LIST *list;
343{
344  char *s;
345  int r, result;
346
347  if (hist_last_line_pushed == 0 && hist_last_line_added && delete_last_history () == 0)
348    return EXECUTION_FAILURE;
349  result = EXECUTION_SUCCESS;
350  while (list)
351    {
352      r = history_expand (list->word->word, &s);
353      if (r < 0)
354	{
355	  builtin_error (_("%s: history expansion failed"), list->word->word);
356	  result = EXECUTION_FAILURE;
357	}
358      else
359	{
360	  fputs (s, stdout);
361	  putchar ('\n');
362	}
363      FREE (s);
364      list = list->next;
365    }
366  fflush (stdout);
367  return result;
368}
369#endif /* BANG_HISTORY */
370#endif /* HISTORY */
371