summaryrefslogtreecommitdiffstats
path: root/contrib/nvi/common/recover.c
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/nvi/common/recover.c')
-rw-r--r--contrib/nvi/common/recover.c878
1 files changed, 878 insertions, 0 deletions
diff --git a/contrib/nvi/common/recover.c b/contrib/nvi/common/recover.c
new file mode 100644
index 0000000..f3abaab
--- /dev/null
+++ b/contrib/nvi/common/recover.c
@@ -0,0 +1,878 @@
+/*-
+ * Copyright (c) 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ * Copyright (c) 1993, 1994, 1995, 1996
+ * Keith Bostic. All rights reserved.
+ *
+ * See the LICENSE file for redistribution information.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)recover.c 10.21 (Berkeley) 9/15/96";
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/types.h> /* XXX: param.h may not have included types.h */
+#include <sys/queue.h>
+#include <sys/stat.h>
+
+/*
+ * We include <sys/file.h>, because the open #defines were found there
+ * on historical systems. We also include <fcntl.h> because the open(2)
+ * #defines are found there on newer systems.
+ */
+#include <sys/file.h>
+
+#include <bitstring.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "common.h"
+#include "pathnames.h"
+
+/*
+ * Recovery code.
+ *
+ * The basic scheme is as follows. In the EXF structure, we maintain full
+ * paths of a b+tree file and a mail recovery file. The former is the file
+ * used as backing store by the DB package. The latter is the file that
+ * contains an email message to be sent to the user if we crash. The two
+ * simple states of recovery are:
+ *
+ * + first starting the edit session:
+ * the b+tree file exists and is mode 700, the mail recovery
+ * file doesn't exist.
+ * + after the file has been modified:
+ * the b+tree file exists and is mode 600, the mail recovery
+ * file exists, and is exclusively locked.
+ *
+ * In the EXF structure we maintain a file descriptor that is the locked
+ * file descriptor for the mail recovery file. NOTE: we sometimes have to
+ * do locking with fcntl(2). This is a problem because if you close(2) any
+ * file descriptor associated with the file, ALL of the locks go away. Be
+ * sure to remember that if you have to modify the recovery code. (It has
+ * been rhetorically asked of what the designers could have been thinking
+ * when they did that interface. The answer is simple: they weren't.)
+ *
+ * To find out if a recovery file/backing file pair are in use, try to get
+ * a lock on the recovery file.
+ *
+ * To find out if a backing file can be deleted at boot time, check for an
+ * owner execute bit. (Yes, I know it's ugly, but it's either that or put
+ * special stuff into the backing file itself, or correlate the files at
+ * boot time, neither of which looks like fun.) Note also that there's a
+ * window between when the file is created and the X bit is set. It's small,
+ * but it's there. To fix the window, check for 0 length files as well.
+ *
+ * To find out if a file can be recovered, check the F_RCV_ON bit. Note,
+ * this DOES NOT mean that any initialization has been done, only that we
+ * haven't yet failed at setting up or doing recovery.
+ *
+ * To preserve a recovery file/backing file pair, set the F_RCV_NORM bit.
+ * If that bit is not set when ending a file session:
+ * If the EXF structure paths (rcv_path and rcv_mpath) are not NULL,
+ * they are unlink(2)'d, and free(3)'d.
+ * If the EXF file descriptor (rcv_fd) is not -1, it is closed.
+ *
+ * The backing b+tree file is set up when a file is first edited, so that
+ * the DB package can use it for on-disk caching and/or to snapshot the
+ * file. When the file is first modified, the mail recovery file is created,
+ * the backing file permissions are updated, the file is sync(2)'d to disk,
+ * and the timer is started. Then, at RCV_PERIOD second intervals, the
+ * b+tree file is synced to disk. RCV_PERIOD is measured using SIGALRM, which
+ * means that the data structures (SCR, EXF, the underlying tree structures)
+ * must be consistent when the signal arrives.
+ *
+ * The recovery mail file contains normal mail headers, with two additions,
+ * which occur in THIS order, as the FIRST TWO headers:
+ *
+ * X-vi-recover-file: file_name
+ * X-vi-recover-path: recover_path
+ *
+ * Since newlines delimit the headers, this means that file names cannot have
+ * newlines in them, but that's probably okay. As these files aren't intended
+ * to be long-lived, changing their format won't be too painful.
+ *
+ * Btree files are named "vi.XXXX" and recovery files are named "recover.XXXX".
+ */
+
+#define VI_FHEADER "X-vi-recover-file: "
+#define VI_PHEADER "X-vi-recover-path: "
+
+static int rcv_copy __P((SCR *, int, char *));
+static void rcv_email __P((SCR *, char *));
+static char *rcv_gets __P((char *, size_t, int));
+static int rcv_mailfile __P((SCR *, int, char *));
+static int rcv_mktemp __P((SCR *, char *, char *, int));
+
+/*
+ * rcv_tmp --
+ * Build a file name that will be used as the recovery file.
+ *
+ * PUBLIC: int rcv_tmp __P((SCR *, EXF *, char *));
+ */
+int
+rcv_tmp(sp, ep, name)
+ SCR *sp;
+ EXF *ep;
+ char *name;
+{
+ struct stat sb;
+ int fd;
+ char *dp, *p, path[MAXPATHLEN];
+
+ /*
+ * !!!
+ * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER.
+ *
+ *
+ * If the recovery directory doesn't exist, try and create it. As
+ * the recovery files are themselves protected from reading/writing
+ * by other than the owner, the worst that can happen is that a user
+ * would have permission to remove other user's recovery files. If
+ * the sticky bit has the BSD semantics, that too will be impossible.
+ */
+ if (opts_empty(sp, O_RECDIR, 0))
+ goto err;
+ dp = O_STR(sp, O_RECDIR);
+ if (stat(dp, &sb)) {
+ if (errno != ENOENT || mkdir(dp, 0)) {
+ msgq(sp, M_SYSERR, "%s", dp);
+ goto err;
+ }
+ (void)chmod(dp, S_IRWXU | S_IRWXG | S_IRWXO | S_ISVTX);
+ }
+
+ /* Newlines delimit the mail messages. */
+ for (p = name; *p; ++p)
+ if (*p == '\n') {
+ msgq(sp, M_ERR,
+ "055|Files with newlines in the name are unrecoverable");
+ goto err;
+ }
+
+ (void)snprintf(path, sizeof(path), "%s/vi.XXXXXX", dp);
+ if ((fd = rcv_mktemp(sp, path, dp, S_IRWXU)) == -1)
+ goto err;
+ (void)close(fd);
+
+ if ((ep->rcv_path = strdup(path)) == NULL) {
+ msgq(sp, M_SYSERR, NULL);
+ (void)unlink(path);
+err: msgq(sp, M_ERR,
+ "056|Modifications not recoverable if the session fails");
+ return (1);
+ }
+
+ /* We believe the file is recoverable. */
+ F_SET(ep, F_RCV_ON);
+ return (0);
+}
+
+/*
+ * rcv_init --
+ * Force the file to be snapshotted for recovery.
+ *
+ * PUBLIC: int rcv_init __P((SCR *));
+ */
+int
+rcv_init(sp)
+ SCR *sp;
+{
+ EXF *ep;
+ recno_t lno;
+
+ ep = sp->ep;
+
+ /* Only do this once. */
+ F_CLR(ep, F_FIRSTMODIFY);
+
+ /* If we already know the file isn't recoverable, we're done. */
+ if (!F_ISSET(ep, F_RCV_ON))
+ return (0);
+
+ /* Turn off recoverability until we figure out if this will work. */
+ F_CLR(ep, F_RCV_ON);
+
+ /* Test if we're recovering a file, not editing one. */
+ if (ep->rcv_mpath == NULL) {
+ /* Build a file to mail to the user. */
+ if (rcv_mailfile(sp, 0, NULL))
+ goto err;
+
+ /* Force a read of the entire file. */
+ if (db_last(sp, &lno))
+ goto err;
+
+ /* Turn on a busy message, and sync it to backing store. */
+ sp->gp->scr_busy(sp,
+ "057|Copying file for recovery...", BUSY_ON);
+ if (ep->db->sync(ep->db, R_RECNOSYNC)) {
+ msgq_str(sp, M_SYSERR, ep->rcv_path,
+ "058|Preservation failed: %s");
+ sp->gp->scr_busy(sp, NULL, BUSY_OFF);
+ goto err;
+ }
+ sp->gp->scr_busy(sp, NULL, BUSY_OFF);
+ }
+
+ /* Turn off the owner execute bit. */
+ (void)chmod(ep->rcv_path, S_IRUSR | S_IWUSR);
+
+ /* We believe the file is recoverable. */
+ F_SET(ep, F_RCV_ON);
+ return (0);
+
+err: msgq(sp, M_ERR,
+ "059|Modifications not recoverable if the session fails");
+ return (1);
+}
+
+/*
+ * rcv_sync --
+ * Sync the file, optionally:
+ * flagging the backup file to be preserved
+ * snapshotting the backup file and send email to the user
+ * sending email to the user if the file was modified
+ * ending the file session
+ *
+ * PUBLIC: int rcv_sync __P((SCR *, u_int));
+ */
+int
+rcv_sync(sp, flags)
+ SCR *sp;
+ u_int flags;
+{
+ EXF *ep;
+ int fd, rval;
+ char *dp, buf[1024];
+
+ /* Make sure that there's something to recover/sync. */
+ ep = sp->ep;
+ if (ep == NULL || !F_ISSET(ep, F_RCV_ON))
+ return (0);
+
+ /* Sync the file if it's been modified. */
+ if (F_ISSET(ep, F_MODIFIED)) {
+ SIGBLOCK;
+ if (ep->db->sync(ep->db, R_RECNOSYNC)) {
+ F_CLR(ep, F_RCV_ON | F_RCV_NORM);
+ msgq_str(sp, M_SYSERR,
+ ep->rcv_path, "060|File backup failed: %s");
+ SIGUNBLOCK;
+ return (1);
+ }
+ SIGUNBLOCK;
+
+ /* REQUEST: don't remove backing file on exit. */
+ if (LF_ISSET(RCV_PRESERVE))
+ F_SET(ep, F_RCV_NORM);
+
+ /* REQUEST: send email. */
+ if (LF_ISSET(RCV_EMAIL))
+ rcv_email(sp, ep->rcv_mpath);
+ }
+
+ /*
+ * !!!
+ * Each time the user exec's :preserve, we have to snapshot all of
+ * the recovery information, i.e. it's like the user re-edited the
+ * file. We copy the DB(3) backing file, and then create a new mail
+ * recovery file, it's simpler than exiting and reopening all of the
+ * underlying files.
+ *
+ * REQUEST: snapshot the file.
+ */
+ rval = 0;
+ if (LF_ISSET(RCV_SNAPSHOT)) {
+ if (opts_empty(sp, O_RECDIR, 0))
+ goto err;
+ dp = O_STR(sp, O_RECDIR);
+ (void)snprintf(buf, sizeof(buf), "%s/vi.XXXXXX", dp);
+ if ((fd = rcv_mktemp(sp, buf, dp, S_IRUSR | S_IWUSR)) == -1)
+ goto err;
+ sp->gp->scr_busy(sp,
+ "061|Copying file for recovery...", BUSY_ON);
+ if (rcv_copy(sp, fd, ep->rcv_path) ||
+ close(fd) || rcv_mailfile(sp, 1, buf)) {
+ (void)unlink(buf);
+ (void)close(fd);
+ rval = 1;
+ }
+ sp->gp->scr_busy(sp, NULL, BUSY_OFF);
+ }
+ if (0) {
+err: rval = 1;
+ }
+
+ /* REQUEST: end the file session. */
+ if (LF_ISSET(RCV_ENDSESSION) && file_end(sp, NULL, 1))
+ rval = 1;
+
+ return (rval);
+}
+
+/*
+ * rcv_mailfile --
+ * Build the file to mail to the user.
+ */
+static int
+rcv_mailfile(sp, issync, cp_path)
+ SCR *sp;
+ int issync;
+ char *cp_path;
+{
+ EXF *ep;
+ GS *gp;
+ struct passwd *pw;
+ size_t len;
+ time_t now;
+ uid_t uid;
+ int fd;
+ char *dp, *p, *t, buf[4096], mpath[MAXPATHLEN];
+ char *t1, *t2, *t3;
+
+ /*
+ * XXX
+ * MAXHOSTNAMELEN is in various places on various systems, including
+ * <netdb.h> and <sys/socket.h>. If not found, use a large default.
+ */
+#ifndef MAXHOSTNAMELEN
+#define MAXHOSTNAMELEN 1024
+#endif
+ char host[MAXHOSTNAMELEN];
+
+ gp = sp->gp;
+ if ((pw = getpwuid(uid = getuid())) == NULL) {
+ msgq(sp, M_ERR,
+ "062|Information on user id %u not found", uid);
+ return (1);
+ }
+
+ if (opts_empty(sp, O_RECDIR, 0))
+ return (1);
+ dp = O_STR(sp, O_RECDIR);
+ (void)snprintf(mpath, sizeof(mpath), "%s/recover.XXXXXX", dp);
+ if ((fd = rcv_mktemp(sp, mpath, dp, S_IRUSR | S_IWUSR)) == -1)
+ return (1);
+
+ /*
+ * XXX
+ * We keep an open lock on the file so that the recover option can
+ * distinguish between files that are live and those that need to
+ * be recovered. There's an obvious window between the mkstemp call
+ * and the lock, but it's pretty small.
+ */
+ ep = sp->ep;
+ if (file_lock(sp, NULL, NULL, fd, 1) != LOCK_SUCCESS)
+ msgq(sp, M_SYSERR, "063|Unable to lock recovery file");
+ if (!issync) {
+ /* Save the recover file descriptor, and mail path. */
+ ep->rcv_fd = fd;
+ if ((ep->rcv_mpath = strdup(mpath)) == NULL) {
+ msgq(sp, M_SYSERR, NULL);
+ goto err;
+ }
+ cp_path = ep->rcv_path;
+ }
+
+ /*
+ * XXX
+ * We can't use stdio(3) here. The problem is that we may be using
+ * fcntl(2), so if ANY file descriptor into the file is closed, the
+ * lock is lost. So, we could never close the FILE *, even if we
+ * dup'd the fd first.
+ */
+ t = sp->frp->name;
+ if ((p = strrchr(t, '/')) == NULL)
+ p = t;
+ else
+ ++p;
+ (void)time(&now);
+ (void)gethostname(host, sizeof(host));
+ len = snprintf(buf, sizeof(buf),
+ "%s%s\n%s%s\n%s\n%s\n%s%s\n%s%s\n%s\n\n",
+ VI_FHEADER, t, /* Non-standard. */
+ VI_PHEADER, cp_path, /* Non-standard. */
+ "Reply-To: root",
+ "From: root (Nvi recovery program)",
+ "To: ", pw->pw_name,
+ "Subject: Nvi saved the file ", p,
+ "Precedence: bulk"); /* For vacation(1). */
+ if (len > sizeof(buf) - 1)
+ goto lerr;
+ if (write(fd, buf, len) != len)
+ goto werr;
+
+ len = snprintf(buf, sizeof(buf),
+ "%s%.24s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n\n",
+ "On ", ctime(&now), ", the user ", pw->pw_name,
+ " was editing a file named ", t, " on the machine ",
+ host, ", when it was saved for recovery. ",
+ "You can recover most, if not all, of the changes ",
+ "to this file using the -r option to ", gp->progname, ":\n\n\t",
+ gp->progname, " -r ", t);
+ if (len > sizeof(buf) - 1) {
+lerr: msgq(sp, M_ERR, "064|Recovery file buffer overrun");
+ goto err;
+ }
+
+ /*
+ * Format the message. (Yes, I know it's silly.)
+ * Requires that the message end in a <newline>.
+ */
+#define FMTCOLS 60
+ for (t1 = buf; len > 0; len -= t2 - t1, t1 = t2) {
+ /* Check for a short length. */
+ if (len <= FMTCOLS) {
+ t2 = t1 + (len - 1);
+ goto wout;
+ }
+
+ /* Check for a required <newline>. */
+ t2 = strchr(t1, '\n');
+ if (t2 - t1 <= FMTCOLS)
+ goto wout;
+
+ /* Find the closest space, if any. */
+ for (t3 = t2; t2 > t1; --t2)
+ if (*t2 == ' ') {
+ if (t2 - t1 <= FMTCOLS)
+ goto wout;
+ t3 = t2;
+ }
+ t2 = t3;
+
+ /* t2 points to the last character to display. */
+wout: *t2++ = '\n';
+
+ /* t2 points one after the last character to display. */
+ if (write(fd, t1, t2 - t1) != t2 - t1)
+ goto werr;
+ }
+
+ if (issync) {
+ rcv_email(sp, mpath);
+ if (close(fd)) {
+werr: msgq(sp, M_SYSERR, "065|Recovery file");
+ goto err;
+ }
+ }
+ return (0);
+
+err: if (!issync)
+ ep->rcv_fd = -1;
+ if (fd != -1)
+ (void)close(fd);
+ return (1);
+}
+
+/*
+ * people making love
+ * never exactly the same
+ * just like a snowflake
+ *
+ * rcv_list --
+ * List the files that can be recovered by this user.
+ *
+ * PUBLIC: int rcv_list __P((SCR *));
+ */
+int
+rcv_list(sp)
+ SCR *sp;
+{
+ struct dirent *dp;
+ struct stat sb;
+ DIR *dirp;
+ FILE *fp;
+ int found;
+ char *p, *t, file[MAXPATHLEN], path[MAXPATHLEN];
+
+ /* Open the recovery directory for reading. */
+ if (opts_empty(sp, O_RECDIR, 0))
+ return (1);
+ p = O_STR(sp, O_RECDIR);
+ if (chdir(p) || (dirp = opendir(".")) == NULL) {
+ msgq_str(sp, M_SYSERR, p, "recdir: %s");
+ return (1);
+ }
+
+ /* Read the directory. */
+ for (found = 0; (dp = readdir(dirp)) != NULL;) {
+ if (strncmp(dp->d_name, "recover.", 8))
+ continue;
+
+ /*
+ * If it's readable, it's recoverable.
+ *
+ * XXX
+ * Should be "r", we don't want to write the file. However,
+ * if we're using fcntl(2), there's no way to lock a file
+ * descriptor that's not open for writing.
+ */
+ if ((fp = fopen(dp->d_name, "r+")) == NULL)
+ continue;
+
+ switch (file_lock(sp, NULL, NULL, fileno(fp), 1)) {
+ case LOCK_FAILED:
+ /*
+ * XXX
+ * Assume that a lock can't be acquired, but that we
+ * should permit recovery anyway. If this is wrong,
+ * and someone else is using the file, we're going to
+ * die horribly.
+ */
+ break;
+ case LOCK_SUCCESS:
+ break;
+ case LOCK_UNAVAIL:
+ /* If it's locked, it's live. */
+ (void)fclose(fp);
+ continue;
+ }
+
+ /* Check the headers. */
+ if (fgets(file, sizeof(file), fp) == NULL ||
+ strncmp(file, VI_FHEADER, sizeof(VI_FHEADER) - 1) ||
+ (p = strchr(file, '\n')) == NULL ||
+ fgets(path, sizeof(path), fp) == NULL ||
+ strncmp(path, VI_PHEADER, sizeof(VI_PHEADER) - 1) ||
+ (t = strchr(path, '\n')) == NULL) {
+ msgq_str(sp, M_ERR, dp->d_name,
+ "066|%s: malformed recovery file");
+ goto next;
+ }
+ *p = *t = '\0';
+
+ /*
+ * If the file doesn't exist, it's an orphaned recovery file,
+ * toss it.
+ *
+ * XXX
+ * This can occur if the backup file was deleted and we crashed
+ * before deleting the email file.
+ */
+ errno = 0;
+ if (stat(path + sizeof(VI_PHEADER) - 1, &sb) &&
+ errno == ENOENT) {
+ (void)unlink(dp->d_name);
+ goto next;
+ }
+
+ /* Get the last modification time and display. */
+ (void)fstat(fileno(fp), &sb);
+ (void)printf("%.24s: %s\n",
+ ctime(&sb.st_mtime), file + sizeof(VI_FHEADER) - 1);
+ found = 1;
+
+ /* Close, discarding lock. */
+next: (void)fclose(fp);
+ }
+ if (found == 0)
+ (void)printf("vi: no files to recover.\n");
+ (void)closedir(dirp);
+ return (0);
+}
+
+/*
+ * rcv_read --
+ * Start a recovered file as the file to edit.
+ *
+ * PUBLIC: int rcv_read __P((SCR *, FREF *));
+ */
+int
+rcv_read(sp, frp)
+ SCR *sp;
+ FREF *frp;
+{
+ struct dirent *dp;
+ struct stat sb;
+ DIR *dirp;
+ EXF *ep;
+ time_t rec_mtime;
+ int fd, found, locked, requested, sv_fd;
+ char *name, *p, *t, *rp, *recp, *pathp;
+ char file[MAXPATHLEN], path[MAXPATHLEN], recpath[MAXPATHLEN];
+
+ if (opts_empty(sp, O_RECDIR, 0))
+ return (1);
+ rp = O_STR(sp, O_RECDIR);
+ if ((dirp = opendir(rp)) == NULL) {
+ msgq_str(sp, M_ERR, rp, "%s");
+ return (1);
+ }
+
+ name = frp->name;
+ sv_fd = -1;
+ rec_mtime = 0;
+ recp = pathp = NULL;
+ for (found = requested = 0; (dp = readdir(dirp)) != NULL;) {
+ if (strncmp(dp->d_name, "recover.", 8))
+ continue;
+ (void)snprintf(recpath,
+ sizeof(recpath), "%s/%s", rp, dp->d_name);
+
+ /*
+ * If it's readable, it's recoverable. It would be very
+ * nice to use stdio(3), but, we can't because that would
+ * require closing and then reopening the file so that we
+ * could have a lock and still close the FP. Another tip
+ * of the hat to fcntl(2).
+ *
+ * XXX
+ * Should be O_RDONLY, we don't want to write it. However,
+ * if we're using fcntl(2), there's no way to lock a file
+ * descriptor that's not open for writing.
+ */
+ if ((fd = open(recpath, O_RDWR, 0)) == -1)
+ continue;
+
+ switch (file_lock(sp, NULL, NULL, fd, 1)) {
+ case LOCK_FAILED:
+ /*
+ * XXX
+ * Assume that a lock can't be acquired, but that we
+ * should permit recovery anyway. If this is wrong,
+ * and someone else is using the file, we're going to
+ * die horribly.
+ */
+ locked = 0;
+ break;
+ case LOCK_SUCCESS:
+ locked = 1;
+ break;
+ case LOCK_UNAVAIL:
+ /* If it's locked, it's live. */
+ (void)close(fd);
+ continue;
+ }
+
+ /* Check the headers. */
+ if (rcv_gets(file, sizeof(file), fd) == NULL ||
+ strncmp(file, VI_FHEADER, sizeof(VI_FHEADER) - 1) ||
+ (p = strchr(file, '\n')) == NULL ||
+ rcv_gets(path, sizeof(path), fd) == NULL ||
+ strncmp(path, VI_PHEADER, sizeof(VI_PHEADER) - 1) ||
+ (t = strchr(path, '\n')) == NULL) {
+ msgq_str(sp, M_ERR, recpath,
+ "067|%s: malformed recovery file");
+ goto next;
+ }
+ *p = *t = '\0';
+ ++found;
+
+ /*
+ * If the file doesn't exist, it's an orphaned recovery file,
+ * toss it.
+ *
+ * XXX
+ * This can occur if the backup file was deleted and we crashed
+ * before deleting the email file.
+ */
+ errno = 0;
+ if (stat(path + sizeof(VI_PHEADER) - 1, &sb) &&
+ errno == ENOENT) {
+ (void)unlink(dp->d_name);
+ goto next;
+ }
+
+ /* Check the file name. */
+ if (strcmp(file + sizeof(VI_FHEADER) - 1, name))
+ goto next;
+
+ ++requested;
+
+ /*
+ * If we've found more than one, take the most recent.
+ *
+ * XXX
+ * Since we're using st_mtime, for portability reasons,
+ * we only get a single second granularity, instead of
+ * getting it right.
+ */
+ (void)fstat(fd, &sb);
+ if (recp == NULL || rec_mtime < sb.st_mtime) {
+ p = recp;
+ t = pathp;
+ if ((recp = strdup(recpath)) == NULL) {
+ msgq(sp, M_SYSERR, NULL);
+ recp = p;
+ goto next;
+ }
+ if ((pathp = strdup(path)) == NULL) {
+ msgq(sp, M_SYSERR, NULL);
+ free(recp);
+ recp = p;
+ pathp = t;
+ goto next;
+ }
+ if (p != NULL) {
+ free(p);
+ free(t);
+ }
+ rec_mtime = sb.st_mtime;
+ if (sv_fd != -1)
+ (void)close(sv_fd);
+ sv_fd = fd;
+ } else
+next: (void)close(fd);
+ }
+ (void)closedir(dirp);
+
+ if (recp == NULL) {
+ msgq_str(sp, M_INFO, name,
+ "068|No files named %s, readable by you, to recover");
+ return (1);
+ }
+ if (found) {
+ if (requested > 1)
+ msgq(sp, M_INFO,
+ "069|There are older versions of this file for you to recover");
+ if (found > requested)
+ msgq(sp, M_INFO,
+ "070|There are other files for you to recover");
+ }
+
+ /*
+ * Create the FREF structure, start the btree file.
+ *
+ * XXX
+ * file_init() is going to set ep->rcv_path.
+ */
+ if (file_init(sp, frp, pathp + sizeof(VI_PHEADER) - 1, 0)) {
+ free(recp);
+ free(pathp);
+ (void)close(sv_fd);
+ return (1);
+ }
+
+ /*
+ * We keep an open lock on the file so that the recover option can
+ * distinguish between files that are live and those that need to
+ * be recovered. The lock is already acquired, just copy it.
+ */
+ ep = sp->ep;
+ ep->rcv_mpath = recp;
+ ep->rcv_fd = sv_fd;
+ if (!locked)
+ F_SET(frp, FR_UNLOCKED);
+
+ /* We believe the file is recoverable. */
+ F_SET(ep, F_RCV_ON);
+ return (0);
+}
+
+/*
+ * rcv_copy --
+ * Copy a recovery file.
+ */
+static int
+rcv_copy(sp, wfd, fname)
+ SCR *sp;
+ int wfd;
+ char *fname;
+{
+ int nr, nw, off, rfd;
+ char buf[8 * 1024];
+
+ if ((rfd = open(fname, O_RDONLY, 0)) == -1)
+ goto err;
+ while ((nr = read(rfd, buf, sizeof(buf))) > 0)
+ for (off = 0; nr; nr -= nw, off += nw)
+ if ((nw = write(wfd, buf + off, nr)) < 0)
+ goto err;
+ if (nr == 0)
+ return (0);
+
+err: msgq_str(sp, M_SYSERR, fname, "%s");
+ return (1);
+}
+
+/*
+ * rcv_gets --
+ * Fgets(3) for a file descriptor.
+ */
+static char *
+rcv_gets(buf, len, fd)
+ char *buf;
+ size_t len;
+ int fd;
+{
+ int nr;
+ char *p;
+
+ if ((nr = read(fd, buf, len - 1)) == -1)
+ return (NULL);
+ if ((p = strchr(buf, '\n')) == NULL)
+ return (NULL);
+ (void)lseek(fd, (off_t)((p - buf) + 1), SEEK_SET);
+ return (buf);
+}
+
+/*
+ * rcv_mktemp --
+ * Paranoid make temporary file routine.
+ */
+static int
+rcv_mktemp(sp, path, dname, perms)
+ SCR *sp;
+ char *path, *dname;
+ int perms;
+{
+ int fd;
+
+ /*
+ * !!!
+ * We expect mkstemp(3) to set the permissions correctly. On
+ * historic System V systems, mkstemp didn't. Do it here, on
+ * GP's.
+ *
+ * XXX
+ * The variable perms should really be a mode_t, and it would
+ * be nice to use fchmod(2) instead of chmod(2), here.
+ */
+ if ((fd = mkstemp(path)) == -1)
+ msgq_str(sp, M_SYSERR, dname, "%s");
+ else
+ (void)chmod(path, perms);
+ return (fd);
+}
+
+/*
+ * rcv_email --
+ * Send email.
+ */
+static void
+rcv_email(sp, fname)
+ SCR *sp;
+ char *fname;
+{
+ struct stat sb;
+ char buf[MAXPATHLEN * 2 + 20];
+
+ if (_PATH_SENDMAIL[0] != '/' || stat(_PATH_SENDMAIL, &sb))
+ msgq_str(sp, M_SYSERR,
+ _PATH_SENDMAIL, "071|not sending email: %s");
+ else {
+ /*
+ * !!!
+ * If you need to port this to a system that doesn't have
+ * sendmail, the -t flag causes sendmail to read the message
+ * for the recipients instead of specifying them some other
+ * way.
+ */
+ (void)snprintf(buf, sizeof(buf),
+ "%s -t < %s", _PATH_SENDMAIL, fname);
+ (void)system(buf);
+ }
+}
OpenPOWER on IntegriCloud