trondd <tro...@kagu-tsuchi.com> wrote: > trondd <tro...@kagu-tsuchi.com> wrote: > > > > > While investigating an occasional crash when recovering a file with 'vi -r' > > after a power failure, I noticed that the recovery files are actually never > > updated during an editing session. The recovery files are created upon > > initial modification of the file which saves the state of the file at the > > time of the edit. You can work on a file for as long as you want and even > > write it to disk but the recovery file is never updated. If the session is > > then lost due to power failure or a SIGKILL and you attempt to recover with > > -r, you'll be presented with the contents of the file from that first edit. > > It won't contain unsaved changes nor even any changes manually written to > > disk to the original file. Accepting the recovered version would lose all > > of your work. > > > > Reading the vi docs, man page, and source comments in the OpenBSD tree, they > > all mention the use of SIGALRM to periodically save changes to the recovery > > file. However, the code never sets up a handler or captures SIGALRM. It > > only ever updates the recovery file on a SIGTERM but then also exists, I > > guess to cover the case of an inadvertent clean system shutdown. > > > > I dug through an nvi source repository[0] that seemed to cover it's entire > > history and it seems this functionality was lost somewhere around 1994 and I > > don't see it having been replaced by anything else. Our version seems to be > > from 1996 and editors/nvi in ports still lacks code to update the recovery > > file, as well. > > > > What I've done is re-implement periodic updates to the recovery file using > > SIGALRM and a timer like the original implementation but rewritten a bit to > > fit the newer source file layout and event handling. That keeps the > > recovery > > updated every 2 minutes. Then it seemed silly to be able to write changes > > to > > the original file and if a crash happens before the next SIGALRM, recovery > > would try to roll you back to before those saved changes. So I've also > > added > > a call to sync the recovery file if you explicitly write changes to disk. I > > don't think the recovery system should try to punish you for actively saving > > your work even if it is only at most 2 minutes worth. > > > > Comments or feedback? I'm unsure I've covered all caveats with this code or > > if there are vi/ex usecases where it won't work correctly. For testing, > > I've > > covered my usage and several scenarios I could contrive but I don't > > regularly > > use ex, for example, or change many options from the default. I've been > > running with this code for a week. And I suppose there must be a reason no > > one has noticed or cared about this for over 20 years. Everyone else uses > > vim, I guess? > > > > Tim. > > > > [0] https://repo.or.cz/nvi.git > > > > Got positive testing from one user. Anyone else interested? Or any other > feedback? > > Tim. >
Throwing this hat back into the ring post-release. Still only gotten user feedback so far. Tim. Index: cl/cl.h =================================================================== RCS file: /cvs/src/usr.bin/vi/cl/cl.h,v retrieving revision 1.10 diff -u -p -r1.10 cl.h --- cl/cl.h 27 May 2016 09:18:11 -0000 1.10 +++ cl/cl.h 26 Dec 2020 17:47:32 -0000 @@ -30,8 +30,9 @@ typedef struct _cl_private { #define INDX_HUP 0 #define INDX_INT 1 #define INDX_TERM 2 -#define INDX_WINCH 3 -#define INDX_MAX 4 /* Original signal information. */ +#define INDX_ALRM 3 +#define INDX_WINCH 4 +#define INDX_MAX 5 /* Original signal information. */ struct sigaction oact[INDX_MAX]; enum { /* Tty group write mode. */ @@ -48,8 +49,9 @@ typedef struct _cl_private { #define CL_SIGHUP 0x0020 /* SIGHUP arrived. */ #define CL_SIGINT 0x0040 /* SIGINT arrived. */ #define CL_SIGTERM 0x0080 /* SIGTERM arrived. */ -#define CL_SIGWINCH 0x0100 /* SIGWINCH arrived. */ -#define CL_STDIN_TTY 0x0200 /* Talking to a terminal. */ +#define CL_SIGALRM 0x0100 /* SIGALRM arrived. */ +#define CL_SIGWINCH 0x0200 /* SIGWINCH arrived. */ +#define CL_STDIN_TTY 0x0400 /* Talking to a terminal. */ u_int32_t flags; } CL_PRIVATE; Index: cl/cl_main.c =================================================================== RCS file: /cvs/src/usr.bin/vi/cl/cl_main.c,v retrieving revision 1.33 diff -u -p -r1.33 cl_main.c --- cl/cl_main.c 5 May 2016 20:36:41 -0000 1.33 +++ cl/cl_main.c 26 Dec 2020 17:47:32 -0000 @@ -239,6 +239,14 @@ h_term(int signo) } static void +h_alrm(int signo) +{ + GLOBAL_CLP; + + F_SET(clp, CL_SIGALRM); +} + +static void h_winch(int signo) { GLOBAL_CLP; @@ -264,6 +272,7 @@ sig_init(GS *gp, SCR *sp) if (setsig(SIGHUP, &clp->oact[INDX_HUP], h_hup) || setsig(SIGINT, &clp->oact[INDX_INT], h_int) || setsig(SIGTERM, &clp->oact[INDX_TERM], h_term) || + setsig(SIGALRM, &clp->oact[INDX_ALRM], h_alrm) || setsig(SIGWINCH, &clp->oact[INDX_WINCH], h_winch) ) err(1, NULL); @@ -271,6 +280,7 @@ sig_init(GS *gp, SCR *sp) if (setsig(SIGHUP, NULL, h_hup) || setsig(SIGINT, NULL, h_int) || setsig(SIGTERM, NULL, h_term) || + setsig(SIGALRM, NULL, h_alrm) || setsig(SIGWINCH, NULL, h_winch) ) { msgq(sp, M_SYSERR, "signal-reset"); @@ -313,6 +323,7 @@ sig_end(GS *gp) (void)sigaction(SIGHUP, NULL, &clp->oact[INDX_HUP]); (void)sigaction(SIGINT, NULL, &clp->oact[INDX_INT]); (void)sigaction(SIGTERM, NULL, &clp->oact[INDX_TERM]); + (void)sigaction(SIGALRM, NULL, &clp->oact[INDX_ALRM]); (void)sigaction(SIGWINCH, NULL, &clp->oact[INDX_WINCH]); } Index: cl/cl_read.c =================================================================== RCS file: /cvs/src/usr.bin/vi/cl/cl_read.c,v retrieving revision 1.21 diff -u -p -r1.21 cl_read.c --- cl/cl_read.c 27 May 2016 09:18:11 -0000 1.21 +++ cl/cl_read.c 26 Dec 2020 17:47:32 -0000 @@ -62,7 +62,7 @@ retest: if (LF_ISSET(EC_INTERRUPT) || F_ evp->e_event = E_TIMEOUT; return (0); } - if (F_ISSET(clp, CL_SIGHUP | CL_SIGTERM | CL_SIGWINCH)) { + if (F_ISSET(clp, CL_SIGHUP | CL_SIGTERM | CL_SIGWINCH | CL_SIGALRM)) { if (F_ISSET(clp, CL_SIGHUP)) { evp->e_event = E_SIGHUP; return (0); @@ -81,6 +81,11 @@ retest: if (LF_ISSET(EC_INTERRUPT) || F_ return (0); } /* No real change, ignore the signal. */ + } + if (F_ISSET(clp, CL_SIGALRM)) { + F_CLR(clp, CL_SIGALRM); + evp->e_event = E_SIGALRM; + return (0); } } Index: common/key.c =================================================================== RCS file: /cvs/src/usr.bin/vi/common/key.c,v retrieving revision 1.19 diff -u -p -r1.19 key.c --- common/key.c 18 Apr 2017 01:45:35 -0000 1.19 +++ common/key.c 26 Dec 2020 17:47:32 -0000 @@ -542,6 +542,10 @@ loop: if (gp->scr_event(sp, argp, v_sync(sp, RCV_ENDSESSION | RCV_PRESERVE | (argp->e_event == E_SIGTERM ? 0: RCV_EMAIL)); return (1); + case E_SIGALRM: + /* Sync to recovery if the file has been modified. */ + v_sync(sp, 0); + return(0); case E_TIMEOUT: istimeout = 1; break; @@ -756,6 +760,9 @@ v_event_err(SCR *sp, EVENT *evp) break; case E_WRITE: msgq(sp, M_ERR, "Unexpected write event"); + break; + case E_SIGALRM: + msgq(sp, M_ERR, "Unexpected SIGALRM event"); break; /* Index: common/key.h =================================================================== RCS file: /cvs/src/usr.bin/vi/common/key.h,v retrieving revision 1.8 diff -u -p -r1.8 key.h --- common/key.h 27 May 2016 09:18:11 -0000 1.8 +++ common/key.h 26 Dec 2020 17:47:32 -0000 @@ -42,6 +42,7 @@ typedef enum { E_QUIT, /* Quit. */ E_REPAINT, /* Repaint: e_flno, e_tlno set. */ E_SIGHUP, /* SIGHUP. */ + E_SIGALRM, /* SIGALRM. */ E_SIGTERM, /* SIGTERM. */ E_STRING, /* Input string: e_csp, e_len set. */ E_TIMEOUT, /* Timeout. */ Index: common/recover.c =================================================================== RCS file: /cvs/src/usr.bin/vi/common/recover.c,v retrieving revision 1.30 diff -u -p -r1.30 recover.c --- common/recover.c 22 Jul 2019 12:39:02 -0000 1.30 +++ common/recover.c 26 Dec 2020 17:47:32 -0000 @@ -30,6 +30,7 @@ #include <limits.h> #include <paths.h> #include <pwd.h> +#include <signal.h> #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -180,6 +181,7 @@ int rcv_init(SCR *sp) { EXF *ep; + struct itimerval value; recno_t lno; ep = sp->ep; @@ -219,6 +221,18 @@ rcv_init(SCR *sp) /* Turn off the owner execute bit. */ (void)chmod(ep->rcv_path, S_IRUSR | S_IWUSR); + if (!F_ISSET(sp->gp, G_RECOVER_SET)) { + /* Start the recovery timer. */ + value.it_interval.tv_sec = value.it_value.tv_sec = RCV_PERIOD; + value.it_interval.tv_usec = value.it_value.tv_usec = 0; + if (setitimer(ITIMER_REAL, &value, NULL)) { + msgq(sp, M_ERR, + "Error: setitimer: %s", strerror(errno)); + goto err; + } + } + + /* We believe the file is recoverable. */ F_SET(ep, F_RCV_ON); return (0); @@ -244,6 +258,7 @@ rcv_sync(SCR *sp, u_int flags) EXF *ep; int fd, rval; char *dp, buf[1024]; + sigset_t mask, omask; /* Make sure that there's something to recover/sync. */ ep = sp->ep; @@ -252,12 +267,21 @@ rcv_sync(SCR *sp, u_int flags) /* Sync the file if it's been modified. */ if (F_ISSET(ep, F_MODIFIED)) { + /* + * Prevent SIGALRM during sync (which I am not sure + * would actually matter since it just creates an event). + */ + sigemptyset(&mask); + sigaddset(&mask, SIGALRM); + sigprocmask(SIG_BLOCK, &mask, &omask); if (ep->db->sync(ep->db, R_RECNOSYNC)) { F_CLR(ep, F_RCV_ON | F_RCV_NORM); msgq_str(sp, M_SYSERR, ep->rcv_path, "File backup failed: %s"); + sigprocmask(SIG_SETMASK, &omask, NULL); return (1); } + sigprocmask(SIG_SETMASK, &omask, NULL); /* REQUEST: don't remove backing file on exit. */ if (LF_ISSET(RCV_PRESERVE)) Index: ex/ex_txt.c =================================================================== RCS file: /cvs/src/usr.bin/vi/ex/ex_txt.c,v retrieving revision 1.17 diff -u -p -r1.17 ex_txt.c --- ex/ex_txt.c 30 Apr 2020 10:40:21 -0000 1.17 +++ ex/ex_txt.c 26 Dec 2020 17:47:32 -0000 @@ -117,6 +117,7 @@ newtp: if ((tp = text_init(sp, NULL, 0, break; case E_ERR: goto err; + case E_SIGALRM: case E_REPAINT: case E_WRESIZE: continue; Index: ex/ex_write.c =================================================================== RCS file: /cvs/src/usr.bin/vi/ex/ex_write.c,v retrieving revision 1.13 diff -u -p -r1.13 ex_write.c --- ex/ex_write.c 6 Jan 2016 22:28:52 -0000 1.13 +++ ex/ex_write.c 26 Dec 2020 17:47:32 -0000 @@ -127,6 +127,9 @@ exwr(SCR *sp, EXCMD *cmdp, enum which cm int flags; char *name, *p = NULL; + /* Sync recovery file, too. */ + rcv_sync(sp, 0); + NEEDFILE(sp, cmdp); /* All write commands can have an associated '!'. */ Index: vi/v_replace.c =================================================================== RCS file: /cvs/src/usr.bin/vi/vi/v_replace.c,v retrieving revision 1.9 diff -u -p -r1.9 v_replace.c --- vi/v_replace.c 6 Jan 2016 22:28:52 -0000 1.9 +++ vi/v_replace.c 26 Dec 2020 17:47:32 -0000 @@ -125,6 +125,9 @@ next: if (v_event_get(sp, &ev, 0, 0)) case E_INTERRUPT: /* <interrupt> means they changed their minds. */ return (0); + case E_SIGALRM: + /* Do nothing. */ + goto next; case E_WRESIZE: /* <resize> interrupts the input mode. */ v_emsg(sp, NULL, VIM_WRESIZE); Index: vi/v_txt.c =================================================================== RCS file: /cvs/src/usr.bin/vi/vi/v_txt.c,v retrieving revision 1.34 diff -u -p -r1.34 v_txt.c --- vi/v_txt.c 30 Apr 2020 10:40:21 -0000 1.34 +++ vi/v_txt.c 26 Dec 2020 17:47:32 -0000 @@ -502,6 +502,9 @@ next: if (v_event_get(sp, evp, 0, ec_fla case E_WRESIZE: /* <resize> interrupts the input mode. */ v_emsg(sp, NULL, VIM_WRESIZE); + case E_SIGALRM: + /* Don't repeat last input. */ + goto next; /* FALLTHROUGH */ default: if (evp->e_event != E_INTERRUPT && evp->e_event != E_WRESIZE) Index: vi/vi.c =================================================================== RCS file: /cvs/src/usr.bin/vi/vi/vi.c,v retrieving revision 1.22 diff -u -p -r1.22 vi.c --- vi/vi.c 24 Jan 2019 15:09:41 -0000 1.22 +++ vi/vi.c 26 Dec 2020 17:47:32 -0000 @@ -1197,6 +1197,9 @@ v_key(SCR *sp, int command_events, EVENT break; case E_WRESIZE: return (GC_ERR); + case E_SIGALRM: + /* Prevent unexpected sigalarm message. */ + return (GC_ERR); case E_QUIT: case E_WRITE: if (command_events)