119304Speter/*- 219304Speter * Copyright (c) 1993, 1994 319304Speter * The Regents of the University of California. All rights reserved. 419304Speter * Copyright (c) 1993, 1994, 1995, 1996 519304Speter * Keith Bostic. All rights reserved. 619304Speter * 719304Speter * See the LICENSE file for redistribution information. 819304Speter */ 919304Speter 1019304Speter#include "config.h" 1119304Speter 1219304Speter#ifndef lint 1319304Speterstatic const char sccsid[] = "@(#)recover.c 10.21 (Berkeley) 9/15/96"; 1419304Speter#endif /* not lint */ 1519304Speter 1619304Speter#include <sys/param.h> 1719304Speter#include <sys/types.h> /* XXX: param.h may not have included types.h */ 1819304Speter#include <sys/queue.h> 1919304Speter#include <sys/stat.h> 2019304Speter 2119304Speter/* 2219304Speter * We include <sys/file.h>, because the open #defines were found there 2319304Speter * on historical systems. We also include <fcntl.h> because the open(2) 2419304Speter * #defines are found there on newer systems. 2519304Speter */ 2619304Speter#include <sys/file.h> 2719304Speter 2819304Speter#include <bitstring.h> 2919304Speter#include <dirent.h> 3019304Speter#include <errno.h> 3119304Speter#include <fcntl.h> 3219304Speter#include <limits.h> 3319304Speter#include <pwd.h> 3419304Speter#include <stdio.h> 3519304Speter#include <stdlib.h> 3619304Speter#include <string.h> 3719304Speter#include <time.h> 3819304Speter#include <unistd.h> 3919304Speter 4019304Speter#include "common.h" 4119304Speter#include "pathnames.h" 4219304Speter 4319304Speter/* 4419304Speter * Recovery code. 4519304Speter * 4619304Speter * The basic scheme is as follows. In the EXF structure, we maintain full 4719304Speter * paths of a b+tree file and a mail recovery file. The former is the file 4819304Speter * used as backing store by the DB package. The latter is the file that 4919304Speter * contains an email message to be sent to the user if we crash. The two 5019304Speter * simple states of recovery are: 5119304Speter * 5219304Speter * + first starting the edit session: 5319304Speter * the b+tree file exists and is mode 700, the mail recovery 5419304Speter * file doesn't exist. 5519304Speter * + after the file has been modified: 5619304Speter * the b+tree file exists and is mode 600, the mail recovery 5719304Speter * file exists, and is exclusively locked. 5819304Speter * 5919304Speter * In the EXF structure we maintain a file descriptor that is the locked 6019304Speter * file descriptor for the mail recovery file. NOTE: we sometimes have to 6119304Speter * do locking with fcntl(2). This is a problem because if you close(2) any 6219304Speter * file descriptor associated with the file, ALL of the locks go away. Be 6319304Speter * sure to remember that if you have to modify the recovery code. (It has 6419304Speter * been rhetorically asked of what the designers could have been thinking 6519304Speter * when they did that interface. The answer is simple: they weren't.) 6619304Speter * 6719304Speter * To find out if a recovery file/backing file pair are in use, try to get 6819304Speter * a lock on the recovery file. 6919304Speter * 7019304Speter * To find out if a backing file can be deleted at boot time, check for an 7119304Speter * owner execute bit. (Yes, I know it's ugly, but it's either that or put 7219304Speter * special stuff into the backing file itself, or correlate the files at 7319304Speter * boot time, neither of which looks like fun.) Note also that there's a 7419304Speter * window between when the file is created and the X bit is set. It's small, 7519304Speter * but it's there. To fix the window, check for 0 length files as well. 7619304Speter * 7719304Speter * To find out if a file can be recovered, check the F_RCV_ON bit. Note, 7819304Speter * this DOES NOT mean that any initialization has been done, only that we 7919304Speter * haven't yet failed at setting up or doing recovery. 8019304Speter * 8119304Speter * To preserve a recovery file/backing file pair, set the F_RCV_NORM bit. 8219304Speter * If that bit is not set when ending a file session: 8319304Speter * If the EXF structure paths (rcv_path and rcv_mpath) are not NULL, 8419304Speter * they are unlink(2)'d, and free(3)'d. 8519304Speter * If the EXF file descriptor (rcv_fd) is not -1, it is closed. 8619304Speter * 8719304Speter * The backing b+tree file is set up when a file is first edited, so that 8819304Speter * the DB package can use it for on-disk caching and/or to snapshot the 8919304Speter * file. When the file is first modified, the mail recovery file is created, 9019304Speter * the backing file permissions are updated, the file is sync(2)'d to disk, 9119304Speter * and the timer is started. Then, at RCV_PERIOD second intervals, the 9219304Speter * b+tree file is synced to disk. RCV_PERIOD is measured using SIGALRM, which 9319304Speter * means that the data structures (SCR, EXF, the underlying tree structures) 9419304Speter * must be consistent when the signal arrives. 9519304Speter * 9619304Speter * The recovery mail file contains normal mail headers, with two additions, 9719304Speter * which occur in THIS order, as the FIRST TWO headers: 9819304Speter * 9919304Speter * X-vi-recover-file: file_name 10019304Speter * X-vi-recover-path: recover_path 10119304Speter * 10219304Speter * Since newlines delimit the headers, this means that file names cannot have 10319304Speter * newlines in them, but that's probably okay. As these files aren't intended 10419304Speter * to be long-lived, changing their format won't be too painful. 10519304Speter * 10619304Speter * Btree files are named "vi.XXXX" and recovery files are named "recover.XXXX". 10719304Speter */ 10819304Speter 10919304Speter#define VI_FHEADER "X-vi-recover-file: " 11019304Speter#define VI_PHEADER "X-vi-recover-path: " 11119304Speter 11219304Speterstatic int rcv_copy __P((SCR *, int, char *)); 11319304Speterstatic void rcv_email __P((SCR *, char *)); 11419304Speterstatic char *rcv_gets __P((char *, size_t, int)); 11519304Speterstatic int rcv_mailfile __P((SCR *, int, char *)); 11619304Speterstatic int rcv_mktemp __P((SCR *, char *, char *, int)); 11719304Speter 11819304Speter/* 11919304Speter * rcv_tmp -- 12019304Speter * Build a file name that will be used as the recovery file. 12119304Speter * 12219304Speter * PUBLIC: int rcv_tmp __P((SCR *, EXF *, char *)); 12319304Speter */ 12419304Speterint 12519304Speterrcv_tmp(sp, ep, name) 12619304Speter SCR *sp; 12719304Speter EXF *ep; 12819304Speter char *name; 12919304Speter{ 13019304Speter struct stat sb; 13119304Speter int fd; 13219304Speter char *dp, *p, path[MAXPATHLEN]; 13319304Speter 13419304Speter /* 13519304Speter * !!! 13619304Speter * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER. 13719304Speter * 13819304Speter * 13919304Speter * If the recovery directory doesn't exist, try and create it. As 14019304Speter * the recovery files are themselves protected from reading/writing 14119304Speter * by other than the owner, the worst that can happen is that a user 14219304Speter * would have permission to remove other user's recovery files. If 14319304Speter * the sticky bit has the BSD semantics, that too will be impossible. 14419304Speter */ 14519304Speter if (opts_empty(sp, O_RECDIR, 0)) 14619304Speter goto err; 14719304Speter dp = O_STR(sp, O_RECDIR); 14819304Speter if (stat(dp, &sb)) { 14919304Speter if (errno != ENOENT || mkdir(dp, 0)) { 15019304Speter msgq(sp, M_SYSERR, "%s", dp); 15119304Speter goto err; 15219304Speter } 15319304Speter (void)chmod(dp, S_IRWXU | S_IRWXG | S_IRWXO | S_ISVTX); 15419304Speter } 15519304Speter 15619304Speter /* Newlines delimit the mail messages. */ 15719304Speter for (p = name; *p; ++p) 15819304Speter if (*p == '\n') { 15919304Speter msgq(sp, M_ERR, 16019304Speter "055|Files with newlines in the name are unrecoverable"); 16119304Speter goto err; 16219304Speter } 16319304Speter 16419304Speter (void)snprintf(path, sizeof(path), "%s/vi.XXXXXX", dp); 16519304Speter if ((fd = rcv_mktemp(sp, path, dp, S_IRWXU)) == -1) 16619304Speter goto err; 16719304Speter (void)close(fd); 16819304Speter 16919304Speter if ((ep->rcv_path = strdup(path)) == NULL) { 17019304Speter msgq(sp, M_SYSERR, NULL); 17119304Speter (void)unlink(path); 17219304Spetererr: msgq(sp, M_ERR, 17319304Speter "056|Modifications not recoverable if the session fails"); 17419304Speter return (1); 17519304Speter } 17619304Speter 17719304Speter /* We believe the file is recoverable. */ 17819304Speter F_SET(ep, F_RCV_ON); 17919304Speter return (0); 18019304Speter} 18119304Speter 18219304Speter/* 18319304Speter * rcv_init -- 18419304Speter * Force the file to be snapshotted for recovery. 18519304Speter * 18619304Speter * PUBLIC: int rcv_init __P((SCR *)); 18719304Speter */ 18819304Speterint 18919304Speterrcv_init(sp) 19019304Speter SCR *sp; 19119304Speter{ 19219304Speter EXF *ep; 19319304Speter recno_t lno; 19419304Speter 19519304Speter ep = sp->ep; 19619304Speter 19719304Speter /* Only do this once. */ 19819304Speter F_CLR(ep, F_FIRSTMODIFY); 19919304Speter 20019304Speter /* If we already know the file isn't recoverable, we're done. */ 20119304Speter if (!F_ISSET(ep, F_RCV_ON)) 20219304Speter return (0); 20319304Speter 20419304Speter /* Turn off recoverability until we figure out if this will work. */ 20519304Speter F_CLR(ep, F_RCV_ON); 20619304Speter 20719304Speter /* Test if we're recovering a file, not editing one. */ 20819304Speter if (ep->rcv_mpath == NULL) { 20919304Speter /* Build a file to mail to the user. */ 21019304Speter if (rcv_mailfile(sp, 0, NULL)) 21119304Speter goto err; 21219304Speter 21319304Speter /* Force a read of the entire file. */ 21419304Speter if (db_last(sp, &lno)) 21519304Speter goto err; 21619304Speter 21719304Speter /* Turn on a busy message, and sync it to backing store. */ 21819304Speter sp->gp->scr_busy(sp, 21919304Speter "057|Copying file for recovery...", BUSY_ON); 22019304Speter if (ep->db->sync(ep->db, R_RECNOSYNC)) { 22119304Speter msgq_str(sp, M_SYSERR, ep->rcv_path, 22219304Speter "058|Preservation failed: %s"); 22319304Speter sp->gp->scr_busy(sp, NULL, BUSY_OFF); 22419304Speter goto err; 22519304Speter } 22619304Speter sp->gp->scr_busy(sp, NULL, BUSY_OFF); 22719304Speter } 22819304Speter 22919304Speter /* Turn off the owner execute bit. */ 23019304Speter (void)chmod(ep->rcv_path, S_IRUSR | S_IWUSR); 23119304Speter 23219304Speter /* We believe the file is recoverable. */ 23319304Speter F_SET(ep, F_RCV_ON); 23419304Speter return (0); 23519304Speter 23619304Spetererr: msgq(sp, M_ERR, 23719304Speter "059|Modifications not recoverable if the session fails"); 23819304Speter return (1); 23919304Speter} 24019304Speter 24119304Speter/* 24219304Speter * rcv_sync -- 24319304Speter * Sync the file, optionally: 24419304Speter * flagging the backup file to be preserved 24519304Speter * snapshotting the backup file and send email to the user 24619304Speter * sending email to the user if the file was modified 24719304Speter * ending the file session 24819304Speter * 24919304Speter * PUBLIC: int rcv_sync __P((SCR *, u_int)); 25019304Speter */ 25119304Speterint 25219304Speterrcv_sync(sp, flags) 25319304Speter SCR *sp; 25419304Speter u_int flags; 25519304Speter{ 25619304Speter EXF *ep; 25719304Speter int fd, rval; 25819304Speter char *dp, buf[1024]; 25919304Speter 26019304Speter /* Make sure that there's something to recover/sync. */ 26119304Speter ep = sp->ep; 26219304Speter if (ep == NULL || !F_ISSET(ep, F_RCV_ON)) 26319304Speter return (0); 26419304Speter 26519304Speter /* Sync the file if it's been modified. */ 26619304Speter if (F_ISSET(ep, F_MODIFIED)) { 26719304Speter SIGBLOCK; 26819304Speter if (ep->db->sync(ep->db, R_RECNOSYNC)) { 26919304Speter F_CLR(ep, F_RCV_ON | F_RCV_NORM); 27019304Speter msgq_str(sp, M_SYSERR, 27119304Speter ep->rcv_path, "060|File backup failed: %s"); 27219304Speter SIGUNBLOCK; 27319304Speter return (1); 27419304Speter } 27519304Speter SIGUNBLOCK; 27619304Speter 27719304Speter /* REQUEST: don't remove backing file on exit. */ 27819304Speter if (LF_ISSET(RCV_PRESERVE)) 27919304Speter F_SET(ep, F_RCV_NORM); 28019304Speter 28119304Speter /* REQUEST: send email. */ 28219304Speter if (LF_ISSET(RCV_EMAIL)) 28319304Speter rcv_email(sp, ep->rcv_mpath); 28419304Speter } 28519304Speter 28619304Speter /* 28719304Speter * !!! 28819304Speter * Each time the user exec's :preserve, we have to snapshot all of 28919304Speter * the recovery information, i.e. it's like the user re-edited the 29019304Speter * file. We copy the DB(3) backing file, and then create a new mail 29119304Speter * recovery file, it's simpler than exiting and reopening all of the 29219304Speter * underlying files. 29319304Speter * 29419304Speter * REQUEST: snapshot the file. 29519304Speter */ 29619304Speter rval = 0; 29719304Speter if (LF_ISSET(RCV_SNAPSHOT)) { 29819304Speter if (opts_empty(sp, O_RECDIR, 0)) 29919304Speter goto err; 30019304Speter dp = O_STR(sp, O_RECDIR); 30119304Speter (void)snprintf(buf, sizeof(buf), "%s/vi.XXXXXX", dp); 30219304Speter if ((fd = rcv_mktemp(sp, buf, dp, S_IRUSR | S_IWUSR)) == -1) 30319304Speter goto err; 30419304Speter sp->gp->scr_busy(sp, 30519304Speter "061|Copying file for recovery...", BUSY_ON); 30619304Speter if (rcv_copy(sp, fd, ep->rcv_path) || 30719304Speter close(fd) || rcv_mailfile(sp, 1, buf)) { 30819304Speter (void)unlink(buf); 30919304Speter (void)close(fd); 31019304Speter rval = 1; 31119304Speter } 31219304Speter sp->gp->scr_busy(sp, NULL, BUSY_OFF); 31319304Speter } 31419304Speter if (0) { 31519304Spetererr: rval = 1; 31619304Speter } 31719304Speter 31819304Speter /* REQUEST: end the file session. */ 31919304Speter if (LF_ISSET(RCV_ENDSESSION) && file_end(sp, NULL, 1)) 32019304Speter rval = 1; 32119304Speter 32219304Speter return (rval); 32319304Speter} 32419304Speter 32519304Speter/* 32619304Speter * rcv_mailfile -- 32719304Speter * Build the file to mail to the user. 32819304Speter */ 32919304Speterstatic int 33019304Speterrcv_mailfile(sp, issync, cp_path) 33119304Speter SCR *sp; 33219304Speter int issync; 33319304Speter char *cp_path; 33419304Speter{ 33519304Speter EXF *ep; 33619304Speter GS *gp; 33719304Speter struct passwd *pw; 33819304Speter size_t len; 33919304Speter time_t now; 34019304Speter uid_t uid; 34119304Speter int fd; 34219304Speter char *dp, *p, *t, buf[4096], mpath[MAXPATHLEN]; 34319304Speter char *t1, *t2, *t3; 34419304Speter 34519304Speter /* 34619304Speter * XXX 34719304Speter * MAXHOSTNAMELEN is in various places on various systems, including 34819304Speter * <netdb.h> and <sys/socket.h>. If not found, use a large default. 34919304Speter */ 35019304Speter#ifndef MAXHOSTNAMELEN 35119304Speter#define MAXHOSTNAMELEN 1024 35219304Speter#endif 35319304Speter char host[MAXHOSTNAMELEN]; 35419304Speter 35519304Speter gp = sp->gp; 35619304Speter if ((pw = getpwuid(uid = getuid())) == NULL) { 35719304Speter msgq(sp, M_ERR, 35819304Speter "062|Information on user id %u not found", uid); 35919304Speter return (1); 36019304Speter } 36119304Speter 36219304Speter if (opts_empty(sp, O_RECDIR, 0)) 36319304Speter return (1); 36419304Speter dp = O_STR(sp, O_RECDIR); 36519304Speter (void)snprintf(mpath, sizeof(mpath), "%s/recover.XXXXXX", dp); 36619304Speter if ((fd = rcv_mktemp(sp, mpath, dp, S_IRUSR | S_IWUSR)) == -1) 36719304Speter return (1); 36819304Speter 36919304Speter /* 37019304Speter * XXX 37119304Speter * We keep an open lock on the file so that the recover option can 37219304Speter * distinguish between files that are live and those that need to 37319304Speter * be recovered. There's an obvious window between the mkstemp call 37419304Speter * and the lock, but it's pretty small. 37519304Speter */ 37619304Speter ep = sp->ep; 37719304Speter if (file_lock(sp, NULL, NULL, fd, 1) != LOCK_SUCCESS) 37819304Speter msgq(sp, M_SYSERR, "063|Unable to lock recovery file"); 37919304Speter if (!issync) { 38019304Speter /* Save the recover file descriptor, and mail path. */ 38119304Speter ep->rcv_fd = fd; 38219304Speter if ((ep->rcv_mpath = strdup(mpath)) == NULL) { 38319304Speter msgq(sp, M_SYSERR, NULL); 38419304Speter goto err; 38519304Speter } 38619304Speter cp_path = ep->rcv_path; 38719304Speter } 38819304Speter 38919304Speter /* 39019304Speter * XXX 39119304Speter * We can't use stdio(3) here. The problem is that we may be using 39219304Speter * fcntl(2), so if ANY file descriptor into the file is closed, the 39319304Speter * lock is lost. So, we could never close the FILE *, even if we 39419304Speter * dup'd the fd first. 39519304Speter */ 39619304Speter t = sp->frp->name; 39719304Speter if ((p = strrchr(t, '/')) == NULL) 39819304Speter p = t; 39919304Speter else 40019304Speter ++p; 40119304Speter (void)time(&now); 40219304Speter (void)gethostname(host, sizeof(host)); 40319304Speter len = snprintf(buf, sizeof(buf), 40419304Speter "%s%s\n%s%s\n%s\n%s\n%s%s\n%s%s\n%s\n\n", 40519304Speter VI_FHEADER, t, /* Non-standard. */ 40619304Speter VI_PHEADER, cp_path, /* Non-standard. */ 40719304Speter "Reply-To: root", 40819304Speter "From: root (Nvi recovery program)", 40919304Speter "To: ", pw->pw_name, 41019304Speter "Subject: Nvi saved the file ", p, 41119304Speter "Precedence: bulk"); /* For vacation(1). */ 41219304Speter if (len > sizeof(buf) - 1) 41319304Speter goto lerr; 41419304Speter if (write(fd, buf, len) != len) 41519304Speter goto werr; 41619304Speter 41719304Speter len = snprintf(buf, sizeof(buf), 41819304Speter "%s%.24s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n\n", 41919304Speter "On ", ctime(&now), ", the user ", pw->pw_name, 42019304Speter " was editing a file named ", t, " on the machine ", 42119304Speter host, ", when it was saved for recovery. ", 42219304Speter "You can recover most, if not all, of the changes ", 42319304Speter "to this file using the -r option to ", gp->progname, ":\n\n\t", 42419304Speter gp->progname, " -r ", t); 42519304Speter if (len > sizeof(buf) - 1) { 42619304Speterlerr: msgq(sp, M_ERR, "064|Recovery file buffer overrun"); 42719304Speter goto err; 42819304Speter } 42919304Speter 43019304Speter /* 43119304Speter * Format the message. (Yes, I know it's silly.) 43219304Speter * Requires that the message end in a <newline>. 43319304Speter */ 43419304Speter#define FMTCOLS 60 43519304Speter for (t1 = buf; len > 0; len -= t2 - t1, t1 = t2) { 43619304Speter /* Check for a short length. */ 43719304Speter if (len <= FMTCOLS) { 43819304Speter t2 = t1 + (len - 1); 43919304Speter goto wout; 44019304Speter } 44119304Speter 44219304Speter /* Check for a required <newline>. */ 44319304Speter t2 = strchr(t1, '\n'); 44419304Speter if (t2 - t1 <= FMTCOLS) 44519304Speter goto wout; 44619304Speter 44719304Speter /* Find the closest space, if any. */ 44819304Speter for (t3 = t2; t2 > t1; --t2) 44919304Speter if (*t2 == ' ') { 45019304Speter if (t2 - t1 <= FMTCOLS) 45119304Speter goto wout; 45219304Speter t3 = t2; 45319304Speter } 45419304Speter t2 = t3; 45519304Speter 45619304Speter /* t2 points to the last character to display. */ 45719304Speterwout: *t2++ = '\n'; 45819304Speter 45919304Speter /* t2 points one after the last character to display. */ 46019304Speter if (write(fd, t1, t2 - t1) != t2 - t1) 46119304Speter goto werr; 46219304Speter } 46319304Speter 46419304Speter if (issync) { 46519304Speter rcv_email(sp, mpath); 46619304Speter if (close(fd)) { 46719304Speterwerr: msgq(sp, M_SYSERR, "065|Recovery file"); 46819304Speter goto err; 46919304Speter } 47019304Speter } 47119304Speter return (0); 47219304Speter 47319304Spetererr: if (!issync) 47419304Speter ep->rcv_fd = -1; 47519304Speter if (fd != -1) 47619304Speter (void)close(fd); 47719304Speter return (1); 47819304Speter} 47919304Speter 48019304Speter/* 48119304Speter * people making love 48219304Speter * never exactly the same 48319304Speter * just like a snowflake 48419304Speter * 48519304Speter * rcv_list -- 48619304Speter * List the files that can be recovered by this user. 48719304Speter * 48819304Speter * PUBLIC: int rcv_list __P((SCR *)); 48919304Speter */ 49019304Speterint 49119304Speterrcv_list(sp) 49219304Speter SCR *sp; 49319304Speter{ 49419304Speter struct dirent *dp; 49519304Speter struct stat sb; 49619304Speter DIR *dirp; 49719304Speter FILE *fp; 49819304Speter int found; 49919304Speter char *p, *t, file[MAXPATHLEN], path[MAXPATHLEN]; 50019304Speter 50119304Speter /* Open the recovery directory for reading. */ 50219304Speter if (opts_empty(sp, O_RECDIR, 0)) 50319304Speter return (1); 50419304Speter p = O_STR(sp, O_RECDIR); 50519304Speter if (chdir(p) || (dirp = opendir(".")) == NULL) { 50619304Speter msgq_str(sp, M_SYSERR, p, "recdir: %s"); 50719304Speter return (1); 50819304Speter } 50919304Speter 51019304Speter /* Read the directory. */ 51119304Speter for (found = 0; (dp = readdir(dirp)) != NULL;) { 51219304Speter if (strncmp(dp->d_name, "recover.", 8)) 51319304Speter continue; 51419304Speter 51519304Speter /* 51619304Speter * If it's readable, it's recoverable. 51719304Speter * 51819304Speter * XXX 51919304Speter * Should be "r", we don't want to write the file. However, 52019304Speter * if we're using fcntl(2), there's no way to lock a file 52119304Speter * descriptor that's not open for writing. 52219304Speter */ 52319304Speter if ((fp = fopen(dp->d_name, "r+")) == NULL) 52419304Speter continue; 52519304Speter 52619304Speter switch (file_lock(sp, NULL, NULL, fileno(fp), 1)) { 52719304Speter case LOCK_FAILED: 52819304Speter /* 52919304Speter * XXX 53019304Speter * Assume that a lock can't be acquired, but that we 53119304Speter * should permit recovery anyway. If this is wrong, 53219304Speter * and someone else is using the file, we're going to 53319304Speter * die horribly. 53419304Speter */ 53519304Speter break; 53619304Speter case LOCK_SUCCESS: 53719304Speter break; 53819304Speter case LOCK_UNAVAIL: 53919304Speter /* If it's locked, it's live. */ 54019304Speter (void)fclose(fp); 54119304Speter continue; 54219304Speter } 54319304Speter 54419304Speter /* Check the headers. */ 54519304Speter if (fgets(file, sizeof(file), fp) == NULL || 54619304Speter strncmp(file, VI_FHEADER, sizeof(VI_FHEADER) - 1) || 54719304Speter (p = strchr(file, '\n')) == NULL || 54819304Speter fgets(path, sizeof(path), fp) == NULL || 54919304Speter strncmp(path, VI_PHEADER, sizeof(VI_PHEADER) - 1) || 55019304Speter (t = strchr(path, '\n')) == NULL) { 55119304Speter msgq_str(sp, M_ERR, dp->d_name, 55219304Speter "066|%s: malformed recovery file"); 55319304Speter goto next; 55419304Speter } 55519304Speter *p = *t = '\0'; 55619304Speter 55719304Speter /* 55819304Speter * If the file doesn't exist, it's an orphaned recovery file, 55919304Speter * toss it. 56019304Speter * 56119304Speter * XXX 56219304Speter * This can occur if the backup file was deleted and we crashed 56319304Speter * before deleting the email file. 56419304Speter */ 56519304Speter errno = 0; 56619304Speter if (stat(path + sizeof(VI_PHEADER) - 1, &sb) && 56719304Speter errno == ENOENT) { 56819304Speter (void)unlink(dp->d_name); 56919304Speter goto next; 57019304Speter } 57119304Speter 57219304Speter /* Get the last modification time and display. */ 57319304Speter (void)fstat(fileno(fp), &sb); 57419304Speter (void)printf("%.24s: %s\n", 57519304Speter ctime(&sb.st_mtime), file + sizeof(VI_FHEADER) - 1); 57619304Speter found = 1; 57719304Speter 57819304Speter /* Close, discarding lock. */ 57919304Speternext: (void)fclose(fp); 58019304Speter } 58119304Speter if (found == 0) 58219304Speter (void)printf("vi: no files to recover.\n"); 58319304Speter (void)closedir(dirp); 58419304Speter return (0); 58519304Speter} 58619304Speter 58719304Speter/* 58819304Speter * rcv_read -- 58919304Speter * Start a recovered file as the file to edit. 59019304Speter * 59119304Speter * PUBLIC: int rcv_read __P((SCR *, FREF *)); 59219304Speter */ 59319304Speterint 59419304Speterrcv_read(sp, frp) 59519304Speter SCR *sp; 59619304Speter FREF *frp; 59719304Speter{ 59819304Speter struct dirent *dp; 59919304Speter struct stat sb; 60019304Speter DIR *dirp; 60119304Speter EXF *ep; 60219304Speter time_t rec_mtime; 60319304Speter int fd, found, locked, requested, sv_fd; 60419304Speter char *name, *p, *t, *rp, *recp, *pathp; 60519304Speter char file[MAXPATHLEN], path[MAXPATHLEN], recpath[MAXPATHLEN]; 60619304Speter 60719304Speter if (opts_empty(sp, O_RECDIR, 0)) 60819304Speter return (1); 60919304Speter rp = O_STR(sp, O_RECDIR); 61019304Speter if ((dirp = opendir(rp)) == NULL) { 61119304Speter msgq_str(sp, M_ERR, rp, "%s"); 61219304Speter return (1); 61319304Speter } 61419304Speter 61519304Speter name = frp->name; 61619304Speter sv_fd = -1; 61719304Speter rec_mtime = 0; 61819304Speter recp = pathp = NULL; 61919304Speter for (found = requested = 0; (dp = readdir(dirp)) != NULL;) { 62019304Speter if (strncmp(dp->d_name, "recover.", 8)) 62119304Speter continue; 62219304Speter (void)snprintf(recpath, 62319304Speter sizeof(recpath), "%s/%s", rp, dp->d_name); 62419304Speter 62519304Speter /* 62619304Speter * If it's readable, it's recoverable. It would be very 62719304Speter * nice to use stdio(3), but, we can't because that would 62819304Speter * require closing and then reopening the file so that we 62919304Speter * could have a lock and still close the FP. Another tip 63019304Speter * of the hat to fcntl(2). 63119304Speter * 63219304Speter * XXX 63319304Speter * Should be O_RDONLY, we don't want to write it. However, 63419304Speter * if we're using fcntl(2), there's no way to lock a file 63519304Speter * descriptor that's not open for writing. 63619304Speter */ 63719304Speter if ((fd = open(recpath, O_RDWR, 0)) == -1) 63819304Speter continue; 63919304Speter 64019304Speter switch (file_lock(sp, NULL, NULL, fd, 1)) { 64119304Speter case LOCK_FAILED: 64219304Speter /* 64319304Speter * XXX 64419304Speter * Assume that a lock can't be acquired, but that we 64519304Speter * should permit recovery anyway. If this is wrong, 64619304Speter * and someone else is using the file, we're going to 64719304Speter * die horribly. 64819304Speter */ 64919304Speter locked = 0; 65019304Speter break; 65119304Speter case LOCK_SUCCESS: 65219304Speter locked = 1; 65319304Speter break; 65419304Speter case LOCK_UNAVAIL: 65519304Speter /* If it's locked, it's live. */ 65619304Speter (void)close(fd); 65719304Speter continue; 65819304Speter } 65919304Speter 66019304Speter /* Check the headers. */ 66119304Speter if (rcv_gets(file, sizeof(file), fd) == NULL || 66219304Speter strncmp(file, VI_FHEADER, sizeof(VI_FHEADER) - 1) || 66319304Speter (p = strchr(file, '\n')) == NULL || 66419304Speter rcv_gets(path, sizeof(path), fd) == NULL || 66519304Speter strncmp(path, VI_PHEADER, sizeof(VI_PHEADER) - 1) || 66619304Speter (t = strchr(path, '\n')) == NULL) { 66719304Speter msgq_str(sp, M_ERR, recpath, 66819304Speter "067|%s: malformed recovery file"); 66919304Speter goto next; 67019304Speter } 67119304Speter *p = *t = '\0'; 67219304Speter ++found; 67319304Speter 67419304Speter /* 67519304Speter * If the file doesn't exist, it's an orphaned recovery file, 67619304Speter * toss it. 67719304Speter * 67819304Speter * XXX 67919304Speter * This can occur if the backup file was deleted and we crashed 68019304Speter * before deleting the email file. 68119304Speter */ 68219304Speter errno = 0; 68319304Speter if (stat(path + sizeof(VI_PHEADER) - 1, &sb) && 68419304Speter errno == ENOENT) { 68519304Speter (void)unlink(dp->d_name); 68619304Speter goto next; 68719304Speter } 68819304Speter 68919304Speter /* Check the file name. */ 69019304Speter if (strcmp(file + sizeof(VI_FHEADER) - 1, name)) 69119304Speter goto next; 69219304Speter 69319304Speter ++requested; 69419304Speter 69519304Speter /* 69619304Speter * If we've found more than one, take the most recent. 69719304Speter * 69819304Speter * XXX 69919304Speter * Since we're using st_mtime, for portability reasons, 70019304Speter * we only get a single second granularity, instead of 70119304Speter * getting it right. 70219304Speter */ 70319304Speter (void)fstat(fd, &sb); 70419304Speter if (recp == NULL || rec_mtime < sb.st_mtime) { 70519304Speter p = recp; 70619304Speter t = pathp; 70719304Speter if ((recp = strdup(recpath)) == NULL) { 70819304Speter msgq(sp, M_SYSERR, NULL); 70919304Speter recp = p; 71019304Speter goto next; 71119304Speter } 71219304Speter if ((pathp = strdup(path)) == NULL) { 71319304Speter msgq(sp, M_SYSERR, NULL); 71419304Speter free(recp); 71519304Speter recp = p; 71619304Speter pathp = t; 71719304Speter goto next; 71819304Speter } 71919304Speter if (p != NULL) { 72019304Speter free(p); 72119304Speter free(t); 72219304Speter } 72319304Speter rec_mtime = sb.st_mtime; 72419304Speter if (sv_fd != -1) 72519304Speter (void)close(sv_fd); 72619304Speter sv_fd = fd; 72719304Speter } else 72819304Speternext: (void)close(fd); 72919304Speter } 73019304Speter (void)closedir(dirp); 73119304Speter 73219304Speter if (recp == NULL) { 73319304Speter msgq_str(sp, M_INFO, name, 73419304Speter "068|No files named %s, readable by you, to recover"); 73519304Speter return (1); 73619304Speter } 73719304Speter if (found) { 73819304Speter if (requested > 1) 73919304Speter msgq(sp, M_INFO, 74019304Speter "069|There are older versions of this file for you to recover"); 74119304Speter if (found > requested) 74219304Speter msgq(sp, M_INFO, 74319304Speter "070|There are other files for you to recover"); 74419304Speter } 74519304Speter 74619304Speter /* 74719304Speter * Create the FREF structure, start the btree file. 74819304Speter * 74919304Speter * XXX 75019304Speter * file_init() is going to set ep->rcv_path. 75119304Speter */ 75219304Speter if (file_init(sp, frp, pathp + sizeof(VI_PHEADER) - 1, 0)) { 75319304Speter free(recp); 75419304Speter free(pathp); 75519304Speter (void)close(sv_fd); 75619304Speter return (1); 75719304Speter } 75819304Speter 75919304Speter /* 76019304Speter * We keep an open lock on the file so that the recover option can 76119304Speter * distinguish between files that are live and those that need to 76219304Speter * be recovered. The lock is already acquired, just copy it. 76319304Speter */ 76419304Speter ep = sp->ep; 76519304Speter ep->rcv_mpath = recp; 76619304Speter ep->rcv_fd = sv_fd; 76719304Speter if (!locked) 76819304Speter F_SET(frp, FR_UNLOCKED); 76919304Speter 77019304Speter /* We believe the file is recoverable. */ 77119304Speter F_SET(ep, F_RCV_ON); 77219304Speter return (0); 77319304Speter} 77419304Speter 77519304Speter/* 77619304Speter * rcv_copy -- 77719304Speter * Copy a recovery file. 77819304Speter */ 77919304Speterstatic int 78019304Speterrcv_copy(sp, wfd, fname) 78119304Speter SCR *sp; 78219304Speter int wfd; 78319304Speter char *fname; 78419304Speter{ 78519304Speter int nr, nw, off, rfd; 78619304Speter char buf[8 * 1024]; 78719304Speter 78819304Speter if ((rfd = open(fname, O_RDONLY, 0)) == -1) 78919304Speter goto err; 79019304Speter while ((nr = read(rfd, buf, sizeof(buf))) > 0) 79119304Speter for (off = 0; nr; nr -= nw, off += nw) 79219304Speter if ((nw = write(wfd, buf + off, nr)) < 0) 79319304Speter goto err; 79419304Speter if (nr == 0) 79519304Speter return (0); 79619304Speter 79719304Spetererr: msgq_str(sp, M_SYSERR, fname, "%s"); 79819304Speter return (1); 79919304Speter} 80019304Speter 80119304Speter/* 80219304Speter * rcv_gets -- 80319304Speter * Fgets(3) for a file descriptor. 80419304Speter */ 80519304Speterstatic char * 80619304Speterrcv_gets(buf, len, fd) 80719304Speter char *buf; 80819304Speter size_t len; 80919304Speter int fd; 81019304Speter{ 81119304Speter int nr; 81219304Speter char *p; 81319304Speter 81419304Speter if ((nr = read(fd, buf, len - 1)) == -1) 81519304Speter return (NULL); 81619304Speter if ((p = strchr(buf, '\n')) == NULL) 81719304Speter return (NULL); 81819304Speter (void)lseek(fd, (off_t)((p - buf) + 1), SEEK_SET); 81919304Speter return (buf); 82019304Speter} 82119304Speter 82219304Speter/* 82319304Speter * rcv_mktemp -- 82419304Speter * Paranoid make temporary file routine. 82519304Speter */ 82619304Speterstatic int 82719304Speterrcv_mktemp(sp, path, dname, perms) 82819304Speter SCR *sp; 82919304Speter char *path, *dname; 83019304Speter int perms; 83119304Speter{ 83219304Speter int fd; 83319304Speter 83419304Speter /* 83519304Speter * !!! 83619304Speter * We expect mkstemp(3) to set the permissions correctly. On 83719304Speter * historic System V systems, mkstemp didn't. Do it here, on 83819304Speter * GP's. 83919304Speter * 84019304Speter * XXX 84119304Speter * The variable perms should really be a mode_t, and it would 84219304Speter * be nice to use fchmod(2) instead of chmod(2), here. 84319304Speter */ 84419304Speter if ((fd = mkstemp(path)) == -1) 84519304Speter msgq_str(sp, M_SYSERR, dname, "%s"); 84619304Speter else 84719304Speter (void)chmod(path, perms); 84819304Speter return (fd); 84919304Speter} 85019304Speter 85119304Speter/* 85219304Speter * rcv_email -- 85319304Speter * Send email. 85419304Speter */ 85519304Speterstatic void 85619304Speterrcv_email(sp, fname) 85719304Speter SCR *sp; 85819304Speter char *fname; 85919304Speter{ 86019304Speter struct stat sb; 86119304Speter char buf[MAXPATHLEN * 2 + 20]; 86219304Speter 86319304Speter if (_PATH_SENDMAIL[0] != '/' || stat(_PATH_SENDMAIL, &sb)) 86419304Speter msgq_str(sp, M_SYSERR, 86519304Speter _PATH_SENDMAIL, "071|not sending email: %s"); 86619304Speter else { 86719304Speter /* 86819304Speter * !!! 86919304Speter * If you need to port this to a system that doesn't have 87019304Speter * sendmail, the -t flag causes sendmail to read the message 87119304Speter * for the recipients instead of specifying them some other 87219304Speter * way. 87319304Speter */ 87419304Speter (void)snprintf(buf, sizeof(buf), 87519304Speter "%s -t < %s", _PATH_SENDMAIL, fname); 87619304Speter (void)system(buf); 87719304Speter } 87819304Speter} 879