->90
[nvi.git] / common / recover.c
blob755e8d50fafc117c4f8fe4f7714b5dffa4b25229
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.39 1993/11/20 10:05:20 bostic Exp $ (Berkeley) $Date: 1993/11/20 10:05:20 $";
10 #endif /* not lint */
12 #include <sys/param.h>
13 #include <sys/stat.h>
14 #include <sys/time.h>
17 * We include <sys/file.h>, because the flock(2) #defines were
18 * found there on historical systems. We also include <fcntl.h>
19 * because the open(2) #defines are found there on newer systems.
21 #include <sys/file.h>
23 #include <netdb.h> /* MAXHOSTNAMELEN on some systems. */
25 #include <dirent.h>
26 #include <errno.h>
27 #include <fcntl.h>
28 #include <pwd.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <unistd.h>
33 #include "vi.h"
34 #include "pathnames.h"
37 * Recovery code.
39 * The basic scheme is there's a btree file, whose name we specify. The first
40 * time a file is modified, and then at RCV_PERIOD intervals after that, the
41 * btree file is synced to disk. Each time a keystroke is requested for a file
42 * the terminal routines check to see if the file needs to be synced. This, of
43 * course means that the data structures had better be consistent each time the
44 * key routines are called.
46 * We don't use timers other than to flag that the file should be synced. This
47 * would require that the SCR and EXF data structures be locked, the dbopen(3)
48 * routines lock out the timers for each update, etc. It's just not worth it.
49 * The only way we can lose in the current scheme is if the file is saved, then
50 * the user types furiously for RCV_PERIOD - 1 seconds, and types nothing more.
51 * Not likely.
53 * When a file is first modified, a file which can be handed off to the mailer
54 * is created. The file contains normal headers, with two additions, which
55 * occur in THIS order, as the FIRST TWO headers:
57 * Vi-recover-file: file_name
58 * Vi-recover-path: recover_path
60 * Since newlines delimit the headers, this means that file names cannot
61 * have newlines in them, but that's probably okay.
63 * Btree files are named "vi.XXXX" and recovery files are named "recover.XXXX".
66 #define VI_FHEADER "Vi-recover-file: "
67 #define VI_PHEADER "Vi-recover-path: "
69 static void rcv_alrm __P((int));
70 static int rcv_mailfile __P((SCR *, EXF *));
71 static void rcv_syncit __P((SCR *, int));
74 * rcv_tmp --
75 * Build a file name that will be used as the recovery file.
77 int
78 rcv_tmp(sp, ep, name)
79 SCR *sp;
80 EXF *ep;
81 char *name;
83 struct stat sb;
84 int fd;
85 char *dp, *p, path[MAXPATHLEN];
88 * If the recovery directory doesn't exist, try and create it. As
89 * the recovery files are themselves protected from reading/writing
90 * by other than the owner, the worst that can happen is that a user
91 * would have permission to remove other users recovery files. If
92 * the sticky bit has the BSD semantics, that too will be impossible.
94 dp = O_STR(sp, O_DIRECTORY);
95 if (stat(dp, &sb)) {
96 if (errno != ENOENT || mkdir(dp, 0)) {
97 msgq(sp, M_ERR, "Error: %s: %s", dp, strerror(errno));
98 return (1);
100 (void)chmod(dp, S_IRWXU | S_IRWXG | S_IRWXO | S_ISVTX);
103 /* Newlines delimit the mail messages. */
104 for (p = name; *p; ++p)
105 if (*p == '\n') {
106 msgq(sp, M_ERR,
107 "Files with newlines in the name are unrecoverable.");
108 return (1);
111 (void)snprintf(path, sizeof(path), "%s/vi.XXXXXX", dp);
114 * !!!
115 * We depend on mkstemp(3) setting the permissions correctly.
116 * GP's, we do it ourselves, to keep the window as small as
117 * possible.
119 if ((fd = mkstemp(path)) == -1) {
120 msgq(sp, M_ERR, "Error: %s: %s", dp, strerror(errno));
121 return (1);
123 (void)chmod(path, S_IRUSR | S_IWUSR);
124 (void)close(fd);
126 if ((ep->rcv_path = strdup(path)) == NULL) {
127 msgq(sp, M_SYSERR, NULL);
128 (void)unlink(path);
129 return (1);
132 /* We believe the file is recoverable. */
133 F_SET(ep, F_RCV_ON);
134 return (0);
138 * rcv_init --
139 * Force the file to be snapshotted for recovery.
142 rcv_init(sp, ep)
143 SCR *sp;
144 EXF *ep;
146 struct itimerval value;
147 struct sigaction act;
148 recno_t lno;
150 F_CLR(ep, F_FIRSTMODIFY | F_RCV_ON);
152 /* Build file to mail to the user. */
153 if (rcv_mailfile(sp, ep))
154 goto err;
156 /* Force read of entire file. */
157 if (file_lline(sp, ep, &lno))
158 goto err;
160 /* Turn on a busy message, and sync it to backing store. */
161 busy_on(sp, 1, "Copying file for recovery...");
162 if (ep->db->sync(ep->db, R_RECNOSYNC)) {
163 msgq(sp, M_ERR, "Preservation failed: %s: %s",
164 ep->rcv_path, strerror(errno));
165 busy_off(sp);
166 goto err;
168 busy_off(sp);
170 if (!F_ISSET(sp->gp, G_RECOVER_SET)) {
171 /* Install the recovery timer handler. */
172 act.sa_handler = rcv_alrm;
173 sigemptyset(&act.sa_mask);
174 act.sa_flags = 0;
175 (void)sigaction(SIGALRM, &act, NULL);
177 /* Start the recovery timer. */
178 value.it_interval.tv_sec = value.it_value.tv_sec = RCV_PERIOD;
179 value.it_interval.tv_usec = value.it_value.tv_usec = 0;
180 if (setitimer(ITIMER_REAL, &value, NULL)) {
181 msgq(sp, M_ERR,
182 "Error: setitimer: %s", strerror(errno));
183 err: msgq(sp, M_ERR,
184 "Recovery after system crash not possible.");
185 return (1);
189 /* We believe the file is recoverable. */
190 F_SET(ep, F_RCV_ON);
191 return (0);
195 * rcv_alrm --
196 * Recovery timer interrupt handler.
198 static void
199 rcv_alrm(signo)
200 int signo;
202 F_SET(__global_list, G_SIGALRM);
206 * rcv_mailfile --
207 * Build the file to mail to the user.
209 static int
210 rcv_mailfile(sp, ep)
211 SCR *sp;
212 EXF *ep;
214 struct passwd *pw;
215 uid_t uid;
216 FILE *fp;
217 time_t now;
218 int fd;
219 char *p, *t, host[MAXHOSTNAMELEN], path[MAXPATHLEN];
221 if ((pw = getpwuid(uid = getuid())) == NULL) {
222 msgq(sp, M_ERR, "Information on user id %u not found.", uid);
223 return (1);
226 (void)snprintf(path, sizeof(path),
227 "%s/recover.XXXXXX", O_STR(sp, O_DIRECTORY));
228 if ((fd = mkstemp(path)) == -1 || (fp = fdopen(fd, "w")) == NULL) {
229 msgq(sp, M_ERR,
230 "Error: %s: %s", O_STR(sp, O_DIRECTORY), strerror(errno));
231 if (fd != -1)
232 (void)close(fd);
233 return (1);
237 * We keep an open lock on the file so that the recover option can
238 * distinguish between files that are live and those that need to
239 * be recovered. There's an obvious window between the mkstemp call
240 * and the lock, but it's pretty small.
242 if ((ep->rcv_fd = dup(fd)) != -1)
243 (void)flock(ep->rcv_fd, LOCK_EX | LOCK_NB);
245 if ((ep->rcv_mpath = strdup(path)) == NULL) {
246 msgq(sp, M_SYSERR, NULL);
247 (void)fclose(fp);
248 return (1);
251 t = FILENAME(sp->frp);
252 if ((p = strrchr(t, '/')) == NULL)
253 p = t;
254 else
255 ++p;
256 (void)time(&now);
257 (void)gethostname(host, sizeof(host));
258 (void)fprintf(fp, "%s%s\n%s%s\n%s\n%s\n%s%s\n%s%s\n%s\n\n",
259 VI_FHEADER, p, /* Non-standard. */
260 VI_PHEADER, ep->rcv_path, /* Non-standard. */
261 "Reply-To: root",
262 "From: root (Nvi recovery program)",
263 "To: ", pw->pw_name,
264 "Subject: Nvi saved the file ", p,
265 "Precedence: bulk"); /* For vacation(1). */
266 (void)fprintf(fp, "%s%.24s%s%s\n%s%s",
267 "On ", ctime(&now),
268 ", the user ", pw->pw_name,
269 "was editing a file named ", p);
270 if (p != t)
271 (void)fprintf(fp, " (%s)", t);
272 (void)fprintf(fp, "\n%s%s%s\n",
273 "on the machine ", host, ", when it was saved for\nrecovery.");
274 (void)fprintf(fp, "\n%s\n%s\n%s\n\n",
275 "You can recover most, if not all, of the changes",
276 "to this file using the -l and -r options to nvi(1)",
277 "or nex(1).");
279 if (fflush(fp) || ferror(fp)) {
280 msgq(sp, M_SYSERR, NULL);
281 (void)fclose(fp);
282 return (1);
284 return (0);
288 * rcv_sync --
289 * Sync the backing file.
292 rcv_sync(sp, ep)
293 SCR *sp;
294 EXF *ep;
296 struct itimerval value;
298 if (ep->db->sync(ep->db, R_RECNOSYNC)) {
299 msgq(sp, M_ERR, "Automatic file backup failed: %s: %s",
300 ep->rcv_path, strerror(errno));
301 value.it_interval.tv_sec = value.it_interval.tv_usec = 0;
302 value.it_value.tv_sec = value.it_value.tv_usec = 0;
303 (void)setitimer(ITIMER_REAL, &value, NULL);
304 F_CLR(ep, F_RCV_ON);
305 return (1);
307 return (0);
311 * rcv_hup --
312 * Recovery SIGHUP interrupt handler. (Modem line dropped, or
313 * xterm window closed.)
315 void
316 rcv_hup()
318 SCR *sp;
321 * Walk the lists of screens, sync'ing the files; only sync
322 * each file once. Send email to the user for each file saved.
324 for (sp = __global_list->dq.cqh_first;
325 sp != (void *)&__global_list->dq; sp = sp->q.cqe_next)
326 rcv_syncit(sp, 1);
327 for (sp = __global_list->hq.cqh_first;
328 sp != (void *)&__global_list->hq; sp = sp->q.cqe_next)
329 rcv_syncit(sp, 1);
332 * Die with the proper exit status. Don't bother using
333 * sigaction(2) 'cause we want the default behavior.
335 (void)signal(SIGHUP, SIG_DFL);
336 (void)kill(0, SIGHUP);
338 /* NOTREACHED */
339 exit (1);
343 * rcv_term --
344 * Recovery SIGTERM interrupt handler. (Reboot or halt is running.)
346 void
347 rcv_term()
349 SCR *sp;
352 * Walk the lists of screens, sync'ing the files; only sync
353 * each file once.
355 for (sp = __global_list->dq.cqh_first;
356 sp != (void *)&__global_list->dq; sp = sp->q.cqe_next)
357 rcv_syncit(sp, 0);
358 for (sp = __global_list->hq.cqh_first;
359 sp != (void *)&__global_list->hq; sp = sp->q.cqe_next)
360 rcv_syncit(sp, 0);
363 * Die with the proper exit status. Don't bother using
364 * sigaction(2) 'cause we want the default behavior.
366 (void)signal(SIGTERM, SIG_DFL);
367 (void)kill(0, SIGTERM);
369 /* NOTREACHED */
370 exit (1);
374 * rcv_syncit --
375 * Sync the file, optionally send mail.
377 static void
378 rcv_syncit(sp, email)
379 SCR *sp;
380 int email;
382 EXF *ep;
383 char comm[1024];
385 if ((ep = sp->ep) == NULL ||
386 !F_ISSET(ep, F_MODIFIED) || !F_ISSET(ep, F_RCV_ON))
387 return;
389 (void)ep->db->sync(ep->db, R_RECNOSYNC);
390 F_SET(ep, F_RCV_NORM);
393 * !!!
394 * If you need to port this to a system that doesn't have sendmail,
395 * the -t flag being used causes sendmail to read the message for
396 * the recipients instead of us specifying them some other way.
398 if (email) {
399 (void)snprintf(comm, sizeof(comm),
400 "%s -t < %s", _PATH_SENDMAIL, ep->rcv_mpath);
401 (void)system(comm);
403 (void)file_end(sp, ep, 1);
407 * people making love
408 * never exactly the same
409 * just like a snowflake
411 * rcv_list --
412 * List the files that can be recovered by this user.
415 rcv_list(sp)
416 SCR *sp;
418 struct dirent *dp;
419 struct stat sb;
420 DIR *dirp;
421 FILE *fp;
422 int found;
423 char *p, file[1024];
425 if (chdir(O_STR(sp, O_DIRECTORY)) || (dirp = opendir(".")) == NULL) {
426 (void)fprintf(stderr,
427 "vi: %s: %s\n", O_STR(sp, O_DIRECTORY), strerror(errno));
428 return (1);
431 for (found = 0; (dp = readdir(dirp)) != NULL;) {
432 if (strncmp(dp->d_name, "recover.", 8))
433 continue;
435 /* If it's readable, it's recoverable. */
436 if ((fp = fopen(dp->d_name, "r")) == NULL)
437 continue;
439 /* If it's locked, it's live. */
440 if (flock(fileno(fp), LOCK_EX | LOCK_NB)) {
441 (void)fclose(fp);
442 continue;
445 /* Check the header, get the file name. */
446 if (fgets(file, sizeof(file), fp) == NULL ||
447 strncmp(file, VI_FHEADER, sizeof(VI_FHEADER) - 1) ||
448 (p = strchr(file, '\n')) == NULL) {
449 (void)fprintf(stderr,
450 "vi: %s: malformed recovery file.\n", dp->d_name);
451 goto next;
453 *p = '\0';
455 /* Get the last modification time. */
456 if (fstat(fileno(fp), &sb)) {
457 (void)fprintf(stderr,
458 "vi: %s: %s\n", dp->d_name, strerror(errno));
459 goto next;
462 /* Display. */
463 (void)printf("%s: %s",
464 file + sizeof(VI_FHEADER) - 1, ctime(&sb.st_mtime));
465 found = 1;
467 next: (void)fclose(fp);
469 if (found == 0)
470 (void)printf("vi: no files to recover.\n");
471 (void)closedir(dirp);
472 return (0);
476 * rcv_read --
477 * Start a recovered file as the file to edit.
480 rcv_read(sp, name)
481 SCR *sp;
482 char *name;
484 struct dirent *dp;
485 struct stat sb;
486 DIR *dirp;
487 FREF *frp;
488 FILE *fp;
489 time_t rec_mtime;
490 int found, requested;
491 char *p, *t, *recp, *pathp;
492 char recpath[MAXPATHLEN], file[MAXPATHLEN], path[MAXPATHLEN];
494 if ((dirp = opendir(O_STR(sp, O_DIRECTORY))) == NULL) {
495 msgq(sp, M_ERR,
496 "%s: %s", O_STR(sp, O_DIRECTORY), strerror(errno));
497 return (1);
500 recp = pathp = NULL;
501 for (found = requested = 0; (dp = readdir(dirp)) != NULL;) {
502 if (strncmp(dp->d_name, "recover.", 8))
503 continue;
505 /* If it's readable, it's recoverable. */
506 (void)snprintf(recpath, sizeof(recpath),
507 "%s/%s", O_STR(sp, O_DIRECTORY), dp->d_name);
508 if ((fp = fopen(recpath, "r")) == NULL)
509 continue;
511 /* If it's locked, it's live. */
512 if (flock(fileno(fp), LOCK_EX | LOCK_NB)) {
513 (void)fclose(fp);
514 continue;
517 /* Check the headers. */
518 if (fgets(file, sizeof(file), fp) == NULL ||
519 strncmp(file, VI_FHEADER, sizeof(VI_FHEADER) - 1) ||
520 (p = strchr(file, '\n')) == NULL ||
521 fgets(path, sizeof(path), fp) == NULL ||
522 strncmp(path, VI_PHEADER, sizeof(VI_PHEADER) - 1) ||
523 (t = strchr(path, '\n')) == NULL) {
524 msgq(sp, M_ERR,
525 "%s: malformed recovery file.", recpath);
526 goto next;
528 ++found;
529 *t = *p = '\0';
531 /* Get the last modification time. */
532 if (fstat(fileno(fp), &sb)) {
533 msgq(sp, M_ERR,
534 "vi: %s: %s", dp->d_name, strerror(errno));
535 goto next;
538 /* Check the file name. */
539 if (strcmp(file + sizeof(VI_FHEADER) - 1, name))
540 goto next;
542 ++requested;
544 /* If we've found more than one, take the most recent. */
545 if (recp == NULL || rec_mtime < sb.st_mtime) {
546 p = recp;
547 t = pathp;
548 if ((recp = strdup(recpath)) == NULL) {
549 msgq(sp, M_ERR,
550 "vi: Error: %s.\n", strerror(errno));
551 recp = p;
552 goto next;
554 if ((pathp = strdup(path)) == NULL) {
555 msgq(sp, M_ERR,
556 "vi: Error: %s.\n", strerror(errno));
557 FREE(recp, strlen(recp) + 1);
558 recp = p;
559 pathp = t;
560 goto next;
562 if (p != NULL) {
563 FREE(p, strlen(p) + 1);
564 FREE(t, strlen(t) + 1);
566 rec_mtime = sb.st_mtime;
569 next: (void)fclose(fp);
571 (void)closedir(dirp);
573 if (recp == NULL) {
574 msgq(sp, M_INFO,
575 "No files named %s, owned by you, to edit.", name);
576 return (1);
578 if (found) {
579 if (requested > 1)
580 msgq(sp, M_INFO,
581 "There are older versions of this file for you to recover.");
582 if (found > requested)
583 msgq(sp, M_INFO,
584 "There are other files that you can recover.");
587 /* Create the FREF structure, start the btree file. */
588 if ((frp = file_add(sp, NULL, name, 0)) == NULL ||
589 file_init(sp, frp, pathp + sizeof(VI_PHEADER) - 1, 0)) {
590 FREE(recp, strlen(recp) + 1);
591 FREE(pathp, strlen(pathp) + 1);
592 return (1);
594 sp->ep->rcv_mpath = recp;
595 return (0);