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 13254225Speterstatic const char sccsid[] = "$Id: recover.c,v 11.2 2012/10/09 08:06:58 zy Exp $"; 1419304Speter#endif /* not lint */ 1519304Speter 16254225Speter#include <sys/types.h> 1719304Speter#include <sys/queue.h> 1819304Speter#include <sys/stat.h> 1919304Speter 2019304Speter/* 2119304Speter * We include <sys/file.h>, because the open #defines were found there 2219304Speter * on historical systems. We also include <fcntl.h> because the open(2) 2319304Speter * #defines are found there on newer systems. 2419304Speter */ 2519304Speter#include <sys/file.h> 2619304Speter 2719304Speter#include <bitstring.h> 2819304Speter#include <dirent.h> 2919304Speter#include <errno.h> 3019304Speter#include <fcntl.h> 3119304Speter#include <limits.h> 3219304Speter#include <pwd.h> 33254225Speter#include <netinet/in.h> /* Required by resolv.h. */ 34254225Speter#include <resolv.h> 3519304Speter#include <stdio.h> 3619304Speter#include <stdlib.h> 3719304Speter#include <string.h> 3819304Speter#include <time.h> 3919304Speter#include <unistd.h> 4019304Speter 41254225Speter#include "../ex/version.h" 4219304Speter#include "common.h" 4319304Speter#include "pathnames.h" 4419304Speter 4519304Speter/* 4619304Speter * Recovery code. 4719304Speter * 4819304Speter * The basic scheme is as follows. In the EXF structure, we maintain full 4919304Speter * paths of a b+tree file and a mail recovery file. The former is the file 5019304Speter * used as backing store by the DB package. The latter is the file that 5119304Speter * contains an email message to be sent to the user if we crash. The two 5219304Speter * simple states of recovery are: 5319304Speter * 5419304Speter * + first starting the edit session: 5519304Speter * the b+tree file exists and is mode 700, the mail recovery 5619304Speter * file doesn't exist. 5719304Speter * + after the file has been modified: 5819304Speter * the b+tree file exists and is mode 600, the mail recovery 5919304Speter * file exists, and is exclusively locked. 6019304Speter * 6119304Speter * In the EXF structure we maintain a file descriptor that is the locked 62254225Speter * file descriptor for the mail recovery file. 6319304Speter * 6419304Speter * To find out if a recovery file/backing file pair are in use, try to get 6519304Speter * a lock on the recovery file. 6619304Speter * 6719304Speter * To find out if a backing file can be deleted at boot time, check for an 6819304Speter * owner execute bit. (Yes, I know it's ugly, but it's either that or put 6919304Speter * special stuff into the backing file itself, or correlate the files at 7019304Speter * boot time, neither of which looks like fun.) Note also that there's a 7119304Speter * window between when the file is created and the X bit is set. It's small, 7219304Speter * but it's there. To fix the window, check for 0 length files as well. 7319304Speter * 7419304Speter * To find out if a file can be recovered, check the F_RCV_ON bit. Note, 7519304Speter * this DOES NOT mean that any initialization has been done, only that we 7619304Speter * haven't yet failed at setting up or doing recovery. 7719304Speter * 7819304Speter * To preserve a recovery file/backing file pair, set the F_RCV_NORM bit. 7919304Speter * If that bit is not set when ending a file session: 8019304Speter * If the EXF structure paths (rcv_path and rcv_mpath) are not NULL, 8119304Speter * they are unlink(2)'d, and free(3)'d. 8219304Speter * If the EXF file descriptor (rcv_fd) is not -1, it is closed. 8319304Speter * 8419304Speter * The backing b+tree file is set up when a file is first edited, so that 8519304Speter * the DB package can use it for on-disk caching and/or to snapshot the 8619304Speter * file. When the file is first modified, the mail recovery file is created, 8719304Speter * the backing file permissions are updated, the file is sync(2)'d to disk, 8819304Speter * and the timer is started. Then, at RCV_PERIOD second intervals, the 8919304Speter * b+tree file is synced to disk. RCV_PERIOD is measured using SIGALRM, which 9019304Speter * means that the data structures (SCR, EXF, the underlying tree structures) 9119304Speter * must be consistent when the signal arrives. 9219304Speter * 93254225Speter * The recovery mail file contains normal mail headers, with two additional 9419304Speter * 95254225Speter * X-vi-data: <file|path>;<base64 encoded path> 9619304Speter * 97254225Speter * MIME headers; the folding character is limited to ' '. 9819304Speter * 99254225Speter * Btree files are named "vi.XXXXXX" and recovery files are named 100254225Speter * "recover.XXXXXX". 10119304Speter */ 10219304Speter 103254225Speter#define VI_DHEADER "X-vi-data:" 10419304Speter 10519304Speterstatic int rcv_copy __P((SCR *, int, char *)); 10619304Speterstatic void rcv_email __P((SCR *, char *)); 10719304Speterstatic int rcv_mailfile __P((SCR *, int, char *)); 108254225Speterstatic int rcv_mktemp __P((SCR *, char *, char *)); 109254225Speterstatic int rcv_dlnwrite __P((SCR *, const char *, const char *, FILE *)); 110254225Speterstatic int rcv_dlnread __P((SCR *, char **, char **, FILE *)); 11119304Speter 11219304Speter/* 11319304Speter * rcv_tmp -- 11419304Speter * Build a file name that will be used as the recovery file. 11519304Speter * 11619304Speter * PUBLIC: int rcv_tmp __P((SCR *, EXF *, char *)); 11719304Speter */ 11819304Speterint 119254225Speterrcv_tmp( 120254225Speter SCR *sp, 121254225Speter EXF *ep, 122254225Speter char *name) 12319304Speter{ 12419304Speter struct stat sb; 12519304Speter int fd; 126254225Speter char *dp, *path; 12719304Speter 12819304Speter /* 12919304Speter * !!! 13019304Speter * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER. 13119304Speter * 13219304Speter * 13319304Speter * If the recovery directory doesn't exist, try and create it. As 13419304Speter * the recovery files are themselves protected from reading/writing 13519304Speter * by other than the owner, the worst that can happen is that a user 13619304Speter * would have permission to remove other user's recovery files. If 13719304Speter * the sticky bit has the BSD semantics, that too will be impossible. 13819304Speter */ 13919304Speter if (opts_empty(sp, O_RECDIR, 0)) 14019304Speter goto err; 14119304Speter dp = O_STR(sp, O_RECDIR); 14219304Speter if (stat(dp, &sb)) { 14319304Speter if (errno != ENOENT || mkdir(dp, 0)) { 14419304Speter msgq(sp, M_SYSERR, "%s", dp); 14519304Speter goto err; 14619304Speter } 14719304Speter (void)chmod(dp, S_IRWXU | S_IRWXG | S_IRWXO | S_ISVTX); 14819304Speter } 14919304Speter 150254225Speter if ((path = join(dp, "vi.XXXXXX")) == NULL) 15119304Speter goto err; 152254225Speter if ((fd = rcv_mktemp(sp, path, dp)) == -1) { 153254225Speter free(path); 154254225Speter goto err; 155254225Speter } 156254225Speter (void)fchmod(fd, S_IRWXU); 15719304Speter (void)close(fd); 15819304Speter 159254225Speter ep->rcv_path = path; 160254225Speter if (0) { 16119304Spetererr: msgq(sp, M_ERR, 16219304Speter "056|Modifications not recoverable if the session fails"); 16319304Speter return (1); 16419304Speter } 16519304Speter 16619304Speter /* We believe the file is recoverable. */ 16719304Speter F_SET(ep, F_RCV_ON); 16819304Speter return (0); 16919304Speter} 17019304Speter 17119304Speter/* 17219304Speter * rcv_init -- 17319304Speter * Force the file to be snapshotted for recovery. 17419304Speter * 17519304Speter * PUBLIC: int rcv_init __P((SCR *)); 17619304Speter */ 17719304Speterint 178254225Speterrcv_init(SCR *sp) 17919304Speter{ 18019304Speter EXF *ep; 18119304Speter recno_t lno; 18219304Speter 18319304Speter ep = sp->ep; 18419304Speter 18519304Speter /* Only do this once. */ 18619304Speter F_CLR(ep, F_FIRSTMODIFY); 18719304Speter 18819304Speter /* If we already know the file isn't recoverable, we're done. */ 18919304Speter if (!F_ISSET(ep, F_RCV_ON)) 19019304Speter return (0); 19119304Speter 19219304Speter /* Turn off recoverability until we figure out if this will work. */ 19319304Speter F_CLR(ep, F_RCV_ON); 19419304Speter 19519304Speter /* Test if we're recovering a file, not editing one. */ 19619304Speter if (ep->rcv_mpath == NULL) { 19719304Speter /* Build a file to mail to the user. */ 19819304Speter if (rcv_mailfile(sp, 0, NULL)) 19919304Speter goto err; 20019304Speter 20119304Speter /* Force a read of the entire file. */ 20219304Speter if (db_last(sp, &lno)) 20319304Speter goto err; 20419304Speter 20519304Speter /* Turn on a busy message, and sync it to backing store. */ 20619304Speter sp->gp->scr_busy(sp, 20719304Speter "057|Copying file for recovery...", BUSY_ON); 20819304Speter if (ep->db->sync(ep->db, R_RECNOSYNC)) { 20919304Speter msgq_str(sp, M_SYSERR, ep->rcv_path, 21019304Speter "058|Preservation failed: %s"); 21119304Speter sp->gp->scr_busy(sp, NULL, BUSY_OFF); 21219304Speter goto err; 21319304Speter } 21419304Speter sp->gp->scr_busy(sp, NULL, BUSY_OFF); 21519304Speter } 21619304Speter 21719304Speter /* Turn off the owner execute bit. */ 21819304Speter (void)chmod(ep->rcv_path, S_IRUSR | S_IWUSR); 21919304Speter 22019304Speter /* We believe the file is recoverable. */ 22119304Speter F_SET(ep, F_RCV_ON); 22219304Speter return (0); 22319304Speter 22419304Spetererr: msgq(sp, M_ERR, 22519304Speter "059|Modifications not recoverable if the session fails"); 22619304Speter return (1); 22719304Speter} 22819304Speter 22919304Speter/* 23019304Speter * rcv_sync -- 23119304Speter * Sync the file, optionally: 23219304Speter * flagging the backup file to be preserved 23319304Speter * snapshotting the backup file and send email to the user 23419304Speter * sending email to the user if the file was modified 23519304Speter * ending the file session 23619304Speter * 23719304Speter * PUBLIC: int rcv_sync __P((SCR *, u_int)); 23819304Speter */ 23919304Speterint 240254225Speterrcv_sync( 241254225Speter SCR *sp, 242254225Speter u_int flags) 24319304Speter{ 24419304Speter EXF *ep; 24519304Speter int fd, rval; 246254225Speter char *dp, *buf; 24719304Speter 24819304Speter /* Make sure that there's something to recover/sync. */ 24919304Speter ep = sp->ep; 25019304Speter if (ep == NULL || !F_ISSET(ep, F_RCV_ON)) 25119304Speter return (0); 25219304Speter 25319304Speter /* Sync the file if it's been modified. */ 25419304Speter if (F_ISSET(ep, F_MODIFIED)) { 25519304Speter SIGBLOCK; 25619304Speter if (ep->db->sync(ep->db, R_RECNOSYNC)) { 25719304Speter F_CLR(ep, F_RCV_ON | F_RCV_NORM); 25819304Speter msgq_str(sp, M_SYSERR, 25919304Speter ep->rcv_path, "060|File backup failed: %s"); 26019304Speter SIGUNBLOCK; 26119304Speter return (1); 26219304Speter } 26319304Speter SIGUNBLOCK; 26419304Speter 26519304Speter /* REQUEST: don't remove backing file on exit. */ 26619304Speter if (LF_ISSET(RCV_PRESERVE)) 26719304Speter F_SET(ep, F_RCV_NORM); 26819304Speter 26919304Speter /* REQUEST: send email. */ 27019304Speter if (LF_ISSET(RCV_EMAIL)) 27119304Speter rcv_email(sp, ep->rcv_mpath); 27219304Speter } 27319304Speter 27419304Speter /* 27519304Speter * !!! 27619304Speter * Each time the user exec's :preserve, we have to snapshot all of 27719304Speter * the recovery information, i.e. it's like the user re-edited the 27819304Speter * file. We copy the DB(3) backing file, and then create a new mail 27919304Speter * recovery file, it's simpler than exiting and reopening all of the 28019304Speter * underlying files. 28119304Speter * 28219304Speter * REQUEST: snapshot the file. 28319304Speter */ 28419304Speter rval = 0; 28519304Speter if (LF_ISSET(RCV_SNAPSHOT)) { 28619304Speter if (opts_empty(sp, O_RECDIR, 0)) 28719304Speter goto err; 28819304Speter dp = O_STR(sp, O_RECDIR); 289254225Speter if ((buf = join(dp, "vi.XXXXXX")) == NULL) { 290254225Speter msgq(sp, M_SYSERR, NULL); 29119304Speter goto err; 292254225Speter } 293254225Speter if ((fd = rcv_mktemp(sp, buf, dp)) == -1) { 294254225Speter free(buf); 295254225Speter goto err; 296254225Speter } 29719304Speter sp->gp->scr_busy(sp, 29819304Speter "061|Copying file for recovery...", BUSY_ON); 29919304Speter if (rcv_copy(sp, fd, ep->rcv_path) || 30019304Speter close(fd) || rcv_mailfile(sp, 1, buf)) { 30119304Speter (void)unlink(buf); 30219304Speter (void)close(fd); 30319304Speter rval = 1; 30419304Speter } 305254225Speter free(buf); 30619304Speter sp->gp->scr_busy(sp, NULL, BUSY_OFF); 30719304Speter } 30819304Speter if (0) { 30919304Spetererr: rval = 1; 31019304Speter } 31119304Speter 31219304Speter /* REQUEST: end the file session. */ 31319304Speter if (LF_ISSET(RCV_ENDSESSION) && file_end(sp, NULL, 1)) 31419304Speter rval = 1; 31519304Speter 31619304Speter return (rval); 31719304Speter} 31819304Speter 31919304Speter/* 32019304Speter * rcv_mailfile -- 32119304Speter * Build the file to mail to the user. 32219304Speter */ 32319304Speterstatic int 324254225Speterrcv_mailfile( 325254225Speter SCR *sp, 326254225Speter int issync, 327254225Speter char *cp_path) 32819304Speter{ 32919304Speter EXF *ep; 33019304Speter GS *gp; 33119304Speter struct passwd *pw; 332254225Speter int len; 33319304Speter time_t now; 33419304Speter uid_t uid; 33519304Speter int fd; 336254225Speter FILE *fp; 337254225Speter char *dp, *p, *t, *qt, *buf, *mpath; 33819304Speter char *t1, *t2, *t3; 339254225Speter int st; 34019304Speter 34119304Speter /* 34219304Speter * XXX 343254225Speter * MAXHOSTNAMELEN/HOST_NAME_MAX are deprecated. We try sysconf(3) 344254225Speter * first, then fallback to _POSIX_HOST_NAME_MAX. 34519304Speter */ 346254225Speter char *host; 347254225Speter long hostmax = sysconf(_SC_HOST_NAME_MAX); 348254225Speter if (hostmax < 0) 349254225Speter hostmax = _POSIX_HOST_NAME_MAX; 35019304Speter 35119304Speter gp = sp->gp; 35219304Speter if ((pw = getpwuid(uid = getuid())) == NULL) { 35319304Speter msgq(sp, M_ERR, 35419304Speter "062|Information on user id %u not found", uid); 35519304Speter return (1); 35619304Speter } 35719304Speter 35819304Speter if (opts_empty(sp, O_RECDIR, 0)) 35919304Speter return (1); 36019304Speter dp = O_STR(sp, O_RECDIR); 361254225Speter if ((mpath = join(dp, "recover.XXXXXX")) == NULL) { 362254225Speter msgq(sp, M_SYSERR, NULL); 36319304Speter return (1); 364254225Speter } 365254225Speter if ((fd = rcv_mktemp(sp, mpath, dp)) == -1) { 366254225Speter free(mpath); 367254225Speter return (1); 368254225Speter } 369254225Speter if ((fp = fdopen(fd, "w")) == NULL) { 370254225Speter free(mpath); 371254225Speter close(fd); 372254225Speter return (1); 373254225Speter } 37419304Speter 37519304Speter /* 37619304Speter * XXX 37719304Speter * We keep an open lock on the file so that the recover option can 37819304Speter * distinguish between files that are live and those that need to 37919304Speter * be recovered. There's an obvious window between the mkstemp call 38019304Speter * and the lock, but it's pretty small. 38119304Speter */ 38219304Speter ep = sp->ep; 383254225Speter if (file_lock(sp, NULL, fd, 1) != LOCK_SUCCESS) 38419304Speter msgq(sp, M_SYSERR, "063|Unable to lock recovery file"); 38519304Speter if (!issync) { 38619304Speter /* Save the recover file descriptor, and mail path. */ 387254225Speter ep->rcv_fd = dup(fd); 388254225Speter ep->rcv_mpath = mpath; 38919304Speter cp_path = ep->rcv_path; 39019304Speter } 39119304Speter 39219304Speter t = sp->frp->name; 39319304Speter if ((p = strrchr(t, '/')) == NULL) 39419304Speter p = t; 39519304Speter else 39619304Speter ++p; 39719304Speter (void)time(&now); 398254225Speter 399254225Speter if ((st = rcv_dlnwrite(sp, "file", t, fp))) { 400254225Speter if (st == 1) 401254225Speter goto werr; 402254225Speter goto err; 403254225Speter } 404254225Speter if ((st = rcv_dlnwrite(sp, "path", cp_path, fp))) { 405254225Speter if (st == 1) 406254225Speter goto werr; 407254225Speter goto err; 408254225Speter } 409254225Speter 410254225Speter MALLOC(sp, host, char *, hostmax + 1); 411254225Speter if (host == NULL) 412254225Speter goto err; 413254225Speter (void)gethostname(host, hostmax + 1); 414254225Speter 415254225Speter len = fprintf(fp, "%s%s%s\n%s%s%s%s\n%s%.40s\n%s\n\n", 416254225Speter "From: root@", host, " (Nvi recovery program)", 417254225Speter "To: ", pw->pw_name, "@", host, 41819304Speter "Subject: Nvi saved the file ", p, 41919304Speter "Precedence: bulk"); /* For vacation(1). */ 420254225Speter if (len < 0) { 421254225Speter free(host); 42219304Speter goto werr; 423254225Speter } 42419304Speter 425254225Speter if ((qt = quote(t)) == NULL) { 426254225Speter free(host); 427254225Speter msgq(sp, M_SYSERR, NULL); 428254225Speter goto err; 429254225Speter } 430254225Speter len = asprintf(&buf, "%s%.24s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n\n", 43119304Speter "On ", ctime(&now), ", the user ", pw->pw_name, 43219304Speter " was editing a file named ", t, " on the machine ", 43319304Speter host, ", when it was saved for recovery. ", 43419304Speter "You can recover most, if not all, of the changes ", 43519304Speter "to this file using the -r option to ", gp->progname, ":\n\n\t", 436254225Speter gp->progname, " -r ", qt); 437254225Speter free(qt); 438254225Speter free(host); 439254225Speter if (buf == NULL) { 440254225Speter msgq(sp, M_SYSERR, NULL); 44119304Speter goto err; 44219304Speter } 44319304Speter 44419304Speter /* 44519304Speter * Format the message. (Yes, I know it's silly.) 44619304Speter * Requires that the message end in a <newline>. 44719304Speter */ 44819304Speter#define FMTCOLS 60 44919304Speter for (t1 = buf; len > 0; len -= t2 - t1, t1 = t2) { 45019304Speter /* Check for a short length. */ 45119304Speter if (len <= FMTCOLS) { 45219304Speter t2 = t1 + (len - 1); 45319304Speter goto wout; 45419304Speter } 45519304Speter 45619304Speter /* Check for a required <newline>. */ 45719304Speter t2 = strchr(t1, '\n'); 45819304Speter if (t2 - t1 <= FMTCOLS) 45919304Speter goto wout; 46019304Speter 46119304Speter /* Find the closest space, if any. */ 46219304Speter for (t3 = t2; t2 > t1; --t2) 46319304Speter if (*t2 == ' ') { 46419304Speter if (t2 - t1 <= FMTCOLS) 46519304Speter goto wout; 46619304Speter t3 = t2; 46719304Speter } 46819304Speter t2 = t3; 46919304Speter 47019304Speter /* t2 points to the last character to display. */ 47119304Speterwout: *t2++ = '\n'; 47219304Speter 47319304Speter /* t2 points one after the last character to display. */ 474254225Speter if (fwrite(t1, 1, t2 - t1, fp) != t2 - t1) { 475254225Speter free(buf); 47619304Speter goto werr; 477254225Speter } 47819304Speter } 47919304Speter 48019304Speter if (issync) { 481254225Speter fflush(fp); 48219304Speter rcv_email(sp, mpath); 483254225Speter free(mpath); 48419304Speter } 485254225Speter if (fclose(fp)) { 486254225Speter free(buf); 487254225Speterwerr: msgq(sp, M_SYSERR, "065|Recovery file"); 488254225Speter goto err; 489254225Speter } 490254225Speter free(buf); 49119304Speter return (0); 49219304Speter 49319304Spetererr: if (!issync) 49419304Speter ep->rcv_fd = -1; 495254225Speter if (fp != NULL) 496254225Speter (void)fclose(fp); 49719304Speter return (1); 49819304Speter} 49919304Speter 50019304Speter/* 50119304Speter * people making love 50219304Speter * never exactly the same 50319304Speter * just like a snowflake 50419304Speter * 50519304Speter * rcv_list -- 50619304Speter * List the files that can be recovered by this user. 50719304Speter * 50819304Speter * PUBLIC: int rcv_list __P((SCR *)); 50919304Speter */ 51019304Speterint 511254225Speterrcv_list(SCR *sp) 51219304Speter{ 51319304Speter struct dirent *dp; 51419304Speter struct stat sb; 51519304Speter DIR *dirp; 51619304Speter FILE *fp; 51719304Speter int found; 518254225Speter char *p, *file, *path; 519254225Speter char *dtype, *data; 520254225Speter int st; 52119304Speter 52219304Speter /* Open the recovery directory for reading. */ 52319304Speter if (opts_empty(sp, O_RECDIR, 0)) 52419304Speter return (1); 52519304Speter p = O_STR(sp, O_RECDIR); 52619304Speter if (chdir(p) || (dirp = opendir(".")) == NULL) { 52719304Speter msgq_str(sp, M_SYSERR, p, "recdir: %s"); 52819304Speter return (1); 52919304Speter } 53019304Speter 53119304Speter /* Read the directory. */ 53219304Speter for (found = 0; (dp = readdir(dirp)) != NULL;) { 53319304Speter if (strncmp(dp->d_name, "recover.", 8)) 53419304Speter continue; 53519304Speter 536254225Speter /* If it's readable, it's recoverable. */ 537254225Speter if ((fp = fopen(dp->d_name, "r")) == NULL) 53819304Speter continue; 53919304Speter 540254225Speter switch (file_lock(sp, NULL, fileno(fp), 1)) { 54119304Speter case LOCK_FAILED: 54219304Speter /* 54319304Speter * XXX 54419304Speter * Assume that a lock can't be acquired, but that we 54519304Speter * should permit recovery anyway. If this is wrong, 54619304Speter * and someone else is using the file, we're going to 54719304Speter * die horribly. 54819304Speter */ 54919304Speter break; 55019304Speter case LOCK_SUCCESS: 55119304Speter break; 55219304Speter case LOCK_UNAVAIL: 55319304Speter /* If it's locked, it's live. */ 55419304Speter (void)fclose(fp); 55519304Speter continue; 55619304Speter } 55719304Speter 55819304Speter /* Check the headers. */ 559254225Speter for (file = NULL, path = NULL; 560254225Speter file == NULL || path == NULL;) { 561254225Speter if ((st = rcv_dlnread(sp, &dtype, &data, fp))) { 562254225Speter if (st == 1) 563254225Speter msgq_str(sp, M_ERR, dp->d_name, 564254225Speter "066|%s: malformed recovery file"); 565254225Speter goto next; 566254225Speter } 567254225Speter if (dtype == NULL) 568254225Speter continue; 569254225Speter if (!strcmp(dtype, "file")) 570254225Speter file = data; 571254225Speter else if (!strcmp(dtype, "path")) 572254225Speter path = data; 573254225Speter else 574254225Speter free(data); 57519304Speter } 57619304Speter 57719304Speter /* 57819304Speter * If the file doesn't exist, it's an orphaned recovery file, 57919304Speter * toss it. 58019304Speter * 58119304Speter * XXX 58219304Speter * This can occur if the backup file was deleted and we crashed 58319304Speter * before deleting the email file. 58419304Speter */ 58519304Speter errno = 0; 586254225Speter if (stat(path, &sb) && 58719304Speter errno == ENOENT) { 58819304Speter (void)unlink(dp->d_name); 58919304Speter goto next; 59019304Speter } 59119304Speter 59219304Speter /* Get the last modification time and display. */ 59319304Speter (void)fstat(fileno(fp), &sb); 59419304Speter (void)printf("%.24s: %s\n", 595254225Speter ctime(&sb.st_mtime), file); 59619304Speter found = 1; 59719304Speter 59819304Speter /* Close, discarding lock. */ 59919304Speternext: (void)fclose(fp); 600254225Speter if (file != NULL) 601254225Speter free(file); 602254225Speter if (path != NULL) 603254225Speter free(path); 60419304Speter } 60519304Speter if (found == 0) 606254225Speter (void)printf("%s: No files to recover\n", sp->gp->progname); 60719304Speter (void)closedir(dirp); 60819304Speter return (0); 60919304Speter} 61019304Speter 61119304Speter/* 61219304Speter * rcv_read -- 61319304Speter * Start a recovered file as the file to edit. 61419304Speter * 61519304Speter * PUBLIC: int rcv_read __P((SCR *, FREF *)); 61619304Speter */ 61719304Speterint 618254225Speterrcv_read( 619254225Speter SCR *sp, 620254225Speter FREF *frp) 62119304Speter{ 62219304Speter struct dirent *dp; 62319304Speter struct stat sb; 62419304Speter DIR *dirp; 625254225Speter FILE *fp; 62619304Speter EXF *ep; 627254225Speter struct timespec rec_mtim = { 0, 0 }; 628254225Speter int found, locked = 0, requested, sv_fd; 62919304Speter char *name, *p, *t, *rp, *recp, *pathp; 630254225Speter char *file, *path, *recpath; 631254225Speter char *dtype, *data; 632254225Speter int st; 63319304Speter 63419304Speter if (opts_empty(sp, O_RECDIR, 0)) 63519304Speter return (1); 63619304Speter rp = O_STR(sp, O_RECDIR); 63719304Speter if ((dirp = opendir(rp)) == NULL) { 63819304Speter msgq_str(sp, M_ERR, rp, "%s"); 63919304Speter return (1); 64019304Speter } 64119304Speter 64219304Speter name = frp->name; 64319304Speter sv_fd = -1; 64419304Speter recp = pathp = NULL; 64519304Speter for (found = requested = 0; (dp = readdir(dirp)) != NULL;) { 64619304Speter if (strncmp(dp->d_name, "recover.", 8)) 64719304Speter continue; 648254225Speter if ((recpath = join(rp, dp->d_name)) == NULL) { 649254225Speter msgq(sp, M_SYSERR, NULL); 650254225Speter continue; 651254225Speter } 65219304Speter 653254225Speter /* If it's readable, it's recoverable. */ 654254225Speter if ((fp = fopen(recpath, "r")) == NULL) { 655254225Speter free(recpath); 65619304Speter continue; 657254225Speter } 65819304Speter 659254225Speter switch (file_lock(sp, NULL, fileno(fp), 1)) { 66019304Speter case LOCK_FAILED: 66119304Speter /* 66219304Speter * XXX 66319304Speter * Assume that a lock can't be acquired, but that we 66419304Speter * should permit recovery anyway. If this is wrong, 66519304Speter * and someone else is using the file, we're going to 66619304Speter * die horribly. 66719304Speter */ 66819304Speter locked = 0; 66919304Speter break; 67019304Speter case LOCK_SUCCESS: 67119304Speter locked = 1; 67219304Speter break; 67319304Speter case LOCK_UNAVAIL: 67419304Speter /* If it's locked, it's live. */ 675254225Speter (void)fclose(fp); 67619304Speter continue; 67719304Speter } 67819304Speter 67919304Speter /* Check the headers. */ 680254225Speter for (file = NULL, path = NULL; 681254225Speter file == NULL || path == NULL;) { 682254225Speter if ((st = rcv_dlnread(sp, &dtype, &data, fp))) { 683254225Speter if (st == 1) 684254225Speter msgq_str(sp, M_ERR, dp->d_name, 685254225Speter "067|%s: malformed recovery file"); 686254225Speter goto next; 687254225Speter } 688254225Speter if (dtype == NULL) 689254225Speter continue; 690254225Speter if (!strcmp(dtype, "file")) 691254225Speter file = data; 692254225Speter else if (!strcmp(dtype, "path")) 693254225Speter path = data; 694254225Speter else 695254225Speter free(data); 69619304Speter } 69719304Speter ++found; 69819304Speter 69919304Speter /* 70019304Speter * If the file doesn't exist, it's an orphaned recovery file, 70119304Speter * toss it. 70219304Speter * 70319304Speter * XXX 70419304Speter * This can occur if the backup file was deleted and we crashed 70519304Speter * before deleting the email file. 70619304Speter */ 70719304Speter errno = 0; 708254225Speter if (stat(path, &sb) && 70919304Speter errno == ENOENT) { 71019304Speter (void)unlink(dp->d_name); 71119304Speter goto next; 71219304Speter } 71319304Speter 71419304Speter /* Check the file name. */ 715254225Speter if (strcmp(file, name)) 71619304Speter goto next; 71719304Speter 71819304Speter ++requested; 71919304Speter 720254225Speter /* If we've found more than one, take the most recent. */ 721254225Speter (void)fstat(fileno(fp), &sb); 722254225Speter if (recp == NULL || 723254225Speter timespeccmp(&rec_mtim, &sb.st_mtimespec, <)) { 72419304Speter p = recp; 72519304Speter t = pathp; 726254225Speter recp = recpath; 727254225Speter pathp = path; 72819304Speter if (p != NULL) { 72919304Speter free(p); 73019304Speter free(t); 73119304Speter } 732254225Speter rec_mtim = sb.st_mtimespec; 73319304Speter if (sv_fd != -1) 73419304Speter (void)close(sv_fd); 735254225Speter sv_fd = dup(fileno(fp)); 736254225Speter } else { 737254225Speternext: free(recpath); 738254225Speter if (path != NULL) 739254225Speter free(path); 740254225Speter } 741254225Speter (void)fclose(fp); 742254225Speter if (file != NULL) 743254225Speter free(file); 74419304Speter } 74519304Speter (void)closedir(dirp); 74619304Speter 74719304Speter if (recp == NULL) { 74819304Speter msgq_str(sp, M_INFO, name, 74919304Speter "068|No files named %s, readable by you, to recover"); 75019304Speter return (1); 75119304Speter } 75219304Speter if (found) { 75319304Speter if (requested > 1) 75419304Speter msgq(sp, M_INFO, 75519304Speter "069|There are older versions of this file for you to recover"); 75619304Speter if (found > requested) 75719304Speter msgq(sp, M_INFO, 75819304Speter "070|There are other files for you to recover"); 75919304Speter } 76019304Speter 76119304Speter /* 76219304Speter * Create the FREF structure, start the btree file. 76319304Speter * 76419304Speter * XXX 76519304Speter * file_init() is going to set ep->rcv_path. 76619304Speter */ 767254225Speter if (file_init(sp, frp, pathp, 0)) { 76819304Speter free(recp); 76919304Speter free(pathp); 77019304Speter (void)close(sv_fd); 77119304Speter return (1); 77219304Speter } 773254225Speter free(pathp); 77419304Speter 77519304Speter /* 77619304Speter * We keep an open lock on the file so that the recover option can 77719304Speter * distinguish between files that are live and those that need to 77819304Speter * be recovered. The lock is already acquired, just copy it. 77919304Speter */ 78019304Speter ep = sp->ep; 78119304Speter ep->rcv_mpath = recp; 78219304Speter ep->rcv_fd = sv_fd; 78319304Speter if (!locked) 78419304Speter F_SET(frp, FR_UNLOCKED); 78519304Speter 78619304Speter /* We believe the file is recoverable. */ 78719304Speter F_SET(ep, F_RCV_ON); 78819304Speter return (0); 78919304Speter} 79019304Speter 79119304Speter/* 79219304Speter * rcv_copy -- 79319304Speter * Copy a recovery file. 79419304Speter */ 79519304Speterstatic int 796254225Speterrcv_copy( 797254225Speter SCR *sp, 798254225Speter int wfd, 799254225Speter char *fname) 80019304Speter{ 80119304Speter int nr, nw, off, rfd; 80219304Speter char buf[8 * 1024]; 80319304Speter 80419304Speter if ((rfd = open(fname, O_RDONLY, 0)) == -1) 80519304Speter goto err; 80619304Speter while ((nr = read(rfd, buf, sizeof(buf))) > 0) 80719304Speter for (off = 0; nr; nr -= nw, off += nw) 80819304Speter if ((nw = write(wfd, buf + off, nr)) < 0) 80919304Speter goto err; 81019304Speter if (nr == 0) 81119304Speter return (0); 81219304Speter 81319304Spetererr: msgq_str(sp, M_SYSERR, fname, "%s"); 81419304Speter return (1); 81519304Speter} 81619304Speter 81719304Speter/* 81819304Speter * rcv_mktemp -- 81919304Speter * Paranoid make temporary file routine. 82019304Speter */ 82119304Speterstatic int 822254225Speterrcv_mktemp( 823254225Speter SCR *sp, 824254225Speter char *path, 825254225Speter char *dname) 82619304Speter{ 82719304Speter int fd; 82819304Speter 82919304Speter if ((fd = mkstemp(path)) == -1) 83019304Speter msgq_str(sp, M_SYSERR, dname, "%s"); 83119304Speter return (fd); 83219304Speter} 83319304Speter 83419304Speter/* 83519304Speter * rcv_email -- 83619304Speter * Send email. 83719304Speter */ 83819304Speterstatic void 839254225Speterrcv_email( 840254225Speter SCR *sp, 841254225Speter char *fname) 84219304Speter{ 843254225Speter char *buf; 84419304Speter 845254225Speter (void)asprintf(&buf, _PATH_SENDMAIL " -odb -t < %s", fname); 846254225Speter if (buf == NULL) { 847254225Speter msgq_str(sp, M_ERR, strerror(errno), 848254225Speter "071|not sending email: %s"); 849254225Speter return; 85019304Speter } 851254225Speter (void)system(buf); 852254225Speter free(buf); 85319304Speter} 854254225Speter 855254225Speter/* 856254225Speter * rcv_dlnwrite -- 857254225Speter * Encode a string into an X-vi-data line and write it. 858254225Speter */ 859254225Speterstatic int 860254225Speterrcv_dlnwrite( 861254225Speter SCR *sp, 862254225Speter const char *dtype, 863254225Speter const char *src, 864254225Speter FILE *fp) 865254225Speter{ 866254225Speter char *bp = NULL, *p; 867254225Speter size_t blen = 0; 868254225Speter size_t dlen, len; 869254225Speter int plen, xlen; 870254225Speter 871254225Speter len = strlen(src); 872254225Speter dlen = strlen(dtype); 873254225Speter GET_SPACE_GOTOC(sp, bp, blen, (len + 2) / 3 * 4 + dlen + 2); 874254225Speter (void)memcpy(bp, dtype, dlen); 875254225Speter bp[dlen] = ';'; 876254225Speter if ((xlen = b64_ntop((u_char *)src, 877254225Speter len, bp + dlen + 1, blen)) == -1) 878254225Speter goto err; 879254225Speter xlen += dlen + 1; 880254225Speter 881254225Speter /* Output as an MIME folding header. */ 882254225Speter if ((plen = fprintf(fp, VI_DHEADER " %.*s\n", 883254225Speter FMTCOLS - (int)sizeof(VI_DHEADER), bp)) < 0) 884254225Speter goto err; 885254225Speter plen -= (int)sizeof(VI_DHEADER) + 1; 886254225Speter for (p = bp, xlen -= plen; xlen > 0; xlen -= plen) { 887254225Speter p += plen; 888254225Speter if ((plen = fprintf(fp, " %.*s\n", FMTCOLS - 1, p)) < 0) 889254225Speter goto err; 890254225Speter plen -= 2; 891254225Speter } 892254225Speter FREE_SPACE(sp, bp, blen); 893254225Speter return (0); 894254225Speter 895254225Spetererr: FREE_SPACE(sp, bp, blen); 896254225Speter return (1); 897254225Speteralloc_err: 898254225Speter msgq(sp, M_SYSERR, NULL); 899254225Speter return (-1); 900254225Speter} 901254225Speter 902254225Speter/* 903254225Speter * rcv_dlnread -- 904254225Speter * Read an X-vi-data line and decode it. 905254225Speter */ 906254225Speterstatic int 907254225Speterrcv_dlnread( 908254225Speter SCR *sp, 909254225Speter char **dtypep, 910254225Speter char **datap, /* free *datap if != NULL after use. */ 911254225Speter FILE *fp) 912254225Speter{ 913254225Speter int ch; 914254225Speter char buf[1024]; 915254225Speter char *bp = NULL, *p, *src; 916254225Speter size_t blen = 0; 917254225Speter size_t len, off, dlen; 918254225Speter char *dtype, *data; 919254225Speter int xlen; 920254225Speter 921254225Speter if (fgets(buf, sizeof(buf), fp) == NULL) 922254225Speter return (1); 923254225Speter if (strncmp(buf, VI_DHEADER, sizeof(VI_DHEADER) - 1)) { 924254225Speter *dtypep = NULL; 925254225Speter *datap = NULL; 926254225Speter return (0); 927254225Speter } 928254225Speter 929254225Speter /* Fetch an MIME folding header. */ 930254225Speter len = strlen(buf) - sizeof(VI_DHEADER) + 1; 931254225Speter GET_SPACE_GOTOC(sp, bp, blen, len); 932254225Speter (void)memcpy(bp, buf + sizeof(VI_DHEADER) - 1, len); 933254225Speter p = bp + len; 934254225Speter while ((ch = fgetc(fp)) == ' ') { 935254225Speter if (fgets(buf, sizeof(buf), fp) == NULL) 936254225Speter goto err; 937254225Speter off = strlen(buf); 938254225Speter len += off; 939254225Speter ADD_SPACE_GOTOC(sp, bp, blen, len); 940254225Speter p = bp + len - off; 941254225Speter (void)memcpy(p, buf, off); 942254225Speter } 943254225Speter bp[len] = '\0'; 944254225Speter (void)ungetc(ch, fp); 945254225Speter 946254225Speter for (p = bp; *p == ' ' || *p == '\n'; p++); 947254225Speter if ((src = strchr(p, ';')) == NULL) 948254225Speter goto err; 949254225Speter dlen = src - p; 950254225Speter src += 1; 951254225Speter len -= src - bp; 952254225Speter 953254225Speter /* Memory looks like: "<data>\0<dtype>\0". */ 954254225Speter MALLOC(sp, data, char *, dlen + len / 4 * 3 + 2); 955254225Speter if (data == NULL) 956254225Speter goto err; 957254225Speter if ((xlen = (b64_pton(p + dlen + 1, 958254225Speter (u_char *)data, len / 4 * 3 + 1))) == -1) { 959254225Speter free(data); 960254225Speter goto err; 961254225Speter } 962254225Speter data[xlen] = '\0'; 963254225Speter dtype = data + xlen + 1; 964254225Speter (void)memcpy(dtype, p, dlen); 965254225Speter dtype[dlen] = '\0'; 966254225Speter FREE_SPACE(sp, bp, blen); 967254225Speter *dtypep = dtype; 968254225Speter *datap = data; 969254225Speter return (0); 970254225Speter 971254225Spetererr: FREE_SPACE(sp, bp, blen); 972254225Speter return (1); 973254225Speteralloc_err: 974254225Speter msgq(sp, M_SYSERR, NULL); 975254225Speter return (-1); 976254225Speter} 977