GCC Code Coverage Report | |||||||||||||||||||||
|
|||||||||||||||||||||
Line | Branch | Exec | Source |
1 |
/* $OpenBSD: history.c,v 1.56 2015/12/30 09:07:00 tedu 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. You need to have the mmap system call for this |
||
13 |
* to work on your system |
||
14 |
*/ |
||
15 |
|||
16 |
#include <sys/stat.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 |
|||
25 |
#include "sh.h" |
||
26 |
|||
27 |
#ifdef HISTORY |
||
28 |
# include <sys/mman.h> |
||
29 |
|||
30 |
/* |
||
31 |
* variables for handling the data file |
||
32 |
*/ |
||
33 |
static int histfd; |
||
34 |
static int hsize; |
||
35 |
|||
36 |
static int hist_count_lines(unsigned char *, int); |
||
37 |
static int hist_shrink(unsigned char *, int); |
||
38 |
static unsigned char *hist_skip_back(unsigned char *,int *,int); |
||
39 |
static void histload(Source *, unsigned char *, int); |
||
40 |
static void histinsert(Source *, int, unsigned char *); |
||
41 |
static void writehistfile(int, char *); |
||
42 |
static int sprinkle(int); |
||
43 |
|||
44 |
static int hist_execute(char *); |
||
45 |
static int hist_replace(char **, const char *, const char *, int); |
||
46 |
static char **hist_get(const char *, int, int); |
||
47 |
static char **hist_get_oldest(void); |
||
48 |
static void histbackup(void); |
||
49 |
|||
50 |
static char **current; /* current position in history[] */ |
||
51 |
static char *hname; /* current name of history file */ |
||
52 |
static int hstarted; /* set after hist_init() called */ |
||
53 |
static Source *hist_source; |
||
54 |
|||
55 |
|||
56 |
int |
||
57 |
c_fc(char **wp) |
||
58 |
46 |
{ |
|
59 |
struct shf *shf; |
||
60 |
46 |
struct temp *tf = NULL; |
|
61 |
46 |
char *p, *editor = NULL; |
|
62 |
46 |
int gflag = 0, lflag = 0, nflag = 0, sflag = 0, rflag = 0; |
|
63 |
int optc; |
||
64 |
46 |
char *first = NULL, *last = NULL; |
|
65 |
char **hfirst, **hlast, **hp; |
||
66 |
|||
67 |
✗✓ | 46 |
if (!Flag(FTALKING_I)) { |
68 |
bi_errorf("history functions not available"); |
||
69 |
return 1; |
||
70 |
} |
||
71 |
|||
72 |
✓✓ | 114 |
while ((optc = ksh_getopt(wp, &builtin_opt, |
73 |
"e:glnrs0,1,2,3,4,5,6,7,8,9,")) != -1) |
||
74 |
✓✓✓✗ ✓✗✓✗ ✗ |
68 |
switch (optc) { |
75 |
case 'e': |
||
76 |
20 |
p = builtin_opt.optarg; |
|
77 |
✓✗ | 20 |
if (strcmp(p, "-") == 0) |
78 |
20 |
sflag++; |
|
79 |
else { |
||
80 |
size_t len = strlen(p) + 4; |
||
81 |
editor = str_nsave(p, len, ATEMP); |
||
82 |
strlcat(editor, " $_", len); |
||
83 |
} |
||
84 |
break; |
||
85 |
case 'g': /* non-at&t ksh */ |
||
86 |
2 |
gflag++; |
|
87 |
2 |
break; |
|
88 |
case 'l': |
||
89 |
26 |
lflag++; |
|
90 |
26 |
break; |
|
91 |
case 'n': |
||
92 |
nflag++; |
||
93 |
break; |
||
94 |
case 'r': |
||
95 |
4 |
rflag++; |
|
96 |
4 |
break; |
|
97 |
case 's': /* posix version of -e - */ |
||
98 |
sflag++; |
||
99 |
break; |
||
100 |
/* kludge city - accept -num as -- -num (kind of) */ |
||
101 |
case '0': case '1': case '2': case '3': case '4': |
||
102 |
case '5': case '6': case '7': case '8': case '9': |
||
103 |
16 |
p = shf_smprintf("-%c%s", |
|
104 |
optc, builtin_opt.optarg); |
||
105 |
✓✓ | 16 |
if (!first) |
106 |
8 |
first = p; |
|
107 |
✓✗ | 8 |
else if (!last) |
108 |
8 |
last = p; |
|
109 |
else { |
||
110 |
bi_errorf("too many arguments"); |
||
111 |
return 1; |
||
112 |
} |
||
113 |
break; |
||
114 |
case '?': |
||
115 |
return 1; |
||
116 |
} |
||
117 |
46 |
wp += builtin_opt.optind; |
|
118 |
|||
119 |
/* Substitute and execute command */ |
||
120 |
✓✓ | 46 |
if (sflag) { |
121 |
20 |
char *pat = NULL, *rep = NULL; |
|
122 |
|||
123 |
✓✗✗✓ |
20 |
if (editor || lflag || nflag || rflag) { |
124 |
bi_errorf("can't use -e, -l, -n, -r with -s (-e -)"); |
||
125 |
return 1; |
||
126 |
} |
||
127 |
|||
128 |
/* Check for pattern replacement argument */ |
||
129 |
✓✓✓✗ ✓✓ |
20 |
if (*wp && **wp && (p = strchr(*wp + 1, '='))) { |
130 |
10 |
pat = str_save(*wp, ATEMP); |
|
131 |
10 |
p = pat + (p - *wp); |
|
132 |
10 |
*p++ = '\0'; |
|
133 |
10 |
rep = p; |
|
134 |
10 |
wp++; |
|
135 |
} |
||
136 |
/* Check for search prefix */ |
||
137 |
✓✗✓✓ |
20 |
if (!first && (first = *wp)) |
138 |
8 |
wp++; |
|
139 |
✓✗✗✓ |
20 |
if (last || *wp) { |
140 |
bi_errorf("too many arguments"); |
||
141 |
return 1; |
||
142 |
} |
||
143 |
|||
144 |
✓✓ | 20 |
hp = first ? hist_get(first, false, false) : |
145 |
hist_get_newest(false); |
||
146 |
✓✓ | 20 |
if (!hp) |
147 |
2 |
return 1; |
|
148 |
18 |
return hist_replace(hp, pat, rep, gflag); |
|
149 |
} |
||
150 |
|||
151 |
✗✓✗✗ |
26 |
if (editor && (lflag || nflag)) { |
152 |
bi_errorf("can't use -l, -n with -e"); |
||
153 |
return 1; |
||
154 |
} |
||
155 |
|||
156 |
✓✓✓✓ |
26 |
if (!first && (first = *wp)) |
157 |
14 |
wp++; |
|
158 |
✓✓✓✓ |
26 |
if (!last && (last = *wp)) |
159 |
10 |
wp++; |
|
160 |
✗✓ | 26 |
if (*wp) { |
161 |
bi_errorf("too many arguments"); |
||
162 |
return 1; |
||
163 |
} |
||
164 |
✓✓ | 26 |
if (!first) { |
165 |
✓✗ | 4 |
hfirst = lflag ? hist_get("-16", true, true) : |
166 |
hist_get_newest(false); |
||
167 |
✗✓ | 4 |
if (!hfirst) |
168 |
return 1; |
||
169 |
/* can't fail if hfirst didn't fail */ |
||
170 |
4 |
hlast = hist_get_newest(false); |
|
171 |
} else { |
||
172 |
/* POSIX says not an error if first/last out of bounds |
||
173 |
* when range is specified; at&t ksh and pdksh allow out of |
||
174 |
* bounds for -l as well. |
||
175 |
*/ |
||
176 |
22 |
hfirst = hist_get(first, (lflag || last) ? true : false, |
|
177 |
lflag ? true : false); |
||
178 |
✗✓ | 22 |
if (!hfirst) |
179 |
return 1; |
||
180 |
✓✓✓✗ |
22 |
hlast = last ? hist_get(last, true, lflag ? true : false) : |
181 |
(lflag ? hist_get_newest(false) : hfirst); |
||
182 |
✗✓ | 22 |
if (!hlast) |
183 |
return 1; |
||
184 |
} |
||
185 |
✓✓ | 26 |
if (hfirst > hlast) { |
186 |
char **temp; |
||
187 |
|||
188 |
4 |
temp = hfirst; hfirst = hlast; hlast = temp; |
|
189 |
4 |
rflag = !rflag; /* POSIX */ |
|
190 |
} |
||
191 |
|||
192 |
/* List history */ |
||
193 |
✓✗ | 26 |
if (lflag) { |
194 |
char *s, *t; |
||
195 |
✗✓ | 26 |
const char *nfmt = nflag ? "\t" : "%d\t"; |
196 |
|||
197 |
✓✓ | 26 |
for (hp = rflag ? hlast : hfirst; |
198 |
✓✓✓✓ |
84 |
hp >= hfirst && hp <= hlast; hp += rflag ? -1 : 1) { |
199 |
58 |
shf_fprintf(shl_stdout, nfmt, |
|
200 |
hist_source->line - (int) (histptr - hp)); |
||
201 |
/* print multi-line commands correctly */ |
||
202 |
✗✓ | 58 |
for (s = *hp; (t = strchr(s, '\n')); s = t) |
203 |
shf_fprintf(shl_stdout, "%.*s\t", ++t - s, s); |
||
204 |
58 |
shf_fprintf(shl_stdout, "%s\n", s); |
|
205 |
} |
||
206 |
26 |
shf_flush(shl_stdout); |
|
207 |
26 |
return 0; |
|
208 |
} |
||
209 |
|||
210 |
/* Run editor on selected lines, then run resulting commands */ |
||
211 |
|||
212 |
tf = maketemp(ATEMP, TT_HIST_EDIT, &genv->temps); |
||
213 |
if (!(shf = tf->shf)) { |
||
214 |
bi_errorf("cannot create temp file %s - %s", |
||
215 |
tf->name, strerror(errno)); |
||
216 |
return 1; |
||
217 |
} |
||
218 |
for (hp = rflag ? hlast : hfirst; |
||
219 |
hp >= hfirst && hp <= hlast; hp += rflag ? -1 : 1) |
||
220 |
shf_fprintf(shf, "%s\n", *hp); |
||
221 |
if (shf_close(shf) == EOF) { |
||
222 |
bi_errorf("error writing temporary file - %s", strerror(errno)); |
||
223 |
return 1; |
||
224 |
} |
||
225 |
|||
226 |
/* Ignore setstr errors here (arbitrary) */ |
||
227 |
setstr(local("_", false), tf->name, KSH_RETURN_ERROR); |
||
228 |
|||
229 |
/* XXX: source should not get trashed by this.. */ |
||
230 |
{ |
||
231 |
Source *sold = source; |
||
232 |
int ret; |
||
233 |
|||
234 |
ret = command(editor ? editor : "${FCEDIT:-/bin/ed} $_", 0); |
||
235 |
source = sold; |
||
236 |
if (ret) |
||
237 |
return ret; |
||
238 |
} |
||
239 |
|||
240 |
{ |
||
241 |
struct stat statb; |
||
242 |
XString xs; |
||
243 |
char *xp; |
||
244 |
int n; |
||
245 |
|||
246 |
if (!(shf = shf_open(tf->name, O_RDONLY, 0, 0))) { |
||
247 |
bi_errorf("cannot open temp file %s", tf->name); |
||
248 |
return 1; |
||
249 |
} |
||
250 |
|||
251 |
n = fstat(shf->fd, &statb) < 0 ? 128 : |
||
252 |
statb.st_size + 1; |
||
253 |
Xinit(xs, xp, n, hist_source->areap); |
||
254 |
while ((n = shf_read(xp, Xnleft(xs, xp), shf)) > 0) { |
||
255 |
xp += n; |
||
256 |
if (Xnleft(xs, xp) <= 0) |
||
257 |
XcheckN(xs, xp, Xlength(xs, xp)); |
||
258 |
} |
||
259 |
if (n < 0) { |
||
260 |
bi_errorf("error reading temp file %s - %s", |
||
261 |
tf->name, strerror(shf->errno_)); |
||
262 |
shf_close(shf); |
||
263 |
return 1; |
||
264 |
} |
||
265 |
shf_close(shf); |
||
266 |
*xp = '\0'; |
||
267 |
strip_nuls(Xstring(xs, xp), Xlength(xs, xp)); |
||
268 |
return hist_execute(Xstring(xs, xp)); |
||
269 |
} |
||
270 |
} |
||
271 |
|||
272 |
/* Save cmd in history, execute cmd (cmd gets trashed) */ |
||
273 |
static int |
||
274 |
hist_execute(char *cmd) |
||
275 |
18 |
{ |
|
276 |
Source *sold; |
||
277 |
int ret; |
||
278 |
char *p, *q; |
||
279 |
|||
280 |
18 |
histbackup(); |
|
281 |
|||
282 |
✓✓ | 36 |
for (p = cmd; p; p = q) { |
283 |
✗✓ | 18 |
if ((q = strchr(p, '\n'))) { |
284 |
*q++ = '\0'; /* kill the newline */ |
||
285 |
if (!*q) /* ignore trailing newline */ |
||
286 |
q = NULL; |
||
287 |
} |
||
288 |
18 |
histsave(++(hist_source->line), p, 1); |
|
289 |
|||
290 |
18 |
shellf("%s\n", p); /* POSIX doesn't say this is done... */ |
|
291 |
✗✓ | 18 |
if ((p = q)) /* restore \n (trailing \n not restored) */ |
292 |
q[-1] = '\n'; |
||
293 |
} |
||
294 |
|||
295 |
/* Commands are executed here instead of pushing them onto the |
||
296 |
* input 'cause posix says the redirection and variable assignments |
||
297 |
* in |
||
298 |
* X=y fc -e - 42 2> /dev/null |
||
299 |
* are to effect the repeated commands environment. |
||
300 |
*/ |
||
301 |
/* XXX: source should not get trashed by this.. */ |
||
302 |
18 |
sold = source; |
|
303 |
18 |
ret = command(cmd, 0); |
|
304 |
18 |
source = sold; |
|
305 |
18 |
return ret; |
|
306 |
} |
||
307 |
|||
308 |
static int |
||
309 |
hist_replace(char **hp, const char *pat, const char *rep, int global) |
||
310 |
18 |
{ |
|
311 |
char *line; |
||
312 |
|||
313 |
✓✓ | 18 |
if (!pat) |
314 |
8 |
line = str_save(*hp, ATEMP); |
|
315 |
else { |
||
316 |
char *s, *s1; |
||
317 |
10 |
int pat_len = strlen(pat); |
|
318 |
10 |
int rep_len = strlen(rep); |
|
319 |
int len; |
||
320 |
XString xs; |
||
321 |
char *xp; |
||
322 |
10 |
int any_subst = 0; |
|
323 |
|||
324 |
10 |
Xinit(xs, xp, 128, ATEMP); |
|
325 |
✓✓✓✗ |
40 |
for (s = *hp; (s1 = strstr(s, pat)) && (!any_subst || global); |
326 |
20 |
s = s1 + pat_len) { |
|
327 |
20 |
any_subst = 1; |
|
328 |
20 |
len = s1 - s; |
|
329 |
✗✓ | 20 |
XcheckN(xs, xp, len + rep_len); |
330 |
20 |
memcpy(xp, s, len); /* first part */ |
|
331 |
20 |
xp += len; |
|
332 |
20 |
memcpy(xp, rep, rep_len); /* replacement */ |
|
333 |
20 |
xp += rep_len; |
|
334 |
} |
||
335 |
✗✓ | 10 |
if (!any_subst) { |
336 |
bi_errorf("substitution failed"); |
||
337 |
return 1; |
||
338 |
} |
||
339 |
10 |
len = strlen(s) + 1; |
|
340 |
✗✓ | 10 |
XcheckN(xs, xp, len); |
341 |
10 |
memcpy(xp, s, len); |
|
342 |
10 |
xp += len; |
|
343 |
10 |
line = Xclose(xs, xp); |
|
344 |
} |
||
345 |
18 |
return hist_execute(line); |
|
346 |
} |
||
347 |
|||
348 |
/* |
||
349 |
* get pointer to history given pattern |
||
350 |
* pattern is a number or string |
||
351 |
*/ |
||
352 |
static char ** |
||
353 |
hist_get(const char *str, int approx, int allow_cur) |
||
354 |
52 |
{ |
|
355 |
52 |
char **hp = NULL; |
|
356 |
int n; |
||
357 |
|||
358 |
✓✓ | 52 |
if (getn(str, &n)) { |
359 |
✓✓ | 44 |
hp = histptr + (n < 0 ? n : (n - hist_source->line)); |
360 |
✓✓ | 44 |
if ((long)hp < (long)history) { |
361 |
✓✗ | 8 |
if (approx) |
362 |
8 |
hp = hist_get_oldest(); |
|
363 |
else { |
||
364 |
bi_errorf("%s: not in history", str); |
||
365 |
hp = NULL; |
||
366 |
} |
||
367 |
✓✓ | 36 |
} else if (hp > histptr) { |
368 |
✓✗ | 2 |
if (approx) |
369 |
2 |
hp = hist_get_newest(allow_cur); |
|
370 |
else { |
||
371 |
bi_errorf("%s: not in history", str); |
||
372 |
hp = NULL; |
||
373 |
} |
||
374 |
✗✓✗✗ |
34 |
} else if (!allow_cur && hp == histptr) { |
375 |
bi_errorf("%s: invalid range", str); |
||
376 |
hp = NULL; |
||
377 |
} |
||
378 |
} else { |
||
379 |
✓✓ | 8 |
int anchored = *str == '?' ? (++str, 0) : 1; |
380 |
|||
381 |
/* the -1 is to avoid the current fc command */ |
||
382 |
8 |
n = findhist(histptr - history - 1, 0, str, anchored); |
|
383 |
✗✓ | 8 |
if (n < 0) { |
384 |
bi_errorf("%s: not in history", str); |
||
385 |
hp = NULL; |
||
386 |
} else |
||
387 |
8 |
hp = &history[n]; |
|
388 |
} |
||
389 |
52 |
return hp; |
|
390 |
} |
||
391 |
|||
392 |
/* Return a pointer to the newest command in the history */ |
||
393 |
char ** |
||
394 |
hist_get_newest(int allow_cur) |
||
395 |
22 |
{ |
|
396 |
✓✗✓✓ ✓✓ |
22 |
if (histptr < history || (!allow_cur && histptr == history)) { |
397 |
2 |
bi_errorf("no history (yet)"); |
|
398 |
2 |
return NULL; |
|
399 |
} |
||
400 |
✓✓ | 20 |
if (allow_cur) |
401 |
2 |
return histptr; |
|
402 |
18 |
return histptr - 1; |
|
403 |
} |
||
404 |
|||
405 |
/* Return a pointer to the oldest command in the history */ |
||
406 |
static char ** |
||
407 |
hist_get_oldest(void) |
||
408 |
8 |
{ |
|
409 |
✗✓ | 8 |
if (histptr <= history) { |
410 |
bi_errorf("no history (yet)"); |
||
411 |
return NULL; |
||
412 |
} |
||
413 |
8 |
return history; |
|
414 |
} |
||
415 |
|||
416 |
/******************************/ |
||
417 |
/* Back up over last histsave */ |
||
418 |
/******************************/ |
||
419 |
static void |
||
420 |
histbackup(void) |
||
421 |
18 |
{ |
|
422 |
static int last_line = -1; |
||
423 |
|||
424 |
✓✗✓✗ |
18 |
if (histptr >= history && last_line != hist_source->line) { |
425 |
18 |
hist_source->line--; |
|
426 |
18 |
afree(*histptr, APERM); |
|
427 |
18 |
histptr--; |
|
428 |
18 |
last_line = hist_source->line; |
|
429 |
} |
||
430 |
18 |
} |
|
431 |
|||
432 |
/* |
||
433 |
* Return the current position. |
||
434 |
*/ |
||
435 |
char ** |
||
436 |
histpos(void) |
||
437 |
{ |
||
438 |
return current; |
||
439 |
} |
||
440 |
|||
441 |
int |
||
442 |
histnum(int n) |
||
443 |
{ |
||
444 |
int last = histptr - history; |
||
445 |
|||
446 |
if (n < 0 || n >= last) { |
||
447 |
current = histptr; |
||
448 |
return last; |
||
449 |
} else { |
||
450 |
current = &history[n]; |
||
451 |
return n; |
||
452 |
} |
||
453 |
} |
||
454 |
|||
455 |
/* |
||
456 |
* This will become unnecessary if hist_get is modified to allow |
||
457 |
* searching from positions other than the end, and in either |
||
458 |
* direction. |
||
459 |
*/ |
||
460 |
int |
||
461 |
findhist(int start, int fwd, const char *str, int anchored) |
||
462 |
8 |
{ |
|
463 |
char **hp; |
||
464 |
8 |
int maxhist = histptr - history; |
|
465 |
✗✓ | 8 |
int incr = fwd ? 1 : -1; |
466 |
8 |
int len = strlen(str); |
|
467 |
|||
468 |
✗✓ | 8 |
if (start < 0 || start >= maxhist) |
469 |
start = maxhist; |
||
470 |
|||
471 |
8 |
hp = &history[start]; |
|
472 |
✓✗✓✗ |
12 |
for (; hp >= history && hp <= histptr; hp += incr) |
473 |
✓✓✓✓ ✓✓✓✓ |
12 |
if ((anchored && strncmp(*hp, str, len) == 0) || |
474 |
(!anchored && strstr(*hp, str))) |
||
475 |
8 |
return hp - history; |
|
476 |
|||
477 |
return -1; |
||
478 |
} |
||
479 |
|||
480 |
int |
||
481 |
findhistrel(const char *str) |
||
482 |
{ |
||
483 |
int maxhist = histptr - history; |
||
484 |
int start = maxhist - 1; |
||
485 |
int rec = atoi(str); |
||
486 |
|||
487 |
if (rec == 0) |
||
488 |
return -1; |
||
489 |
if (rec > 0) { |
||
490 |
if (rec > maxhist) |
||
491 |
return -1; |
||
492 |
return rec - 1; |
||
493 |
} |
||
494 |
if (rec > maxhist) |
||
495 |
return -1; |
||
496 |
return start + rec + 1; |
||
497 |
} |
||
498 |
|||
499 |
/* |
||
500 |
* set history |
||
501 |
* this means reallocating the dataspace |
||
502 |
*/ |
||
503 |
void |
||
504 |
sethistsize(int n) |
||
505 |
4 |
{ |
|
506 |
✓✗✓✗ |
4 |
if (n > 0 && n != histsize) { |
507 |
4 |
int cursize = histptr - history; |
|
508 |
|||
509 |
/* save most recent history */ |
||
510 |
✗✓ | 4 |
if (n < cursize) { |
511 |
memmove(history, histptr - n, n * sizeof(char *)); |
||
512 |
cursize = n; |
||
513 |
} |
||
514 |
|||
515 |
4 |
history = areallocarray(history, n, sizeof(char *), APERM); |
|
516 |
|||
517 |
4 |
histsize = n; |
|
518 |
4 |
histptr = history + cursize; |
|
519 |
} |
||
520 |
4 |
} |
|
521 |
|||
522 |
/* |
||
523 |
* set history file |
||
524 |
* This can mean reloading/resetting/starting history file |
||
525 |
* maintenance |
||
526 |
*/ |
||
527 |
void |
||
528 |
sethistfile(const char *name) |
||
529 |
42 |
{ |
|
530 |
/* if not started then nothing to do */ |
||
531 |
✓✗ | 42 |
if (hstarted == 0) |
532 |
42 |
return; |
|
533 |
|||
534 |
/* if the name is the same as the name we have */ |
||
535 |
if (hname && strcmp(hname, name) == 0) |
||
536 |
return; |
||
537 |
|||
538 |
/* |
||
539 |
* its a new name - possibly |
||
540 |
*/ |
||
541 |
if (histfd) { |
||
542 |
/* yes the file is open */ |
||
543 |
(void) close(histfd); |
||
544 |
histfd = 0; |
||
545 |
hsize = 0; |
||
546 |
afree(hname, APERM); |
||
547 |
hname = NULL; |
||
548 |
/* let's reset the history */ |
||
549 |
histptr = history - 1; |
||
550 |
hist_source->line = 0; |
||
551 |
} |
||
552 |
|||
553 |
hist_init(hist_source); |
||
554 |
} |
||
555 |
|||
556 |
/* |
||
557 |
* initialise the history vector |
||
558 |
*/ |
||
559 |
void |
||
560 |
init_histvec(void) |
||
561 |
3525 |
{ |
|
562 |
✓✗ | 3525 |
if (history == NULL) { |
563 |
3525 |
histsize = HISTORYSIZE; |
|
564 |
3525 |
history = areallocarray(NULL, histsize, sizeof(char *), APERM); |
|
565 |
3525 |
histptr = history - 1; |
|
566 |
} |
||
567 |
3525 |
} |
|
568 |
|||
569 |
|||
570 |
/* |
||
571 |
* Routines added by Peter Collinson BSDI(Europe)/Hillside Systems to |
||
572 |
* a) permit HISTSIZE to control number of lines of history stored |
||
573 |
* b) maintain a physical history file |
||
574 |
* |
||
575 |
* It turns out that there is a lot of ghastly hackery here |
||
576 |
*/ |
||
577 |
|||
578 |
|||
579 |
/* |
||
580 |
* save command in history |
||
581 |
*/ |
||
582 |
void |
||
583 |
histsave(int lno, const char *cmd, int dowrite) |
||
584 |
248 |
{ |
|
585 |
char **hp; |
||
586 |
char *c, *cp; |
||
587 |
|||
588 |
248 |
c = str_save(cmd, APERM); |
|
589 |
✓✓ | 248 |
if ((cp = strchr(c, '\n')) != NULL) |
590 |
230 |
*cp = '\0'; |
|
591 |
|||
592 |
✓✓ | 248 |
if (histfd && dowrite) |
593 |
190 |
writehistfile(lno, c); |
|
594 |
|||
595 |
248 |
hp = histptr; |
|
596 |
|||
597 |
✓✓ | 248 |
if (++hp >= history + histsize) { /* remove oldest command */ |
598 |
12 |
afree(*history, APERM); |
|
599 |
✓✓ | 36 |
for (hp = history; hp < history + histsize - 1; hp++) |
600 |
24 |
hp[0] = hp[1]; |
|
601 |
} |
||
602 |
248 |
*hp = c; |
|
603 |
248 |
histptr = hp; |
|
604 |
248 |
} |
|
605 |
|||
606 |
/* |
||
607 |
* Write history data to a file nominated by HISTFILE. If HISTFILE |
||
608 |
* is unset then history is still recorded, but the data is not |
||
609 |
* written to a file. All copies of ksh looking at the file will |
||
610 |
* maintain the same history. This is ksh behaviour. |
||
611 |
*/ |
||
612 |
|||
613 |
/* |
||
614 |
* History file format: |
||
615 |
* Bytes 1, 2: HMAGIC - just to check that we are dealing with |
||
616 |
the correct object |
||
617 |
* Each command, in the format: |
||
618 |
<command byte><command number(4 bytes)><bytes><null> |
||
619 |
*/ |
||
620 |
#define HMAGIC1 0xab |
||
621 |
#define HMAGIC2 0xcd |
||
622 |
#define COMMAND 0xff |
||
623 |
|||
624 |
void |
||
625 |
hist_init(Source *s) |
||
626 |
63 |
{ |
|
627 |
unsigned char *base; |
||
628 |
int lines; |
||
629 |
int fd; |
||
630 |
struct stat sb; |
||
631 |
|||
632 |
✗✓ | 63 |
if (Flag(FTALKING) == 0) |
633 |
return; |
||
634 |
|||
635 |
63 |
hstarted = 1; |
|
636 |
|||
637 |
63 |
hist_source = s; |
|
638 |
|||
639 |
63 |
hname = str_val(global("HISTFILE")); |
|
640 |
✗✓ | 63 |
if (hname == NULL) |
641 |
return; |
||
642 |
63 |
hname = str_save(hname, APERM); |
|
643 |
|||
644 |
63 |
retry: |
|
645 |
/* we have a file and are interactive */ |
||
646 |
✓✓ | 63 |
if ((fd = open(hname, O_RDWR|O_CREAT|O_APPEND, 0600)) < 0) |
647 |
21 |
return; |
|
648 |
✓✗✗✓ |
42 |
if (fstat(fd, &sb) == -1 || sb.st_uid != getuid()) { |
649 |
close(fd); |
||
650 |
return; |
||
651 |
} |
||
652 |
|||
653 |
42 |
histfd = savefd(fd); |
|
654 |
✓✗ | 42 |
if (histfd != fd) |
655 |
42 |
close(fd); |
|
656 |
|||
657 |
42 |
(void) flock(histfd, LOCK_EX); |
|
658 |
|||
659 |
42 |
hsize = lseek(histfd, 0L, SEEK_END); |
|
660 |
|||
661 |
✓✗ | 42 |
if (hsize == 0) { |
662 |
/* add magic */ |
||
663 |
✗✓ | 42 |
if (sprinkle(histfd)) { |
664 |
hist_finish(); |
||
665 |
return; |
||
666 |
} |
||
667 |
} |
||
668 |
else if (hsize > 0) { |
||
669 |
/* |
||
670 |
* we have some data |
||
671 |
*/ |
||
672 |
base = mmap(0, hsize, PROT_READ, |
||
673 |
MAP_FILE|MAP_PRIVATE, histfd, 0); |
||
674 |
/* |
||
675 |
* check on its validity |
||
676 |
*/ |
||
677 |
if (base == MAP_FAILED || *base != HMAGIC1 || base[1] != HMAGIC2) { |
||
678 |
if (base != MAP_FAILED) |
||
679 |
munmap((caddr_t)base, hsize); |
||
680 |
hist_finish(); |
||
681 |
if (unlink(hname) != 0) |
||
682 |
return; |
||
683 |
goto retry; |
||
684 |
} |
||
685 |
if (hsize > 2) { |
||
686 |
lines = hist_count_lines(base+2, hsize-2); |
||
687 |
if (lines > histsize) { |
||
688 |
/* we need to make the file smaller */ |
||
689 |
if (hist_shrink(base, hsize)) |
||
690 |
if (unlink(hname) != 0) |
||
691 |
return; |
||
692 |
munmap((caddr_t)base, hsize); |
||
693 |
hist_finish(); |
||
694 |
goto retry; |
||
695 |
} |
||
696 |
} |
||
697 |
histload(hist_source, base+2, hsize-2); |
||
698 |
munmap((caddr_t)base, hsize); |
||
699 |
} |
||
700 |
42 |
(void) flock(histfd, LOCK_UN); |
|
701 |
42 |
hsize = lseek(histfd, 0L, SEEK_END); |
|
702 |
} |
||
703 |
|||
704 |
typedef enum state { |
||
705 |
shdr, /* expecting a header */ |
||
706 |
sline, /* looking for a null byte to end the line */ |
||
707 |
sn1, /* bytes 1 to 4 of a line no */ |
||
708 |
sn2, sn3, sn4 |
||
709 |
} State; |
||
710 |
|||
711 |
static int |
||
712 |
hist_count_lines(unsigned char *base, int bytes) |
||
713 |
{ |
||
714 |
State state = shdr; |
||
715 |
int lines = 0; |
||
716 |
|||
717 |
while (bytes--) { |
||
718 |
switch (state) { |
||
719 |
case shdr: |
||
720 |
if (*base == COMMAND) |
||
721 |
state = sn1; |
||
722 |
break; |
||
723 |
case sn1: |
||
724 |
state = sn2; break; |
||
725 |
case sn2: |
||
726 |
state = sn3; break; |
||
727 |
case sn3: |
||
728 |
state = sn4; break; |
||
729 |
case sn4: |
||
730 |
state = sline; break; |
||
731 |
case sline: |
||
732 |
if (*base == '\0') |
||
733 |
lines++, state = shdr; |
||
734 |
} |
||
735 |
base++; |
||
736 |
} |
||
737 |
return lines; |
||
738 |
} |
||
739 |
|||
740 |
/* |
||
741 |
* Shrink the history file to histsize lines |
||
742 |
*/ |
||
743 |
static int |
||
744 |
hist_shrink(unsigned char *oldbase, int oldbytes) |
||
745 |
{ |
||
746 |
int fd; |
||
747 |
char nfile[1024]; |
||
748 |
unsigned char *nbase = oldbase; |
||
749 |
int nbytes = oldbytes; |
||
750 |
|||
751 |
nbase = hist_skip_back(nbase, &nbytes, histsize); |
||
752 |
if (nbase == NULL) |
||
753 |
return 1; |
||
754 |
if (nbase == oldbase) |
||
755 |
return 0; |
||
756 |
|||
757 |
/* |
||
758 |
* create temp file |
||
759 |
*/ |
||
760 |
(void) shf_snprintf(nfile, sizeof(nfile), "%s.%d", hname, procpid); |
||
761 |
if ((fd = open(nfile, O_CREAT | O_TRUNC | O_WRONLY, 0600)) < 0) |
||
762 |
return 1; |
||
763 |
|||
764 |
if (sprinkle(fd)) { |
||
765 |
close(fd); |
||
766 |
unlink(nfile); |
||
767 |
return 1; |
||
768 |
} |
||
769 |
if (write(fd, nbase, nbytes) != nbytes) { |
||
770 |
close(fd); |
||
771 |
unlink(nfile); |
||
772 |
return 1; |
||
773 |
} |
||
774 |
close(fd); |
||
775 |
|||
776 |
/* |
||
777 |
* rename |
||
778 |
*/ |
||
779 |
if (rename(nfile, hname) < 0) |
||
780 |
return 1; |
||
781 |
return 0; |
||
782 |
} |
||
783 |
|||
784 |
|||
785 |
/* |
||
786 |
* find a pointer to the data `no' back from the end of the file |
||
787 |
* return the pointer and the number of bytes left |
||
788 |
*/ |
||
789 |
static unsigned char * |
||
790 |
hist_skip_back(unsigned char *base, int *bytes, int no) |
||
791 |
{ |
||
792 |
int lines = 0; |
||
793 |
unsigned char *ep; |
||
794 |
|||
795 |
for (ep = base + *bytes; --ep > base; ) { |
||
796 |
/* this doesn't really work: the 4 byte line number that is |
||
797 |
* encoded after the COMMAND byte can itself contain the |
||
798 |
* COMMAND byte.... |
||
799 |
*/ |
||
800 |
for (; ep > base && *ep != COMMAND; ep--) |
||
801 |
; |
||
802 |
if (ep == base) |
||
803 |
break; |
||
804 |
if (++lines == no) { |
||
805 |
*bytes = *bytes - ((char *)ep - (char *)base); |
||
806 |
return ep; |
||
807 |
} |
||
808 |
} |
||
809 |
return NULL; |
||
810 |
} |
||
811 |
|||
812 |
/* |
||
813 |
* load the history structure from the stored data |
||
814 |
*/ |
||
815 |
static void |
||
816 |
histload(Source *s, unsigned char *base, int bytes) |
||
817 |
2 |
{ |
|
818 |
State state; |
||
819 |
2 |
int lno = 0; |
|
820 |
2 |
unsigned char *line = NULL; |
|
821 |
|||
822 |
✓✓ | 30 |
for (state = shdr; bytes-- > 0; base++) { |
823 |
✓✓✓✓ ✓✓✗ |
28 |
switch (state) { |
824 |
case shdr: |
||
825 |
✓✗ | 2 |
if (*base == COMMAND) |
826 |
2 |
state = sn1; |
|
827 |
break; |
||
828 |
case sn1: |
||
829 |
2 |
lno = (((*base)&0xff)<<24); |
|
830 |
2 |
state = sn2; |
|
831 |
2 |
break; |
|
832 |
case sn2: |
||
833 |
2 |
lno |= (((*base)&0xff)<<16); |
|
834 |
2 |
state = sn3; |
|
835 |
2 |
break; |
|
836 |
case sn3: |
||
837 |
2 |
lno |= (((*base)&0xff)<<8); |
|
838 |
2 |
state = sn4; |
|
839 |
2 |
break; |
|
840 |
case sn4: |
||
841 |
2 |
lno |= (*base)&0xff; |
|
842 |
2 |
line = base+1; |
|
843 |
2 |
state = sline; |
|
844 |
2 |
break; |
|
845 |
case sline: |
||
846 |
✓✓ | 18 |
if (*base == '\0') { |
847 |
/* worry about line numbers */ |
||
848 |
✓✗✓✗ |
4 |
if (histptr >= history && lno-1 != s->line) { |
849 |
/* a replacement ? */ |
||
850 |
2 |
histinsert(s, lno, line); |
|
851 |
} |
||
852 |
else { |
||
853 |
s->line = lno; |
||
854 |
s->cmd_offset = lno; |
||
855 |
histsave(lno, (char *)line, 0); |
||
856 |
} |
||
857 |
2 |
state = shdr; |
|
858 |
} |
||
859 |
} |
||
860 |
} |
||
861 |
2 |
} |
|
862 |
|||
863 |
/* |
||
864 |
* Insert a line into the history at a specified number |
||
865 |
*/ |
||
866 |
static void |
||
867 |
histinsert(Source *s, int lno, unsigned char *line) |
||
868 |
2 |
{ |
|
869 |
char **hp; |
||
870 |
|||
871 |
✓✗✓✗ |
2 |
if (lno >= s->line-(histptr-history) && lno <= s->line) { |
872 |
2 |
hp = &histptr[lno-s->line]; |
|
873 |
2 |
afree(*hp, APERM); |
|
874 |
2 |
*hp = str_save((char *)line, APERM); |
|
875 |
} |
||
876 |
2 |
} |
|
877 |
|||
878 |
/* |
||
879 |
* write a command to the end of the history file |
||
880 |
* This *MAY* seem easy but it's also necessary to check |
||
881 |
* that the history file has not changed in size. |
||
882 |
* If it has - then some other shell has written to it |
||
883 |
* and we should read those commands to update our history |
||
884 |
*/ |
||
885 |
static void |
||
886 |
writehistfile(int lno, char *cmd) |
||
887 |
190 |
{ |
|
888 |
int sizenow; |
||
889 |
unsigned char *base; |
||
890 |
unsigned char *new; |
||
891 |
int bytes; |
||
892 |
unsigned char hdr[5]; |
||
893 |
|||
894 |
190 |
(void) flock(histfd, LOCK_EX); |
|
895 |
190 |
sizenow = lseek(histfd, 0L, SEEK_END); |
|
896 |
✓✓ | 190 |
if (sizenow != hsize) { |
897 |
/* |
||
898 |
* Things have changed |
||
899 |
*/ |
||
900 |
✓✗ | 2 |
if (sizenow > hsize) { |
901 |
/* someone has added some lines */ |
||
902 |
2 |
bytes = sizenow - hsize; |
|
903 |
2 |
base = mmap(0, sizenow, |
|
904 |
PROT_READ, MAP_FILE|MAP_PRIVATE, histfd, 0); |
||
905 |
✗✓ | 2 |
if (base == MAP_FAILED) |
906 |
goto bad; |
||
907 |
2 |
new = base + hsize; |
|
908 |
✗✓ | 2 |
if (*new != COMMAND) { |
909 |
munmap((caddr_t)base, sizenow); |
||
910 |
goto bad; |
||
911 |
} |
||
912 |
2 |
hist_source->line--; |
|
913 |
2 |
histload(hist_source, new, bytes); |
|
914 |
2 |
hist_source->line++; |
|
915 |
2 |
lno = hist_source->line; |
|
916 |
2 |
munmap((caddr_t)base, sizenow); |
|
917 |
2 |
hsize = sizenow; |
|
918 |
} else { |
||
919 |
/* it has shrunk */ |
||
920 |
/* but to what? */ |
||
921 |
/* we'll give up for now */ |
||
922 |
goto bad; |
||
923 |
} |
||
924 |
} |
||
925 |
/* |
||
926 |
* we can write our bit now |
||
927 |
*/ |
||
928 |
190 |
hdr[0] = COMMAND; |
|
929 |
190 |
hdr[1] = (lno>>24)&0xff; |
|
930 |
190 |
hdr[2] = (lno>>16)&0xff; |
|
931 |
190 |
hdr[3] = (lno>>8)&0xff; |
|
932 |
190 |
hdr[4] = lno&0xff; |
|
933 |
190 |
(void) write(histfd, hdr, 5); |
|
934 |
190 |
(void) write(histfd, cmd, strlen(cmd)+1); |
|
935 |
190 |
hsize = lseek(histfd, 0L, SEEK_END); |
|
936 |
190 |
(void) flock(histfd, LOCK_UN); |
|
937 |
190 |
return; |
|
938 |
bad: |
||
939 |
hist_finish(); |
||
940 |
} |
||
941 |
|||
942 |
void |
||
943 |
hist_finish(void) |
||
944 |
62 |
{ |
|
945 |
62 |
(void) flock(histfd, LOCK_UN); |
|
946 |
62 |
(void) close(histfd); |
|
947 |
62 |
histfd = 0; |
|
948 |
62 |
} |
|
949 |
|||
950 |
/* |
||
951 |
* add magic to the history file |
||
952 |
*/ |
||
953 |
static int |
||
954 |
sprinkle(int fd) |
||
955 |
42 |
{ |
|
956 |
static unsigned char mag[] = { HMAGIC1, HMAGIC2 }; |
||
957 |
|||
958 |
42 |
return(write(fd, mag, 2) != 2); |
|
959 |
} |
||
960 |
|||
961 |
#else /* HISTORY */ |
||
962 |
|||
963 |
/* No history to be compiled in: dummy routines to avoid lots more ifdefs */ |
||
964 |
void |
||
965 |
init_histvec(void) |
||
966 |
{ |
||
967 |
} |
||
968 |
void |
||
969 |
hist_init(Source *s) |
||
970 |
{ |
||
971 |
} |
||
972 |
void |
||
973 |
hist_finish(void) |
||
974 |
{ |
||
975 |
} |
||
976 |
void |
||
977 |
histsave(int lno, const char *cmd, int dowrite) |
||
978 |
{ |
||
979 |
errorf("history not enabled"); |
||
980 |
} |
||
981 |
#endif /* HISTORY */ |
Generated by: GCOVR (Version 3.3) |