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[] = "$Id: recover.c,v 11.2 2012/10/09 08:06:58 zy Exp $";
16 #include <sys/types.h>
17 #include <sys/queue.h>
21 * We include <sys/file.h>, because the open #defines were found there
22 * on historical systems. We also include <fcntl.h> because the open(2)
23 * #defines are found there on newer systems.
27 #include <bitstring.h>
33 #include <netinet/in.h> /* Required by resolv.h. */
41 #include "../ex/version.h"
43 #include "pathnames.h"
48 * The basic scheme is as follows. In the EXF structure, we maintain full
49 * paths of a b+tree file and a mail recovery file. The former is the file
50 * used as backing store by the DB package. The latter is the file that
51 * contains an email message to be sent to the user if we crash. The two
52 * simple states of recovery are:
54 * + first starting the edit session:
55 * the b+tree file exists and is mode 700, the mail recovery
57 * + after the file has been modified:
58 * the b+tree file exists and is mode 600, the mail recovery
59 * file exists, and is exclusively locked.
61 * In the EXF structure we maintain a file descriptor that is the locked
62 * file descriptor for the mail recovery file.
64 * To find out if a recovery file/backing file pair are in use, try to get
65 * a lock on the recovery file.
67 * To find out if a backing file can be deleted at boot time, check for an
68 * owner execute bit. (Yes, I know it's ugly, but it's either that or put
69 * special stuff into the backing file itself, or correlate the files at
70 * boot time, neither of which looks like fun.) Note also that there's a
71 * window between when the file is created and the X bit is set. It's small,
72 * but it's there. To fix the window, check for 0 length files as well.
74 * To find out if a file can be recovered, check the F_RCV_ON bit. Note,
75 * this DOES NOT mean that any initialization has been done, only that we
76 * haven't yet failed at setting up or doing recovery.
78 * To preserve a recovery file/backing file pair, set the F_RCV_NORM bit.
79 * If that bit is not set when ending a file session:
80 * If the EXF structure paths (rcv_path and rcv_mpath) are not NULL,
81 * they are unlink(2)'d, and free(3)'d.
82 * If the EXF file descriptor (rcv_fd) is not -1, it is closed.
84 * The backing b+tree file is set up when a file is first edited, so that
85 * the DB package can use it for on-disk caching and/or to snapshot the
86 * file. When the file is first modified, the mail recovery file is created,
87 * the backing file permissions are updated, the file is sync(2)'d to disk,
88 * and the timer is started. Then, at RCV_PERIOD second intervals, the
89 * b+tree file is synced to disk. RCV_PERIOD is measured using SIGALRM, which
90 * means that the data structures (SCR, EXF, the underlying tree structures)
91 * must be consistent when the signal arrives.
93 * The recovery mail file contains normal mail headers, with two additional
95 * X-vi-data: <file|path>;<base64 encoded path>
97 * MIME headers; the folding character is limited to ' '.
99 * Btree files are named "vi.XXXXXX" and recovery files are named
103 #define VI_DHEADER "X-vi-data:"
105 static int rcv_copy __P((SCR *, int, char *));
106 static void rcv_email __P((SCR *, char *));
107 static int rcv_mailfile __P((SCR *, int, char *));
108 static int rcv_mktemp __P((SCR *, char *, char *));
109 static int rcv_dlnwrite __P((SCR *, const char *, const char *, FILE *));
110 static int rcv_dlnread __P((SCR *, char **, char **, FILE *));
114 * Build a file name that will be used as the recovery file.
116 * PUBLIC: int rcv_tmp __P((SCR *, EXF *, char *));
130 * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER.
133 * If the recovery directory doesn't exist, try and create it. As
134 * the recovery files are themselves protected from reading/writing
135 * by other than the owner, the worst that can happen is that a user
136 * would have permission to remove other user's recovery files. If
137 * the sticky bit has the BSD semantics, that too will be impossible.
139 if (opts_empty(sp, O_RECDIR, 0))
141 dp = O_STR(sp, O_RECDIR);
143 if (errno != ENOENT || mkdir(dp, 0)) {
144 msgq(sp, M_SYSERR, "%s", dp);
147 (void)chmod(dp, S_IRWXU | S_IRWXG | S_IRWXO | S_ISVTX);
150 if ((path = join(dp, "vi.XXXXXX")) == NULL)
152 if ((fd = rcv_mktemp(sp, path, dp)) == -1) {
156 (void)fchmod(fd, S_IRWXU);
162 "056|Modifications not recoverable if the session fails");
166 /* We believe the file is recoverable. */
173 * Force the file to be snapshotted for recovery.
175 * PUBLIC: int rcv_init __P((SCR *));
185 /* Only do this once. */
186 F_CLR(ep, F_FIRSTMODIFY);
188 /* If we already know the file isn't recoverable, we're done. */
189 if (!F_ISSET(ep, F_RCV_ON))
192 /* Turn off recoverability until we figure out if this will work. */
195 /* Test if we're recovering a file, not editing one. */
196 if (ep->rcv_mpath == NULL) {
197 /* Build a file to mail to the user. */
198 if (rcv_mailfile(sp, 0, NULL))
201 /* Force a read of the entire file. */
202 if (db_last(sp, &lno))
205 /* Turn on a busy message, and sync it to backing store. */
207 "057|Copying file for recovery...", BUSY_ON);
208 if (ep->db->sync(ep->db, R_RECNOSYNC)) {
209 msgq_str(sp, M_SYSERR, ep->rcv_path,
210 "058|Preservation failed: %s");
211 sp->gp->scr_busy(sp, NULL, BUSY_OFF);
214 sp->gp->scr_busy(sp, NULL, BUSY_OFF);
217 /* Turn off the owner execute bit. */
218 (void)chmod(ep->rcv_path, S_IRUSR | S_IWUSR);
220 /* We believe the file is recoverable. */
225 "059|Modifications not recoverable if the session fails");
231 * Sync the file, optionally:
232 * flagging the backup file to be preserved
233 * snapshotting the backup file and send email to the user
234 * sending email to the user if the file was modified
235 * ending the file session
237 * PUBLIC: int rcv_sync __P((SCR *, u_int));
248 /* Make sure that there's something to recover/sync. */
250 if (ep == NULL || !F_ISSET(ep, F_RCV_ON))
253 /* Sync the file if it's been modified. */
254 if (F_ISSET(ep, F_MODIFIED)) {
256 if (ep->db->sync(ep->db, R_RECNOSYNC)) {
257 F_CLR(ep, F_RCV_ON | F_RCV_NORM);
258 msgq_str(sp, M_SYSERR,
259 ep->rcv_path, "060|File backup failed: %s");
265 /* REQUEST: don't remove backing file on exit. */
266 if (LF_ISSET(RCV_PRESERVE))
267 F_SET(ep, F_RCV_NORM);
269 /* REQUEST: send email. */
270 if (LF_ISSET(RCV_EMAIL))
271 rcv_email(sp, ep->rcv_mpath);
276 * Each time the user exec's :preserve, we have to snapshot all of
277 * the recovery information, i.e. it's like the user re-edited the
278 * file. We copy the DB(3) backing file, and then create a new mail
279 * recovery file, it's simpler than exiting and reopening all of the
282 * REQUEST: snapshot the file.
285 if (LF_ISSET(RCV_SNAPSHOT)) {
286 if (opts_empty(sp, O_RECDIR, 0))
288 dp = O_STR(sp, O_RECDIR);
289 if ((buf = join(dp, "vi.XXXXXX")) == NULL) {
290 msgq(sp, M_SYSERR, NULL);
293 if ((fd = rcv_mktemp(sp, buf, dp)) == -1) {
298 "061|Copying file for recovery...", BUSY_ON);
299 if (rcv_copy(sp, fd, ep->rcv_path) ||
300 close(fd) || rcv_mailfile(sp, 1, buf)) {
306 sp->gp->scr_busy(sp, NULL, BUSY_OFF);
312 /* REQUEST: end the file session. */
313 if (LF_ISSET(RCV_ENDSESSION) && file_end(sp, NULL, 1))
321 * Build the file to mail to the user.
337 char *dp, *p, *t, *qt, *buf, *mpath;
343 * MAXHOSTNAMELEN/HOST_NAME_MAX are deprecated. We try sysconf(3)
344 * first, then fallback to _POSIX_HOST_NAME_MAX.
347 long hostmax = sysconf(_SC_HOST_NAME_MAX);
349 hostmax = _POSIX_HOST_NAME_MAX;
352 if ((pw = getpwuid(uid = getuid())) == NULL) {
354 "062|Information on user id %u not found", uid);
358 if (opts_empty(sp, O_RECDIR, 0))
360 dp = O_STR(sp, O_RECDIR);
361 if ((mpath = join(dp, "recover.XXXXXX")) == NULL) {
362 msgq(sp, M_SYSERR, NULL);
365 if ((fd = rcv_mktemp(sp, mpath, dp)) == -1) {
369 if ((fp = fdopen(fd, "w")) == NULL) {
377 * We keep an open lock on the file so that the recover option can
378 * distinguish between files that are live and those that need to
379 * be recovered. There's an obvious window between the mkstemp call
380 * and the lock, but it's pretty small.
383 if (file_lock(sp, NULL, fd, 1) != LOCK_SUCCESS)
384 msgq(sp, M_SYSERR, "063|Unable to lock recovery file");
386 /* Save the recover file descriptor, and mail path. */
387 ep->rcv_fd = dup(fd);
388 ep->rcv_mpath = mpath;
389 cp_path = ep->rcv_path;
393 if ((p = strrchr(t, '/')) == NULL)
399 if ((st = rcv_dlnwrite(sp, "file", t, fp))) {
404 if ((st = rcv_dlnwrite(sp, "path", cp_path, fp))) {
410 MALLOC(sp, host, char *, hostmax + 1);
413 (void)gethostname(host, hostmax + 1);
415 len = fprintf(fp, "%s%s%s\n%s%s%s%s\n%s%.40s\n%s\n\n",
416 "From: root@", host, " (Nvi recovery program)",
417 "To: ", pw->pw_name, "@", host,
418 "Subject: Nvi saved the file ", p,
419 "Precedence: bulk"); /* For vacation(1). */
425 if ((qt = quote(t)) == NULL) {
427 msgq(sp, M_SYSERR, NULL);
430 len = asprintf(&buf, "%s%.24s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n\n",
431 "On ", ctime(&now), ", the user ", pw->pw_name,
432 " was editing a file named ", t, " on the machine ",
433 host, ", when it was saved for recovery. ",
434 "You can recover most, if not all, of the changes ",
435 "to this file using the -r option to ", gp->progname, ":\n\n\t",
436 gp->progname, " -r ", qt);
440 msgq(sp, M_SYSERR, NULL);
445 * Format the message. (Yes, I know it's silly.)
446 * Requires that the message end in a <newline>.
449 for (t1 = buf; len > 0; len -= t2 - t1, t1 = t2) {
450 /* Check for a short length. */
451 if (len <= FMTCOLS) {
456 /* Check for a required <newline>. */
457 t2 = strchr(t1, '\n');
458 if (t2 - t1 <= FMTCOLS)
461 /* Find the closest space, if any. */
462 for (t3 = t2; t2 > t1; --t2)
464 if (t2 - t1 <= FMTCOLS)
470 /* t2 points to the last character to display. */
473 /* t2 points one after the last character to display. */
474 if (fwrite(t1, 1, t2 - t1, fp) != t2 - t1) {
482 rcv_email(sp, mpath);
487 werr: msgq(sp, M_SYSERR, "065|Recovery file");
502 * never exactly the same
503 * just like a snowflake
506 * List the files that can be recovered by this user.
508 * PUBLIC: int rcv_list __P((SCR *));
518 char *p, *file, *path;
522 /* Open the recovery directory for reading. */
523 if (opts_empty(sp, O_RECDIR, 0))
525 p = O_STR(sp, O_RECDIR);
526 if (chdir(p) || (dirp = opendir(".")) == NULL) {
527 msgq_str(sp, M_SYSERR, p, "recdir: %s");
531 /* Read the directory. */
532 for (found = 0; (dp = readdir(dirp)) != NULL;) {
533 if (strncmp(dp->d_name, "recover.", 8))
536 /* If it's readable, it's recoverable. */
537 if ((fp = fopen(dp->d_name, "r")) == NULL)
540 switch (file_lock(sp, NULL, fileno(fp), 1)) {
544 * Assume that a lock can't be acquired, but that we
545 * should permit recovery anyway. If this is wrong,
546 * and someone else is using the file, we're going to
553 /* If it's locked, it's live. */
558 /* Check the headers. */
559 for (file = NULL, path = NULL;
560 file == NULL || path == NULL;) {
561 if ((st = rcv_dlnread(sp, &dtype, &data, fp))) {
563 msgq_str(sp, M_ERR, dp->d_name,
564 "066|%s: malformed recovery file");
569 if (!strcmp(dtype, "file"))
571 else if (!strcmp(dtype, "path"))
578 * If the file doesn't exist, it's an orphaned recovery file,
582 * This can occur if the backup file was deleted and we crashed
583 * before deleting the email file.
586 if (stat(path, &sb) &&
588 (void)unlink(dp->d_name);
592 /* Get the last modification time and display. */
593 (void)fstat(fileno(fp), &sb);
594 (void)printf("%.24s: %s\n",
595 ctime(&sb.st_mtime), file);
598 /* Close, discarding lock. */
599 next: (void)fclose(fp);
606 (void)printf("%s: No files to recover\n", sp->gp->progname);
607 (void)closedir(dirp);
613 * Start a recovered file as the file to edit.
615 * PUBLIC: int rcv_read __P((SCR *, FREF *));
627 struct timespec rec_mtim = { 0, 0 };
628 int found, locked = 0, requested, sv_fd;
629 char *name, *p, *t, *rp, *recp, *pathp;
630 char *file, *path, *recpath;
634 if (opts_empty(sp, O_RECDIR, 0))
636 rp = O_STR(sp, O_RECDIR);
637 if ((dirp = opendir(rp)) == NULL) {
638 msgq_str(sp, M_ERR, rp, "%s");
645 for (found = requested = 0; (dp = readdir(dirp)) != NULL;) {
646 if (strncmp(dp->d_name, "recover.", 8))
648 if ((recpath = join(rp, dp->d_name)) == NULL) {
649 msgq(sp, M_SYSERR, NULL);
653 /* If it's readable, it's recoverable. */
654 if ((fp = fopen(recpath, "r")) == NULL) {
659 switch (file_lock(sp, NULL, fileno(fp), 1)) {
663 * Assume that a lock can't be acquired, but that we
664 * should permit recovery anyway. If this is wrong,
665 * and someone else is using the file, we're going to
674 /* If it's locked, it's live. */
679 /* Check the headers. */
680 for (file = NULL, path = NULL;
681 file == NULL || path == NULL;) {
682 if ((st = rcv_dlnread(sp, &dtype, &data, fp))) {
684 msgq_str(sp, M_ERR, dp->d_name,
685 "067|%s: malformed recovery file");
690 if (!strcmp(dtype, "file"))
692 else if (!strcmp(dtype, "path"))
700 * If the file doesn't exist, it's an orphaned recovery file,
704 * This can occur if the backup file was deleted and we crashed
705 * before deleting the email file.
708 if (stat(path, &sb) &&
710 (void)unlink(dp->d_name);
714 /* Check the file name. */
715 if (strcmp(file, name))
720 /* If we've found more than one, take the most recent. */
721 (void)fstat(fileno(fp), &sb);
723 timespeccmp(&rec_mtim, &sb.st_mtimespec, <)) {
732 rec_mtim = sb.st_mtimespec;
735 sv_fd = dup(fileno(fp));
745 (void)closedir(dirp);
748 msgq_str(sp, M_INFO, name,
749 "068|No files named %s, readable by you, to recover");
755 "069|There are older versions of this file for you to recover");
756 if (found > requested)
758 "070|There are other files for you to recover");
762 * Create the FREF structure, start the btree file.
765 * file_init() is going to set ep->rcv_path.
767 if (file_init(sp, frp, pathp, 0)) {
776 * We keep an open lock on the file so that the recover option can
777 * distinguish between files that are live and those that need to
778 * be recovered. The lock is already acquired, just copy it.
781 ep->rcv_mpath = recp;
784 F_SET(frp, FR_UNLOCKED);
786 /* We believe the file is recoverable. */
793 * Copy a recovery file.
801 int nr, nw, off, rfd;
804 if ((rfd = open(fname, O_RDONLY, 0)) == -1)
806 while ((nr = read(rfd, buf, sizeof(buf))) > 0)
807 for (off = 0; nr; nr -= nw, off += nw)
808 if ((nw = write(wfd, buf + off, nr)) < 0)
813 err: msgq_str(sp, M_SYSERR, fname, "%s");
819 * Paranoid make temporary file routine.
829 if ((fd = mkstemp(path)) == -1)
830 msgq_str(sp, M_SYSERR, dname, "%s");
845 (void)asprintf(&buf, _PATH_SENDMAIL " -odb -t < %s", fname);
847 msgq_str(sp, M_ERR, strerror(errno),
848 "071|not sending email: %s");
857 * Encode a string into an X-vi-data line and write it.
872 dlen = strlen(dtype);
873 GET_SPACE_GOTOC(sp, bp, blen, (len + 2) / 3 * 4 + dlen + 2);
874 (void)memcpy(bp, dtype, dlen);
876 if ((xlen = b64_ntop((u_char *)src,
877 len, bp + dlen + 1, blen)) == -1)
881 /* Output as an MIME folding header. */
882 if ((plen = fprintf(fp, VI_DHEADER " %.*s\n",
883 FMTCOLS - (int)sizeof(VI_DHEADER), bp)) < 0)
885 plen -= (int)sizeof(VI_DHEADER) + 1;
886 for (p = bp, xlen -= plen; xlen > 0; xlen -= plen) {
888 if ((plen = fprintf(fp, " %.*s\n", FMTCOLS - 1, p)) < 0)
892 FREE_SPACE(sp, bp, blen);
895 err: FREE_SPACE(sp, bp, blen);
898 msgq(sp, M_SYSERR, NULL);
904 * Read an X-vi-data line and decode it.
910 char **datap, /* free *datap if != NULL after use. */
915 char *bp = NULL, *p, *src;
917 size_t len, off, dlen;
921 if (fgets(buf, sizeof(buf), fp) == NULL)
923 if (strncmp(buf, VI_DHEADER, sizeof(VI_DHEADER) - 1)) {
929 /* Fetch an MIME folding header. */
930 len = strlen(buf) - sizeof(VI_DHEADER) + 1;
931 GET_SPACE_GOTOC(sp, bp, blen, len);
932 (void)memcpy(bp, buf + sizeof(VI_DHEADER) - 1, len);
934 while ((ch = fgetc(fp)) == ' ') {
935 if (fgets(buf, sizeof(buf), fp) == NULL)
939 ADD_SPACE_GOTOC(sp, bp, blen, len);
941 (void)memcpy(p, buf, off);
944 (void)ungetc(ch, fp);
946 for (p = bp; *p == ' ' || *p == '\n'; p++);
947 if ((src = strchr(p, ';')) == NULL)
953 /* Memory looks like: "<data>\0<dtype>\0". */
954 MALLOC(sp, data, char *, dlen + len / 4 * 3 + 2);
957 if ((xlen = (b64_pton(p + dlen + 1,
958 (u_char *)data, len / 4 * 3 + 1))) == -1) {
963 dtype = data + xlen + 1;
964 (void)memcpy(dtype, p, dlen);
966 FREE_SPACE(sp, bp, blen);
971 err: FREE_SPACE(sp, bp, blen);
974 msgq(sp, M_SYSERR, NULL);