GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: bin/ksh/history.c Lines: 256 442 57.9 %
Date: 2016-12-06 Branches: 150 315 47.6 %

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 */