1 /* ========================================================================
2 * Copyright 2006-2008 University of Washington
3 * Copyright 2013-2022 Eduardo Chappa
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * ========================================================================
14 /*======================================================================
15 Different signal handlers for different signals
16 - Catches all the abort signals, cleans up tty modes and then coredumps
17 - Not much to do for SIGHUP
18 - Not much to do for SIGTERM
19 - turn SIGWINCH into a KEY_RESIZE command
20 - No signals for ^Z/suspend, but do it here anyway
21 - Also set up the signal handlers, and hold signals for
22 critical imap sections of code.
34 #include "../pith/state.h"
35 #include "../pith/conf.h"
36 #include "../pith/detach.h"
37 #include "../pith/pipe.h"
38 #include "../pith/util.h"
39 #include "../pith/icache.h"
40 #include "../pith/newmail.h"
41 #include "../pith/imap.h"
42 #include "../pith/adrbklib.h"
43 #include "../pith/remote.h"
44 #include "../pith/osdep/pipe.h"
50 static RETSIGTYPE
auger_in_signal(int);
51 void init_sighup(void);
52 void end_sighup(void);
53 RETSIGTYPE
term_signal(void);
54 void fast_clean_up(void);
55 static RETSIGTYPE
usr2_signal(int);
56 static RETSIGTYPE
winch_signal(int);
57 static RETSIGTYPE
intr_signal(int);
58 void suspend_notice(char *);
59 void suspend_warning(void);
63 static int cleanup_called_from_sig_handler
;
67 /*----------------------------------------------------------------------
68 Install handlers for all the signals we care to catch
69 ----------------------------------------------------------------------*/
73 dprint((9, "init_signals()\n"));
79 # define CUSHION_SIG (debug < 7)
81 # define CUSHION_SIG (1)
85 signal(SIGILL
, auger_in_signal
);
86 signal(SIGTRAP
, auger_in_signal
);
88 signal(SIGEMT
, auger_in_signal
);
90 signal(SIGBUS
, auger_in_signal
);
91 signal(SIGSEGV
, auger_in_signal
);
92 signal(SIGSYS
, auger_in_signal
);
93 signal(SIGQUIT
, auger_in_signal
);
94 /* Don't catch SIGFPE cause it's rare and we use it in a hack below*/
100 * Set up SIGUSR2 to catch signal from other software using the
101 * c-client to tell us that other access to the folder is being
102 * attempted. THIS IS A TEST: if it turns out that simply
103 * going R/O when another pine is started or the same folder is opened,
104 * then we may want to install a smarter handler that uses idle time
105 * or even prompts the user to see if it's ok to give up R/O access...
107 signal(SIGUSR2
, usr2_signal
);
109 signal(SIGPIPE
, SIG_IGN
);
110 signal(SIGINT
, SIG_IGN
);
113 /* Some unexplained behaviour on Ultrix 4.2 (Hardy) seems to be
114 resulting in Pine getting sent a SIGTSTP. Ignore it here.
115 probably better to ignore it than let it happen in any case
117 signal(SIGTSTP
, SIG_IGN
);
118 # endif /* SIGTSTP */
121 signal(SIGCHLD
, child_signal
);
123 #endif /* !_WINDOWS */
127 /*----------------------------------------------------------------------
128 Return all signal handling back to normal
129 ----------------------------------------------------------------------*/
131 end_signals(int blockem
)
135 signal(SIGHUP
, blockem
? SIG_IGN
: SIG_DFL
);
139 #define SIG_ERR (RETSIGTYPE (*)())-1
142 dprint((5, "end_signals(%d)\n", blockem
));
143 if(signal(SIGILL
, blockem
? SIG_IGN
: SIG_DFL
) == SIG_ERR
){
144 fprintf(stderr
, "Error resetting signals: %s\n",
145 error_description(errno
));
149 signal(SIGTRAP
, blockem
? SIG_IGN
: SIG_DFL
);
151 signal(SIGEMT
, blockem
? SIG_IGN
: SIG_DFL
);
153 signal(SIGBUS
, blockem
? SIG_IGN
: SIG_DFL
);
154 signal(SIGSEGV
, blockem
? SIG_IGN
: SIG_DFL
);
155 signal(SIGSYS
, blockem
? SIG_IGN
: SIG_DFL
);
156 #if defined(SIGWINCH) && defined(TIOCGWINSZ)
157 signal(SIGWINCH
, blockem
? SIG_IGN
: SIG_DFL
);
159 signal(SIGQUIT
, blockem
? SIG_IGN
: SIG_DFL
);
161 signal(SIGTSTP
, blockem
? SIG_IGN
: SIG_DFL
);
163 signal(SIGHUP
, blockem
? SIG_IGN
: SIG_DFL
);
164 signal(SIGTERM
, blockem
? SIG_IGN
: SIG_DFL
);
165 signal(SIGINT
, blockem
? SIG_IGN
: SIG_DFL
);
167 #endif /* !_WINDOWS */
171 /*----------------------------------------------------------------------
172 Handle signals caused by aborts -- SIGSEGV, SIGILL, etc
174 Call panic which cleans up tty modes and then core dumps
175 ----------------------------------------------------------------------*/
177 auger_in_signal(int sig
)
181 end_signals(1); /* don't catch any more signals */
182 imap_flush_passwd_cache(FALSE
);
184 snprintf(buf
, sizeof(buf
), "Received abort signal(sig=%d)", sig
);
185 buf
[sizeof(buf
)-1] = '\0';
187 alpine_panic(buf
); /* clean up and get out */
189 exit(-1); /* in case panic doesn't kill us */
193 /*----------------------------------------------------------------------
194 Install signal handler to deal with hang up signals -- SIGHUP, SIGTERM
196 ----------------------------------------------------------------------*/
200 #if !(defined(DOS) && !defined(_WINDOWS))
201 #if defined(_WINDOWS) || defined(OS2)
202 signal(SIGHUP
, (void *) hup_signal
);
204 signal(SIGHUP
, hup_signal
);
207 #if !(defined(DOS) || defined(OS2))
208 signal(SIGTERM
, term_signal
);
213 /*----------------------------------------------------------------------
214 De-Install signal handler to deal with hang up signals -- SIGHUP, SIGTERM
216 ----------------------------------------------------------------------*/
220 #if !(defined(DOS) && !defined(_WINDOWS))
221 signal(SIGHUP
, SIG_IGN
);
223 #if !(defined(DOS) || defined(OS2))
224 signal(SIGTERM
, SIG_IGN
);
229 /*----------------------------------------------------------------------
230 handle hang up signal -- SIGHUP
232 Not much to do. Rely on periodic mail file check pointing.
233 ----------------------------------------------------------------------*/
238 ps_global
->signal_in_progress
= 1;
239 end_signals(1); /* don't catch any more signals */
240 dprint((1, "\n\n** Received SIGHUP **\n\n\n\n"));
241 cleanup_called_from_sig_handler
= 1;
248 _exit(0); /* cleaning up can crash */
252 /*----------------------------------------------------------------------
253 Timeout when no user input for a long, long time.
254 Treat it pretty much the same as if we got a HUP.
255 Only difference is we sometimes turns the timeout off (when composing).
256 ----------------------------------------------------------------------*/
258 user_input_timeout_exit(int to_hours
)
263 "\n\n** Exiting: user input timeout (%d hours) **\n\n\n\n",
265 snprintf(msg
, sizeof(msg
), _("\n\nAlpine timed out (No user input for %d %s)\n"), to_hours
,
266 to_hours
> 1 ? "hours" : "hour");
267 msg
[sizeof(msg
)-1] = '\0';
272 end_keyboard(F_ON(F_USE_FK
,ps_global
));
273 end_tty_driver(ps_global
);
275 #if defined(DEBUG) && (!defined(DOS) || defined(_WINDOWS))
283 /*----------------------------------------------------------------------
284 handle terminate signal -- SIGTERM
286 Not much to do. Rely on periodic mail file check pointing.
287 ----------------------------------------------------------------------*/
291 #if !defined(DOS) && !defined(OS2)
292 end_signals(1); /* don't catch any more signals */
293 dprint((1, "\n\n** Received SIGTERM **\n\n\n\n"));
294 cleanup_called_from_sig_handler
= 1;
296 #if defined(DEBUG) && (!defined(DOS) || defined(_WINDOWS))
300 printf(_("\n\nAlpine finished. Received terminate signal\n\n"));
306 /*----------------------------------------------------------------------
307 Handle cleaning up mail streams and tty modes...
308 Not much to do. Rely on periodic mail file check pointing. Don't try
309 cleaning up screen or flushing output since stdout is likely already
310 gone. To be safe, though, we'll at least restore the original tty mode.
311 Also delete any remnant _DATAFILE_ from sending-filters.
312 ----------------------------------------------------------------------*/
316 #if !defined(DOS) && !defined(OS2)
321 dprint((1, "fast_clean_up()\n"));
323 if(ps_global
->expunge_in_progress
){
329 * This gets rid of temporary cache files for remote addrbooks.
331 completely_done_with_adrbks();
334 * This flushes out deferred changes and gets rid of temporary cache
335 * files for remote config files.
338 if(ps_global
->prc
->outstanding_pinerc_changes
)
339 write_pinerc(ps_global
, Main
,
340 cleanup_called_from_sig_handler
? WRP_NOUSER
: WRP_NONE
);
342 if(ps_global
->prc
->rd
)
343 rd_close_remdata(&ps_global
->prc
->rd
);
345 free_pinerc_s(&ps_global
->prc
);
349 if(ps_global
->post_prc
){
350 if(ps_global
->post_prc
->outstanding_pinerc_changes
)
351 write_pinerc(ps_global
, Post
,
352 cleanup_called_from_sig_handler
? WRP_NOUSER
: WRP_NONE
);
354 if(ps_global
->post_prc
->rd
)
355 rd_close_remdata(&ps_global
->post_prc
->rd
);
357 free_pinerc_s(&ps_global
->post_prc
);
361 * Can't figure out why this section is inside the ifdef, but no
362 * harm leaving it that way, I guess.
364 #if !defined(DOS) && !defined(OS2)
365 for(i
= 0; i
< ps_global
->s_pool
.nstream
; i
++){
366 m
= ps_global
->s_pool
.streams
[i
];
368 pine_mail_actually_close(m
);
375 imap_flush_passwd_cache(TRUE
);
377 dprint((1, "done with fast_clean_up\n"));
381 #if !defined(DOS) && !defined(OS2)
382 /*----------------------------------------------------------------------
383 handle hang up signal -- SIGUSR2
385 Not much to do. Rely on periodic mail file check pointing.
386 ----------------------------------------------------------------------*/
390 char c
, *mbox
, mboxbuf
[20];
395 for(i
= 0; i
< ps_global
->s_pool
.nstream
; i
++){
396 stream
= ps_global
->s_pool
.streams
[i
];
398 && sp_flagged(stream
, SP_LOCKED
)
399 && !sp_dead_stream(stream
)
403 && (c
= *stream
->mailbox
) != '{' && c
!= '*'){
404 pine_mail_check(stream
); /* write latest state */
405 stream
->rdonly
= 1; /* and become read-only */
406 (void) pine_mail_ping(stream
);
407 mbox
= stream
->mailbox
;
408 if(!strucmp(stream
->mailbox
, ps_global
->inbox_name
)
409 || !strcmp(stream
->mailbox
, ps_global
->VAR_INBOX_PATH
)
410 || !strucmp(stream
->original_mailbox
, ps_global
->inbox_name
)
411 || !strcmp(stream
->original_mailbox
, ps_global
->VAR_INBOX_PATH
))
413 else if(mail_valid_net_parse(stream
->mailbox
, &mb
) && mb
.mailbox
)
416 q_status_message1(SM_ASYNC
, 3, 7,
417 _("Another email program is accessing %s. Session now Read-Only."),
418 short_str((mbox
&& *mbox
) ? mbox
: "folder",
419 mboxbuf
, sizeof(mboxbuf
), 19, FrontDots
));
420 dprint((1, "** folder %s went read-only **\n\n",
428 /*----------------------------------------------------------------------
429 Install signal handler to deal with window resize signal -- SIGWINCH
431 ----------------------------------------------------------------------*/
435 #if defined(SIGWINCH) && defined(TIOCGWINSZ)
436 signal(SIGWINCH
, winch_signal
);
441 #if defined(SIGWINCH) && defined(TIOCGWINSZ)
442 /*----------------------------------------------------------------------
443 Handle window resize signal -- SIGWINCH
445 The planned strategy is just force a redraw command. This is similar
446 to new mail handling which forces a noop command. The signals are
447 help until pine reads input. Then a KEY_RESIZE is forced into the command
449 Note that ready_for_winch is only non-zero inside the read_char function,
450 so the longjmp only ever happens there, and it is really just a jump
451 from lower down in the function up to the top of that function. Its
452 purpose is to return a KEY_RESIZE from read_char when interrupted
453 out of the select lower down in read_char.
454 ----------------------------------------------------------------------*/
455 extern jmp_buf winch_state
;
456 extern int ready_for_winch
, winch_occured
;
459 winch_signal(int sig
)
474 #if defined(SIGWINCH) && defined(TIOCGWINSZ)
476 longjmp(winch_state
, 1);
484 /*----------------------------------------------------------------------
485 Handle child status change -- SIGCHLD
487 The strategy here is to install the handler when we spawn a child, and
488 to let the system tell us when the child's state has changed. In the
489 mean time, we can do whatever. Typically, "whatever" is spinning in a
490 loop alternating between sleep and new_mail calls intended to keep the
493 ----------------------------------------------------------------------*/
494 static short child_signalled
, child_jump
;
495 static jmp_buf child_state
;
498 child_signal(int sig
)
500 #ifdef BACKGROUND_POST
502 * reap background posting process
504 if(ps_global
->post
&& ps_global
->post
->pid
){
508 pid
= process_reap(ps_global
->post
->pid
, &es
, PR_NOHANG
);
509 if(pid
== ps_global
->post
->pid
){
510 ps_global
->post
->status
= es
;
511 ps_global
->post
->pid
= 0;
514 else if(pid
< 0 && errno
!= EINTR
){
515 fs_give((void **) &ps_global
->post
);
518 #endif /* BACKGROUND_POST */
522 longjmp(child_state
, 1);
528 * pipe_callback - handle pine-specific pipe setup like child
529 * signal handler and possibly any display stuff
530 * BUG: this function should probably be in a "alpine/pipe.c"
533 pipe_callback(PIPE_S
*syspipe
, int flags
, void *data
)
538 if(flags
& OSB_PRE_OPEN
){
539 dprint((5, "Opening pipe: (%s%s%s%s%s%s)\n",
540 (syspipe
->mode
& PIPE_WRITE
) ? "W":"", (syspipe
->mode
& PIPE_READ
) ? "R":"",
541 (syspipe
->mode
& PIPE_NOSHELL
) ? "N":"", (syspipe
->mode
& PIPE_PROT
) ? "P":"",
542 (syspipe
->mode
& PIPE_USER
) ? "U":"", (syspipe
->mode
& PIPE_RESET
) ? "T":""));
545 q_status_message(SM_ORDER
, 0, 0,
546 "Waiting for called program to finish...");
548 flush_status_messages(1);
550 draw_keymenu(&pipe_cancel_keymenu
, bitmap
, ps_global
->ttyo
->screen_cols
,
551 1-FOOTER_ROWS(ps_global
), 0, FirstMenu
);
554 if(!(syspipe
->mode
& (PIPE_WRITE
| PIPE_READ
)) && !(syspipe
->mode
& PIPE_SILENT
)){
555 flush_status_messages(0); /* just clean up display */
560 if(syspipe
->mode
& PIPE_RESET
)
565 * Prepare for demise of child. Use SIGCHLD if it's available so
566 * we can do useful things, like keep the IMAP stream alive, while
567 * we're waiting on the child. The handler may have been changed by
568 * something in the composer, in particular, by an alt_editor call.
569 * So we need to re-set it to child_signal and then set it back
572 child_signalled
= child_jump
= 0;
573 syspipe
->chld
= signal(SIGCHLD
, child_signal
);
577 else if(flags
& OSB_POST_OPEN
){
580 clearfooter(ps_global
);
581 ps_global
->mangled_footer
= 1;
584 q_status_message1(SM_ORDER
, 2, 3, "Ignoring completion of %s", syspipe
->command
);
587 if(!(syspipe
->mode
& (PIPE_WRITE
| PIPE_READ
)) && !(syspipe
->mode
& PIPE_SILENT
)){
589 ps_global
->mangled_screen
= 1;
592 if(syspipe
->mode
& PIPE_RESET
){
594 ps_global
->mangled_screen
= 1;
598 (void) signal(SIGCHLD
, SIG_DFL
);
602 else if(flags
& OSB_PRE_CLOSE
){
605 * this is here so close_system_pipe so it has something
606 * to do while we're waiting on the other end of the
607 * pipe to complete. When we're in the background for
608 * a shell, the the side effect is pinging
610 RETSIGTYPE (*alarm_sig
)();
611 int old_cue
= F_ON(F_SHOW_DELAY_CUE
, ps_global
);
614 * remember the current SIGALRM handler, and make sure it's
615 * installed when we're finished just in case the longjmp
616 * out of the SIGCHLD handler caused sleep() to lose it.
617 * Don't pay any attention to that man behind the curtain.
619 alarm_sig
= signal(SIGALRM
, SIG_IGN
);
620 (void) F_SET(F_SHOW_DELAY_CUE
, ps_global
, 0);
621 ps_global
->noshow_timeout
= 1;
622 while(!child_signalled
){
623 /* wake up and prod server */
624 if(!(syspipe
->mode
& PIPE_NONEWMAIL
))
626 (syspipe
->mode
& PIPE_RESET
) ? NM_NONE
: NM_DEFER_SORT
);
628 if(!child_signalled
){
629 if(setjmp(child_state
) == 0){
630 child_jump
= 1; /* prepare to wake up */
631 sleep(600); /* give it 5mins to happen */
634 our_sigunblock(SIGCHLD
);
640 ps_global
->noshow_timeout
= 0;
641 F_SET(F_SHOW_DELAY_CUE
, ps_global
, old_cue
);
642 (void) signal(SIGALRM
, alarm_sig
);
643 (void) signal(SIGCHLD
, syspipe
->chld
);
646 else if(flags
& OSB_POST_CLOSE
){
647 if(syspipe
->mode
& PIPE_RESET
){ /* restore our tty modes */
649 ps_global
->mangled_screen
= 1;
652 if(!(syspipe
->mode
& (PIPE_WRITE
| PIPE_READ
| PIPE_SILENT
))){
653 ClearScreen(); /* No I/O to forked child */
654 ps_global
->mangled_screen
= 1;
661 * Command interrupt support.
667 ps_global
->intr_pending
= 1;
672 intr_handling_on(void)
675 return 0; /* No interrupts in Windows */
677 if(signal(SIGINT
, intr_signal
) == intr_signal
)
678 return 0; /* already installed */
681 if(ps_global
&& ps_global
->ttyo
)
682 draw_cancel_keymenu();
690 intr_handling_off(void)
694 if(signal(SIGINT
, SIG_IGN
) == SIG_IGN
) /* already off! */
697 ps_global
->intr_pending
= 0;
699 if(ps_global
&& ps_global
->ttyo
)
700 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
702 ps_global
->mangled_footer
= 1;
707 /*----------------------------------------------------------------------
708 Set or reset what needs to be set when coming out of pico to run
711 Args: come_back -- If come_back is 0 we're going out of our environment
712 to set up for an external editor.
713 If come_back is 1 we're coming back into pine.
714 ----------------------------------------------------------------------*/
716 ttyfix(int come_back
)
718 #if defined(DEBUG) && (!defined(DOS) || defined(_WINDOWS))
725 enter_text_mode(NULL
);
728 init_tty_driver(ps_global
);
729 init_keyboard(F_ON(F_USE_FK
,ps_global
));
733 fix_windsize(ps_global
);
737 end_keyboard(F_ON(F_USE_FK
,ps_global
));
738 end_tty_driver(ps_global
);
749 /*----------------------------------------------------------------------
750 Suspend Pine. Reset tty and suspend. Suspend is finished when this returns
752 Args: The pine structure
754 Result: Execution suspended for a while. Screen will need redrawing
757 Instead of the usual handling of ^Z by catching a signal, we actually read
758 the ^Z and then clean up the tty driver, then kill ourself to stop, and
759 pick up where we left off when execution resumes.
760 ----------------------------------------------------------------------*/
764 struct pine
*pine
= ps_global
;
767 #if defined(DOS) || defined(OS2)
768 int result
, orig_cols
, orig_rows
;
773 static char *shell
= NULL
;
774 #define STD_SHELL "COMMAND.COM"
777 static char *shell
= NULL
;
778 #define STD_SHELL "CMD.EXE"
782 if(!have_job_control()){
783 bogus_command(ctrl('Z'), F_ON(F_USE_FK
, pine
) ? "F1" : "?");
784 return(NO_OP_COMMAND
);
787 if(F_OFF(F_CAN_SUSPEND
, pine
)){
788 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
789 _("Alpine suspension not enabled - see help text"));
790 return(NO_OP_COMMAND
);
795 return(NO_OP_COMMAND
);
798 isremote
= (ps_global
->mail_stream
&& ps_global
->mail_stream
->mailbox
799 && (*ps_global
->mail_stream
->mailbox
== '{'
800 || (*ps_global
->mail_stream
->mailbox
== '*'
801 && *(ps_global
->mail_stream
->mailbox
+ 1) == '{')));
803 now
= time((time_t *)0);
804 dprint((1, "\n\n --- %s - SUSPEND ---- %s",
805 isremote
? "REMOTE" : "LOCAL", ctime(&now
)));
808 #if defined(DOS) || defined(OS2)
809 suspend_notice("exit");
813 if (!((shell
= getenv("SHELL")) || (shell
= getenv("COMSPEC"))))
816 shell
= cpystr(shell
); /* copy in free storage */
817 for(p
= shell
; (p
= strchr(p
, '/')); p
++)
821 result
= system(shell
);
823 if(F_ON(F_SUSPEND_SPAWNS
, ps_global
)){
825 int flag
= some_stream_is_locked() ? PIPE_NONEWMAIL
: 0;
827 flag
|= PIPE_USER
|PIPE_RESET
;
828 if((syspipe
= open_system_pipe(NULL
, NULL
, NULL
, flag
,
829 0, pipe_callback
, pipe_report_error
)) != NULL
){
830 suspend_notice("exit");
835 (void) close_system_pipe(&syspipe
, NULL
, pipe_callback
);
839 suspend_notice("fg");
848 now
= time((time_t *)0);
849 dprint((1, "\n\n ---- RETURN FROM SUSPEND ---- %s",
854 #if defined(DOS) || defined(OS2)
855 orig_cols
= pine
->ttyo
->screen_cols
;
856 orig_rows
= pine
->ttyo
->screen_rows
;
861 #if defined(DOS) || defined(OS2)
862 if(orig_cols
!= pine
->ttyo
->screen_cols
||
863 orig_rows
!= pine
->ttyo
->screen_rows
)
869 #if defined(DOS) || defined(OS2)
871 q_status_message1(SM_ORDER
| SM_DING
, 3, 4,
872 _("Error loading \"%s\""), shell
);
875 if(isremote
&& !ps_global
->mail_stream
->lock
876 && !pine_mail_ping(ps_global
->mail_stream
))
877 q_status_message(SM_ORDER
| SM_DING
, 4, 9,
878 _("Suspended for too long, IMAP connection broken"));
881 #endif /* !_WINDOWS */
886 /*----------------------------------------------------------------------
889 suspend_notice(char *s
)
891 printf(_("\nAlpine suspended. Give the \"%s\" command to come back.\n"), s
);
896 /*----------------------------------------------------------------------
899 suspend_warning(void)
901 puts(_("Warning: Your IMAP connection will be closed if Alpine"));
902 puts(_("is suspended for more than 30 minutes\n"));
907 /*----------------------------------------------------------------------
910 fix_windsize(struct pine
*pine
)
912 int old_width
= pine
->ttyo
->screen_cols
;
914 dprint((9, "fix_windsize()\n"));
915 mark_keymenu_dirty();
917 mark_titlebar_dirty();
919 get_windsize(pine
->ttyo
);
921 if(old_width
!= pine
->ttyo
->screen_cols
)
922 clear_index_cache(pine
->mail_stream
, 0);