1 /* $OpenBSD: history.c,v 1.39 2010/05/19 17:36:08 jasper Exp $ */
2 /* $OpenBSD: trap.c,v 1.23 2010/05/19 17:36:08 jasper Exp $ */
3
4 /*-
5 * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
6 * 2011, 2012, 2014
7 * Thorsten Glaser <tg@mirbsd.org>
8 *
9 * Provided that these terms and disclaimer and all copyright notices
10 * are retained or reproduced in an accompanying document, permission
11 * is granted to deal in this work without restriction, including un-
12 * limited rights to use, publicly perform, distribute, sell, modify,
13 * merge, give away, or sublicence.
14 *
15 * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
16 * the utmost extent permitted by applicable law, neither express nor
17 * implied; without malicious intent or gross negligence. In no event
18 * may a licensor, author or contributor be held liable for indirect,
19 * direct, other damage, loss, or other issues arising in any way out
20 * of dealing in the work, even if advised of the possibility of such
21 * damage or existence of a defect, except proven that it results out
22 * of said person's immediate fault when using the work as intended.
23 */
24
25 #include "sh.h"
26 #if HAVE_SYS_FILE_H
27 #include <sys/file.h>
28 #endif
29
30 __RCSID("$MirOS: src/bin/mksh/histrap.c,v 1.134 2014/06/09 13:25:53 tg Exp $");
31
32 Trap sigtraps[NSIG + 1];
33 static struct sigaction Sigact_ign;
34
35 #if HAVE_PERSISTENT_HISTORY
36 static int histload(Source *, unsigned char *, size_t);
37 static int writehistline(int, int, const char *);
38 static void writehistfile(int, const char *);
39 #endif
40
41 static int hist_execute(char *);
42 static char **hist_get(const char *, bool, bool);
43 static char **hist_get_oldest(void);
44
45 static bool hstarted; /* set after hist_init() called */
46 static Source *hist_source;
47
48 #if HAVE_PERSISTENT_HISTORY
49 /*XXX imake style */
50 #if defined(__linux)
51 #define caddr_cast(x) ((void *)(x))
52 #else
53 #define caddr_cast(x) ((caddr_t)(x))
54 #endif
55
56 /* several OEs do not have these constants */
57 #ifndef MAP_FAILED
58 #define MAP_FAILED caddr_cast(-1)
59 #endif
60
61 /* some OEs need the default mapping type specified */
62 #ifndef MAP_FILE
63 #define MAP_FILE 0
64 #endif
65
66 /* current history file: name, fd, size */
67 static char *hname;
68 static int histfd = -1;
69 static off_t histfsize;
70 #endif
71
72 static const char Tnot_in_history[] = "not in history";
73 #define Thistory (Tnot_in_history + 7)
74
75 static const char TFCEDIT_dollaru[] = "${FCEDIT:-/bin/ed} $_";
76 #define Tspdollaru (TFCEDIT_dollaru + 18)
77
78 /* HISTSIZE default: size of saved history, persistent or standard */
79 #ifdef MKSH_SMALL
80 #define MKSH_DEFHISTSIZE 255
81 #else
82 #define MKSH_DEFHISTSIZE 2047
83 #endif
84 /* maximum considered size of persistent history file */
85 #define MKSH_MAXHISTFSIZE ((off_t)1048576 * 96)
86
87 int
c_fc(const char ** wp)88 c_fc(const char **wp)
89 {
90 struct shf *shf;
91 struct temp *tf;
92 bool gflag = false, lflag = false, nflag = false, rflag = false,
93 sflag = false;
94 int optc;
95 const char *p, *first = NULL, *last = NULL;
96 char **hfirst, **hlast, **hp, *editor = NULL;
97
98 if (!Flag(FTALKING_I)) {
99 bi_errorf("history %ss not available", Tfunction);
100 return (1);
101 }
102
103 while ((optc = ksh_getopt(wp, &builtin_opt,
104 "e:glnrs0,1,2,3,4,5,6,7,8,9,")) != -1)
105 switch (optc) {
106
107 case 'e':
108 p = builtin_opt.optarg;
109 if (ksh_isdash(p))
110 sflag = true;
111 else {
112 size_t len = strlen(p);
113
114 /* almost certainly not overflowing */
115 editor = alloc(len + 4, ATEMP);
116 memcpy(editor, p, len);
117 memcpy(editor + len, Tspdollaru, 4);
118 }
119 break;
120
121 /* non-AT&T ksh */
122 case 'g':
123 gflag = true;
124 break;
125
126 case 'l':
127 lflag = true;
128 break;
129
130 case 'n':
131 nflag = true;
132 break;
133
134 case 'r':
135 rflag = true;
136 break;
137
138 /* POSIX version of -e - */
139 case 's':
140 sflag = true;
141 break;
142
143 /* kludge city - accept -num as -- -num (kind of) */
144 case '0': case '1': case '2': case '3': case '4':
145 case '5': case '6': case '7': case '8': case '9':
146 p = shf_smprintf("-%c%s",
147 optc, builtin_opt.optarg);
148 if (!first)
149 first = p;
150 else if (!last)
151 last = p;
152 else {
153 bi_errorf("too many arguments");
154 return (1);
155 }
156 break;
157
158 case '?':
159 return (1);
160 }
161 wp += builtin_opt.optind;
162
163 /* Substitute and execute command */
164 if (sflag) {
165 char *pat = NULL, *rep = NULL, *line;
166
167 if (editor || lflag || nflag || rflag) {
168 bi_errorf("can't use -e, -l, -n, -r with -s (-e -)");
169 return (1);
170 }
171
172 /* Check for pattern replacement argument */
173 if (*wp && **wp && (p = cstrchr(*wp + 1, '='))) {
174 strdupx(pat, *wp, ATEMP);
175 rep = pat + (p - *wp);
176 *rep++ = '\0';
177 wp++;
178 }
179 /* Check for search prefix */
180 if (!first && (first = *wp))
181 wp++;
182 if (last || *wp) {
183 bi_errorf("too many arguments");
184 return (1);
185 }
186
187 hp = first ? hist_get(first, false, false) :
188 hist_get_newest(false);
189 if (!hp)
190 return (1);
191 /* hist_replace */
192 if (!pat)
193 strdupx(line, *hp, ATEMP);
194 else {
195 char *s, *s1;
196 size_t len, pat_len, rep_len;
197 XString xs;
198 char *xp;
199 bool any_subst = false;
200
201 pat_len = strlen(pat);
202 rep_len = strlen(rep);
203 Xinit(xs, xp, 128, ATEMP);
204 for (s = *hp; (s1 = strstr(s, pat)) &&
205 (!any_subst || gflag); s = s1 + pat_len) {
206 any_subst = true;
207 len = s1 - s;
208 XcheckN(xs, xp, len + rep_len);
209 /*; first part */
210 memcpy(xp, s, len);
211 xp += len;
212 /* replacement */
213 memcpy(xp, rep, rep_len);
214 xp += rep_len;
215 }
216 if (!any_subst) {
217 bi_errorf("bad substitution");
218 return (1);
219 }
220 len = strlen(s) + 1;
221 XcheckN(xs, xp, len);
222 memcpy(xp, s, len);
223 xp += len;
224 line = Xclose(xs, xp);
225 }
226 return (hist_execute(line));
227 }
228
229 if (editor && (lflag || nflag)) {
230 bi_errorf("can't use -l, -n with -e");
231 return (1);
232 }
233
234 if (!first && (first = *wp))
235 wp++;
236 if (!last && (last = *wp))
237 wp++;
238 if (*wp) {
239 bi_errorf("too many arguments");
240 return (1);
241 }
242 if (!first) {
243 hfirst = lflag ? hist_get("-16", true, true) :
244 hist_get_newest(false);
245 if (!hfirst)
246 return (1);
247 /* can't fail if hfirst didn't fail */
248 hlast = hist_get_newest(false);
249 } else {
250 /*
251 * POSIX says not an error if first/last out of bounds
252 * when range is specified; AT&T ksh and pdksh allow out
253 * of bounds for -l as well.
254 */
255 hfirst = hist_get(first, tobool(lflag || last), lflag);
256 if (!hfirst)
257 return (1);
258 hlast = last ? hist_get(last, true, lflag) :
259 (lflag ? hist_get_newest(false) : hfirst);
260 if (!hlast)
261 return (1);
262 }
263 if (hfirst > hlast) {
264 char **temp;
265
266 temp = hfirst; hfirst = hlast; hlast = temp;
267 /* POSIX */
268 rflag = !rflag;
269 }
270
271 /* List history */
272 if (lflag) {
273 char *s, *t;
274
275 for (hp = rflag ? hlast : hfirst;
276 hp >= hfirst && hp <= hlast; hp += rflag ? -1 : 1) {
277 if (!nflag)
278 shf_fprintf(shl_stdout, "%d",
279 hist_source->line - (int)(histptr - hp));
280 shf_putc('\t', shl_stdout);
281 /* print multi-line commands correctly */
282 s = *hp;
283 while ((t = strchr(s, '\n'))) {
284 *t = '\0';
285 shf_fprintf(shl_stdout, "%s\n\t", s);
286 *t++ = '\n';
287 s = t;
288 }
289 shf_fprintf(shl_stdout, "%s\n", s);
290 }
291 shf_flush(shl_stdout);
292 return (0);
293 }
294
295 /* Run editor on selected lines, then run resulting commands */
296
297 tf = maketemp(ATEMP, TT_HIST_EDIT, &e->temps);
298 if (!(shf = tf->shf)) {
299 bi_errorf("can't %s temporary file %s: %s",
300 "create", tf->tffn, cstrerror(errno));
301 return (1);
302 }
303 for (hp = rflag ? hlast : hfirst;
304 hp >= hfirst && hp <= hlast; hp += rflag ? -1 : 1)
305 shf_fprintf(shf, "%s\n", *hp);
306 if (shf_close(shf) == EOF) {
307 bi_errorf("can't %s temporary file %s: %s",
308 "write", tf->tffn, cstrerror(errno));
309 return (1);
310 }
311
312 /* Ignore setstr errors here (arbitrary) */
313 setstr(local("_", false), tf->tffn, KSH_RETURN_ERROR);
314
315 /* XXX: source should not get trashed by this.. */
316 {
317 Source *sold = source;
318 int ret;
319
320 ret = command(editor ? editor : TFCEDIT_dollaru, 0);
321 source = sold;
322 if (ret)
323 return (ret);
324 }
325
326 {
327 struct stat statb;
328 XString xs;
329 char *xp;
330 ssize_t n;
331
332 if (!(shf = shf_open(tf->tffn, O_RDONLY, 0, 0))) {
333 bi_errorf("can't %s temporary file %s: %s",
334 "open", tf->tffn, cstrerror(errno));
335 return (1);
336 }
337
338 if (stat(tf->tffn, &statb) < 0)
339 n = 128;
340 else if ((off_t)statb.st_size > MKSH_MAXHISTFSIZE) {
341 bi_errorf("%s %s too large: %lu", Thistory,
342 "file", (unsigned long)statb.st_size);
343 goto errout;
344 } else
345 n = (size_t)statb.st_size + 1;
346 Xinit(xs, xp, n, hist_source->areap);
347 while ((n = shf_read(xp, Xnleft(xs, xp), shf)) > 0) {
348 xp += n;
349 if (Xnleft(xs, xp) <= 0)
350 XcheckN(xs, xp, Xlength(xs, xp));
351 }
352 if (n < 0) {
353 bi_errorf("can't %s temporary file %s: %s",
354 "read", tf->tffn, cstrerror(shf_errno(shf)));
355 errout:
356 shf_close(shf);
357 return (1);
358 }
359 shf_close(shf);
360 *xp = '\0';
361 strip_nuls(Xstring(xs, xp), Xlength(xs, xp));
362 return (hist_execute(Xstring(xs, xp)));
363 }
364 }
365
366 /* Save cmd in history, execute cmd (cmd gets trashed) */
367 static int
hist_execute(char * cmd)368 hist_execute(char *cmd)
369 {
370 static int last_line = -1;
371 Source *sold;
372 int ret;
373 char *p, *q;
374
375 /* Back up over last histsave */
376 if (histptr >= history && last_line != hist_source->line) {
377 hist_source->line--;
378 afree(*histptr, APERM);
379 histptr--;
380 last_line = hist_source->line;
381 }
382
383 for (p = cmd; p; p = q) {
384 if ((q = strchr(p, '\n'))) {
385 /* kill the newline */
386 *q++ = '\0';
387 if (!*q)
388 /* ignore trailing newline */
389 q = NULL;
390 }
391 histsave(&hist_source->line, p, true, true);
392
393 /* POSIX doesn't say this is done... */
394 shellf("%s\n", p);
395 if (q)
396 /* restore \n (trailing \n not restored) */
397 q[-1] = '\n';
398 }
399
400 /*-
401 * Commands are executed here instead of pushing them onto the
402 * input 'cause POSIX says the redirection and variable assignments
403 * in
404 * X=y fc -e - 42 2> /dev/null
405 * are to effect the repeated commands environment.
406 */
407 /* XXX: source should not get trashed by this.. */
408 sold = source;
409 ret = command(cmd, 0);
410 source = sold;
411 return (ret);
412 }
413
414 /*
415 * get pointer to history given pattern
416 * pattern is a number or string
417 */
418 static char **
hist_get(const char * str,bool approx,bool allow_cur)419 hist_get(const char *str, bool approx, bool allow_cur)
420 {
421 char **hp = NULL;
422 int n;
423
424 if (getn(str, &n)) {
425 hp = histptr + (n < 0 ? n : (n - hist_source->line));
426 if ((ptrdiff_t)hp < (ptrdiff_t)history) {
427 if (approx)
428 hp = hist_get_oldest();
429 else {
430 bi_errorf("%s: %s", str, Tnot_in_history);
431 hp = NULL;
432 }
433 } else if ((ptrdiff_t)hp > (ptrdiff_t)histptr) {
434 if (approx)
435 hp = hist_get_newest(allow_cur);
436 else {
437 bi_errorf("%s: %s", str, Tnot_in_history);
438 hp = NULL;
439 }
440 } else if (!allow_cur && hp == histptr) {
441 bi_errorf("%s: %s", str, "invalid range");
442 hp = NULL;
443 }
444 } else {
445 bool anchored = *str == '?' ? (++str, false) : true;
446
447 /* the -1 is to avoid the current fc command */
448 if ((n = findhist(histptr - history - 1, 0, str, anchored)) < 0)
449 bi_errorf("%s: %s", str, Tnot_in_history);
450 else
451 hp = &history[n];
452 }
453 return (hp);
454 }
455
456 /* Return a pointer to the newest command in the history */
457 char **
hist_get_newest(bool allow_cur)458 hist_get_newest(bool allow_cur)
459 {
460 if (histptr < history || (!allow_cur && histptr == history)) {
461 bi_errorf("no history (yet)");
462 return (NULL);
463 }
464 return (allow_cur ? histptr : histptr - 1);
465 }
466
467 /* Return a pointer to the oldest command in the history */
468 static char **
hist_get_oldest(void)469 hist_get_oldest(void)
470 {
471 if (histptr <= history) {
472 bi_errorf("no history (yet)");
473 return (NULL);
474 }
475 return (history);
476 }
477
478 #if !defined(MKSH_NO_CMDLINE_EDITING) && !MKSH_S_NOVI
479 /* current position in history[] */
480 static char **current;
481
482 /*
483 * Return the current position.
484 */
485 char **
histpos(void)486 histpos(void)
487 {
488 return (current);
489 }
490
491 int
histnum(int n)492 histnum(int n)
493 {
494 int last = histptr - history;
495
496 if (n < 0 || n >= last) {
497 current = histptr;
498 return (last);
499 } else {
500 current = &history[n];
501 return (n);
502 }
503 }
504 #endif
505
506 /*
507 * This will become unnecessary if hist_get is modified to allow
508 * searching from positions other than the end, and in either
509 * direction.
510 */
511 int
findhist(int start,int fwd,const char * str,bool anchored)512 findhist(int start, int fwd, const char *str, bool anchored)
513 {
514 char **hp;
515 int maxhist = histptr - history;
516 int incr = fwd ? 1 : -1;
517 size_t len = strlen(str);
518
519 if (start < 0 || start >= maxhist)
520 start = maxhist;
521
522 hp = &history[start];
523 for (; hp >= history && hp <= histptr; hp += incr)
524 if ((anchored && strncmp(*hp, str, len) == 0) ||
525 (!anchored && strstr(*hp, str)))
526 return (hp - history);
527
528 return (-1);
529 }
530
531 /*
532 * set history; this means reallocating the dataspace
533 */
534 void
sethistsize(mksh_ari_t n)535 sethistsize(mksh_ari_t n)
536 {
537 if (n > 0 && n != histsize) {
538 int cursize = histptr - history;
539
540 /* save most recent history */
541 if (n < cursize) {
542 memmove(history, histptr - n + 1, n * sizeof(char *));
543 cursize = n - 1;
544 }
545
546 history = aresize2(history, n, sizeof(char *), APERM);
547
548 histsize = n;
549 histptr = history + cursize;
550 }
551 }
552
553 #if HAVE_PERSISTENT_HISTORY
554 /*
555 * set history file; this can mean reloading/resetting/starting
556 * history file maintenance
557 */
558 void
sethistfile(const char * name)559 sethistfile(const char *name)
560 {
561 /* if not started then nothing to do */
562 if (hstarted == false)
563 return;
564
565 /* if the name is the same as the name we have */
566 if (hname && strcmp(hname, name) == 0)
567 return;
568
569 /*
570 * it's a new name - possibly
571 */
572 if (histfd != -1) {
573 /* yes the file is open */
574 (void)close(histfd);
575 histfd = -1;
576 histfsize = 0;
577 afree(hname, APERM);
578 hname = NULL;
579 /* let's reset the history */
580 histptr = history - 1;
581 hist_source->line = 0;
582 }
583
584 hist_init(hist_source);
585 }
586 #endif
587
588 /*
589 * initialise the history vector
590 */
591 void
init_histvec(void)592 init_histvec(void)
593 {
594 if (history == (char **)NULL) {
595 histsize = MKSH_DEFHISTSIZE;
596 history = alloc2(histsize, sizeof(char *), APERM);
597 histptr = history - 1;
598 }
599 }
600
601
602 /*
603 * It turns out that there is a lot of ghastly hackery here
604 */
605
606 #if !defined(MKSH_SMALL) && HAVE_PERSISTENT_HISTORY
607 /* do not save command in history but possibly sync */
608 bool
histsync(void)609 histsync(void)
610 {
611 bool changed = false;
612
613 if (histfd != -1) {
614 int lno = hist_source->line;
615
616 hist_source->line++;
617 writehistfile(0, NULL);
618 hist_source->line--;
619
620 if (lno != hist_source->line)
621 changed = true;
622 }
623
624 return (changed);
625 }
626 #endif
627
628 /*
629 * save command in history
630 */
631 void
histsave(int * lnp,const char * cmd,bool dowrite MKSH_A_UNUSED,bool ignoredups)632 histsave(int *lnp, const char *cmd, bool dowrite MKSH_A_UNUSED, bool ignoredups)
633 {
634 char **hp;
635 char *c, *cp;
636
637 mkssert(cmd != NULL);
638 strdupx(c, cmd, APERM);
639 if ((cp = strchr(c, '\n')) != NULL)
640 *cp = '\0';
641
642 if (ignoredups && !strcmp(c, *histptr)
643 #if !defined(MKSH_SMALL) && HAVE_PERSISTENT_HISTORY
644 && !histsync()
645 #endif
646 ) {
647 afree(c, APERM);
648 return;
649 }
650 ++*lnp;
651
652 #if HAVE_PERSISTENT_HISTORY
653 if (dowrite && histfd != -1)
654 writehistfile(*lnp, c);
655 #endif
656
657 hp = histptr;
658
659 if (++hp >= history + histsize) {
660 /* remove oldest command */
661 afree(*history, APERM);
662 for (hp = history; hp < history + histsize - 1; hp++)
663 hp[0] = hp[1];
664 }
665 *hp = c;
666 histptr = hp;
667 }
668
669 /*
670 * Write history data to a file nominated by HISTFILE;
671 * if HISTFILE is unset then history still happens, but
672 * the data is not written to a file. All copies of ksh
673 * looking at the file will maintain the same history.
674 * This is ksh behaviour.
675 *
676 * This stuff uses mmap()
677 *
678 * This stuff is so totally broken it must eventually be
679 * redesigned, without mmap, better checks, support for
680 * larger files, etc. and handle partially corrupted files
681 */
682
683 /*-
684 * Open a history file
685 * Format is:
686 * Bytes 1, 2:
687 * HMAGIC - just to check that we are dealing with the correct object
688 * Then follows a number of stored commands
689 * Each command is
690 * <command byte><command number(4 octets, big endian)><bytes><NUL>
691 */
692 #define HMAGIC1 0xAB
693 #define HMAGIC2 0xCD
694 #define COMMAND 0xFF
695
696 #if HAVE_PERSISTENT_HISTORY
697 static const unsigned char sprinkle[2] = { HMAGIC1, HMAGIC2 };
698 #endif
699
700 void
hist_init(Source * s)701 hist_init(Source *s)
702 {
703 #if HAVE_PERSISTENT_HISTORY
704 unsigned char *base;
705 int lines, fd;
706 enum { hist_init_first, hist_init_retry, hist_init_restore } hs;
707 #endif
708
709 if (Flag(FTALKING) == 0)
710 return;
711
712 hstarted = true;
713 hist_source = s;
714
715 #if HAVE_PERSISTENT_HISTORY
716 if ((hname = str_val(global("HISTFILE"))) == NULL)
717 return;
718 strdupx(hname, hname, APERM);
719 hs = hist_init_first;
720
721 retry:
722 /* we have a file and are interactive */
723 if ((fd = open(hname, O_RDWR | O_CREAT | O_APPEND | O_BINARY,
724 0600)) < 0)
725 return;
726
727 histfd = savefd(fd);
728 if (histfd != fd)
729 close(fd);
730
731 mksh_lockfd(histfd);
732
733 histfsize = lseek(histfd, (off_t)0, SEEK_END);
734 if (histfsize > MKSH_MAXHISTFSIZE || hs == hist_init_restore) {
735 /* we ignore too large files but still append to them */
736 /* we also don't need to re-read after truncation */
737 goto hist_init_tail;
738 } else if (histfsize > 2) {
739 /* we have some data, check its validity */
740 base = (void *)mmap(NULL, (size_t)histfsize, PROT_READ,
741 MAP_FILE | MAP_PRIVATE, histfd, (off_t)0);
742 if (base == (unsigned char *)MAP_FAILED)
743 goto hist_init_fail;
744 if (base[0] != HMAGIC1 || base[1] != HMAGIC2) {
745 munmap(caddr_cast(base), (size_t)histfsize);
746 goto hist_init_fail;
747 }
748 /* load _all_ data */
749 lines = histload(hist_source, base + 2, (size_t)histfsize - 2);
750 munmap(caddr_cast(base), (size_t)histfsize);
751 /* check if the file needs to be truncated */
752 if (lines > histsize && histptr >= history) {
753 /* you're fucked up with the current code, trust me */
754 char *nhname, **hp;
755 struct stat sb;
756
757 /* create temporary file */
758 nhname = shf_smprintf("%s.%d", hname, (int)procpid);
759 if ((fd = open(nhname, O_RDWR | O_CREAT | O_TRUNC |
760 O_EXCL | O_BINARY, 0600)) < 0) {
761 /* just don't truncate then, meh. */
762 goto hist_trunc_dont;
763 }
764 if (fstat(histfd, &sb) >= 0 &&
765 chown(nhname, sb.st_uid, sb.st_gid)) {
766 /* abort the truncation then, meh. */
767 goto hist_trunc_abort;
768 }
769 /* we definitively want some magic in that file */
770 if (write(fd, sprinkle, 2) != 2)
771 goto hist_trunc_abort;
772 /* and of course the entries */
773 hp = history;
774 while (hp < histptr) {
775 if (!writehistline(fd,
776 s->line - (histptr - hp), *hp))
777 goto hist_trunc_abort;
778 ++hp;
779 }
780 /* now unlock, close both, rename, rinse, repeat */
781 close(fd);
782 fd = -1;
783 hist_finish();
784 if (rename(nhname, hname) < 0) {
785 hist_trunc_abort:
786 if (fd != -1)
787 close(fd);
788 unlink(nhname);
789 if (fd != -1)
790 goto hist_trunc_dont;
791 /* darn! restore histfd and pray */
792 }
793 hs = hist_init_restore;
794 hist_trunc_dont:
795 afree(nhname, ATEMP);
796 if (hs == hist_init_restore)
797 goto retry;
798 }
799 } else if (histfsize != 0) {
800 /* negative or too small... */
801 hist_init_fail:
802 /* ... or mmap failed or illegal */
803 hist_finish();
804 /* nuke the bogus file then retry, at most once */
805 if (!unlink(hname) && hs != hist_init_retry) {
806 hs = hist_init_retry;
807 goto retry;
808 }
809 if (hs != hist_init_retry)
810 bi_errorf("can't %s %s: %s",
811 "unlink HISTFILE", hname, cstrerror(errno));
812 histfsize = 0;
813 return;
814 } else {
815 /* size 0, add magic to the history file */
816 if (write(histfd, sprinkle, 2) != 2) {
817 hist_finish();
818 return;
819 }
820 }
821 histfsize = lseek(histfd, (off_t)0, SEEK_END);
822 hist_init_tail:
823 mksh_unlkfd(histfd);
824 #endif
825 }
826
827 #if HAVE_PERSISTENT_HISTORY
828 /*
829 * load the history structure from the stored data
830 */
831 static int
histload(Source * s,unsigned char * base,size_t bytes)832 histload(Source *s, unsigned char *base, size_t bytes)
833 {
834 int lno = 0, lines = 0;
835 unsigned char *cp;
836
837 histload_loop:
838 /* !bytes check as some systems (older FreeBSDs) have buggy memchr */
839 if (!bytes || (cp = memchr(base, COMMAND, bytes)) == NULL)
840 return (lines);
841 /* advance base pointer past COMMAND byte */
842 bytes -= ++cp - base;
843 base = cp;
844 /* if there is no full string left, don't bother with the rest */
845 if (bytes < 5 || (cp = memchr(base + 4, '\0', bytes - 4)) == NULL)
846 return (lines);
847 /* load the stored line number */
848 lno = ((base[0] & 0xFF) << 24) | ((base[1] & 0xFF) << 16) |
849 ((base[2] & 0xFF) << 8) | (base[3] & 0xFF);
850 /* store away the found line (@base[4]) */
851 ++lines;
852 if (histptr >= history && lno - 1 != s->line) {
853 /* a replacement? */
854 char **hp;
855
856 if (lno >= s->line - (histptr - history) && lno <= s->line) {
857 hp = &histptr[lno - s->line];
858 if (*hp)
859 afree(*hp, APERM);
860 strdupx(*hp, (char *)(base + 4), APERM);
861 }
862 } else {
863 s->line = lno--;
864 histsave(&lno, (char *)(base + 4), false, false);
865 }
866 /* advance base pointer past NUL */
867 bytes -= ++cp - base;
868 base = cp;
869 /* repeat until no more */
870 goto histload_loop;
871 }
872
873 /*
874 * write a command to the end of the history file
875 *
876 * This *MAY* seem easy but it's also necessary to check
877 * that the history file has not changed in size.
878 * If it has - then some other shell has written to it and
879 * we should (re)read those commands to update our history
880 */
881 static void
writehistfile(int lno,const char * cmd)882 writehistfile(int lno, const char *cmd)
883 {
884 off_t sizenow;
885 size_t bytes;
886 unsigned char *base, *news;
887
888 mksh_lockfd(histfd);
889 sizenow = lseek(histfd, (off_t)0, SEEK_END);
890 if (sizenow < histfsize) {
891 /* the file has shrunk; give up */
892 goto bad;
893 }
894 if (
895 /* ignore changes when the file is too large */
896 sizenow <= MKSH_MAXHISTFSIZE
897 &&
898 /* the size has changed, we need to do read updates */
899 sizenow > histfsize
900 ) {
901 /* both sizenow and histfsize are <= MKSH_MAXHISTFSIZE */
902 bytes = (size_t)(sizenow - histfsize);
903 base = (void *)mmap(NULL, (size_t)sizenow, PROT_READ,
904 MAP_FILE | MAP_PRIVATE, histfd, (off_t)0);
905 if (base == (unsigned char *)MAP_FAILED)
906 goto bad;
907 news = base + (size_t)histfsize;
908 if (*news == COMMAND) {
909 hist_source->line--;
910 histload(hist_source, news, bytes);
911 hist_source->line++;
912 lno = hist_source->line;
913 } else
914 bytes = 0;
915 munmap(caddr_cast(base), (size_t)sizenow);
916 if (!bytes)
917 goto bad;
918 }
919 if (cmd && !writehistline(histfd, lno, cmd)) {
920 bad:
921 hist_finish();
922 return;
923 }
924 histfsize = lseek(histfd, (off_t)0, SEEK_END);
925 mksh_unlkfd(histfd);
926 }
927
928 static int
writehistline(int fd,int lno,const char * cmd)929 writehistline(int fd, int lno, const char *cmd)
930 {
931 ssize_t n;
932 unsigned char hdr[5];
933
934 hdr[0] = COMMAND;
935 hdr[1] = (lno >> 24) & 0xFF;
936 hdr[2] = (lno >> 16) & 0xFF;
937 hdr[3] = (lno >> 8) & 0xFF;
938 hdr[4] = lno & 0xFF;
939 n = strlen(cmd) + 1;
940 return (write(fd, hdr, 5) == 5 && write(fd, cmd, n) == n);
941 }
942
943 void
hist_finish(void)944 hist_finish(void)
945 {
946 if (histfd >= 0) {
947 mksh_unlkfd(histfd);
948 (void)close(histfd);
949 }
950 histfd = -1;
951 }
952 #endif
953
954
955 #if !HAVE_SYS_SIGNAME
956 static const struct mksh_sigpair {
957 const char * const name;
958 int nr;
959 } mksh_sigpairs[] = {
960 #include "signames.inc"
961 { NULL, 0 }
962 };
963 #endif
964
965 #if HAVE_SYS_SIGLIST
966 #if !HAVE_SYS_SIGLIST_DECL
967 extern const char * const sys_siglist[];
968 #endif
969 #endif
970
971 void
inittraps(void)972 inittraps(void)
973 {
974 int i;
975 const char *cs;
976
977 trap_exstat = -1;
978
979 /* Populate sigtraps based on sys_signame and sys_siglist. */
980 /*XXX this is idiotic, use a multi-key/value hashtable! */
981 for (i = 0; i <= NSIG; i++) {
982 sigtraps[i].signal = i;
983 if (i == ksh_SIGERR) {
984 sigtraps[i].name = "ERR";
985 sigtraps[i].mess = "Error handler";
986 } else {
987 #if HAVE_SYS_SIGNAME
988 cs = sys_signame[i];
989 #else
990 const struct mksh_sigpair *pair = mksh_sigpairs;
991 while ((pair->nr != i) && (pair->name != NULL))
992 ++pair;
993 cs = pair->name;
994 #endif
995 if ((cs == NULL) ||
996 (cs[0] == '\0'))
997 sigtraps[i].name = shf_smprintf("%d", i);
998 else {
999 char *s;
1000
1001 /* this is not optimal, what about SIGSIG1? */
1002 if ((cs[0] & 0xDF) == 'S' &&
1003 (cs[1] & 0xDF) == 'I' &&
1004 (cs[2] & 0xDF) == 'G' &&
1005 cs[3] != '\0') {
1006 /* skip leading "SIG" */
1007 cs += 3;
1008 }
1009 strdupx(s, cs, APERM);
1010 sigtraps[i].name = s;
1011 while ((*s = ksh_toupper(*s)))
1012 ++s;
1013 }
1014 #if HAVE_SYS_SIGLIST
1015 sigtraps[i].mess = sys_siglist[i];
1016 #elif HAVE_STRSIGNAL
1017 sigtraps[i].mess = strsignal(i);
1018 #else
1019 sigtraps[i].mess = NULL;
1020 #endif
1021 if ((sigtraps[i].mess == NULL) ||
1022 (sigtraps[i].mess[0] == '\0'))
1023 sigtraps[i].mess = shf_smprintf("%s %d",
1024 "Signal", i);
1025 }
1026 }
1027 /* our name for signal 0 */
1028 sigtraps[ksh_SIGEXIT].name = "EXIT";
1029
1030 (void)sigemptyset(&Sigact_ign.sa_mask);
1031 Sigact_ign.sa_flags = 0; /* interruptible */
1032 Sigact_ign.sa_handler = SIG_IGN;
1033
1034 sigtraps[SIGINT].flags |= TF_DFL_INTR | TF_TTY_INTR;
1035 sigtraps[SIGQUIT].flags |= TF_DFL_INTR | TF_TTY_INTR;
1036 /* SIGTERM is not fatal for interactive */
1037 sigtraps[SIGTERM].flags |= TF_DFL_INTR;
1038 sigtraps[SIGHUP].flags |= TF_FATAL;
1039 sigtraps[SIGCHLD].flags |= TF_SHELL_USES;
1040
1041 /* these are always caught so we can clean up any temporary files. */
1042 setsig(&sigtraps[SIGINT], trapsig, SS_RESTORE_ORIG);
1043 setsig(&sigtraps[SIGQUIT], trapsig, SS_RESTORE_ORIG);
1044 setsig(&sigtraps[SIGTERM], trapsig, SS_RESTORE_ORIG);
1045 setsig(&sigtraps[SIGHUP], trapsig, SS_RESTORE_ORIG);
1046 }
1047
1048 static void alarm_catcher(int sig);
1049
1050 void
alarm_init(void)1051 alarm_init(void)
1052 {
1053 sigtraps[SIGALRM].flags |= TF_SHELL_USES;
1054 setsig(&sigtraps[SIGALRM], alarm_catcher,
1055 SS_RESTORE_ORIG|SS_FORCE|SS_SHTRAP);
1056 }
1057
1058 /* ARGSUSED */
1059 static void
alarm_catcher(int sig MKSH_A_UNUSED)1060 alarm_catcher(int sig MKSH_A_UNUSED)
1061 {
1062 /* this runs inside interrupt context, with errno saved */
1063
1064 if (ksh_tmout_state == TMOUT_READING) {
1065 int left = alarm(0);
1066
1067 if (left == 0) {
1068 ksh_tmout_state = TMOUT_LEAVING;
1069 intrsig = 1;
1070 } else
1071 alarm(left);
1072 }
1073 }
1074
1075 Trap *
gettrap(const char * cs,bool igncase)1076 gettrap(const char *cs, bool igncase)
1077 {
1078 int i;
1079 Trap *p;
1080 char *as;
1081
1082 if (ksh_isdigit(*cs)) {
1083 return ((getn(cs, &i) && 0 <= i && i < NSIG) ?
1084 (&sigtraps[i]) : NULL);
1085 }
1086
1087 /* this breaks SIGSIG1, but we do that above anyway */
1088 if ((cs[0] & 0xDF) == 'S' &&
1089 (cs[1] & 0xDF) == 'I' &&
1090 (cs[2] & 0xDF) == 'G' &&
1091 cs[3] != '\0') {
1092 /* skip leading "SIG" */
1093 cs += 3;
1094 }
1095 if (igncase) {
1096 char *s;
1097
1098 strdupx(as, cs, ATEMP);
1099 cs = s = as;
1100 while ((*s = ksh_toupper(*s)))
1101 ++s;
1102 } else
1103 as = NULL;
1104
1105 p = sigtraps;
1106 for (i = 0; i <= NSIG; i++) {
1107 if (!strcmp(p->name, cs))
1108 goto found;
1109 ++p;
1110 }
1111 p = NULL;
1112 found:
1113 afree(as, ATEMP);
1114 return (p);
1115 }
1116
1117 /*
1118 * trap signal handler
1119 */
1120 void
trapsig(int i)1121 trapsig(int i)
1122 {
1123 Trap *p = &sigtraps[i];
1124 int eno = errno;
1125
1126 trap = p->set = 1;
1127 if (p->flags & TF_DFL_INTR)
1128 intrsig = 1;
1129 if ((p->flags & TF_FATAL) && !p->trap) {
1130 fatal_trap = 1;
1131 intrsig = 1;
1132 }
1133 if (p->shtrap)
1134 (*p->shtrap)(i);
1135 errno = eno;
1136 }
1137
1138 /*
1139 * called when we want to allow the user to ^C out of something - won't
1140 * work if user has trapped SIGINT.
1141 */
1142 void
intrcheck(void)1143 intrcheck(void)
1144 {
1145 if (intrsig)
1146 runtraps(TF_DFL_INTR|TF_FATAL);
1147 }
1148
1149 /*
1150 * called after EINTR to check if a signal with normally causes process
1151 * termination has been received.
1152 */
1153 int
fatal_trap_check(void)1154 fatal_trap_check(void)
1155 {
1156 int i;
1157 Trap *p;
1158
1159 /* todo: should check if signal is fatal, not the TF_DFL_INTR flag */
1160 for (p = sigtraps, i = NSIG+1; --i >= 0; p++)
1161 if (p->set && (p->flags & (TF_DFL_INTR|TF_FATAL)))
1162 /* return value is used as an exit code */
1163 return (128 + p->signal);
1164 return (0);
1165 }
1166
1167 /*
1168 * Returns the signal number of any pending traps: ie, a signal which has
1169 * occurred for which a trap has been set or for which the TF_DFL_INTR flag
1170 * is set.
1171 */
1172 int
trap_pending(void)1173 trap_pending(void)
1174 {
1175 int i;
1176 Trap *p;
1177
1178 for (p = sigtraps, i = NSIG+1; --i >= 0; p++)
1179 if (p->set && ((p->trap && p->trap[0]) ||
1180 ((p->flags & (TF_DFL_INTR|TF_FATAL)) && !p->trap)))
1181 return (p->signal);
1182 return (0);
1183 }
1184
1185 /*
1186 * run any pending traps. If intr is set, only run traps that
1187 * can interrupt commands.
1188 */
1189 void
runtraps(int flag)1190 runtraps(int flag)
1191 {
1192 int i;
1193 Trap *p;
1194
1195 if (ksh_tmout_state == TMOUT_LEAVING) {
1196 ksh_tmout_state = TMOUT_EXECUTING;
1197 warningf(false, "timed out waiting for input");
1198 unwind(LEXIT);
1199 } else
1200 /*
1201 * XXX: this means the alarm will have no effect if a trap
1202 * is caught after the alarm() was started...not good.
1203 */
1204 ksh_tmout_state = TMOUT_EXECUTING;
1205 if (!flag)
1206 trap = 0;
1207 if (flag & TF_DFL_INTR)
1208 intrsig = 0;
1209 if (flag & TF_FATAL)
1210 fatal_trap = 0;
1211 ++trap_nested;
1212 for (p = sigtraps, i = NSIG+1; --i >= 0; p++)
1213 if (p->set && (!flag ||
1214 ((p->flags & flag) && p->trap == NULL)))
1215 runtrap(p, false);
1216 if (!--trap_nested)
1217 runtrap(NULL, true);
1218 }
1219
1220 void
runtrap(Trap * p,bool is_last)1221 runtrap(Trap *p, bool is_last)
1222 {
1223 int old_changed = 0, i;
1224 char *trapstr;
1225
1226 if (p == NULL)
1227 /* just clean up, see runtraps() above */
1228 goto donetrap;
1229 i = p->signal;
1230 trapstr = p->trap;
1231 p->set = 0;
1232 if (trapstr == NULL) {
1233 /* SIG_DFL */
1234 if (p->flags & TF_FATAL) {
1235 /* eg, SIGHUP */
1236 exstat = (int)ksh_min(128U + (unsigned)i, 255U);
1237 unwind(LLEAVE);
1238 }
1239 if (p->flags & TF_DFL_INTR) {
1240 /* eg, SIGINT, SIGQUIT, SIGTERM, etc. */
1241 exstat = (int)ksh_min(128U + (unsigned)i, 255U);
1242 unwind(LINTR);
1243 }
1244 goto donetrap;
1245 }
1246 if (trapstr[0] == '\0')
1247 /* SIG_IGN */
1248 goto donetrap;
1249 if (i == ksh_SIGEXIT || i == ksh_SIGERR) {
1250 /* avoid recursion on these */
1251 old_changed = p->flags & TF_CHANGED;
1252 p->flags &= ~TF_CHANGED;
1253 p->trap = NULL;
1254 }
1255 if (trap_exstat == -1)
1256 trap_exstat = exstat & 0xFF;
1257 /*
1258 * Note: trapstr is fully parsed before anything is executed, thus
1259 * no problem with afree(p->trap) in settrap() while still in use.
1260 */
1261 command(trapstr, current_lineno);
1262 if (i == ksh_SIGEXIT || i == ksh_SIGERR) {
1263 if (p->flags & TF_CHANGED)
1264 /* don't clear TF_CHANGED */
1265 afree(trapstr, APERM);
1266 else
1267 p->trap = trapstr;
1268 p->flags |= old_changed;
1269 }
1270
1271 donetrap:
1272 /* we're the last trap of a sequence executed */
1273 if (is_last && trap_exstat != -1) {
1274 exstat = trap_exstat;
1275 trap_exstat = -1;
1276 }
1277 }
1278
1279 /* clear pending traps and reset user's trap handlers; used after fork(2) */
1280 void
cleartraps(void)1281 cleartraps(void)
1282 {
1283 int i;
1284 Trap *p;
1285
1286 trap = 0;
1287 intrsig = 0;
1288 fatal_trap = 0;
1289 for (i = NSIG+1, p = sigtraps; --i >= 0; p++) {
1290 p->set = 0;
1291 if ((p->flags & TF_USER_SET) && (p->trap && p->trap[0]))
1292 settrap(p, NULL);
1293 }
1294 }
1295
1296 /* restore signals just before an exec(2) */
1297 void
restoresigs(void)1298 restoresigs(void)
1299 {
1300 int i;
1301 Trap *p;
1302
1303 for (i = NSIG+1, p = sigtraps; --i >= 0; p++)
1304 if (p->flags & (TF_EXEC_IGN|TF_EXEC_DFL))
1305 setsig(p, (p->flags & TF_EXEC_IGN) ? SIG_IGN : SIG_DFL,
1306 SS_RESTORE_CURR|SS_FORCE);
1307 }
1308
1309 void
settrap(Trap * p,const char * s)1310 settrap(Trap *p, const char *s)
1311 {
1312 sig_t f;
1313
1314 if (p->trap)
1315 afree(p->trap, APERM);
1316 /* handles s == NULL */
1317 strdupx(p->trap, s, APERM);
1318 p->flags |= TF_CHANGED;
1319 f = !s ? SIG_DFL : s[0] ? trapsig : SIG_IGN;
1320
1321 p->flags |= TF_USER_SET;
1322 if ((p->flags & (TF_DFL_INTR|TF_FATAL)) && f == SIG_DFL)
1323 f = trapsig;
1324 else if (p->flags & TF_SHELL_USES) {
1325 if (!(p->flags & TF_ORIG_IGN) || Flag(FTALKING)) {
1326 /* do what user wants at exec time */
1327 p->flags &= ~(TF_EXEC_IGN|TF_EXEC_DFL);
1328 if (f == SIG_IGN)
1329 p->flags |= TF_EXEC_IGN;
1330 else
1331 p->flags |= TF_EXEC_DFL;
1332 }
1333
1334 /*
1335 * assumes handler already set to what shell wants it
1336 * (normally trapsig, but could be j_sigchld() or SIG_IGN)
1337 */
1338 return;
1339 }
1340
1341 /* todo: should we let user know signal is ignored? how? */
1342 setsig(p, f, SS_RESTORE_CURR|SS_USER);
1343 }
1344
1345 /*
1346 * Called by c_print() when writing to a co-process to ensure SIGPIPE won't
1347 * kill shell (unless user catches it and exits)
1348 */
1349 int
block_pipe(void)1350 block_pipe(void)
1351 {
1352 int restore_dfl = 0;
1353 Trap *p = &sigtraps[SIGPIPE];
1354
1355 if (!(p->flags & (TF_ORIG_IGN|TF_ORIG_DFL))) {
1356 setsig(p, SIG_IGN, SS_RESTORE_CURR);
1357 if (p->flags & TF_ORIG_DFL)
1358 restore_dfl = 1;
1359 } else if (p->cursig == SIG_DFL) {
1360 setsig(p, SIG_IGN, SS_RESTORE_CURR);
1361 /* restore to SIG_DFL */
1362 restore_dfl = 1;
1363 }
1364 return (restore_dfl);
1365 }
1366
1367 /* Called by c_print() to undo whatever block_pipe() did */
1368 void
restore_pipe(int restore_dfl)1369 restore_pipe(int restore_dfl)
1370 {
1371 if (restore_dfl)
1372 setsig(&sigtraps[SIGPIPE], SIG_DFL, SS_RESTORE_CURR);
1373 }
1374
1375 /*
1376 * Set action for a signal. Action may not be set if original
1377 * action was SIG_IGN, depending on the value of flags and FTALKING.
1378 */
1379 int
setsig(Trap * p,sig_t f,int flags)1380 setsig(Trap *p, sig_t f, int flags)
1381 {
1382 struct sigaction sigact;
1383
1384 if (p->signal == ksh_SIGEXIT || p->signal == ksh_SIGERR)
1385 return (1);
1386
1387 memset(&sigact, 0, sizeof(sigact));
1388
1389 /*
1390 * First time setting this signal? If so, get and note the current
1391 * setting.
1392 */
1393 if (!(p->flags & (TF_ORIG_IGN|TF_ORIG_DFL))) {
1394 sigaction(p->signal, &Sigact_ign, &sigact);
1395 p->flags |= sigact.sa_handler == SIG_IGN ?
1396 TF_ORIG_IGN : TF_ORIG_DFL;
1397 p->cursig = SIG_IGN;
1398 }
1399
1400 /*-
1401 * Generally, an ignored signal stays ignored, except if
1402 * - the user of an interactive shell wants to change it
1403 * - the shell wants for force a change
1404 */
1405 if ((p->flags & TF_ORIG_IGN) && !(flags & SS_FORCE) &&
1406 (!(flags & SS_USER) || !Flag(FTALKING)))
1407 return (0);
1408
1409 setexecsig(p, flags & SS_RESTORE_MASK);
1410
1411 /*
1412 * This is here 'cause there should be a way of clearing
1413 * shtraps, but don't know if this is a sane way of doing
1414 * it. At the moment, all users of shtrap are lifetime
1415 * users (SIGALRM, SIGCHLD, SIGWINCH).
1416 */
1417 if (!(flags & SS_USER))
1418 p->shtrap = (sig_t)NULL;
1419 if (flags & SS_SHTRAP) {
1420 p->shtrap = f;
1421 f = trapsig;
1422 }
1423
1424 if (p->cursig != f) {
1425 p->cursig = f;
1426 (void)sigemptyset(&sigact.sa_mask);
1427 /* interruptible */
1428 sigact.sa_flags = 0;
1429 sigact.sa_handler = f;
1430 sigaction(p->signal, &sigact, NULL);
1431 }
1432
1433 return (1);
1434 }
1435
1436 /* control what signal is set to before an exec() */
1437 void
setexecsig(Trap * p,int restore)1438 setexecsig(Trap *p, int restore)
1439 {
1440 /* XXX debugging */
1441 if (!(p->flags & (TF_ORIG_IGN|TF_ORIG_DFL)))
1442 internal_errorf("setexecsig: unset signal %d(%s)",
1443 p->signal, p->name);
1444
1445 /* restore original value for exec'd kids */
1446 p->flags &= ~(TF_EXEC_IGN|TF_EXEC_DFL);
1447 switch (restore & SS_RESTORE_MASK) {
1448 case SS_RESTORE_CURR:
1449 /* leave things as they currently are */
1450 break;
1451 case SS_RESTORE_ORIG:
1452 p->flags |= p->flags & TF_ORIG_IGN ? TF_EXEC_IGN : TF_EXEC_DFL;
1453 break;
1454 case SS_RESTORE_DFL:
1455 p->flags |= TF_EXEC_DFL;
1456 break;
1457 case SS_RESTORE_IGN:
1458 p->flags |= TF_EXEC_IGN;
1459 break;
1460 }
1461 }
1462
1463 #if HAVE_PERSISTENT_HISTORY || defined(DF)
1464 /*
1465 * File descriptor locking and unlocking functions.
1466 * Could use some error handling, but hey, this is only
1467 * advisory locking anyway, will often not work over NFS,
1468 * and you are SOL if this fails...
1469 */
1470
1471 void
mksh_lockfd(int fd)1472 mksh_lockfd(int fd)
1473 {
1474 #if defined(__OpenBSD__)
1475 /* flock is not interrupted by signals */
1476 (void)flock(fd, LOCK_EX);
1477 #elif HAVE_FLOCK
1478 int rv;
1479
1480 /* e.g. on Linux */
1481 do {
1482 rv = flock(fd, LOCK_EX);
1483 } while (rv == 1 && errno == EINTR);
1484 #elif HAVE_LOCK_FCNTL
1485 int rv;
1486 struct flock lks;
1487
1488 memset(&lks, 0, sizeof(lks));
1489 lks.l_type = F_WRLCK;
1490 do {
1491 rv = fcntl(fd, F_SETLKW, &lks);
1492 } while (rv == 1 && errno == EINTR);
1493 #endif
1494 }
1495
1496 /* designed to not define mksh_unlkfd if none triggered */
1497 #if HAVE_FLOCK
1498 void
mksh_unlkfd(int fd)1499 mksh_unlkfd(int fd)
1500 {
1501 (void)flock(fd, LOCK_UN);
1502 }
1503 #elif HAVE_LOCK_FCNTL
1504 void
mksh_unlkfd(int fd)1505 mksh_unlkfd(int fd)
1506 {
1507 struct flock lks;
1508
1509 memset(&lks, 0, sizeof(lks));
1510 lks.l_type = F_UNLCK;
1511 (void)fcntl(fd, F_SETLKW, &lks);
1512 }
1513 #endif
1514 #endif
1515