GCC Code Coverage Report | |||||||||||||||||||||
|
|||||||||||||||||||||
Line | Branch | Exec | Source |
1 |
/* $OpenBSD: history.c,v 1.74 2017/10/23 15:43:38 jca Exp $ */ |
||
2 |
|||
3 |
/* |
||
4 |
* command history |
||
5 |
*/ |
||
6 |
|||
7 |
/* |
||
8 |
* This file contains |
||
9 |
* a) the original in-memory history mechanism |
||
10 |
* b) a more complicated mechanism done by pc@hillside.co.uk |
||
11 |
* that more closely follows the real ksh way of doing |
||
12 |
* things. |
||
13 |
*/ |
||
14 |
|||
15 |
#include <sys/stat.h> |
||
16 |
#include <sys/uio.h> |
||
17 |
|||
18 |
#include <errno.h> |
||
19 |
#include <fcntl.h> |
||
20 |
#include <stdlib.h> |
||
21 |
#include <stdio.h> |
||
22 |
#include <string.h> |
||
23 |
#include <unistd.h> |
||
24 |
#include <vis.h> |
||
25 |
|||
26 |
#include "sh.h" |
||
27 |
|||
28 |
#ifdef HISTORY |
||
29 |
|||
30 |
static void history_write(void); |
||
31 |
static FILE *history_open(void); |
||
32 |
static void history_load(Source *); |
||
33 |
static void history_close(void); |
||
34 |
|||
35 |
static int hist_execute(char *); |
||
36 |
static int hist_replace(char **, const char *, const char *, int); |
||
37 |
static char **hist_get(const char *, int, int); |
||
38 |
static char **hist_get_oldest(void); |
||
39 |
static void histbackup(void); |
||
40 |
|||
41 |
static FILE *histfh; |
||
42 |
static char **histbase; /* actual start of the history[] allocation */ |
||
43 |
static char **current; /* current position in history[] */ |
||
44 |
static char *hname; /* current name of history file */ |
||
45 |
static int hstarted; /* set after hist_init() called */ |
||
46 |
static int ignoredups; /* ditch duplicated history lines? */ |
||
47 |
static int ignorespace; /* ditch lines starting with a space? */ |
||
48 |
static Source *hist_source; |
||
49 |
static uint32_t line_co; |
||
50 |
|||
51 |
static struct stat last_sb; |
||
52 |
|||
53 |
int |
||
54 |
c_fc(char **wp) |
||
55 |
{ |
||
56 |
struct shf *shf; |
||
57 |
struct temp *tf = NULL; |
||
58 |
char *p, *editor = NULL; |
||
59 |
int gflag = 0, lflag = 0, nflag = 0, sflag = 0, rflag = 0; |
||
60 |
int optc, ret; |
||
61 |
char *first = NULL, *last = NULL; |
||
62 |
char **hfirst, **hlast, **hp; |
||
63 |
static int depth; |
||
64 |
|||
65 |
✗✓ | 336 |
if (depth != 0) { |
66 |
bi_errorf("history function called recursively"); |
||
67 |
return 1; |
||
68 |
} |
||
69 |
|||
70 |
✗✓ | 168 |
if (!Flag(FTALKING_I)) { |
71 |
bi_errorf("history functions not available"); |
||
72 |
return 1; |
||
73 |
} |
||
74 |
|||
75 |
✓✓ | 1206 |
while ((optc = ksh_getopt(wp, &builtin_opt, |
76 |
402 |
"e:glnrs0,1,2,3,4,5,6,7,8,9,")) != -1) |
|
77 |
✓✓✓✗ ✓✗✗✗ ✗✗✗✗ ✗✗✗✓ ✗✓ |
468 |
switch (optc) { |
78 |
case 'e': |
||
79 |
60 |
p = builtin_opt.optarg; |
|
80 |
✓✗ | 60 |
if (strcmp(p, "-") == 0) |
81 |
60 |
sflag++; |
|
82 |
else { |
||
83 |
size_t len = strlen(p) + 4; |
||
84 |
editor = str_nsave(p, len, ATEMP); |
||
85 |
strlcat(editor, " $_", len); |
||
86 |
} |
||
87 |
break; |
||
88 |
case 'g': /* non-at&t ksh */ |
||
89 |
6 |
gflag++; |
|
90 |
6 |
break; |
|
91 |
case 'l': |
||
92 |
108 |
lflag++; |
|
93 |
108 |
break; |
|
94 |
case 'n': |
||
95 |
nflag++; |
||
96 |
break; |
||
97 |
case 'r': |
||
98 |
12 |
rflag++; |
|
99 |
12 |
break; |
|
100 |
case 's': /* posix version of -e - */ |
||
101 |
sflag++; |
||
102 |
break; |
||
103 |
/* kludge city - accept -num as -- -num (kind of) */ |
||
104 |
case '0': case '1': case '2': case '3': case '4': |
||
105 |
case '5': case '6': case '7': case '8': case '9': |
||
106 |
48 |
p = shf_smprintf("-%c%s", |
|
107 |
48 |
optc, builtin_opt.optarg); |
|
108 |
✓✓ | 48 |
if (!first) |
109 |
24 |
first = p; |
|
110 |
✓✗ | 24 |
else if (!last) |
111 |
last = p; |
||
112 |
else { |
||
113 |
bi_errorf("too many arguments"); |
||
114 |
return 1; |
||
115 |
} |
||
116 |
break; |
||
117 |
case '?': |
||
118 |
return 1; |
||
119 |
} |
||
120 |
168 |
wp += builtin_opt.optind; |
|
121 |
|||
122 |
/* Substitute and execute command */ |
||
123 |
✓✓ | 168 |
if (sflag) { |
124 |
char *pat = NULL, *rep = NULL; |
||
125 |
|||
126 |
✗✓ | 60 |
if (editor || lflag || nflag || rflag) { |
127 |
bi_errorf("can't use -e, -l, -n, -r with -s (-e -)"); |
||
128 |
return 1; |
||
129 |
} |
||
130 |
|||
131 |
/* Check for pattern replacement argument */ |
||
132 |
✓✓✓✗ ✓✓ |
132 |
if (*wp && **wp && (p = strchr(*wp + 1, '='))) { |
133 |
30 |
pat = str_save(*wp, ATEMP); |
|
134 |
30 |
p = pat + (p - *wp); |
|
135 |
30 |
*p++ = '\0'; |
|
136 |
rep = p; |
||
137 |
30 |
wp++; |
|
138 |
30 |
} |
|
139 |
/* Check for search prefix */ |
||
140 |
✓✗✓✓ |
120 |
if (!first && (first = *wp)) |
141 |
24 |
wp++; |
|
142 |
✓✗✗✓ |
120 |
if (last || *wp) { |
143 |
bi_errorf("too many arguments"); |
||
144 |
return 1; |
||
145 |
} |
||
146 |
|||
147 |
✓✓ | 180 |
hp = first ? hist_get(first, false, false) : |
148 |
36 |
hist_get_newest(false); |
|
149 |
✓✓ | 60 |
if (!hp) |
150 |
6 |
return 1; |
|
151 |
54 |
depth++; |
|
152 |
54 |
ret = hist_replace(hp, pat, rep, gflag); |
|
153 |
54 |
depth--; |
|
154 |
54 |
return ret; |
|
155 |
} |
||
156 |
|||
157 |
✗✓✗✗ |
108 |
if (editor && (lflag || nflag)) { |
158 |
bi_errorf("can't use -l, -n with -e"); |
||
159 |
return 1; |
||
160 |
} |
||
161 |
|||
162 |
✓✓✓✓ |
192 |
if (!first && (first = *wp)) |
163 |
42 |
wp++; |
|
164 |
✓✓✓✓ |
192 |
if (!last && (last = *wp)) |
165 |
30 |
wp++; |
|
166 |
✗✓ | 108 |
if (*wp) { |
167 |
bi_errorf("too many arguments"); |
||
168 |
return 1; |
||
169 |
} |
||
170 |
✓✓ | 108 |
if (!first) { |
171 |
✓✗ | 126 |
hfirst = lflag ? hist_get("-16", true, true) : |
172 |
hist_get_newest(false); |
||
173 |
✗✓ | 42 |
if (!hfirst) |
174 |
return 1; |
||
175 |
/* can't fail if hfirst didn't fail */ |
||
176 |
42 |
hlast = hist_get_newest(false); |
|
177 |
42 |
} else { |
|
178 |
/* POSIX says not an error if first/last out of bounds |
||
179 |
* when range is specified; at&t ksh and pdksh allow out of |
||
180 |
* bounds for -l as well. |
||
181 |
*/ |
||
182 |
132 |
hfirst = hist_get(first, (lflag || last) ? true : false, |
|
183 |
66 |
lflag ? true : false); |
|
184 |
✗✓ | 66 |
if (!hfirst) |
185 |
return 1; |
||
186 |
✓✓ | 186 |
hlast = last ? hist_get(last, true, lflag ? true : false) : |
187 |
✓✗ | 24 |
(lflag ? hist_get_newest(false) : hfirst); |
188 |
✗✓ | 66 |
if (!hlast) |
189 |
return 1; |
||
190 |
} |
||
191 |
✓✓ | 108 |
if (hfirst > hlast) { |
192 |
char **temp; |
||
193 |
|||
194 |
temp = hfirst; hfirst = hlast; hlast = temp; |
||
195 |
12 |
rflag = !rflag; /* POSIX */ |
|
196 |
12 |
} |
|
197 |
|||
198 |
/* List history */ |
||
199 |
✓✗ | 108 |
if (lflag) { |
200 |
char *s, *t; |
||
201 |
108 |
const char *nfmt = nflag ? "\t" : "%d\t"; |
|
202 |
|||
203 |
✓✓ | 624 |
for (hp = rflag ? hlast : hfirst; |
204 |
✓✓ | 834 |
hp >= hfirst && hp <= hlast; hp += rflag ? -1 : 1) { |
205 |
210 |
shf_fprintf(shl_stdout, nfmt, |
|
206 |
210 |
hist_source->line - (int) (histptr - hp)); |
|
207 |
/* print multi-line commands correctly */ |
||
208 |
✗✓ | 420 |
for (s = *hp; (t = strchr(s, '\n')); s = t) |
209 |
shf_fprintf(shl_stdout, "%.*s\t", ++t - s, s); |
||
210 |
210 |
shf_fprintf(shl_stdout, "%s\n", s); |
|
211 |
} |
||
212 |
108 |
shf_flush(shl_stdout); |
|
213 |
return 0; |
||
214 |
} |
||
215 |
|||
216 |
/* Run editor on selected lines, then run resulting commands */ |
||
217 |
|||
218 |
tf = maketemp(ATEMP, TT_HIST_EDIT, &genv->temps); |
||
219 |
if (!(shf = tf->shf)) { |
||
220 |
bi_errorf("cannot create temp file %s - %s", |
||
221 |
tf->name, strerror(errno)); |
||
222 |
return 1; |
||
223 |
} |
||
224 |
for (hp = rflag ? hlast : hfirst; |
||
225 |
hp >= hfirst && hp <= hlast; hp += rflag ? -1 : 1) |
||
226 |
shf_fprintf(shf, "%s\n", *hp); |
||
227 |
if (shf_close(shf) == EOF) { |
||
228 |
bi_errorf("error writing temporary file - %s", strerror(errno)); |
||
229 |
return 1; |
||
230 |
} |
||
231 |
|||
232 |
/* Ignore setstr errors here (arbitrary) */ |
||
233 |
setstr(local("_", false), tf->name, KSH_RETURN_ERROR); |
||
234 |
|||
235 |
/* XXX: source should not get trashed by this.. */ |
||
236 |
{ |
||
237 |
Source *sold = source; |
||
238 |
|||
239 |
ret = command(editor ? editor : "${FCEDIT:-/bin/ed} $_", 0); |
||
240 |
source = sold; |
||
241 |
if (ret) |
||
242 |
return ret; |
||
243 |
} |
||
244 |
|||
245 |
{ |
||
246 |
struct stat statb; |
||
247 |
XString xs; |
||
248 |
char *xp; |
||
249 |
int n; |
||
250 |
|||
251 |
if (!(shf = shf_open(tf->name, O_RDONLY, 0, 0))) { |
||
252 |
bi_errorf("cannot open temp file %s", tf->name); |
||
253 |
return 1; |
||
254 |
} |
||
255 |
|||
256 |
n = fstat(shf->fd, &statb) < 0 ? 128 : |
||
257 |
statb.st_size + 1; |
||
258 |
Xinit(xs, xp, n, hist_source->areap); |
||
259 |
while ((n = shf_read(xp, Xnleft(xs, xp), shf)) > 0) { |
||
260 |
xp += n; |
||
261 |
if (Xnleft(xs, xp) <= 0) |
||
262 |
XcheckN(xs, xp, Xlength(xs, xp)); |
||
263 |
} |
||
264 |
if (n < 0) { |
||
265 |
bi_errorf("error reading temp file %s - %s", |
||
266 |
tf->name, strerror(shf->errno_)); |
||
267 |
shf_close(shf); |
||
268 |
return 1; |
||
269 |
} |
||
270 |
shf_close(shf); |
||
271 |
*xp = '\0'; |
||
272 |
strip_nuls(Xstring(xs, xp), Xlength(xs, xp)); |
||
273 |
depth++; |
||
274 |
ret = hist_execute(Xstring(xs, xp)); |
||
275 |
depth--; |
||
276 |
return ret; |
||
277 |
} |
||
278 |
168 |
} |
|
279 |
|||
280 |
/* Save cmd in history, execute cmd (cmd gets trashed) */ |
||
281 |
static int |
||
282 |
hist_execute(char *cmd) |
||
283 |
{ |
||
284 |
Source *sold; |
||
285 |
int ret; |
||
286 |
char *p, *q; |
||
287 |
|||
288 |
108 |
histbackup(); |
|
289 |
|||
290 |
✓✓ | 216 |
for (p = cmd; p; p = q) { |
291 |
✗✓ | 54 |
if ((q = strchr(p, '\n'))) { |
292 |
*q++ = '\0'; /* kill the newline */ |
||
293 |
if (!*q) /* ignore trailing newline */ |
||
294 |
q = NULL; |
||
295 |
} |
||
296 |
54 |
histsave(++(hist_source->line), p, 1); |
|
297 |
|||
298 |
54 |
shellf("%s\n", p); /* POSIX doesn't say this is done... */ |
|
299 |
✗✓ | 54 |
if ((p = q)) /* restore \n (trailing \n not restored) */ |
300 |
q[-1] = '\n'; |
||
301 |
} |
||
302 |
|||
303 |
/* Commands are executed here instead of pushing them onto the |
||
304 |
* input 'cause posix says the redirection and variable assignments |
||
305 |
* in |
||
306 |
* X=y fc -e - 42 2> /dev/null |
||
307 |
* are to effect the repeated commands environment. |
||
308 |
*/ |
||
309 |
/* XXX: source should not get trashed by this.. */ |
||
310 |
54 |
sold = source; |
|
311 |
54 |
ret = command(cmd, 0); |
|
312 |
54 |
source = sold; |
|
313 |
54 |
return ret; |
|
314 |
} |
||
315 |
|||
316 |
static int |
||
317 |
hist_replace(char **hp, const char *pat, const char *rep, int global) |
||
318 |
{ |
||
319 |
char *line; |
||
320 |
|||
321 |
✓✓ | 108 |
if (!pat) |
322 |
24 |
line = str_save(*hp, ATEMP); |
|
323 |
else { |
||
324 |
char *s, *s1; |
||
325 |
30 |
int pat_len = strlen(pat); |
|
326 |
30 |
int rep_len = strlen(rep); |
|
327 |
int len; |
||
328 |
30 |
XString xs; |
|
329 |
char *xp; |
||
330 |
int any_subst = 0; |
||
331 |
|||
332 |
30 |
Xinit(xs, xp, 128, ATEMP); |
|
333 |
✓✓✓✗ |
240 |
for (s = *hp; (s1 = strstr(s, pat)) && (!any_subst || global); |
334 |
60 |
s = s1 + pat_len) { |
|
335 |
any_subst = 1; |
||
336 |
60 |
len = s1 - s; |
|
337 |
✗✓ | 60 |
XcheckN(xs, xp, len + rep_len); |
338 |
60 |
memcpy(xp, s, len); /* first part */ |
|
339 |
60 |
xp += len; |
|
340 |
60 |
memcpy(xp, rep, rep_len); /* replacement */ |
|
341 |
60 |
xp += rep_len; |
|
342 |
} |
||
343 |
✗✓ | 30 |
if (!any_subst) { |
344 |
bi_errorf("substitution failed"); |
||
345 |
return 1; |
||
346 |
} |
||
347 |
30 |
len = strlen(s) + 1; |
|
348 |
✗✓ | 30 |
XcheckN(xs, xp, len); |
349 |
30 |
memcpy(xp, s, len); |
|
350 |
30 |
xp += len; |
|
351 |
30 |
line = Xclose(xs, xp); |
|
352 |
✓✗ | 60 |
} |
353 |
54 |
return hist_execute(line); |
|
354 |
54 |
} |
|
355 |
|||
356 |
/* |
||
357 |
* get pointer to history given pattern |
||
358 |
* pattern is a number or string |
||
359 |
*/ |
||
360 |
static char ** |
||
361 |
hist_get(const char *str, int approx, int allow_cur) |
||
362 |
{ |
||
363 |
char **hp = NULL; |
||
364 |
372 |
int n; |
|
365 |
|||
366 |
✓✓ | 186 |
if (getn(str, &n)) { |
367 |
✓✓ | 384 |
hp = histptr + (n < 0 ? n : (n - hist_source->line)); |
368 |
✓✓ | 162 |
if ((long)hp < (long)history) { |
369 |
✓✗ | 54 |
if (approx) |
370 |
54 |
hp = hist_get_oldest(); |
|
371 |
else { |
||
372 |
bi_errorf("%s: not in history", str); |
||
373 |
hp = NULL; |
||
374 |
} |
||
375 |
✓✓ | 108 |
} else if (hp > histptr) { |
376 |
✓✗ | 6 |
if (approx) |
377 |
6 |
hp = hist_get_newest(allow_cur); |
|
378 |
else { |
||
379 |
bi_errorf("%s: not in history", str); |
||
380 |
hp = NULL; |
||
381 |
} |
||
382 |
✗✓✗✗ |
102 |
} else if (!allow_cur && hp == histptr) { |
383 |
bi_errorf("%s: invalid range", str); |
||
384 |
hp = NULL; |
||
385 |
} |
||
386 |
} else { |
||
387 |
✓✓ | 54 |
int anchored = *str == '?' ? (++str, 0) : 1; |
388 |
|||
389 |
/* the -1 is to avoid the current fc command */ |
||
390 |
24 |
n = findhist(histptr - history - 1, 0, str, anchored); |
|
391 |
✗✓ | 24 |
if (n < 0) { |
392 |
bi_errorf("%s: not in history", str); |
||
393 |
hp = NULL; |
||
394 |
} else |
||
395 |
24 |
hp = &history[n]; |
|
396 |
} |
||
397 |
186 |
return hp; |
|
398 |
186 |
} |
|
399 |
|||
400 |
/* Return a pointer to the newest command in the history */ |
||
401 |
char ** |
||
402 |
hist_get_newest(int allow_cur) |
||
403 |
{ |
||
404 |
✓✗✓✓ ✓✓ |
378 |
if (histptr < history || (!allow_cur && histptr == history)) { |
405 |
6 |
bi_errorf("no history (yet)"); |
|
406 |
6 |
return NULL; |
|
407 |
} |
||
408 |
✓✓ | 90 |
if (allow_cur) |
409 |
6 |
return histptr; |
|
410 |
84 |
return histptr - 1; |
|
411 |
96 |
} |
|
412 |
|||
413 |
/* Return a pointer to the oldest command in the history */ |
||
414 |
static char ** |
||
415 |
hist_get_oldest(void) |
||
416 |
{ |
||
417 |
✗✓ | 108 |
if (histptr <= history) { |
418 |
bi_errorf("no history (yet)"); |
||
419 |
return NULL; |
||
420 |
} |
||
421 |
54 |
return history; |
|
422 |
54 |
} |
|
423 |
|||
424 |
/******************************/ |
||
425 |
/* Back up over last histsave */ |
||
426 |
/******************************/ |
||
427 |
static void |
||
428 |
histbackup(void) |
||
429 |
{ |
||
430 |
static int last_line = -1; |
||
431 |
|||
432 |
✓✗✓✗ |
162 |
if (histptr >= history && last_line != hist_source->line) { |
433 |
54 |
hist_source->line--; |
|
434 |
54 |
afree(*histptr, APERM); |
|
435 |
54 |
histptr--; |
|
436 |
54 |
last_line = hist_source->line; |
|
437 |
54 |
} |
|
438 |
54 |
} |
|
439 |
|||
440 |
static void |
||
441 |
histreset(void) |
||
442 |
{ |
||
443 |
char **hp; |
||
444 |
|||
445 |
for (hp = history; hp <= histptr; hp++) |
||
446 |
afree(*hp, APERM); |
||
447 |
|||
448 |
histptr = history - 1; |
||
449 |
hist_source->line = 0; |
||
450 |
} |
||
451 |
|||
452 |
/* |
||
453 |
* Return the current position. |
||
454 |
*/ |
||
455 |
char ** |
||
456 |
histpos(void) |
||
457 |
{ |
||
458 |
return current; |
||
459 |
} |
||
460 |
|||
461 |
int |
||
462 |
histnum(int n) |
||
463 |
{ |
||
464 |
852 |
int last = histptr - history; |
|
465 |
|||
466 |
✗✓✗✗ |
426 |
if (n < 0 || n >= last) { |
467 |
426 |
current = histptr; |
|
468 |
426 |
return last; |
|
469 |
} else { |
||
470 |
current = &history[n]; |
||
471 |
return n; |
||
472 |
} |
||
473 |
426 |
} |
|
474 |
|||
475 |
/* |
||
476 |
* This will become unnecessary if hist_get is modified to allow |
||
477 |
* searching from positions other than the end, and in either |
||
478 |
* direction. |
||
479 |
*/ |
||
480 |
int |
||
481 |
findhist(int start, int fwd, const char *str, int anchored) |
||
482 |
{ |
||
483 |
char **hp; |
||
484 |
48 |
int maxhist = histptr - history; |
|
485 |
24 |
int incr = fwd ? 1 : -1; |
|
486 |
24 |
int len = strlen(str); |
|
487 |
|||
488 |
✓✗✗✓ |
48 |
if (start < 0 || start >= maxhist) |
489 |
start = maxhist; |
||
490 |
|||
491 |
24 |
hp = &history[start]; |
|
492 |
✓✗✓✗ |
108 |
for (; hp >= history && hp <= histptr; hp += incr) |
493 |
✓✓✓✓ ✓✓ |
72 |
if ((anchored && strncmp(*hp, str, len) == 0) || |
494 |
✓✓ | 30 |
(!anchored && strstr(*hp, str))) |
495 |
24 |
return hp - history; |
|
496 |
|||
497 |
return -1; |
||
498 |
24 |
} |
|
499 |
|||
500 |
int |
||
501 |
findhistrel(const char *str) |
||
502 |
{ |
||
503 |
int maxhist = histptr - history; |
||
504 |
int start = maxhist - 1; |
||
505 |
int rec = atoi(str); |
||
506 |
|||
507 |
if (rec == 0) |
||
508 |
return -1; |
||
509 |
if (rec > 0) { |
||
510 |
if (rec > maxhist) |
||
511 |
return -1; |
||
512 |
return rec - 1; |
||
513 |
} |
||
514 |
if (rec > maxhist) |
||
515 |
return -1; |
||
516 |
return start + rec + 1; |
||
517 |
} |
||
518 |
|||
519 |
void |
||
520 |
sethistcontrol(const char *str) |
||
521 |
{ |
||
522 |
36 |
char *spec, *tok, *state; |
|
523 |
|||
524 |
18 |
ignorespace = 0; |
|
525 |
18 |
ignoredups = 0; |
|
526 |
|||
527 |
✗✓ | 18 |
if (str == NULL) |
528 |
return; |
||
529 |
|||
530 |
18 |
spec = str_save(str, ATEMP); |
|
531 |
✓✓ | 96 |
for (tok = strtok_r(spec, ":", &state); tok != NULL; |
532 |
54 |
tok = strtok_r(NULL, ":", &state)) { |
|
533 |
✓✓ | 30 |
if (strcmp(tok, "ignoredups") == 0) |
534 |
ignoredups = 1; |
||
535 |
✓✓ | 18 |
else if (strcmp(tok, "ignorespace") == 0) |
536 |
ignorespace = 1; |
||
537 |
} |
||
538 |
18 |
afree(spec, ATEMP); |
|
539 |
36 |
} |
|
540 |
|||
541 |
/* |
||
542 |
* set history |
||
543 |
* this means reallocating the dataspace |
||
544 |
*/ |
||
545 |
void |
||
546 |
sethistsize(int n) |
||
547 |
{ |
||
548 |
✓✗✓✗ |
36 |
if (n > 0 && n != histsize) { |
549 |
12 |
int offset = histptr - history; |
|
550 |
|||
551 |
/* save most recent history */ |
||
552 |
✗✓ | 12 |
if (offset > n - 1) { |
553 |
char **hp; |
||
554 |
|||
555 |
offset = n - 1; |
||
556 |
for (hp = history; hp < histptr - offset; hp++) |
||
557 |
afree(*hp, APERM); |
||
558 |
memmove(history, histptr - offset, n * sizeof(char *)); |
||
559 |
} |
||
560 |
|||
561 |
12 |
histsize = n; |
|
562 |
12 |
histbase = areallocarray(histbase, n + 1, sizeof(char *), APERM); |
|
563 |
12 |
history = histbase + 1; |
|
564 |
12 |
histptr = history + offset; |
|
565 |
12 |
} |
|
566 |
12 |
} |
|
567 |
|||
568 |
/* |
||
569 |
* set history file |
||
570 |
* This can mean reloading/resetting/starting history file |
||
571 |
* maintenance |
||
572 |
*/ |
||
573 |
void |
||
574 |
sethistfile(const char *name) |
||
575 |
{ |
||
576 |
/* if not started then nothing to do */ |
||
577 |
✗✓ | 4284 |
if (hstarted == 0) |
578 |
return; |
||
579 |
|||
580 |
/* if the name is the same as the name we have */ |
||
581 |
if (hname && strcmp(hname, name) == 0) |
||
582 |
return; |
||
583 |
/* |
||
584 |
* its a new name - possibly |
||
585 |
*/ |
||
586 |
if (hname) { |
||
587 |
afree(hname, APERM); |
||
588 |
hname = NULL; |
||
589 |
histreset(); |
||
590 |
} |
||
591 |
|||
592 |
history_close(); |
||
593 |
hist_init(hist_source); |
||
594 |
2142 |
} |
|
595 |
|||
596 |
/* |
||
597 |
* initialise the history vector |
||
598 |
*/ |
||
599 |
void |
||
600 |
init_histvec(void) |
||
601 |
{ |
||
602 |
✓✗ | 209526 |
if (histbase == NULL) { |
603 |
104763 |
histsize = HISTORYSIZE; |
|
604 |
/* |
||
605 |
* allocate one extra element so that histptr always |
||
606 |
* lies within array bounds |
||
607 |
*/ |
||
608 |
104763 |
histbase = areallocarray(NULL, histsize + 1, sizeof(char *), |
|
609 |
APERM); |
||
610 |
104763 |
history = histbase + 1; |
|
611 |
104763 |
histptr = history - 1; |
|
612 |
104763 |
} |
|
613 |
104763 |
} |
|
614 |
|||
615 |
static void |
||
616 |
history_lock(int operation) |
||
617 |
{ |
||
618 |
✓✗✗✓ |
7260 |
while (flock(fileno(histfh), operation) != 0) { |
619 |
if (errno == EINTR || errno == EAGAIN) |
||
620 |
continue; |
||
621 |
else |
||
622 |
break; |
||
623 |
} |
||
624 |
1452 |
} |
|
625 |
|||
626 |
/* |
||
627 |
* Routines added by Peter Collinson BSDI(Europe)/Hillside Systems to |
||
628 |
* a) permit HISTSIZE to control number of lines of history stored |
||
629 |
* b) maintain a physical history file |
||
630 |
* |
||
631 |
* It turns out that there is a lot of ghastly hackery here |
||
632 |
*/ |
||
633 |
|||
634 |
|||
635 |
/* |
||
636 |
* save command in history |
||
637 |
*/ |
||
638 |
void |
||
639 |
histsave(int lno, const char *cmd, int dowrite) |
||
640 |
{ |
||
641 |
char *c, *cp; |
||
642 |
|||
643 |
✓✓✓✓ |
1818 |
if (ignorespace && cmd[0] == ' ') |
644 |
12 |
return; |
|
645 |
|||
646 |
876 |
c = str_save(cmd, APERM); |
|
647 |
✓✓ | 876 |
if ((cp = strrchr(c, '\n')) != NULL) |
648 |
804 |
*cp = '\0'; |
|
649 |
|||
650 |
/* |
||
651 |
* XXX to properly check for duplicated lines we should first reload |
||
652 |
* the histfile if needed |
||
653 |
*/ |
||
654 |
✓✓✓✓ ✓✓ |
936 |
if (ignoredups && histptr >= history && strcmp(*histptr, c) == 0) { |
655 |
12 |
afree(c, APERM); |
|
656 |
12 |
return; |
|
657 |
} |
||
658 |
|||
659 |
✓✓ | 864 |
if (dowrite && histfh) { |
660 |
#ifndef SMALL |
||
661 |
642 |
struct stat sb; |
|
662 |
|||
663 |
642 |
history_lock(LOCK_EX); |
|
664 |
✓✗✓✗ |
1926 |
if (fstat(fileno(histfh), &sb) != -1) { |
665 |
✓✗✗✓ ✗✗ |
1284 |
if (timespeccmp(&sb.st_mtim, &last_sb.st_mtim, ==)) |
666 |
; /* file is unchanged */ |
||
667 |
else { |
||
668 |
histreset(); |
||
669 |
history_load(hist_source); |
||
670 |
} |
||
671 |
} |
||
672 |
#endif |
||
673 |
642 |
} |
|
674 |
|||
675 |
✓✓ | 864 |
if (histptr < history + histsize - 1) |
676 |
828 |
histptr++; |
|
677 |
else { /* remove oldest command */ |
||
678 |
36 |
afree(*history, APERM); |
|
679 |
72 |
memmove(history, history + 1, |
|
680 |
36 |
(histsize - 1) * sizeof(*history)); |
|
681 |
} |
||
682 |
864 |
*histptr = c; |
|
683 |
|||
684 |
✓✓ | 864 |
if (dowrite && histfh) { |
685 |
#ifndef SMALL |
||
686 |
642 |
char *encoded; |
|
687 |
|||
688 |
/* append to file */ |
||
689 |
✓✗✓✗ |
1284 |
if (fseeko(histfh, 0, SEEK_END) == 0 && |
690 |
642 |
stravis(&encoded, c, VIS_SAFE | VIS_NL) != -1) { |
|
691 |
642 |
fprintf(histfh, "%s\n", encoded); |
|
692 |
642 |
fflush(histfh); |
|
693 |
✓✗ | 1926 |
fstat(fileno(histfh), &last_sb); |
694 |
642 |
line_co++; |
|
695 |
642 |
history_write(); |
|
696 |
642 |
free(encoded); |
|
697 |
642 |
} |
|
698 |
642 |
history_lock(LOCK_UN); |
|
699 |
#endif |
||
700 |
642 |
} |
|
701 |
1752 |
} |
|
702 |
|||
703 |
static FILE * |
||
704 |
history_open(void) |
||
705 |
{ |
||
706 |
FILE *f = NULL; |
||
707 |
#ifndef SMALL |
||
708 |
1760 |
struct stat sb; |
|
709 |
int fd, fddup; |
||
710 |
|||
711 |
✓✓ | 880 |
if ((fd = open(hname, O_RDWR | O_CREAT | O_EXLOCK, 0600)) == -1) |
712 |
712 |
return NULL; |
|
713 |
✓✗✗✓ |
336 |
if (fstat(fd, &sb) == -1 || sb.st_uid != getuid()) { |
714 |
close(fd); |
||
715 |
return NULL; |
||
716 |
} |
||
717 |
168 |
fddup = savefd(fd); |
|
718 |
✓✗ | 168 |
if (fddup != fd) |
719 |
168 |
close(fd); |
|
720 |
|||
721 |
✗✓ | 168 |
if ((f = fdopen(fddup, "r+")) == NULL) |
722 |
close(fddup); |
||
723 |
else |
||
724 |
168 |
last_sb = sb; |
|
725 |
#endif |
||
726 |
168 |
return f; |
|
727 |
880 |
} |
|
728 |
|||
729 |
static void |
||
730 |
history_close(void) |
||
731 |
{ |
||
732 |
✓✓ | 1712 |
if (histfh) { |
733 |
156 |
fflush(histfh); |
|
734 |
156 |
fclose(histfh); |
|
735 |
156 |
histfh = NULL; |
|
736 |
156 |
} |
|
737 |
856 |
} |
|
738 |
|||
739 |
static void |
||
740 |
history_load(Source *s) |
||
741 |
{ |
||
742 |
336 |
char *p, encoded[LINE + 1], line[LINE + 1]; |
|
743 |
int toolongseen = 0; |
||
744 |
|||
745 |
168 |
rewind(histfh); |
|
746 |
168 |
line_co = 1; |
|
747 |
|||
748 |
/* just read it all; will auto resize history upon next command */ |
||
749 |
✓✓ | 384 |
while (fgets(encoded, sizeof(encoded), histfh)) { |
750 |
✓✓ | 36 |
if ((p = strchr(encoded, '\n')) == NULL) { |
751 |
/* discard overlong line */ |
||
752 |
18 |
do { |
|
753 |
/* maybe a missing trailing newline? */ |
||
754 |
✓✓ | 18 |
if (strlen(encoded) != sizeof(encoded) - 1) { |
755 |
6 |
bi_errorf("history file is corrupt"); |
|
756 |
6 |
return; |
|
757 |
} |
||
758 |
✗✓ | 24 |
} while (fgets(encoded, sizeof(encoded), histfh) |
759 |
✓✗ | 24 |
&& strchr(encoded, '\n') == NULL); |
760 |
|||
761 |
✓✓ | 12 |
if (!toolongseen) { |
762 |
toolongseen = 1; |
||
763 |
6 |
bi_errorf("ignored history line(s) longer than" |
|
764 |
" %d bytes", LINE); |
||
765 |
6 |
} |
|
766 |
|||
767 |
12 |
continue; |
|
768 |
} |
||
769 |
18 |
*p = '\0'; |
|
770 |
18 |
s->line = line_co; |
|
771 |
18 |
s->cmd_offset = line_co; |
|
772 |
18 |
strunvis(line, encoded); |
|
773 |
18 |
histsave(line_co, line, 0); |
|
774 |
18 |
line_co++; |
|
775 |
} |
||
776 |
|||
777 |
162 |
history_write(); |
|
778 |
330 |
} |
|
779 |
|||
780 |
#define HMAGIC1 0xab |
||
781 |
#define HMAGIC2 0xcd |
||
782 |
|||
783 |
void |
||
784 |
hist_init(Source *s) |
||
785 |
{ |
||
786 |
int oldmagic1, oldmagic2; |
||
787 |
|||
788 |
✗✓ | 1760 |
if (Flag(FTALKING) == 0) |
789 |
return; |
||
790 |
|||
791 |
880 |
hstarted = 1; |
|
792 |
|||
793 |
880 |
hist_source = s; |
|
794 |
|||
795 |
880 |
hname = str_val(global("HISTFILE")); |
|
796 |
✗✓ | 880 |
if (hname == NULL) |
797 |
return; |
||
798 |
880 |
hname = str_save(hname, APERM); |
|
799 |
880 |
histfh = history_open(); |
|
800 |
✓✓ | 880 |
if (histfh == NULL) |
801 |
712 |
return; |
|
802 |
|||
803 |
168 |
oldmagic1 = fgetc(histfh); |
|
804 |
168 |
oldmagic2 = fgetc(histfh); |
|
805 |
|||
806 |
✓✓ | 168 |
if (oldmagic1 == EOF || oldmagic2 == EOF) { |
807 |
✓✗✗✓ ✗✗✗✗ ✗✗✗✗ |
312 |
if (!feof(histfh) && ferror(histfh)) { |
808 |
history_close(); |
||
809 |
return; |
||
810 |
} |
||
811 |
✗✓ | 12 |
} else if (oldmagic1 == HMAGIC1 && oldmagic2 == HMAGIC2) { |
812 |
bi_errorf("ignoring old style history file"); |
||
813 |
history_close(); |
||
814 |
return; |
||
815 |
} |
||
816 |
|||
817 |
168 |
history_load(s); |
|
818 |
|||
819 |
168 |
history_lock(LOCK_UN); |
|
820 |
1048 |
} |
|
821 |
|||
822 |
static void |
||
823 |
history_write(void) |
||
824 |
{ |
||
825 |
1608 |
char **hp, *encoded; |
|
826 |
|||
827 |
/* see if file has grown over 25% */ |
||
828 |
✓✓ | 804 |
if (line_co < histsize + (histsize / 4)) |
829 |
744 |
return; |
|
830 |
|||
831 |
/* rewrite the whole caboodle */ |
||
832 |
60 |
rewind(histfh); |
|
833 |
✓✗✗✓ |
180 |
if (ftruncate(fileno(histfh), 0) == -1) { |
834 |
bi_errorf("failed to rewrite history file - %s", |
||
835 |
strerror(errno)); |
||
836 |
} |
||
837 |
✓✓ | 456 |
for (hp = history; hp <= histptr; hp++) { |
838 |
✓✗ | 168 |
if (stravis(&encoded, *hp, VIS_SAFE | VIS_NL) != -1) { |
839 |
✗✓ | 336 |
if (fprintf(histfh, "%s\n", encoded) == -1) { |
840 |
168 |
free(encoded); |
|
841 |
return; |
||
842 |
} |
||
843 |
free(encoded); |
||
844 |
} |
||
845 |
} |
||
846 |
|||
847 |
60 |
line_co = histsize; |
|
848 |
|||
849 |
60 |
fflush(histfh); |
|
850 |
✓✗ | 180 |
fstat(fileno(histfh), &last_sb); |
851 |
864 |
} |
|
852 |
|||
853 |
void |
||
854 |
hist_finish(void) |
||
855 |
{ |
||
856 |
1712 |
history_close(); |
|
857 |
856 |
} |
|
858 |
|||
859 |
#else /* HISTORY */ |
||
860 |
|||
861 |
/* No history to be compiled in: dummy routines to avoid lots more ifdefs */ |
||
862 |
void |
||
863 |
init_histvec(void) |
||
864 |
{ |
||
865 |
} |
||
866 |
void |
||
867 |
hist_init(Source *s) |
||
868 |
{ |
||
869 |
} |
||
870 |
void |
||
871 |
hist_finish(void) |
||
872 |
{ |
||
873 |
} |
||
874 |
void |
||
875 |
histsave(int lno, const char *cmd, int dowrite) |
||
876 |
{ |
||
877 |
errorf("history not enabled"); |
||
878 |
} |
||
879 |
#endif /* HISTORY */ |
Generated by: GCOVR (Version 3.3) |