2 * Copyright (c) 1993, 1994
3 * The Regents of the University of California. All rights reserved.
4 * Copyright (c) 1993, 1994, 1995, 1996
5 * Keith Bostic. All rights reserved.
7 * See the LICENSE file for redistribution information.
13 static const char sccsid[] = "@(#)recover.c 10.21 (Berkeley) 9/15/96";
16 #include <sys/param.h>
17 #include <sys/types.h> /* XXX: param.h may not have included types.h */
18 #include <sys/queue.h>
22 * We include <sys/file.h>, because the open #defines were found there
23 * on historical systems. We also include <fcntl.h> because the open(2)
24 * #defines are found there on newer systems.
28 #include <bitstring.h>
41 #include "pathnames.h"
46 * The basic scheme is as follows. In the EXF structure, we maintain full
47 * paths of a b+tree file and a mail recovery file. The former is the file
48 * used as backing store by the DB package. The latter is the file that
49 * contains an email message to be sent to the user if we crash. The two
50 * simple states of recovery are:
52 * + first starting the edit session:
53 * the b+tree file exists and is mode 700, the mail recovery
55 * + after the file has been modified:
56 * the b+tree file exists and is mode 600, the mail recovery
57 * file exists, and is exclusively locked.
59 * In the EXF structure we maintain a file descriptor that is the locked
60 * file descriptor for the mail recovery file. NOTE: we sometimes have to
61 * do locking with fcntl(2). This is a problem because if you close(2) any
62 * file descriptor associated with the file, ALL of the locks go away. Be
63 * sure to remember that if you have to modify the recovery code. (It has
64 * been rhetorically asked of what the designers could have been thinking
65 * when they did that interface. The answer is simple: they weren't.)
67 * To find out if a recovery file/backing file pair are in use, try to get
68 * a lock on the recovery file.
70 * To find out if a backing file can be deleted at boot time, check for an
71 * owner execute bit. (Yes, I know it's ugly, but it's either that or put
72 * special stuff into the backing file itself, or correlate the files at
73 * boot time, neither of which looks like fun.) Note also that there's a
74 * window between when the file is created and the X bit is set. It's small,
75 * but it's there. To fix the window, check for 0 length files as well.
77 * To find out if a file can be recovered, check the F_RCV_ON bit. Note,
78 * this DOES NOT mean that any initialization has been done, only that we
79 * haven't yet failed at setting up or doing recovery.
81 * To preserve a recovery file/backing file pair, set the F_RCV_NORM bit.
82 * If that bit is not set when ending a file session:
83 * If the EXF structure paths (rcv_path and rcv_mpath) are not NULL,
84 * they are unlink(2)'d, and free(3)'d.
85 * If the EXF file descriptor (rcv_fd) is not -1, it is closed.
87 * The backing b+tree file is set up when a file is first edited, so that
88 * the DB package can use it for on-disk caching and/or to snapshot the
89 * file. When the file is first modified, the mail recovery file is created,
90 * the backing file permissions are updated, the file is sync(2)'d to disk,
91 * and the timer is started. Then, at RCV_PERIOD second intervals, the
92 * b+tree file is synced to disk. RCV_PERIOD is measured using SIGALRM, which
93 * means that the data structures (SCR, EXF, the underlying tree structures)
94 * must be consistent when the signal arrives.
96 * The recovery mail file contains normal mail headers, with two additions,
97 * which occur in THIS order, as the FIRST TWO headers:
99 * X-vi-recover-file: file_name
100 * X-vi-recover-path: recover_path
102 * Since newlines delimit the headers, this means that file names cannot have
103 * newlines in them, but that's probably okay. As these files aren't intended
104 * to be long-lived, changing their format won't be too painful.
106 * Btree files are named "vi.XXXX" and recovery files are named "recover.XXXX".
109 #define VI_FHEADER "X-vi-recover-file: "
110 #define VI_PHEADER "X-vi-recover-path: "
112 static int rcv_copy __P((SCR *, int, char *));
113 static void rcv_email __P((SCR *, char *));
114 static char *rcv_gets __P((char *, size_t, int));
115 static int rcv_mailfile __P((SCR *, int, char *));
116 static int rcv_mktemp __P((SCR *, char *, char *, int));
120 * Build a file name that will be used as the recovery file.
122 * PUBLIC: int rcv_tmp __P((SCR *, EXF *, char *));
125 rcv_tmp(sp, ep, name)
132 char *dp, *p, path[MAXPATHLEN];
136 * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER.
139 * If the recovery directory doesn't exist, try and create it. As
140 * the recovery files are themselves protected from reading/writing
141 * by other than the owner, the worst that can happen is that a user
142 * would have permission to remove other user's recovery files. If
143 * the sticky bit has the BSD semantics, that too will be impossible.
145 if (opts_empty(sp, O_RECDIR, 0))
147 dp = O_STR(sp, O_RECDIR);
149 if (errno != ENOENT || mkdir(dp, 0)) {
150 msgq(sp, M_SYSERR, "%s", dp);
153 (void)chmod(dp, S_IRWXU | S_IRWXG | S_IRWXO | S_ISVTX);
156 /* Newlines delimit the mail messages. */
157 for (p = name; *p; ++p)
160 "055|Files with newlines in the name are unrecoverable");
164 (void)snprintf(path, sizeof(path), "%s/vi.XXXXXX", dp);
165 if ((fd = rcv_mktemp(sp, path, dp, S_IRWXU)) == -1)
169 if ((ep->rcv_path = strdup(path)) == NULL) {
170 msgq(sp, M_SYSERR, NULL);
173 "056|Modifications not recoverable if the session fails");
177 /* We believe the file is recoverable. */
184 * Force the file to be snapshotted for recovery.
186 * PUBLIC: int rcv_init __P((SCR *));
197 /* Only do this once. */
198 F_CLR(ep, F_FIRSTMODIFY);
200 /* If we already know the file isn't recoverable, we're done. */
201 if (!F_ISSET(ep, F_RCV_ON))
204 /* Turn off recoverability until we figure out if this will work. */
207 /* Test if we're recovering a file, not editing one. */
208 if (ep->rcv_mpath == NULL) {
209 /* Build a file to mail to the user. */
210 if (rcv_mailfile(sp, 0, NULL))
213 /* Force a read of the entire file. */
214 if (db_last(sp, &lno))
217 /* Turn on a busy message, and sync it to backing store. */
219 "057|Copying file for recovery...", BUSY_ON);
220 if (ep->db->sync(ep->db, R_RECNOSYNC)) {
221 msgq_str(sp, M_SYSERR, ep->rcv_path,
222 "058|Preservation failed: %s");
223 sp->gp->scr_busy(sp, NULL, BUSY_OFF);
226 sp->gp->scr_busy(sp, NULL, BUSY_OFF);
229 /* Turn off the owner execute bit. */
230 (void)chmod(ep->rcv_path, S_IRUSR | S_IWUSR);
232 /* We believe the file is recoverable. */
237 "059|Modifications not recoverable if the session fails");
243 * Sync the file, optionally:
244 * flagging the backup file to be preserved
245 * snapshotting the backup file and send email to the user
246 * sending email to the user if the file was modified
247 * ending the file session
249 * PUBLIC: int rcv_sync __P((SCR *, u_int));
260 /* Make sure that there's something to recover/sync. */
262 if (ep == NULL || !F_ISSET(ep, F_RCV_ON))
265 /* Sync the file if it's been modified. */
266 if (F_ISSET(ep, F_MODIFIED)) {
268 if (ep->db->sync(ep->db, R_RECNOSYNC)) {
269 F_CLR(ep, F_RCV_ON | F_RCV_NORM);
270 msgq_str(sp, M_SYSERR,
271 ep->rcv_path, "060|File backup failed: %s");
277 /* REQUEST: don't remove backing file on exit. */
278 if (LF_ISSET(RCV_PRESERVE))
279 F_SET(ep, F_RCV_NORM);
281 /* REQUEST: send email. */
282 if (LF_ISSET(RCV_EMAIL))
283 rcv_email(sp, ep->rcv_mpath);
288 * Each time the user exec's :preserve, we have to snapshot all of
289 * the recovery information, i.e. it's like the user re-edited the
290 * file. We copy the DB(3) backing file, and then create a new mail
291 * recovery file, it's simpler than exiting and reopening all of the
294 * REQUEST: snapshot the file.
297 if (LF_ISSET(RCV_SNAPSHOT)) {
298 if (opts_empty(sp, O_RECDIR, 0))
300 dp = O_STR(sp, O_RECDIR);
301 (void)snprintf(buf, sizeof(buf), "%s/vi.XXXXXX", dp);
302 if ((fd = rcv_mktemp(sp, buf, dp, S_IRUSR | S_IWUSR)) == -1)
305 "061|Copying file for recovery...", BUSY_ON);
306 if (rcv_copy(sp, fd, ep->rcv_path) ||
307 close(fd) || rcv_mailfile(sp, 1, buf)) {
312 sp->gp->scr_busy(sp, NULL, BUSY_OFF);
318 /* REQUEST: end the file session. */
319 if (LF_ISSET(RCV_ENDSESSION) && file_end(sp, NULL, 1))
327 * Build the file to mail to the user.
330 rcv_mailfile(sp, issync, cp_path)
342 char *dp, *p, *t, buf[4096], mpath[MAXPATHLEN];
347 * MAXHOSTNAMELEN is in various places on various systems, including
348 * <netdb.h> and <sys/socket.h>. If not found, use a large default.
350 #ifndef MAXHOSTNAMELEN
351 #define MAXHOSTNAMELEN 1024
353 char host[MAXHOSTNAMELEN];
356 if ((pw = getpwuid(uid = getuid())) == NULL) {
358 "062|Information on user id %u not found", uid);
362 if (opts_empty(sp, O_RECDIR, 0))
364 dp = O_STR(sp, O_RECDIR);
365 (void)snprintf(mpath, sizeof(mpath), "%s/recover.XXXXXX", dp);
366 if ((fd = rcv_mktemp(sp, mpath, dp, S_IRUSR | S_IWUSR)) == -1)
371 * We keep an open lock on the file so that the recover option can
372 * distinguish between files that are live and those that need to
373 * be recovered. There's an obvious window between the mkstemp call
374 * and the lock, but it's pretty small.
377 if (file_lock(sp, NULL, NULL, fd, 1) != LOCK_SUCCESS)
378 msgq(sp, M_SYSERR, "063|Unable to lock recovery file");
380 /* Save the recover file descriptor, and mail path. */
382 if ((ep->rcv_mpath = strdup(mpath)) == NULL) {
383 msgq(sp, M_SYSERR, NULL);
386 cp_path = ep->rcv_path;
391 * We can't use stdio(3) here. The problem is that we may be using
392 * fcntl(2), so if ANY file descriptor into the file is closed, the
393 * lock is lost. So, we could never close the FILE *, even if we
394 * dup'd the fd first.
397 if ((p = strrchr(t, '/')) == NULL)
402 (void)gethostname(host, sizeof(host));
403 len = snprintf(buf, sizeof(buf),
404 "%s%s\n%s%s\n%s\n%s\n%s%s\n%s%s\n%s\n\n",
405 VI_FHEADER, t, /* Non-standard. */
406 VI_PHEADER, cp_path, /* Non-standard. */
408 "From: root (Nvi recovery program)",
410 "Subject: Nvi saved the file ", p,
411 "Precedence: bulk"); /* For vacation(1). */
412 if (len > sizeof(buf) - 1)
414 if (write(fd, buf, len) != len)
417 len = snprintf(buf, sizeof(buf),
418 "%s%.24s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n\n",
419 "On ", ctime(&now), ", the user ", pw->pw_name,
420 " was editing a file named ", t, " on the machine ",
421 host, ", when it was saved for recovery. ",
422 "You can recover most, if not all, of the changes ",
423 "to this file using the -r option to ", gp->progname, ":\n\n\t",
424 gp->progname, " -r ", t);
425 if (len > sizeof(buf) - 1) {
426 lerr: msgq(sp, M_ERR, "064|Recovery file buffer overrun");
431 * Format the message. (Yes, I know it's silly.)
432 * Requires that the message end in a <newline>.
435 for (t1 = buf; len > 0; len -= t2 - t1, t1 = t2) {
436 /* Check for a short length. */
437 if (len <= FMTCOLS) {
442 /* Check for a required <newline>. */
443 t2 = strchr(t1, '\n');
444 if (t2 - t1 <= FMTCOLS)
447 /* Find the closest space, if any. */
448 for (t3 = t2; t2 > t1; --t2)
450 if (t2 - t1 <= FMTCOLS)
456 /* t2 points to the last character to display. */
459 /* t2 points one after the last character to display. */
460 if (write(fd, t1, t2 - t1) != t2 - t1)
465 rcv_email(sp, mpath);
467 werr: msgq(sp, M_SYSERR, "065|Recovery file");
482 * never exactly the same
483 * just like a snowflake
486 * List the files that can be recovered by this user.
488 * PUBLIC: int rcv_list __P((SCR *));
499 char *p, *t, file[MAXPATHLEN], path[MAXPATHLEN];
501 /* Open the recovery directory for reading. */
502 if (opts_empty(sp, O_RECDIR, 0))
504 p = O_STR(sp, O_RECDIR);
505 if (chdir(p) || (dirp = opendir(".")) == NULL) {
506 msgq_str(sp, M_SYSERR, p, "recdir: %s");
510 /* Read the directory. */
511 for (found = 0; (dp = readdir(dirp)) != NULL;) {
512 if (strncmp(dp->d_name, "recover.", 8))
516 * If it's readable, it's recoverable.
519 * Should be "r", we don't want to write the file. However,
520 * if we're using fcntl(2), there's no way to lock a file
521 * descriptor that's not open for writing.
523 if ((fp = fopen(dp->d_name, "r+")) == NULL)
526 switch (file_lock(sp, NULL, NULL, fileno(fp), 1)) {
530 * Assume that a lock can't be acquired, but that we
531 * should permit recovery anyway. If this is wrong,
532 * and someone else is using the file, we're going to
539 /* If it's locked, it's live. */
544 /* Check the headers. */
545 if (fgets(file, sizeof(file), fp) == NULL ||
546 strncmp(file, VI_FHEADER, sizeof(VI_FHEADER) - 1) ||
547 (p = strchr(file, '\n')) == NULL ||
548 fgets(path, sizeof(path), fp) == NULL ||
549 strncmp(path, VI_PHEADER, sizeof(VI_PHEADER) - 1) ||
550 (t = strchr(path, '\n')) == NULL) {
551 msgq_str(sp, M_ERR, dp->d_name,
552 "066|%s: malformed recovery file");
558 * If the file doesn't exist, it's an orphaned recovery file,
562 * This can occur if the backup file was deleted and we crashed
563 * before deleting the email file.
566 if (stat(path + sizeof(VI_PHEADER) - 1, &sb) &&
568 (void)unlink(dp->d_name);
572 /* Get the last modification time and display. */
573 (void)fstat(fileno(fp), &sb);
574 (void)printf("%.24s: %s\n",
575 ctime(&sb.st_mtime), file + sizeof(VI_FHEADER) - 1);
578 /* Close, discarding lock. */
579 next: (void)fclose(fp);
582 (void)printf("vi: no files to recover.\n");
583 (void)closedir(dirp);
589 * Start a recovered file as the file to edit.
591 * PUBLIC: int rcv_read __P((SCR *, FREF *));
603 int fd, found, locked, requested, sv_fd;
604 char *name, *p, *t, *rp, *recp, *pathp;
605 char file[MAXPATHLEN], path[MAXPATHLEN], recpath[MAXPATHLEN];
607 if (opts_empty(sp, O_RECDIR, 0))
609 rp = O_STR(sp, O_RECDIR);
610 if ((dirp = opendir(rp)) == NULL) {
611 msgq_str(sp, M_ERR, rp, "%s");
619 for (found = requested = 0; (dp = readdir(dirp)) != NULL;) {
620 if (strncmp(dp->d_name, "recover.", 8))
622 (void)snprintf(recpath,
623 sizeof(recpath), "%s/%s", rp, dp->d_name);
626 * If it's readable, it's recoverable. It would be very
627 * nice to use stdio(3), but, we can't because that would
628 * require closing and then reopening the file so that we
629 * could have a lock and still close the FP. Another tip
630 * of the hat to fcntl(2).
633 * Should be O_RDONLY, we don't want to write it. However,
634 * if we're using fcntl(2), there's no way to lock a file
635 * descriptor that's not open for writing.
637 if ((fd = open(recpath, O_RDWR, 0)) == -1)
640 switch (file_lock(sp, NULL, NULL, fd, 1)) {
644 * Assume that a lock can't be acquired, but that we
645 * should permit recovery anyway. If this is wrong,
646 * and someone else is using the file, we're going to
655 /* If it's locked, it's live. */
660 /* Check the headers. */
661 if (rcv_gets(file, sizeof(file), fd) == NULL ||
662 strncmp(file, VI_FHEADER, sizeof(VI_FHEADER) - 1) ||
663 (p = strchr(file, '\n')) == NULL ||
664 rcv_gets(path, sizeof(path), fd) == NULL ||
665 strncmp(path, VI_PHEADER, sizeof(VI_PHEADER) - 1) ||
666 (t = strchr(path, '\n')) == NULL) {
667 msgq_str(sp, M_ERR, recpath,
668 "067|%s: malformed recovery file");
675 * If the file doesn't exist, it's an orphaned recovery file,
679 * This can occur if the backup file was deleted and we crashed
680 * before deleting the email file.
683 if (stat(path + sizeof(VI_PHEADER) - 1, &sb) &&
685 (void)unlink(dp->d_name);
689 /* Check the file name. */
690 if (strcmp(file + sizeof(VI_FHEADER) - 1, name))
696 * If we've found more than one, take the most recent.
699 * Since we're using st_mtime, for portability reasons,
700 * we only get a single second granularity, instead of
703 (void)fstat(fd, &sb);
704 if (recp == NULL || rec_mtime < sb.st_mtime) {
707 if ((recp = strdup(recpath)) == NULL) {
708 msgq(sp, M_SYSERR, NULL);
712 if ((pathp = strdup(path)) == NULL) {
713 msgq(sp, M_SYSERR, NULL);
723 rec_mtime = sb.st_mtime;
728 next: (void)close(fd);
730 (void)closedir(dirp);
733 msgq_str(sp, M_INFO, name,
734 "068|No files named %s, readable by you, to recover");
740 "069|There are older versions of this file for you to recover");
741 if (found > requested)
743 "070|There are other files for you to recover");
747 * Create the FREF structure, start the btree file.
750 * file_init() is going to set ep->rcv_path.
752 if (file_init(sp, frp, pathp + sizeof(VI_PHEADER) - 1, 0)) {
760 * We keep an open lock on the file so that the recover option can
761 * distinguish between files that are live and those that need to
762 * be recovered. The lock is already acquired, just copy it.
765 ep->rcv_mpath = recp;
768 F_SET(frp, FR_UNLOCKED);
770 /* We believe the file is recoverable. */
777 * Copy a recovery file.
780 rcv_copy(sp, wfd, fname)
785 int nr, nw, off, rfd;
788 if ((rfd = open(fname, O_RDONLY, 0)) == -1)
790 while ((nr = read(rfd, buf, sizeof(buf))) > 0)
791 for (off = 0; nr; nr -= nw, off += nw)
792 if ((nw = write(wfd, buf + off, nr)) < 0)
797 err: msgq_str(sp, M_SYSERR, fname, "%s");
803 * Fgets(3) for a file descriptor.
806 rcv_gets(buf, len, fd)
814 if ((nr = read(fd, buf, len - 1)) == -1)
816 if ((p = strchr(buf, '\n')) == NULL)
818 (void)lseek(fd, (off_t)((p - buf) + 1), SEEK_SET);
824 * Paranoid make temporary file routine.
827 rcv_mktemp(sp, path, dname, perms)
836 * We expect mkstemp(3) to set the permissions correctly. On
837 * historic System V systems, mkstemp didn't. Do it here, on
841 * The variable perms should really be a mode_t, and it would
842 * be nice to use fchmod(2) instead of chmod(2), here.
844 if ((fd = mkstemp(path)) == -1)
845 msgq_str(sp, M_SYSERR, dname, "%s");
847 (void)chmod(path, perms);
861 char buf[MAXPATHLEN * 2 + 20];
863 if (_PATH_SENDMAIL[0] != '/' || stat(_PATH_SENDMAIL, &sb))
864 msgq_str(sp, M_SYSERR,
865 _PATH_SENDMAIL, "071|not sending email: %s");
869 * If you need to port this to a system that doesn't have
870 * sendmail, the -t flag causes sendmail to read the message
871 * for the recipients instead of specifying them some other
874 (void)snprintf(buf, sizeof(buf),
875 "%s -t < %s", _PATH_SENDMAIL, fname);