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
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.131 2012/12/28 02:28:35 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 int anchored = *str == '?' ? (++str, 0) : 1;
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,int anchored)512 findhist(int start, int fwd, const char *str, int 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, 0600)) < 0)
724 return;
725
726 histfd = savefd(fd);
727 if (histfd != fd)
728 close(fd);
729
730 mksh_lockfd(histfd);
731
732 histfsize = lseek(histfd, (off_t)0, SEEK_END);
733 if (histfsize > MKSH_MAXHISTFSIZE || hs == hist_init_restore) {
734 /* we ignore too large files but still append to them */
735 /* we also don't need to re-read after truncation */
736 goto hist_init_tail;
737 } else if (histfsize > 2) {
738 /* we have some data, check its validity */
739 base = (void *)mmap(NULL, (size_t)histfsize, PROT_READ,
740 MAP_FILE | MAP_PRIVATE, histfd, (off_t)0);
741 if (base == (unsigned char *)MAP_FAILED)
742 goto hist_init_fail;
743 if (base[0] != HMAGIC1 || base[1] != HMAGIC2) {
744 munmap(caddr_cast(base), (size_t)histfsize);
745 goto hist_init_fail;
746 }
747 /* load _all_ data */
748 lines = histload(hist_source, base + 2, (size_t)histfsize - 2);
749 munmap(caddr_cast(base), (size_t)histfsize);
750 /* check if the file needs to be truncated */
751 if (lines > histsize && histptr >= history) {
752 /* you're fucked up with the current code, trust me */
753 char *nhname, **hp;
754 struct stat sb;
755
756 /* create temporary file */
757 nhname = shf_smprintf("%s.%d", hname, (int)procpid);
758 if ((fd = open(nhname, O_RDWR | O_CREAT | O_TRUNC |
759 O_EXCL, 0600)) < 0) {
760 /* just don't truncate then, meh. */
761 goto hist_trunc_dont;
762 }
763 if (fstat(histfd, &sb) >= 0 &&
764 chown(nhname, sb.st_uid, sb.st_gid)) {
765 /* abort the truncation then, meh. */
766 goto hist_trunc_abort;
767 }
768 /* we definitively want some magic in that file */
769 if (write(fd, sprinkle, 2) != 2)
770 goto hist_trunc_abort;
771 /* and of course the entries */
772 hp = history;
773 while (hp < histptr) {
774 if (!writehistline(fd,
775 s->line - (histptr - hp), *hp))
776 goto hist_trunc_abort;
777 ++hp;
778 }
779 /* now unlock, close both, rename, rinse, repeat */
780 close(fd);
781 fd = -1;
782 hist_finish();
783 if (rename(nhname, hname) < 0) {
784 hist_trunc_abort:
785 if (fd != -1)
786 close(fd);
787 unlink(nhname);
788 if (fd != -1)
789 goto hist_trunc_dont;
790 /* darn! restore histfd and pray */
791 }
792 hs = hist_init_restore;
793 hist_trunc_dont:
794 afree(nhname, ATEMP);
795 if (hs == hist_init_restore)
796 goto retry;
797 }
798 } else if (histfsize != 0) {
799 /* negative or too small... */
800 hist_init_fail:
801 /* ... or mmap failed or illegal */
802 hist_finish();
803 /* nuke the bogus file then retry, at most once */
804 if (!unlink(hname) && hs != hist_init_retry) {
805 hs = hist_init_retry;
806 goto retry;
807 }
808 if (hs != hist_init_retry)
809 bi_errorf("can't %s %s: %s",
810 "unlink HISTFILE", hname, cstrerror(errno));
811 histfsize = 0;
812 return;
813 } else {
814 /* size 0, add magic to the history file */
815 if (write(histfd, sprinkle, 2) != 2) {
816 hist_finish();
817 return;
818 }
819 }
820 histfsize = lseek(histfd, (off_t)0, SEEK_END);
821 hist_init_tail:
822 mksh_unlkfd(histfd);
823 #endif
824 }
825
826 #if HAVE_PERSISTENT_HISTORY
827 /*
828 * load the history structure from the stored data
829 */
830 static int
histload(Source * s,unsigned char * base,size_t bytes)831 histload(Source *s, unsigned char *base, size_t bytes)
832 {
833 int lno = 0, lines = 0;
834 unsigned char *cp;
835
836 histload_loop:
837 /* !bytes check as some systems (older FreeBSDs) have buggy memchr */
838 if (!bytes || (cp = memchr(base, COMMAND, bytes)) == NULL)
839 return (lines);
840 /* advance base pointer past COMMAND byte */
841 bytes -= ++cp - base;
842 base = cp;
843 /* if there is no full string left, don't bother with the rest */
844 if (bytes < 5 || (cp = memchr(base + 4, '\0', bytes - 4)) == NULL)
845 return (lines);
846 /* load the stored line number */
847 lno = ((base[0] & 0xFF) << 24) | ((base[1] & 0xFF) << 16) |
848 ((base[2] & 0xFF) << 8) | (base[3] & 0xFF);
849 /* store away the found line (@base[4]) */
850 ++lines;
851 if (histptr >= history && lno - 1 != s->line) {
852 /* a replacement? */
853 char **hp;
854
855 if (lno >= s->line - (histptr - history) && lno <= s->line) {
856 hp = &histptr[lno - s->line];
857 if (*hp)
858 afree(*hp, APERM);
859 strdupx(*hp, (char *)(base + 4), APERM);
860 }
861 } else {
862 s->line = lno--;
863 histsave(&lno, (char *)(base + 4), false, false);
864 }
865 /* advance base pointer past NUL */
866 bytes -= ++cp - base;
867 base = cp;
868 /* repeat until no more */
869 goto histload_loop;
870 }
871
872 /*
873 * write a command to the end of the history file
874 *
875 * This *MAY* seem easy but it's also necessary to check
876 * that the history file has not changed in size.
877 * If it has - then some other shell has written to it and
878 * we should (re)read those commands to update our history
879 */
880 static void
writehistfile(int lno,const char * cmd)881 writehistfile(int lno, const char *cmd)
882 {
883 off_t sizenow;
884 size_t bytes;
885 unsigned char *base, *news;
886
887 mksh_lockfd(histfd);
888 sizenow = lseek(histfd, (off_t)0, SEEK_END);
889 if (sizenow < histfsize) {
890 /* the file has shrunk; give up */
891 goto bad;
892 }
893 if (
894 /* ignore changes when the file is too large */
895 sizenow <= MKSH_MAXHISTFSIZE
896 &&
897 /* the size has changed, we need to do read updates */
898 sizenow > histfsize
899 ) {
900 /* both sizenow and histfsize are <= MKSH_MAXHISTFSIZE */
901 bytes = (size_t)(sizenow - histfsize);
902 base = (void *)mmap(NULL, (size_t)sizenow, PROT_READ,
903 MAP_FILE | MAP_PRIVATE, histfd, (off_t)0);
904 if (base == (unsigned char *)MAP_FAILED)
905 goto bad;
906 news = base + (size_t)histfsize;
907 if (*news == COMMAND) {
908 hist_source->line--;
909 histload(hist_source, news, bytes);
910 hist_source->line++;
911 lno = hist_source->line;
912 } else
913 bytes = 0;
914 munmap(caddr_cast(base), (size_t)sizenow);
915 if (!bytes)
916 goto bad;
917 }
918 if (cmd && !writehistline(histfd, lno, cmd)) {
919 bad:
920 hist_finish();
921 return;
922 }
923 histfsize = lseek(histfd, (off_t)0, SEEK_END);
924 mksh_unlkfd(histfd);
925 }
926
927 static int
writehistline(int fd,int lno,const char * cmd)928 writehistline(int fd, int lno, const char *cmd)
929 {
930 ssize_t n;
931 unsigned char hdr[5];
932
933 hdr[0] = COMMAND;
934 hdr[1] = (lno >> 24) & 0xFF;
935 hdr[2] = (lno >> 16) & 0xFF;
936 hdr[3] = (lno >> 8) & 0xFF;
937 hdr[4] = lno & 0xFF;
938 n = strlen(cmd) + 1;
939 return (write(fd, hdr, 5) == 5 && write(fd, cmd, n) == n);
940 }
941
942 void
hist_finish(void)943 hist_finish(void)
944 {
945 if (histfd >= 0) {
946 mksh_unlkfd(histfd);
947 (void)close(histfd);
948 }
949 histfd = -1;
950 }
951 #endif
952
953
954 #if !HAVE_SYS_SIGNAME
955 static const struct mksh_sigpair {
956 const char * const name;
957 int nr;
958 } mksh_sigpairs[] = {
959 #include "signames.inc"
960 { NULL, 0 }
961 };
962 #endif
963
964 #if HAVE_SYS_SIGLIST
965 #if !HAVE_SYS_SIGLIST_DECL
966 extern const char * const sys_siglist[];
967 #endif
968 #endif
969
970 void
inittraps(void)971 inittraps(void)
972 {
973 int i;
974 const char *cs;
975
976 trap_exstat = -1;
977
978 /* Populate sigtraps based on sys_signame and sys_siglist. */
979 for (i = 0; i <= NSIG; i++) {
980 sigtraps[i].signal = i;
981 if (i == ksh_SIGERR) {
982 sigtraps[i].name = "ERR";
983 sigtraps[i].mess = "Error handler";
984 } else {
985 #if HAVE_SYS_SIGNAME
986 cs = sys_signame[i];
987 #else
988 const struct mksh_sigpair *pair = mksh_sigpairs;
989 while ((pair->nr != i) && (pair->name != NULL))
990 ++pair;
991 cs = pair->name;
992 #endif
993 if ((cs == NULL) ||
994 (cs[0] == '\0'))
995 sigtraps[i].name = shf_smprintf("%d", i);
996 else {
997 char *s;
998
999 /* this is not optimal, what about SIGSIG1? */
1000 if ((cs[0] & 0xDF) == 'S' &&
1001 (cs[1] & 0xDF) == 'I' &&
1002 (cs[2] & 0xDF) == 'G' &&
1003 cs[3] != '\0') {
1004 /* skip leading "SIG" */
1005 cs += 3;
1006 }
1007 strdupx(s, cs, APERM);
1008 sigtraps[i].name = s;
1009 while ((*s = ksh_toupper(*s)))
1010 ++s;
1011 }
1012 #if HAVE_SYS_SIGLIST
1013 sigtraps[i].mess = sys_siglist[i];
1014 #elif HAVE_STRSIGNAL
1015 sigtraps[i].mess = strsignal(i);
1016 #else
1017 sigtraps[i].mess = NULL;
1018 #endif
1019 if ((sigtraps[i].mess == NULL) ||
1020 (sigtraps[i].mess[0] == '\0'))
1021 sigtraps[i].mess = shf_smprintf("%s %d",
1022 "Signal", i);
1023 }
1024 }
1025 /* our name for signal 0 */
1026 sigtraps[ksh_SIGEXIT].name = "EXIT";
1027
1028 (void)sigemptyset(&Sigact_ign.sa_mask);
1029 Sigact_ign.sa_flags = 0; /* interruptible */
1030 Sigact_ign.sa_handler = SIG_IGN;
1031
1032 sigtraps[SIGINT].flags |= TF_DFL_INTR | TF_TTY_INTR;
1033 sigtraps[SIGQUIT].flags |= TF_DFL_INTR | TF_TTY_INTR;
1034 /* SIGTERM is not fatal for interactive */
1035 sigtraps[SIGTERM].flags |= TF_DFL_INTR;
1036 sigtraps[SIGHUP].flags |= TF_FATAL;
1037 sigtraps[SIGCHLD].flags |= TF_SHELL_USES;
1038
1039 /* these are always caught so we can clean up any temporary files. */
1040 setsig(&sigtraps[SIGINT], trapsig, SS_RESTORE_ORIG);
1041 setsig(&sigtraps[SIGQUIT], trapsig, SS_RESTORE_ORIG);
1042 setsig(&sigtraps[SIGTERM], trapsig, SS_RESTORE_ORIG);
1043 setsig(&sigtraps[SIGHUP], trapsig, SS_RESTORE_ORIG);
1044 }
1045
1046 static void alarm_catcher(int sig);
1047
1048 void
alarm_init(void)1049 alarm_init(void)
1050 {
1051 sigtraps[SIGALRM].flags |= TF_SHELL_USES;
1052 setsig(&sigtraps[SIGALRM], alarm_catcher,
1053 SS_RESTORE_ORIG|SS_FORCE|SS_SHTRAP);
1054 }
1055
1056 /* ARGSUSED */
1057 static void
alarm_catcher(int sig MKSH_A_UNUSED)1058 alarm_catcher(int sig MKSH_A_UNUSED)
1059 {
1060 /* this runs inside interrupt context, with errno saved */
1061
1062 if (ksh_tmout_state == TMOUT_READING) {
1063 int left = alarm(0);
1064
1065 if (left == 0) {
1066 ksh_tmout_state = TMOUT_LEAVING;
1067 intrsig = 1;
1068 } else
1069 alarm(left);
1070 }
1071 }
1072
1073 Trap *
gettrap(const char * cs,bool igncase)1074 gettrap(const char *cs, bool igncase)
1075 {
1076 int i;
1077 Trap *p;
1078 char *as;
1079
1080 if (ksh_isdigit(*cs)) {
1081 return ((getn(cs, &i) && 0 <= i && i < NSIG) ?
1082 (&sigtraps[i]) : NULL);
1083 }
1084
1085 /* this breaks SIGSIG1, but we do that above anyway */
1086 if ((cs[0] & 0xDF) == 'S' &&
1087 (cs[1] & 0xDF) == 'I' &&
1088 (cs[2] & 0xDF) == 'G' &&
1089 cs[3] != '\0') {
1090 /* skip leading "SIG" */
1091 cs += 3;
1092 }
1093 if (igncase) {
1094 char *s;
1095
1096 strdupx(as, cs, ATEMP);
1097 cs = s = as;
1098 while ((*s = ksh_toupper(*s)))
1099 ++s;
1100 } else
1101 as = NULL;
1102
1103 p = sigtraps;
1104 for (i = 0; i <= NSIG; i++) {
1105 if (!strcmp(p->name, cs))
1106 goto found;
1107 ++p;
1108 }
1109 p = NULL;
1110 found:
1111 afree(as, ATEMP);
1112 return (p);
1113 }
1114
1115 /*
1116 * trap signal handler
1117 */
1118 void
trapsig(int i)1119 trapsig(int i)
1120 {
1121 Trap *p = &sigtraps[i];
1122 int eno = errno;
1123
1124 trap = p->set = 1;
1125 if (p->flags & TF_DFL_INTR)
1126 intrsig = 1;
1127 if ((p->flags & TF_FATAL) && !p->trap) {
1128 fatal_trap = 1;
1129 intrsig = 1;
1130 }
1131 if (p->shtrap)
1132 (*p->shtrap)(i);
1133 errno = eno;
1134 }
1135
1136 /*
1137 * called when we want to allow the user to ^C out of something - won't
1138 * work if user has trapped SIGINT.
1139 */
1140 void
intrcheck(void)1141 intrcheck(void)
1142 {
1143 if (intrsig)
1144 runtraps(TF_DFL_INTR|TF_FATAL);
1145 }
1146
1147 /*
1148 * called after EINTR to check if a signal with normally causes process
1149 * termination has been received.
1150 */
1151 int
fatal_trap_check(void)1152 fatal_trap_check(void)
1153 {
1154 int i;
1155 Trap *p;
1156
1157 /* todo: should check if signal is fatal, not the TF_DFL_INTR flag */
1158 for (p = sigtraps, i = NSIG+1; --i >= 0; p++)
1159 if (p->set && (p->flags & (TF_DFL_INTR|TF_FATAL)))
1160 /* return value is used as an exit code */
1161 return (128 + p->signal);
1162 return (0);
1163 }
1164
1165 /*
1166 * Returns the signal number of any pending traps: ie, a signal which has
1167 * occurred for which a trap has been set or for which the TF_DFL_INTR flag
1168 * is set.
1169 */
1170 int
trap_pending(void)1171 trap_pending(void)
1172 {
1173 int i;
1174 Trap *p;
1175
1176 for (p = sigtraps, i = NSIG+1; --i >= 0; p++)
1177 if (p->set && ((p->trap && p->trap[0]) ||
1178 ((p->flags & (TF_DFL_INTR|TF_FATAL)) && !p->trap)))
1179 return (p->signal);
1180 return (0);
1181 }
1182
1183 /*
1184 * run any pending traps. If intr is set, only run traps that
1185 * can interrupt commands.
1186 */
1187 void
runtraps(int flag)1188 runtraps(int flag)
1189 {
1190 int i;
1191 Trap *p;
1192
1193 if (ksh_tmout_state == TMOUT_LEAVING) {
1194 ksh_tmout_state = TMOUT_EXECUTING;
1195 warningf(false, "timed out waiting for input");
1196 unwind(LEXIT);
1197 } else
1198 /*
1199 * XXX: this means the alarm will have no effect if a trap
1200 * is caught after the alarm() was started...not good.
1201 */
1202 ksh_tmout_state = TMOUT_EXECUTING;
1203 if (!flag)
1204 trap = 0;
1205 if (flag & TF_DFL_INTR)
1206 intrsig = 0;
1207 if (flag & TF_FATAL)
1208 fatal_trap = 0;
1209 ++trap_nested;
1210 for (p = sigtraps, i = NSIG+1; --i >= 0; p++)
1211 if (p->set && (!flag ||
1212 ((p->flags & flag) && p->trap == NULL)))
1213 runtrap(p, false);
1214 if (!--trap_nested)
1215 runtrap(NULL, true);
1216 }
1217
1218 void
runtrap(Trap * p,bool is_last)1219 runtrap(Trap *p, bool is_last)
1220 {
1221 int old_changed = 0, i;
1222 char *trapstr;
1223
1224 if (p == NULL)
1225 /* just clean up, see runtraps() above */
1226 goto donetrap;
1227 i = p->signal;
1228 trapstr = p->trap;
1229 p->set = 0;
1230 if (trapstr == NULL) {
1231 /* SIG_DFL */
1232 if (p->flags & TF_FATAL) {
1233 /* eg, SIGHUP */
1234 exstat = (int)ksh_min(128U + (unsigned)i, 255U);
1235 unwind(LLEAVE);
1236 }
1237 if (p->flags & TF_DFL_INTR) {
1238 /* eg, SIGINT, SIGQUIT, SIGTERM, etc. */
1239 exstat = (int)ksh_min(128U + (unsigned)i, 255U);
1240 unwind(LINTR);
1241 }
1242 goto donetrap;
1243 }
1244 if (trapstr[0] == '\0')
1245 /* SIG_IGN */
1246 goto donetrap;
1247 if (i == ksh_SIGEXIT || i == ksh_SIGERR) {
1248 /* avoid recursion on these */
1249 old_changed = p->flags & TF_CHANGED;
1250 p->flags &= ~TF_CHANGED;
1251 p->trap = NULL;
1252 }
1253 if (trap_exstat == -1)
1254 trap_exstat = exstat & 0xFF;
1255 /*
1256 * Note: trapstr is fully parsed before anything is executed, thus
1257 * no problem with afree(p->trap) in settrap() while still in use.
1258 */
1259 command(trapstr, current_lineno);
1260 if (i == ksh_SIGEXIT || i == ksh_SIGERR) {
1261 if (p->flags & TF_CHANGED)
1262 /* don't clear TF_CHANGED */
1263 afree(trapstr, APERM);
1264 else
1265 p->trap = trapstr;
1266 p->flags |= old_changed;
1267 }
1268
1269 donetrap:
1270 /* we're the last trap of a sequence executed */
1271 if (is_last && trap_exstat != -1) {
1272 exstat = trap_exstat;
1273 trap_exstat = -1;
1274 }
1275 }
1276
1277 /* clear pending traps and reset user's trap handlers; used after fork(2) */
1278 void
cleartraps(void)1279 cleartraps(void)
1280 {
1281 int i;
1282 Trap *p;
1283
1284 trap = 0;
1285 intrsig = 0;
1286 fatal_trap = 0;
1287 for (i = NSIG+1, p = sigtraps; --i >= 0; p++) {
1288 p->set = 0;
1289 if ((p->flags & TF_USER_SET) && (p->trap && p->trap[0]))
1290 settrap(p, NULL);
1291 }
1292 }
1293
1294 /* restore signals just before an exec(2) */
1295 void
restoresigs(void)1296 restoresigs(void)
1297 {
1298 int i;
1299 Trap *p;
1300
1301 for (i = NSIG+1, p = sigtraps; --i >= 0; p++)
1302 if (p->flags & (TF_EXEC_IGN|TF_EXEC_DFL))
1303 setsig(p, (p->flags & TF_EXEC_IGN) ? SIG_IGN : SIG_DFL,
1304 SS_RESTORE_CURR|SS_FORCE);
1305 }
1306
1307 void
settrap(Trap * p,const char * s)1308 settrap(Trap *p, const char *s)
1309 {
1310 sig_t f;
1311
1312 if (p->trap)
1313 afree(p->trap, APERM);
1314 /* handles s == NULL */
1315 strdupx(p->trap, s, APERM);
1316 p->flags |= TF_CHANGED;
1317 f = !s ? SIG_DFL : s[0] ? trapsig : SIG_IGN;
1318
1319 p->flags |= TF_USER_SET;
1320 if ((p->flags & (TF_DFL_INTR|TF_FATAL)) && f == SIG_DFL)
1321 f = trapsig;
1322 else if (p->flags & TF_SHELL_USES) {
1323 if (!(p->flags & TF_ORIG_IGN) || Flag(FTALKING)) {
1324 /* do what user wants at exec time */
1325 p->flags &= ~(TF_EXEC_IGN|TF_EXEC_DFL);
1326 if (f == SIG_IGN)
1327 p->flags |= TF_EXEC_IGN;
1328 else
1329 p->flags |= TF_EXEC_DFL;
1330 }
1331
1332 /*
1333 * assumes handler already set to what shell wants it
1334 * (normally trapsig, but could be j_sigchld() or SIG_IGN)
1335 */
1336 return;
1337 }
1338
1339 /* todo: should we let user know signal is ignored? how? */
1340 setsig(p, f, SS_RESTORE_CURR|SS_USER);
1341 }
1342
1343 /*
1344 * Called by c_print() when writing to a co-process to ensure SIGPIPE won't
1345 * kill shell (unless user catches it and exits)
1346 */
1347 int
block_pipe(void)1348 block_pipe(void)
1349 {
1350 int restore_dfl = 0;
1351 Trap *p = &sigtraps[SIGPIPE];
1352
1353 if (!(p->flags & (TF_ORIG_IGN|TF_ORIG_DFL))) {
1354 setsig(p, SIG_IGN, SS_RESTORE_CURR);
1355 if (p->flags & TF_ORIG_DFL)
1356 restore_dfl = 1;
1357 } else if (p->cursig == SIG_DFL) {
1358 setsig(p, SIG_IGN, SS_RESTORE_CURR);
1359 /* restore to SIG_DFL */
1360 restore_dfl = 1;
1361 }
1362 return (restore_dfl);
1363 }
1364
1365 /* Called by c_print() to undo whatever block_pipe() did */
1366 void
restore_pipe(int restore_dfl)1367 restore_pipe(int restore_dfl)
1368 {
1369 if (restore_dfl)
1370 setsig(&sigtraps[SIGPIPE], SIG_DFL, SS_RESTORE_CURR);
1371 }
1372
1373 /*
1374 * Set action for a signal. Action may not be set if original
1375 * action was SIG_IGN, depending on the value of flags and FTALKING.
1376 */
1377 int
setsig(Trap * p,sig_t f,int flags)1378 setsig(Trap *p, sig_t f, int flags)
1379 {
1380 struct sigaction sigact;
1381
1382 if (p->signal == ksh_SIGEXIT || p->signal == ksh_SIGERR)
1383 return (1);
1384
1385 memset(&sigact, 0, sizeof(sigact));
1386
1387 /*
1388 * First time setting this signal? If so, get and note the current
1389 * setting.
1390 */
1391 if (!(p->flags & (TF_ORIG_IGN|TF_ORIG_DFL))) {
1392 sigaction(p->signal, &Sigact_ign, &sigact);
1393 p->flags |= sigact.sa_handler == SIG_IGN ?
1394 TF_ORIG_IGN : TF_ORIG_DFL;
1395 p->cursig = SIG_IGN;
1396 }
1397
1398 /*-
1399 * Generally, an ignored signal stays ignored, except if
1400 * - the user of an interactive shell wants to change it
1401 * - the shell wants for force a change
1402 */
1403 if ((p->flags & TF_ORIG_IGN) && !(flags & SS_FORCE) &&
1404 (!(flags & SS_USER) || !Flag(FTALKING)))
1405 return (0);
1406
1407 setexecsig(p, flags & SS_RESTORE_MASK);
1408
1409 /*
1410 * This is here 'cause there should be a way of clearing
1411 * shtraps, but don't know if this is a sane way of doing
1412 * it. At the moment, all users of shtrap are lifetime
1413 * users (SIGALRM, SIGCHLD, SIGWINCH).
1414 */
1415 if (!(flags & SS_USER))
1416 p->shtrap = (sig_t)NULL;
1417 if (flags & SS_SHTRAP) {
1418 p->shtrap = f;
1419 f = trapsig;
1420 }
1421
1422 if (p->cursig != f) {
1423 p->cursig = f;
1424 (void)sigemptyset(&sigact.sa_mask);
1425 /* interruptible */
1426 sigact.sa_flags = 0;
1427 sigact.sa_handler = f;
1428 sigaction(p->signal, &sigact, NULL);
1429 }
1430
1431 return (1);
1432 }
1433
1434 /* control what signal is set to before an exec() */
1435 void
setexecsig(Trap * p,int restore)1436 setexecsig(Trap *p, int restore)
1437 {
1438 /* XXX debugging */
1439 if (!(p->flags & (TF_ORIG_IGN|TF_ORIG_DFL)))
1440 internal_errorf("setexecsig: unset signal %d(%s)",
1441 p->signal, p->name);
1442
1443 /* restore original value for exec'd kids */
1444 p->flags &= ~(TF_EXEC_IGN|TF_EXEC_DFL);
1445 switch (restore & SS_RESTORE_MASK) {
1446 case SS_RESTORE_CURR:
1447 /* leave things as they currently are */
1448 break;
1449 case SS_RESTORE_ORIG:
1450 p->flags |= p->flags & TF_ORIG_IGN ? TF_EXEC_IGN : TF_EXEC_DFL;
1451 break;
1452 case SS_RESTORE_DFL:
1453 p->flags |= TF_EXEC_DFL;
1454 break;
1455 case SS_RESTORE_IGN:
1456 p->flags |= TF_EXEC_IGN;
1457 break;
1458 }
1459 }
1460
1461 #if HAVE_PERSISTENT_HISTORY || defined(DF)
1462 /*
1463 * File descriptor locking and unlocking functions.
1464 * Could use some error handling, but hey, this is only
1465 * advisory locking anyway, will often not work over NFS,
1466 * and you are SOL if this fails...
1467 */
1468
1469 void
mksh_lockfd(int fd)1470 mksh_lockfd(int fd)
1471 {
1472 #if defined(__OpenBSD__)
1473 /* flock is not interrupted by signals */
1474 (void)flock(fd, LOCK_EX);
1475 #elif HAVE_FLOCK
1476 int rv;
1477
1478 /* e.g. on Linux */
1479 do {
1480 rv = flock(fd, LOCK_EX);
1481 } while (rv == 1 && errno == EINTR);
1482 #elif HAVE_LOCK_FCNTL
1483 int rv;
1484 struct flock lks;
1485
1486 memset(&lks, 0, sizeof(lks));
1487 lks.l_type = F_WRLCK;
1488 do {
1489 rv = fcntl(fd, F_SETLKW, &lks);
1490 } while (rv == 1 && errno == EINTR);
1491 #endif
1492 }
1493
1494 /* designed to not define mksh_unlkfd if none triggered */
1495 #if HAVE_FLOCK
1496 void
mksh_unlkfd(int fd)1497 mksh_unlkfd(int fd)
1498 {
1499 (void)flock(fd, LOCK_UN);
1500 }
1501 #elif HAVE_LOCK_FCNTL
1502 void
mksh_unlkfd(int fd)1503 mksh_unlkfd(int fd)
1504 {
1505 struct flock lks;
1506
1507 memset(&lks, 0, sizeof(lks));
1508 lks.l_type = F_UNLCK;
1509 (void)fcntl(fd, F_SETLKW, &lks);
1510 }
1511 #endif
1512 #endif
1513