go to 0.81
[nvi.git] / common / recover.c
blob5066fb952f878bc8e207a47e65e15c0cb25e800a
1 /*-
2 * Copyright (c) 1993
3 * The Regents of the University of California. All rights reserved.
5 * %sccs.include.redist.c%
6 */
8 #ifndef lint
9 static char sccsid[] = "$Id: recover.c,v 8.29 1993/11/04 18:21:44 bostic Exp $ (Berkeley) $Date: 1993/11/04 18:21:44 $";
10 #endif /* not lint */
12 #include <sys/param.h>
13 #include <sys/stat.h>
14 #include <sys/time.h>
16 #include <dirent.h>
17 #include <errno.h>
18 #include <fcntl.h>
19 #include <pwd.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <unistd.h>
24 #include "vi.h"
25 #include "pathnames.h"
26 #include "recover.h"
29 * Recovery code.
31 * The basic scheme is there's a btree file, whose name we specify. The first
32 * time a file is modified, and then at RCV_PERIOD intervals after that, the
33 * btree file is synced to disk. Each time a keystroke is requested for a file
34 * the terminal routines check to see if the file needs to be synced. This, of
35 * course means that the data structures had better be consistent each time the
36 * key routines are called.
38 * We don't use timers other than to flag that the file should be synced. This
39 * would require that the SCR and HDR data structures be locked, the dbopen(3)
40 * routines lock out the timers for each update, etc. It's just not worth it.
41 * The only way we can lose in the current scheme is if the file is saved, then
42 * the user types furiously for RCV_PERIOD - 1 seconds, and types nothing more.
43 * Not likely.
45 * When a file is first modified, a file which can be handed off to the mailer
46 * is created. The file contains normal headers, with two additions, which
47 * occur in THIS order, as the FIRST TWO headers:
49 * Vi-recover-file: file_name
50 * Vi-recover-path: recover_path
52 * Since newlines delimit the headers, this means that file names cannot
53 * have newlines in them, but that's probably okay.
55 * Btree files are named "vi.XXXX" and recovery files are named "recover.XXXX".
58 #define VI_FHEADER "Vi-recover-file: "
59 #define VI_PHEADER "Vi-recover-path: "
61 static void rcv_alrm __P((int));
62 static int rcv_mailfile __P((SCR *, EXF *));
65 * rcv_tmp --
66 * Build a file name that will be used as the recovery file.
68 int
69 rcv_tmp(sp, ep, fname)
70 SCR *sp;
71 EXF *ep;
72 char *fname;
74 struct stat sb;
75 int fd;
76 char *dp, *p, path[MAXPATHLEN];
79 * If the recovery directory doesn't exist, try and create it. As
80 * the recovery files are themselves protected from reading/writing
81 * by other than the owner, the worst that can happen is that a user
82 * would have permission to remove other user's recovery files. If
83 * the sticky bit has the BSD semantics, that too will be impossible.
85 dp = O_STR(sp, O_DIRECTORY);
86 if (stat(dp, &sb)) {
87 if (errno != ENOENT || mkdir(dp, 0)) {
88 msgq(sp, M_ERR, "Error: %s: %s", dp, strerror(errno));
89 return (1);
91 (void)chmod(dp, S_IRWXU | S_IRWXG | S_IRWXO | S_ISVTX);
94 /* Newlines delimit the mail messages. */
95 for (p = fname; *p; ++p)
96 if (*p == '\n') {
97 msgq(sp, M_ERR,
98 "Files with newlines in the name are unrecoverable.");
99 return (1);
102 (void)snprintf(path, sizeof(path), "%s/vi.XXXXXX", dp);
105 * !!!
106 * We depend on mkstemp(3) setting the permissions correctly.
108 if ((fd = mkstemp(path)) == -1) {
109 msgq(sp, M_ERR, "Error: %s: %s", dp, strerror(errno));
110 return (1);
112 (void)close(fd);
114 if ((ep->rcv_path = strdup(path)) == NULL) {
115 msgq(sp, M_ERR, "Error: %s", strerror(errno));
116 (void)unlink(path);
117 return (1);
119 F_SET(ep, F_RCV_ON);
120 return (0);
124 * rcv_init --
125 * Force the file to be snapshotted for recovery.
128 rcv_init(sp, ep)
129 SCR *sp;
130 EXF *ep;
132 struct itimerval value;
133 struct sigaction act;
134 recno_t lno;
136 F_CLR(ep, F_FIRSTMODIFY | F_RCV_ON);
138 /* Build file to mail to the user. */
139 if (rcv_mailfile(sp, ep))
140 goto err1;
142 /* Force read of entire file. */
143 if (file_lline(sp, ep, &lno))
144 goto err2;
146 /* Turn on a busy message. */
147 busy_on(sp, 1, "Copying file for recovery...");
149 /* Sync it to backing store. */
150 if (ep->db->sync(ep->db, R_RECNOSYNC)) {
151 msgq(sp, M_ERR, "Preservation failed: %s: %s",
152 ep->rcv_path, strerror(errno));
153 busy_off(sp);
154 goto err2;
156 busy_off(sp);
158 if (!F_ISSET(sp->gp, G_RECOVER_SET)) {
159 /* Install the recovery timer handler. */
160 act.sa_handler = rcv_alrm;
161 sigemptyset(&act.sa_mask);
162 act.sa_flags = 0;
163 (void)sigaction(SIGALRM, &act, NULL);
165 /* Start the recovery timer. */
166 value.it_interval.tv_sec = value.it_value.tv_sec = RCV_PERIOD;
167 value.it_interval.tv_usec = value.it_value.tv_usec = 0;
168 if (setitimer(ITIMER_REAL, &value, NULL)) {
169 msgq(sp, M_ERR,
170 "Error: setitimer: %s", strerror(errno));
171 goto err2;
175 F_SET(ep, F_RCV_ON);
176 return (0);
178 err2: (void)unlink(ep->rcv_mpath);
179 (void)unlink(ep->rcv_path);
180 F_SET(ep, F_RCV_NORM);
181 err1: msgq(sp, M_ERR, "Recovery after system crash not possible.");
182 return (1);
186 * rcv_mailfile --
187 * Build the file to mail to the user.
189 static int
190 rcv_mailfile(sp, ep)
191 SCR *sp;
192 EXF *ep;
194 struct passwd *pw;
195 uid_t uid;
196 FILE *fp;
197 time_t now;
198 int fd, sverrno;
199 char *p, host[MAXHOSTNAMELEN], path[MAXPATHLEN];
201 if ((pw = getpwuid(uid = getuid())) == NULL) {
202 msgq(sp, M_ERR, "Information on user id %u not found.", uid);
203 return (1);
206 (void)snprintf(path, sizeof(path),
207 "%s/recover.XXXXXX", O_STR(sp, O_DIRECTORY));
208 if ((fd = mkstemp(path)) == -1 || (fp = fdopen(fd, "w")) == NULL) {
209 if (fd != -1)
210 (void)close(fd);
211 msgq(sp, M_ERR,
212 "Error: %s: %s", O_STR(sp, O_DIRECTORY), strerror(errno));
213 return (1);
215 if ((ep->rcv_mpath = strdup(path)) == NULL) {
216 (void)fclose(fp);
217 msgq(sp, M_ERR, "Error: %s", strerror(errno));
218 return (1);
221 if ((p = strrchr(sp->frp->fname, '/')) == NULL)
222 p = sp->frp->fname;
223 else
224 ++p;
225 (void)time(&now);
226 (void)gethostname(host, sizeof(host));
227 (void)fprintf(fp, "%s%s\n%s%s\n%s\n%s\n%s%s\n%s%s\n%s\n\n",
228 VI_FHEADER, p, /* Non-standard. */
229 VI_PHEADER, ep->rcv_path, /* Non-standard. */
230 "Reply-To: root",
231 "From: root (Nvi recovery program)",
232 "To: ", pw->pw_name,
233 "Subject: Nvi saved the file ", p,
234 "Precedence: bulk"); /* For vacation(1). */
235 (void)fprintf(fp, "%s%.24s%s%s\n%s%s",
236 "On ", ctime(&now),
237 ", the user ", pw->pw_name,
238 "was editing a file named ", p);
239 if (p != sp->frp->fname)
240 (void)fprintf(fp, " (%s)", sp->frp->fname);
241 (void)fprintf(fp, "\n%s%s%s\n",
242 "on the machine ", host, ", when it was saved for\nrecovery.");
243 (void)fprintf(fp, "\n%s\n%s\n%s\n\n",
244 "You can recover most, if not all, of the changes",
245 "to this file using the -l and -r options to nvi(1)",
246 "or nex(1).");
248 if (ferror(fp)) {
249 sverrno = errno;
250 (void)fclose(fp);
251 errno = sverrno;
252 goto err;
254 if (fclose(fp)) {
255 err: msgq(sp, M_ERR, "Error: %s", strerror(errno));
258 * XXX
259 * Without a mail file, a recovery file is pretty useless.
260 * This might be worth fixing.
262 (void)unlink(ep->rcv_mpath);
263 (void)unlink(ep->rcv_path);
264 F_SET(ep, F_RCV_NORM);
265 return (1);
267 return (0);
271 * rcv_sync --
272 * Sync the backing file.
275 rcv_sync(sp, ep)
276 SCR *sp;
277 EXF *ep;
279 struct itimerval value;
281 if (ep->db->sync(ep->db, R_RECNOSYNC)) {
282 msgq(sp, M_ERR, "Automatic file backup failed: %s: %s",
283 ep->rcv_path, strerror(errno));
284 value.it_interval.tv_sec = value.it_interval.tv_usec = 0;
285 value.it_value.tv_sec = value.it_value.tv_usec = 0;
286 (void)setitimer(ITIMER_REAL, &value, NULL);
287 F_CLR(ep, F_RCV_ON);
288 return (1);
290 return (0);
294 * rcv_alrm --
295 * Recovery timer interrupt handler.
297 static void
298 rcv_alrm(signo)
299 int signo;
301 F_SET(__global_list, G_SIGALRM);
305 * rcv_hup --
306 * Recovery SIGHUP interrupt handler. (Modem line dropped, or
307 * xterm window closed.)
309 void
310 rcv_hup()
312 SCR *sp;
313 char comm[4096];
316 * Walk the list of screens, sync'ing the files; only sync
317 * each file once. Send email to the user for each file saved.
319 * !!!
320 * If you need to port this to a system that doesn't have sendmail,
321 * the -t flag being used causes sendmail to read the message for
322 * the recipients instead of us specifying them some other way.
324 for (sp = __global_list->screens.le_next;
325 sp != NULL; sp = sp->screenq.qe_next) {
326 F_SET(sp, S_TERMSIGNAL);
327 if (sp->ep != NULL) {
328 if (F_ISSET(sp->ep, F_MODIFIED) &&
329 F_ISSET(sp->ep, F_RCV_ON)) {
330 (void)sp->ep->db->sync(sp->ep->db, R_RECNOSYNC);
331 F_CLR(sp->ep, F_RCV_ON);
332 F_SET(sp->ep, F_RCV_NORM);
333 (void)snprintf(comm, sizeof(comm),
334 "%s -t < %s", _PATH_SENDMAIL,
335 sp->ep->rcv_mpath);
336 (void)system(comm);
338 (void)file_end(sp, sp->ep, 1);
343 * Die with the proper exit status. Don't bother using
344 * sigaction(2) 'cause we want the default behavior.
346 (void)signal(SIGHUP, SIG_DFL);
347 (void)kill(0, SIGHUP);
349 /* NOTREACHED */
350 exit (1);
354 * rcv_term --
355 * Recovery SIGTERM interrupt handler. (Reboot or halt is running.)
357 void
358 rcv_term()
360 SCR *sp;
363 * Walk the list of screens, sync'ing the files; only sync
364 * each file once.
366 for (sp = __global_list->screens.le_next;
367 sp != NULL; sp = sp->screenq.qe_next) {
368 F_SET(sp, S_TERMSIGNAL);
369 if (sp->ep != NULL) {
370 if (F_ISSET(sp->ep, F_MODIFIED) &&
371 F_ISSET(sp->ep, F_RCV_ON)) {
372 (void)sp->ep->db->sync(sp->ep->db, R_RECNOSYNC);
373 F_CLR(sp->ep, F_RCV_ON);
374 F_SET(sp->ep, F_RCV_NORM);
376 (void)file_end(sp, sp->ep, 1);
381 * Die with the proper exit status. Don't bother using
382 * sigaction(2) 'cause we want the default behavior.
384 (void)signal(SIGTERM, SIG_DFL);
385 (void)kill(0, SIGTERM);
387 /* NOTREACHED */
388 exit (1);
392 * people making love
393 * never exactly the same
394 * just like a snowflake
396 * rcv_list --
397 * List the files that can be recovered by this user.
400 rcv_list(sp)
401 SCR *sp;
403 struct dirent *dp;
404 struct stat sb;
405 DIR *dirp;
406 FILE *fp;
407 int found;
408 char *p, file[1024];
410 if (chdir(O_STR(sp, O_DIRECTORY)) || (dirp = opendir(".")) == NULL) {
411 (void)fprintf(stderr,
412 "vi: %s: %s\n", O_STR(sp, O_DIRECTORY), strerror(errno));
413 return (1);
416 for (found = 0; (dp = readdir(dirp)) != NULL;) {
417 if (strncmp(dp->d_name, "recover.", 8))
418 continue;
420 /* If it's readable, it's recoverable. */
421 if ((fp = fopen(dp->d_name, "r")) == NULL)
422 continue;
424 /* Check the header, get the file name. */
425 if (fgets(file, sizeof(file), fp) == NULL ||
426 strncmp(file, VI_FHEADER, sizeof(VI_FHEADER) - 1) ||
427 (p = strchr(file, '\n')) == NULL) {
428 (void)fprintf(stderr,
429 "vi: %s: malformed recovery file.\n", dp->d_name);
430 goto next;
432 *p = '\0';
434 /* Get the last modification time. */
435 if (fstat(fileno(fp), &sb)) {
436 (void)fprintf(stderr,
437 "vi: %s: %s\n", dp->d_name, strerror(errno));
438 goto next;
441 /* Display. */
442 (void)printf("%s: %s",
443 file + sizeof(VI_FHEADER) - 1, ctime(&sb.st_mtime));
444 found = 1;
446 next: (void)fclose(fp);
448 if (found == 0)
449 (void)printf("vi: no files to recover.\n");
450 (void)closedir(dirp);
451 return (0);
455 * rcv_read --
456 * Start a recovered file as the file to edit.
459 rcv_read(sp, name)
460 SCR *sp;
461 char *name;
463 struct dirent *dp;
464 struct stat sb;
465 DIR *dirp;
466 FREF *frp;
467 FILE *fp;
468 time_t rec_mtime;
469 int found, requested;
470 char *p, *t, *recp, *pathp;
471 char recpath[MAXPATHLEN], file[MAXPATHLEN], path[MAXPATHLEN];
473 if ((dirp = opendir(O_STR(sp, O_DIRECTORY))) == NULL) {
474 msgq(sp, M_ERR,
475 "%s: %s", O_STR(sp, O_DIRECTORY), strerror(errno));
476 return (1);
479 recp = pathp = NULL;
480 for (found = requested = 0; (dp = readdir(dirp)) != NULL;) {
481 if (strncmp(dp->d_name, "recover.", 8))
482 continue;
484 /* If it's readable, it's recoverable. */
485 (void)snprintf(recpath, sizeof(recpath),
486 "%s/%s", O_STR(sp, O_DIRECTORY), dp->d_name);
487 if ((fp = fopen(recpath, "r")) == NULL)
488 continue;
490 /* Check the headers. */
491 if (fgets(file, sizeof(file), fp) == NULL ||
492 strncmp(file, VI_FHEADER, sizeof(VI_FHEADER) - 1) ||
493 (p = strchr(file, '\n')) == NULL ||
494 fgets(path, sizeof(path), fp) == NULL ||
495 strncmp(path, VI_PHEADER, sizeof(VI_PHEADER) - 1) ||
496 (t = strchr(path, '\n')) == NULL) {
497 msgq(sp, M_ERR,
498 "%s: malformed recovery file.", recpath);
499 goto next;
501 ++found;
502 *t = *p = '\0';
504 /* Get the last modification time. */
505 if (fstat(fileno(fp), &sb)) {
506 msgq(sp, M_ERR,
507 "vi: %s: %s", dp->d_name, strerror(errno));
508 goto next;
511 /* Check the file name. */
512 if (strcmp(file + sizeof(VI_FHEADER) - 1, name))
513 goto next;
515 ++requested;
517 /* If we've found more than one, take the most recent. */
518 if (recp == NULL || rec_mtime < sb.st_mtime) {
519 p = recp;
520 t = pathp;
521 if ((recp = strdup(recpath)) == NULL) {
522 msgq(sp, M_ERR,
523 "vi: Error: %s.\n", strerror(errno));
524 recp = p;
525 goto next;
527 if ((pathp = strdup(path)) == NULL) {
528 msgq(sp, M_ERR,
529 "vi: Error: %s.\n", strerror(errno));
530 FREE(recp, strlen(recp) + 1);
531 recp = p;
532 pathp = t;
533 goto next;
535 if (p != NULL) {
536 FREE(p, strlen(p) + 1);
537 FREE(t, strlen(t) + 1);
539 rec_mtime = sb.st_mtime;
542 next: (void)fclose(fp);
544 (void)closedir(dirp);
546 if (recp == NULL) {
547 msgq(sp, M_INFO,
548 "No files named %s, owned by you, to edit.", name);
549 return (1);
551 if (found) {
552 if (requested > 1)
553 msgq(sp, M_INFO,
554 "There are older versions of this file for you to recover.");
555 if (found > requested)
556 msgq(sp, M_INFO,
557 "There are other files that you can recover.");
560 /* Create the FREF structure, start the btree file. */
561 if ((frp = file_add(sp, NULL, name, 0)) == NULL ||
562 file_init(sp, frp, pathp + sizeof(VI_PHEADER) - 1, 0)) {
563 FREE(recp, strlen(recp) + 1);
564 FREE(pathp, strlen(pathp) + 1);
565 return (1);
567 sp->ep->rcv_mpath = recp;
568 return (0);