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