trondd <[email protected]> wrote:
> trondd <[email protected]> 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)