NYD: quit.c
[s-mailx.git] / popen.c
blob3d8460b949d078f863b001f3585d530525223f80
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Handling of pipes, child processes, temporary files, file enwrapping.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2014 Steffen "Daode" Nurpmeso <sdaoden@users.sf.net>.
6 */
7 /*
8 * Copyright (c) 1980, 1993
9 * The Regents of the University of California. All rights reserved.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. All advertising materials mentioning features or use of this software
20 * must display the following acknowledgement:
21 * This product includes software developed by the University of
22 * California, Berkeley and its contributors.
23 * 4. Neither the name of the University nor the names of its contributors
24 * may be used to endorse or promote products derived from this software
25 * without specific prior written permission.
27 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
28 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
31 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
33 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
35 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
36 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
37 * SUCH DAMAGE.
40 #ifndef HAVE_AMALGAMATION
41 # include "nail.h"
42 #endif
44 #include <sys/wait.h>
46 #include <fcntl.h>
48 #define READ 0
49 #define WRITE 1
51 #ifndef O_CLOEXEC
52 # define _OUR_CLOEXEC
53 # define O_CLOEXEC 0
54 # define _SET_CLOEXEC(FD) fcntl((FD), F_SETFD, FD_CLOEXEC)
55 #else
56 # define _SET_CLOEXEC(FD)
57 #endif
59 struct fp {
60 FILE *fp;
61 struct fp *link;
62 char *realfile;
63 long offset;
64 int omode;
65 int pipe;
66 int pid;
67 enum {
68 FP_RAW = 0,
69 FP_GZIP = 1<<0,
70 FP_BZIP2 = 1<<2,
71 FP_IMAP = 1<<3,
72 FP_MAILDIR = 1<<4,
73 FP_MASK = (1<<5) - 1,
74 FP_READONLY = 1<<5
75 } compressed;
78 struct child {
79 int pid;
80 char done;
81 char free;
82 int status;
83 struct child *link;
86 static struct fp *fp_head;
87 static struct child *_popen_child;
89 static int scan_mode(char const *mode, int *omode);
90 static void register_file(FILE *fp, int omode, int ispipe, int pid,
91 int compressed, char const *realfile, long offset);
92 static enum okay _compress(struct fp *fpp);
93 static int _decompress(int compression, int infd, int outfd);
94 static enum okay unregister_file(FILE *fp);
95 static int file_pid(FILE *fp);
96 static int wait_command(int pid);
97 static struct child *findchild(int pid);
98 static void delchild(struct child *cp);
100 static int
101 scan_mode(char const *mode, int *omode)
103 static struct {
104 char const mode[4];
105 int omode;
106 } const maps[] = {
107 {"r", O_RDONLY},
108 {"w", O_WRONLY | O_CREAT | O_TRUNC},
109 {"wx", O_WRONLY | O_CREAT | O_EXCL},
110 {"a", O_WRONLY | O_APPEND | O_CREAT},
111 {"a+", O_RDWR | O_APPEND},
112 {"r+", O_RDWR},
113 {"w+", O_RDWR | O_CREAT | O_EXCL}
116 int i;
117 NYD_ENTER;
119 for (i = 0; UICMP(z, i, <, NELEM(maps)); ++i)
120 if (!strcmp(maps[i].mode, mode)) {
121 *omode = maps[i].omode;
122 i = 0;
123 goto jleave;
126 fprintf(stderr, tr(152, "Internal error: bad stdio open mode %s\n"), mode);
127 errno = EINVAL;
128 *omode = 0; /* (silence CC) */
129 i = -1;
130 jleave:
131 NYD_LEAVE;
132 return i;
135 static void
136 register_file(FILE *fp, int omode, int ispipe, int pid, int compressed,
137 char const *realfile, long offset)
139 struct fp *fpp;
140 NYD_ENTER;
142 fpp = smalloc(sizeof *fpp);
143 fpp->fp = fp;
144 fpp->omode = omode;
145 fpp->pipe = ispipe;
146 fpp->pid = pid;
147 fpp->link = fp_head;
148 fpp->compressed = compressed;
149 fpp->realfile = realfile ? sstrdup(realfile) : NULL;
150 fpp->offset = offset;
151 fp_head = fpp;
152 NYD_LEAVE;
155 static enum okay
156 _compress(struct fp *fpp)
158 char const *cmd[2];
159 int outfd;
160 enum okay rv;
161 NYD_ENTER;
163 if (fpp->omode == O_RDONLY) {
164 rv = OKAY;
165 goto jleave;
167 rv = STOP;
169 fflush(fpp->fp);
170 clearerr(fpp->fp);
171 if (fseek(fpp->fp, fpp->offset, SEEK_SET) < 0)
172 goto jleave;
174 #ifdef HAVE_IMAP
175 if ((fpp->compressed & FP_MASK) == FP_IMAP) {
176 rv = imap_append(fpp->realfile, fpp->fp);
177 goto jleave;
179 #endif
180 if ((fpp->compressed & FP_MASK) == FP_MAILDIR) {
181 rv = maildir_append(fpp->realfile, fpp->fp);
182 goto jleave;
185 outfd = open(fpp->realfile, (fpp->omode | O_CREAT) & ~O_EXCL, 0666);
186 if (outfd < 0) {
187 fprintf(stderr, "Fatal: cannot create ");
188 perror(fpp->realfile);
189 goto jleave;
191 if ((fpp->omode & O_APPEND) == 0)
192 ftruncate(outfd, 0);
193 switch (fpp->compressed & FP_MASK) {
194 case FP_GZIP:
195 cmd[0] = "gzip"; cmd[1] = "-c"; break;
196 case FP_BZIP2:
197 cmd[0] = "bzip2"; cmd[1] = "-c"; break;
198 default:
199 cmd[0] = "cat"; cmd[1] = NULL; break;
201 if (run_command(cmd[0], 0, fileno(fpp->fp), outfd, cmd[1], NULL, NULL) >= 0)
202 rv = OKAY;
203 close(outfd);
204 jleave:
205 NYD_LEAVE;
206 return rv;
209 static int
210 _decompress(int compression, int infd, int outfd)
212 char const *cmd[2];
213 int rv;
214 NYD_ENTER;
216 switch (compression & FP_MASK) {
217 case FP_GZIP: cmd[0] = "gzip"; cmd[1] = "-cd"; break;
218 case FP_BZIP2: cmd[0] = "bzip2"; cmd[1] = "-cd"; break;
219 default: cmd[0] = "cat"; cmd[1] = NULL; break;
220 case FP_IMAP:
221 case FP_MAILDIR:
222 rv = 0;
223 goto jleave;
225 rv = run_command(cmd[0], 0, infd, outfd, cmd[1], NULL, NULL);
226 jleave:
227 NYD_LEAVE;
228 return rv;
231 static enum okay
232 unregister_file(FILE *fp)
234 struct fp **pp, *p;
235 enum okay rv = OKAY;
236 NYD_ENTER;
238 for (pp = &fp_head; (p = *pp) != NULL; pp = &p->link)
239 if (p->fp == fp) {
240 if ((p->compressed & FP_MASK) != FP_RAW) /* TODO ;} */
241 rv = _compress(p);
242 *pp = p->link;
243 free(p);
244 goto jleave;
246 rv = STOP;
247 panic(tr(153, "Invalid file pointer"));
248 jleave:
249 NYD_LEAVE;
250 return rv;
253 static int
254 file_pid(FILE *fp)
256 int rv;
257 struct fp *p;
258 NYD_ENTER;
260 rv = -1;
261 for (p = fp_head; p; p = p->link)
262 if (p->fp == fp) {
263 rv = p->pid;
264 break;
266 NYD_LEAVE;
267 return rv;
270 static int
271 wait_command(int pid)
273 int rv = 0;
274 NYD_ENTER;
276 if (!wait_child(pid, NULL)) {
277 if (ok_blook(bsdcompat) || ok_blook(bsdmsgs))
278 fprintf(stderr, tr(154, "Fatal error in process.\n"));
279 rv = -1;
281 NYD_LEAVE;
282 return rv;
285 static struct child *
286 findchild(int pid)
288 struct child **cpp;
289 NYD_ENTER;
291 for (cpp = &_popen_child; *cpp != NULL && (*cpp)->pid != pid;
292 cpp = &(*cpp)->link)
294 if (*cpp == NULL) {
295 *cpp = smalloc(sizeof **cpp);
296 (*cpp)->pid = pid;
297 (*cpp)->done = (*cpp)->free = 0;
298 (*cpp)->link = NULL;
300 NYD_LEAVE;
301 return *cpp;
304 static void
305 delchild(struct child *cp)
307 struct child **cpp;
308 NYD_ENTER;
310 for (cpp = &_popen_child; *cpp != cp; cpp = &(*cpp)->link)
312 *cpp = cp->link;
313 free(cp);
314 NYD_LEAVE;
317 FL FILE *
318 safe_fopen(char const *file, char const *oflags, int *xflags)
320 int osflags, fd;
321 FILE *fp = NULL;
322 NYD_ENTER;
324 if (scan_mode(oflags, &osflags) < 0)
325 goto jleave;
326 osflags |= O_CLOEXEC;
327 if (xflags != NULL)
328 *xflags = osflags;
330 if ((fd = open(file, osflags, 0666)) < 0)
331 goto jleave;
332 _SET_CLOEXEC(fd);
334 fp = fdopen(fd, oflags);
335 jleave:
336 NYD_LEAVE;
337 return fp;
340 FL FILE *
341 Fopen(char const *file, char const *oflags)
343 FILE *fp;
344 int osflags;
345 NYD_ENTER;
347 if ((fp = safe_fopen(file, oflags, &osflags)) != NULL)
348 register_file(fp, osflags, 0, 0, FP_RAW, NULL, 0L);
349 NYD_LEAVE;
350 return fp;
353 FL FILE *
354 Fdopen(int fd, char const *oflags)
356 FILE *fp;
357 int osflags;
358 NYD_ENTER;
360 scan_mode(oflags, &osflags);
361 osflags |= O_CLOEXEC;
363 if ((fp = fdopen(fd, oflags)) != NULL)
364 register_file(fp, osflags, 0, 0, FP_RAW, NULL, 0L);
365 NYD_LEAVE;
366 return fp;
369 FL int
370 Fclose(FILE *fp)
372 int i = 0;
373 NYD_ENTER;
375 if (unregister_file(fp) == OKAY)
376 i |= 1;
377 if (fclose(fp) == 0)
378 i |= 2;
379 NYD_LEAVE;
380 return i == 3 ? 0 : EOF;
383 FL FILE *
384 Zopen(char const *file, char const *oflags, int *compression) /* FIXME MESS! */
386 FILE *rv = NULL;
387 int _compression, osflags, mode, infd;
388 enum oflags rof;
389 long offset;
390 enum protocol p;
391 NYD_ENTER;
393 if (compression == NULL)
394 compression = &_compression;
396 if (scan_mode(oflags, &osflags) < 0)
397 goto jleave;
398 rof = OF_RDWR | OF_UNLINK;
399 if (osflags & O_APPEND)
400 rof |= OF_APPEND;
401 if (osflags == O_RDONLY) {
402 mode = R_OK;
403 *compression = FP_READONLY;
404 } else {
405 mode = R_OK | W_OK;
406 *compression = 0;
409 /* TODO ???? */
410 if ((osflags & O_APPEND) && ((p = which_protocol(file)) == PROTO_IMAP ||
411 p == PROTO_MAILDIR)) {
412 *compression |= (p == PROTO_IMAP) ? FP_IMAP : FP_MAILDIR;
413 osflags = O_RDWR | O_APPEND | O_CREAT;
414 infd = -1;
415 } else {
416 char const *ext;
418 if ((ext = strrchr(file, '.')) != NULL) {
419 if (!strcmp(ext, ".gz"))
420 *compression |= FP_GZIP;
421 else if (!strcmp(ext, ".bz2"))
422 *compression |= FP_BZIP2;
423 else
424 goto jraw;
425 } else {
426 jraw:
427 *compression |= FP_RAW;
428 rv = Fopen(file, oflags);
429 goto jleave;
431 if ((infd = open(file, (mode & W_OK) ? O_RDWR : O_RDONLY)) == -1 &&
432 (!(osflags & O_CREAT) || errno != ENOENT))
433 goto jleave;
436 if ((rv = Ftmp(NULL, "zopen", rof, 0600)) == NULL) {
437 perror(tr(167, "tmpfile"));
438 goto jerr;
440 if (infd >= 0 || (*compression & FP_MASK) == FP_IMAP ||
441 (*compression & FP_MASK) == FP_MAILDIR) {
442 if (_decompress(*compression, infd, fileno(rv)) < 0) {
443 jerr:
444 if (rv != NULL)
445 Fclose(rv);
446 rv = NULL;
447 if (infd >= 0)
448 close(infd);
449 goto jleave;
451 } else {
452 if ((infd = creat(file, 0666)) == -1) {
453 Fclose(rv);
454 rv = NULL;
455 goto jleave;
458 if (infd >= 0)
459 close(infd);
460 fflush(rv);
462 if (!(osflags & O_APPEND))
463 rewind(rv);
464 if ((offset = ftell(rv)) == -1) {
465 Fclose(rv);
466 rv = NULL;
467 goto jleave;
469 register_file(rv, osflags, 0, 0, *compression, file, offset);
470 jleave:
471 NYD_LEAVE;
472 return rv;
475 FL FILE *
476 Ftmp(char **fn, char const *prefix, enum oflags oflags, int mode)
478 FILE *fp = NULL;
479 char *cp_base, *cp;
480 int fd;
481 NYD_ENTER;
483 cp_base =
484 cp = smalloc(strlen(tempdir) + 1 + sizeof("mail") + strlen(prefix) + 7 +1);
485 cp = sstpcpy(cp, tempdir);
486 *cp++ = '/';
487 cp = sstpcpy(cp, "mail");
488 if (*prefix) {
489 *cp++ = '-';
490 cp = sstpcpy(cp, prefix);
492 /* TODO Ftmp(): unroll our own creation loop with atoi(random()) */
493 sstpcpy(cp, ".XXXXXX");
495 hold_all_sigs();
496 #ifdef HAVE_MKSTEMP
497 if ((fd = mkstemp(cp_base)) == -1)
498 goto jfree;
499 if (mode != (S_IRUSR | S_IWUSR) && fchmod(fd, mode) == -1)
500 goto jclose;
501 if (oflags & OF_APPEND) {
502 int f;
504 if ((f = fcntl(fd, F_GETFL)) == -1 ||
505 fcntl(fd, F_SETFL, f | O_APPEND) == -1) {
506 jclose:
507 close(fd);
508 goto junlink;
511 if (!(oflags & OF_REGISTER))
512 fcntl(fd, F_SETFD, FD_CLOEXEC);
513 #else
514 if (mktemp(cp_base) == NULL)
515 goto jfree;
516 if ((fd = open(cp_base, O_CREAT | O_EXCL | O_RDWR | O_CLOEXEC |
517 (oflags & OF_APPEND ? O_APPEND : 0), mode)) < 0)
518 goto junlink;
519 #endif
521 fp = (*((oflags & OF_REGISTER) ? &Fdopen : &fdopen))(fd,
522 (oflags & OF_RDWR ? "w+" : "w"));
523 if (fp == NULL || (oflags & OF_UNLINK)) {
524 junlink:
525 unlink(cp_base);
526 goto jfree;
529 if (fn != NULL)
530 *fn = cp_base;
531 jleave:
532 if (fp == NULL || !(oflags & OF_HOLDSIGS))
533 rele_all_sigs();
534 NYD_LEAVE;
535 return fp;
536 jfree:
537 if ((cp = cp_base) != NULL)
538 free(cp);
539 goto jleave;
542 FL void
543 Ftmp_release(char **fn)
545 char *cp;
546 NYD_ENTER;
548 cp = *fn;
549 *fn = NULL;
550 if (cp != NULL) {
551 unlink(cp);
552 rele_all_sigs();
553 free(cp);
555 NYD_LEAVE;
558 FL void
559 Ftmp_free(char **fn)
561 char *cp;
562 NYD_ENTER;
564 cp = *fn;
565 *fn = NULL;
566 if (cp != NULL)
567 free(cp);
568 NYD_LEAVE;
571 FL bool_t
572 pipe_cloexec(int fd[2])
574 bool_t rv = FAL0;
575 NYD_ENTER;
577 if (pipe(fd) < 0)
578 goto jleave;
579 fcntl(fd[0], F_SETFD, FD_CLOEXEC);
580 fcntl(fd[1], F_SETFD, FD_CLOEXEC);
581 rv = TRU1;
582 jleave:
583 NYD_LEAVE;
584 return rv;
587 FL FILE *
588 Popen(char const *cmd, char const *mode, char const *sh, int newfd1)
590 int p[2], myside, hisside, fd0, fd1, pid;
591 char mod[2] = { '0', '\0' };
592 sigset_t nset;
593 FILE *rv = NULL;
594 NYD_ENTER;
596 if (!pipe_cloexec(p))
597 goto jleave;
599 if (*mode == 'r') {
600 myside = p[READ];
601 fd0 = -1;
602 hisside = fd1 = p[WRITE];
603 mod[0] = *mode;
604 } else if (*mode == 'W') {
605 myside = p[WRITE];
606 hisside = fd0 = p[READ];
607 fd1 = newfd1;
608 mod[0] = 'w';
609 } else {
610 myside = p[WRITE];
611 hisside = fd0 = p[READ];
612 fd1 = -1;
613 mod[0] = 'w';
615 sigemptyset(&nset);
616 if (sh == NULL) {
617 pid = start_command(cmd, &nset, fd0, fd1, NULL, NULL, NULL);
618 } else {
619 pid = start_command(sh, &nset, fd0, fd1, "-c", cmd, NULL);
621 if (pid < 0) {
622 close(p[READ]);
623 close(p[WRITE]);
624 goto jleave;
626 close(hisside);
627 if ((rv = fdopen(myside, mod)) != NULL)
628 register_file(rv, 0, 1, pid, FP_RAW, NULL, 0L);
629 jleave:
630 NYD_LEAVE;
631 return rv;
634 FL bool_t
635 Pclose(FILE *ptr, bool_t dowait)
637 sigset_t nset, oset;
638 int pid;
639 bool_t rv = FAL0;
640 NYD_ENTER;
642 pid = file_pid(ptr);
643 if (pid < 0)
644 goto jleave;
645 unregister_file(ptr);
646 fclose(ptr);
647 if (dowait) {
648 sigemptyset(&nset);
649 sigaddset(&nset, SIGINT);
650 sigaddset(&nset, SIGHUP);
651 sigprocmask(SIG_BLOCK, &nset, &oset);
652 rv = wait_child(pid, NULL);
653 sigprocmask(SIG_SETMASK, &oset, NULL);
654 } else {
655 free_child(pid);
656 rv = TRU1;
658 jleave:
659 NYD_LEAVE;
660 return rv;
663 FL void
664 close_all_files(void)
666 NYD_ENTER;
667 while (fp_head != NULL)
668 if (fp_head->pipe)
669 Pclose(fp_head->fp, TRU1);
670 else
671 Fclose(fp_head->fp);
672 NYD_LEAVE;
675 FL int
676 run_command(char const *cmd, sigset_t *mask, int infd, int outfd,
677 char const *a0, char const *a1, char const *a2)
679 int rv;
680 NYD_ENTER;
682 if ((rv = start_command(cmd, mask, infd, outfd, a0, a1, a2)) < 0)
683 rv = -1;
684 else
685 rv = wait_command(rv);
686 NYD_LEAVE;
687 return rv;
690 FL int
691 start_command(char const *cmd, sigset_t *mask, int infd, int outfd,
692 char const *a0, char const *a1, char const *a2)
694 int rv;
695 NYD_ENTER;
697 if ((rv = fork()) < 0) {
698 perror("fork");
699 rv = -1;
700 } else if (rv == 0) {
701 char *argv[100];
702 int i = getrawlist(cmd, strlen(cmd), argv, NELEM(argv), 0);
704 if ((argv[i++] = UNCONST(a0)) != NULL &&
705 (argv[i++] = UNCONST(a1)) != NULL &&
706 (argv[i++] = UNCONST(a2)) != NULL)
707 argv[i] = NULL;
708 prepare_child(mask, infd, outfd);
709 execvp(argv[0], argv);
710 perror(argv[0]);
711 _exit(1);
713 NYD_LEAVE;
714 return rv;
717 FL void
718 prepare_child(sigset_t *nset, int infd, int outfd)
720 int i;
721 sigset_t fset;
722 NYD_ENTER;
724 /* All file descriptors other than 0, 1, and 2 are supposed to be cloexec */
725 if (infd >= 0)
726 dup2(infd, 0);
727 if (outfd >= 0)
728 dup2(outfd, 1);
730 if (nset) {
731 for (i = 1; i < NSIG; ++i)
732 if (sigismember(nset, i))
733 safe_signal(i, SIG_IGN);
734 if (!sigismember(nset, SIGINT))
735 safe_signal(SIGINT, SIG_DFL);
738 sigemptyset(&fset);
739 sigprocmask(SIG_SETMASK, &fset, NULL);
740 NYD_LEAVE;
743 FL void
744 sigchild(int signo)
746 int pid, status;
747 struct child *cp;
748 NYD_X; /* Signal handler */
749 UNUSED(signo);
751 jagain:
752 while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
753 cp = findchild(pid);
754 if (cp->free)
755 delchild(cp);
756 else {
757 cp->done = 1;
758 cp->status = status;
761 if (pid == -1 && errno == EINTR)
762 goto jagain;
765 FL void
766 free_child(int pid)
768 sigset_t nset, oset;
769 struct child *cp;
770 NYD_ENTER;
772 sigemptyset(&nset);
773 sigaddset(&nset, SIGCHLD);
774 sigprocmask(SIG_BLOCK, &nset, &oset);
776 cp = findchild(pid);
777 if (cp->done)
778 delchild(cp);
779 else
780 cp->free = 1;
782 sigprocmask(SIG_SETMASK, &oset, NULL);
783 NYD_LEAVE;
786 FL bool_t
787 wait_child(int pid, int *wait_status)
789 sigset_t nset, oset;
790 struct child *cp;
791 int ws;
792 bool_t rv;
793 NYD_ENTER;
795 sigemptyset(&nset);
796 sigaddset(&nset, SIGCHLD);
797 sigprocmask(SIG_BLOCK, &nset, &oset);
799 cp = findchild(pid);
800 while (!cp->done)
801 sigsuspend(&oset);
802 ws = cp->status;
803 delchild(cp);
805 sigprocmask(SIG_SETMASK, &oset, NULL);
807 if (wait_status != NULL)
808 *wait_status = ws;
809 rv = (WIFEXITED(ws) && WEXITSTATUS(ws) == 0);
810 NYD_LEAVE;
811 return rv;
814 #ifdef _OUR_CLOEXEC
815 # undef O_CLOEXEC
816 # undef _OUR_CLOEXEC
817 #endif
818 #undef _SET_CLOEXEC
820 /* vim:set fenc=utf-8:s-it-mode */