1 /* $NetBSD: recover.c,v 1.5 2014/01/26 21:43:45 christos Exp $ */
3 * Copyright (c) 1993, 1994
4 * The Regents of the University of California. All rights reserved.
5 * Copyright (c) 1993, 1994, 1995, 1996
6 * Keith Bostic. All rights reserved.
8 * See the LICENSE file for redistribution information.
13 #include <sys/cdefs.h>
16 static const char sccsid
[] = "Id: recover.c,v 10.31 2001/11/01 15:24:44 skimo Exp (Berkeley) Date: 2001/11/01 15:24:44 ";
19 __RCSID("$NetBSD: recover.c,v 1.5 2014/01/26 21:43:45 christos Exp $");
22 #include <sys/param.h>
23 #include <sys/types.h> /* XXX: param.h may not have included types.h */
24 #include <sys/queue.h>
28 * We include <sys/file.h>, because the open #defines were found there
29 * on historical systems. We also include <fcntl.h> because the open(2)
30 * #defines are found there on newer systems.
34 #include <bitstring.h>
47 #include "pathnames.h"
52 * The basic scheme is as follows. In the EXF structure, we maintain full
53 * paths of a b+tree file and a mail recovery file. The former is the file
54 * used as backing store by the DB package. The latter is the file that
55 * contains an email message to be sent to the user if we crash. The two
56 * simple states of recovery are:
58 * + first starting the edit session:
59 * the b+tree file exists and is mode 700, the mail recovery
61 * + after the file has been modified:
62 * the b+tree file exists and is mode 600, the mail recovery
63 * file exists, and is exclusively locked.
65 * In the EXF structure we maintain a file descriptor that is the locked
66 * file descriptor for the mail recovery file. NOTE: we sometimes have to
67 * do locking with fcntl(2). This is a problem because if you close(2) any
68 * file descriptor associated with the file, ALL of the locks go away. Be
69 * sure to remember that if you have to modify the recovery code. (It has
70 * been rhetorically asked of what the designers could have been thinking
71 * when they did that interface. The answer is simple: they weren't.)
73 * To find out if a recovery file/backing file pair are in use, try to get
74 * a lock on the recovery file.
76 * To find out if a backing file can be deleted at boot time, check for an
77 * owner execute bit. (Yes, I know it's ugly, but it's either that or put
78 * special stuff into the backing file itself, or correlate the files at
79 * boot time, neither of which looks like fun.) Note also that there's a
80 * window between when the file is created and the X bit is set. It's small,
81 * but it's there. To fix the window, check for 0 length files as well.
83 * To find out if a file can be recovered, check the F_RCV_ON bit. Note,
84 * this DOES NOT mean that any initialization has been done, only that we
85 * haven't yet failed at setting up or doing recovery.
87 * To preserve a recovery file/backing file pair, set the F_RCV_NORM bit.
88 * If that bit is not set when ending a file session:
89 * If the EXF structure paths (rcv_path and rcv_mpath) are not NULL,
90 * they are unlink(2)'d, and free(3)'d.
91 * If the EXF file descriptor (rcv_fd) is not -1, it is closed.
93 * The backing b+tree file is set up when a file is first edited, so that
94 * the DB package can use it for on-disk caching and/or to snapshot the
95 * file. When the file is first modified, the mail recovery file is created,
96 * the backing file permissions are updated, the file is sync(2)'d to disk,
97 * and the timer is started. Then, at RCV_PERIOD second intervals, the
98 * b+tree file is synced to disk. RCV_PERIOD is measured using SIGALRM, which
99 * means that the data structures (SCR, EXF, the underlying tree structures)
100 * must be consistent when the signal arrives.
102 * The recovery mail file contains normal mail headers, with two additions,
103 * which occur in THIS order, as the FIRST TWO headers:
105 * X-vi-recover-file: file_name
106 * X-vi-recover-path: recover_path
108 * Since newlines delimit the headers, this means that file names cannot have
109 * newlines in them, but that's probably okay. As these files aren't intended
110 * to be long-lived, changing their format won't be too painful.
112 * Btree files are named "vi.XXXX" and recovery files are named "recover.XXXX".
115 #define VI_FHEADER "X-vi-recover-file: "
116 #define VI_PHEADER "X-vi-recover-path: "
118 static int rcv_copy
__P((SCR
*, int, char *));
119 static void rcv_email
__P((SCR
*, char *));
120 static char *rcv_gets
__P((char *, size_t, int));
121 static int rcv_mailfile
__P((SCR
*, int, char *));
122 static int rcv_mktemp
__P((SCR
*, char *, const char *, int));
126 * Build a file name that will be used as the recovery file.
128 * PUBLIC: int rcv_tmp __P((SCR *, EXF *, char *));
131 rcv_tmp(SCR
*sp
, EXF
*ep
, char *name
)
135 char path
[MAXPATHLEN
];
140 * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER.
143 * If the recovery directory doesn't exist, try and create it. As
144 * the recovery files are themselves protected from reading/writing
145 * by other than the owner, the worst that can happen is that a user
146 * would have permission to remove other user's recovery files. If
147 * the sticky bit has the BSD semantics, that too will be impossible.
149 if (opts_empty(sp
, O_RECDIR
, 0))
151 dp
= O_STR(sp
, O_RECDIR
);
153 if (errno
!= ENOENT
|| mkdir(dp
, 0)) {
154 msgq(sp
, M_SYSERR
, "%s", dp
);
157 (void)chmod(dp
, S_IRWXU
| S_IRWXG
| S_IRWXO
| S_ISVTX
);
160 /* Newlines delimit the mail messages. */
161 if (strchr(name
, '\n')) {
163 "055|Files with newlines in the name are unrecoverable");
167 (void)snprintf(path
, sizeof(path
), "%s/vi.XXXXXX", dp
);
168 if ((fd
= rcv_mktemp(sp
, path
, dp
, S_IRWXU
)) == -1)
172 if ((ep
->rcv_path
= strdup(path
)) == NULL
) {
173 msgq(sp
, M_SYSERR
, NULL
);
176 "056|Modifications not recoverable if the session fails");
180 /* We believe the file is recoverable. */
187 * Force the file to be snapshotted for recovery.
189 * PUBLIC: int rcv_init __P((SCR *));
199 /* Only do this once. */
200 F_CLR(ep
, F_FIRSTMODIFY
);
202 /* If we already know the file isn't recoverable, we're done. */
203 if (!F_ISSET(ep
, F_RCV_ON
))
206 /* Turn off recoverability until we figure out if this will work. */
209 /* Test if we're recovering a file, not editing one. */
210 if (ep
->rcv_mpath
== NULL
) {
211 /* Build a file to mail to the user. */
212 if (rcv_mailfile(sp
, 0, NULL
))
215 /* Force a read of the entire file. */
216 if (db_last(sp
, &lno
))
219 /* Turn on a busy message, and sync it to backing store. */
221 "057|Copying file for recovery...", BUSY_ON
);
222 if (ep
->db
->sync(ep
->db
, 0)) {
223 msgq_str(sp
, M_SYSERR
, ep
->rcv_path
,
224 "058|Preservation failed: %s");
225 sp
->gp
->scr_busy(sp
, NULL
, BUSY_OFF
);
228 sp
->gp
->scr_busy(sp
, NULL
, BUSY_OFF
);
231 /* Turn off the owner execute bit. */
232 (void)chmod(ep
->rcv_path
, S_IRUSR
| S_IWUSR
);
234 /* We believe the file is recoverable. */
239 "059|Modifications not recoverable if the session fails");
245 * Sync the file, optionally:
246 * flagging the backup file to be preserved
247 * snapshotting the backup file and send email to the user
248 * sending email to the user if the file was modified
249 * ending the file session
251 * PUBLIC: int rcv_sync __P((SCR *, u_int));
254 rcv_sync(SCR
*sp
, u_int flags
)
261 /* Make sure that there's something to recover/sync. */
263 if (ep
== NULL
|| !F_ISSET(ep
, F_RCV_ON
))
266 /* Sync the file if it's been modified. */
267 if (F_ISSET(ep
, F_MODIFIED
)) {
269 * If we are using a db1 version of the database,
270 * we want to sync the underlying btree not the
271 * recno tree which is transient anyway.
274 #define R_RECNOSYNC 0
276 if (ep
->db
->sync(ep
->db
, R_RECNOSYNC
)) {
277 F_CLR(ep
, F_RCV_ON
| F_RCV_NORM
);
278 msgq_str(sp
, M_SYSERR
,
279 ep
->rcv_path
, "060|File backup failed: %s");
283 /* REQUEST: don't remove backing file on exit. */
284 if (LF_ISSET(RCV_PRESERVE
))
285 F_SET(ep
, F_RCV_NORM
);
287 /* REQUEST: send email. */
288 if (LF_ISSET(RCV_EMAIL
))
289 rcv_email(sp
, ep
->rcv_mpath
);
294 * Each time the user exec's :preserve, we have to snapshot all of
295 * the recovery information, i.e. it's like the user re-edited the
296 * file. We copy the DB(3) backing file, and then create a new mail
297 * recovery file, it's simpler than exiting and reopening all of the
300 * REQUEST: snapshot the file.
303 if (LF_ISSET(RCV_SNAPSHOT
)) {
304 if (opts_empty(sp
, O_RECDIR
, 0))
306 dp
= O_STR(sp
, O_RECDIR
);
307 (void)snprintf(buf
, sizeof(buf
), "%s/vi.XXXXXX", dp
);
308 if ((fd
= rcv_mktemp(sp
, buf
, dp
, S_IRUSR
| S_IWUSR
)) == -1)
311 "061|Copying file for recovery...", BUSY_ON
);
312 if (rcv_copy(sp
, fd
, ep
->rcv_path
) ||
313 close(fd
) || rcv_mailfile(sp
, 1, buf
)) {
318 sp
->gp
->scr_busy(sp
, NULL
, BUSY_OFF
);
324 /* REQUEST: end the file session. */
325 if (LF_ISSET(RCV_ENDSESSION
) && file_end(sp
, NULL
, 1))
333 * Build the file to mail to the user.
336 rcv_mailfile(SCR
*sp
, int issync
, char *cp_path
)
345 char *p
, *t
, buf
[4096], mpath
[MAXPATHLEN
];
351 * MAXHOSTNAMELEN is in various places on various systems, including
352 * <netdb.h> and <sys/socket.h>. If not found, use a large default.
354 #ifndef MAXHOSTNAMELEN
355 #define MAXHOSTNAMELEN 1024
357 char host
[MAXHOSTNAMELEN
];
360 if ((pw
= getpwuid(uid
= getuid())) == NULL
) {
362 "062|Information on user id %u not found", uid
);
366 if (opts_empty(sp
, O_RECDIR
, 0))
368 dp
= O_STR(sp
, O_RECDIR
);
369 (void)snprintf(mpath
, sizeof(mpath
), "%s/recover.XXXXXX", dp
);
370 if ((fd
= rcv_mktemp(sp
, mpath
, dp
, S_IRUSR
| S_IWUSR
)) == -1)
375 * We keep an open lock on the file so that the recover option can
376 * distinguish between files that are live and those that need to
377 * be recovered. There's an obvious window between the mkstemp call
378 * and the lock, but it's pretty small.
381 if (file_lock(sp
, NULL
, NULL
, fd
, 1) != LOCK_SUCCESS
)
382 msgq(sp
, M_SYSERR
, "063|Unable to lock recovery file");
384 /* Save the recover file descriptor, and mail path. */
386 if ((ep
->rcv_mpath
= strdup(mpath
)) == NULL
) {
387 msgq(sp
, M_SYSERR
, NULL
);
390 cp_path
= ep
->rcv_path
;
395 * We can't use stdio(3) here. The problem is that we may be using
396 * fcntl(2), so if ANY file descriptor into the file is closed, the
397 * lock is lost. So, we could never close the FILE *, even if we
398 * dup'd the fd first.
401 if ((p
= strrchr(t
, '/')) == NULL
)
406 (void)gethostname(host
, sizeof(host
));
407 len
= snprintf(buf
, sizeof(buf
),
408 "%s%s\n%s%s\n%s\n%s\n%s%s\n%s%s\n%s\n\n",
409 VI_FHEADER
, t
, /* Non-standard. */
410 VI_PHEADER
, cp_path
, /* Non-standard. */
412 "From: root (Nvi recovery program)",
414 "Subject: Nvi saved the file ", p
,
415 "Precedence: bulk"); /* For vacation(1). */
416 if (len
> sizeof(buf
) - 1)
418 if ((size_t)write(fd
, buf
, len
) != len
)
421 len
= snprintf(buf
, sizeof(buf
),
422 "%s%.24s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n\n",
423 "On ", ctime(&now
), ", the user ", pw
->pw_name
,
424 " was editing a file named ", t
, " on the machine ",
425 host
, ", when it was saved for recovery. ",
426 "You can recover most, if not all, of the changes ",
427 "to this file using the -r option to ", gp
->progname
, ":\n\n\t",
428 gp
->progname
, " -r ", t
);
429 if (len
> sizeof(buf
) - 1) {
430 lerr
: msgq(sp
, M_ERR
, "064|Recovery file buffer overrun");
435 * Format the message. (Yes, I know it's silly.)
436 * Requires that the message end in a <newline>.
439 for (t1
= buf
; len
> 0; len
-= t2
- t1
, t1
= t2
) {
440 /* Check for a short length. */
441 if (len
<= FMTCOLS
) {
446 /* Check for a required <newline>. */
447 t2
= strchr(t1
, '\n');
448 if (t2
- t1
<= FMTCOLS
)
451 /* Find the closest space, if any. */
452 for (t3
= t2
; t2
> t1
; --t2
)
454 if (t2
- t1
<= FMTCOLS
)
460 /* t2 points to the last character to display. */
463 /* t2 points one after the last character to display. */
464 if (write(fd
, t1
, t2
- t1
) != t2
- t1
)
469 rcv_email(sp
, mpath
);
471 werr
: msgq(sp
, M_SYSERR
, "065|Recovery file");
486 * never exactly the same
487 * just like a snowflake
490 * List the files that can be recovered by this user.
492 * PUBLIC: int rcv_list __P((SCR *));
504 char file
[MAXPATHLEN
], path
[MAXPATHLEN
];
506 /* Open the recovery directory for reading. */
507 if (opts_empty(sp
, O_RECDIR
, 0))
509 d
= O_STR(sp
, O_RECDIR
);
510 if (chdir(d
) || (dirp
= opendir(".")) == NULL
) {
511 msgq_str(sp
, M_SYSERR
, d
, "recdir: %s");
515 /* Read the directory. */
516 for (found
= 0; (dp
= readdir(dirp
)) != NULL
;) {
517 if (strncmp(dp
->d_name
, "recover.", 8))
521 * If it's readable, it's recoverable.
524 * Should be "r", we don't want to write the file. However,
525 * if we're using fcntl(2), there's no way to lock a file
526 * descriptor that's not open for writing.
528 if ((fp
= fopen(dp
->d_name
, "r+")) == NULL
)
531 switch (file_lock(sp
, NULL
, NULL
, fileno(fp
), 1)) {
535 * Assume that a lock can't be acquired, but that we
536 * should permit recovery anyway. If this is wrong,
537 * and someone else is using the file, we're going to
544 /* If it's locked, it's live. */
549 /* Check the headers. */
550 if (fgets(file
, sizeof(file
), fp
) == NULL
||
551 strncmp(file
, VI_FHEADER
, sizeof(VI_FHEADER
) - 1) ||
552 (p
= strchr(file
, '\n')) == NULL
||
553 fgets(path
, sizeof(path
), fp
) == NULL
||
554 strncmp(path
, VI_PHEADER
, sizeof(VI_PHEADER
) - 1) ||
555 (t
= strchr(path
, '\n')) == NULL
) {
556 msgq_str(sp
, M_ERR
, dp
->d_name
,
557 "066|%s: malformed recovery file");
563 * If the file doesn't exist, it's an orphaned recovery file,
567 * This can occur if the backup file was deleted and we crashed
568 * before deleting the email file.
571 if (stat(path
+ sizeof(VI_PHEADER
) - 1, &sb
) &&
573 (void)unlink(dp
->d_name
);
577 /* Get the last modification time and display. */
578 (void)fstat(fileno(fp
), &sb
);
579 (void)printf("%.24s: %s\n",
580 ctime(&sb
.st_mtime
), file
+ sizeof(VI_FHEADER
) - 1);
583 /* Close, discarding lock. */
584 next
: (void)fclose(fp
);
587 (void)printf("%s: No files to recover\n", sp
->gp
->progname
);
588 (void)closedir(dirp
);
594 * Start a recovered file as the file to edit.
596 * PUBLIC: int rcv_read __P((SCR *, FREF *));
599 rcv_read(SCR
*sp
, FREF
*frp
)
606 int fd
, found
, locked
= 0, requested
, sv_fd
;
607 char *name
, *p
, *t
, *recp
, *pathp
;
609 char file
[MAXPATHLEN
], path
[MAXPATHLEN
], recpath
[MAXPATHLEN
];
611 if (opts_empty(sp
, O_RECDIR
, 0))
613 rp
= O_STR(sp
, O_RECDIR
);
614 if ((dirp
= opendir(rp
)) == NULL
) {
615 msgq_str(sp
, M_ERR
, rp
, "%s");
623 for (found
= requested
= 0; (dp
= readdir(dirp
)) != NULL
;) {
624 if (strncmp(dp
->d_name
, "recover.", 8))
626 (void)snprintf(recpath
,
627 sizeof(recpath
), "%s/%s", rp
, dp
->d_name
);
630 * If it's readable, it's recoverable. It would be very
631 * nice to use stdio(3), but, we can't because that would
632 * require closing and then reopening the file so that we
633 * could have a lock and still close the FP. Another tip
634 * of the hat to fcntl(2).
637 * Should be O_RDONLY, we don't want to write it. However,
638 * if we're using fcntl(2), there's no way to lock a file
639 * descriptor that's not open for writing.
641 if ((fd
= open(recpath
, O_RDWR
, 0)) == -1)
644 switch (file_lock(sp
, NULL
, NULL
, fd
, 1)) {
648 * Assume that a lock can't be acquired, but that we
649 * should permit recovery anyway. If this is wrong,
650 * and someone else is using the file, we're going to
659 /* If it's locked, it's live. */
664 /* Check the headers. */
665 if (rcv_gets(file
, sizeof(file
), fd
) == NULL
||
666 strncmp(file
, VI_FHEADER
, sizeof(VI_FHEADER
) - 1) ||
667 (p
= strchr(file
, '\n')) == NULL
||
668 rcv_gets(path
, sizeof(path
), fd
) == NULL
||
669 strncmp(path
, VI_PHEADER
, sizeof(VI_PHEADER
) - 1) ||
670 (t
= strchr(path
, '\n')) == NULL
) {
671 msgq_str(sp
, M_ERR
, recpath
,
672 "067|%s: malformed recovery file");
679 * If the file doesn't exist, it's an orphaned recovery file,
683 * This can occur if the backup file was deleted and we crashed
684 * before deleting the email file.
687 if (stat(path
+ sizeof(VI_PHEADER
) - 1, &sb
) &&
689 (void)unlink(dp
->d_name
);
693 /* Check the file name. */
694 if (strcmp(file
+ sizeof(VI_FHEADER
) - 1, name
))
700 * If we've found more than one, take the most recent.
703 * Since we're using st_mtime, for portability reasons,
704 * we only get a single second granularity, instead of
707 (void)fstat(fd
, &sb
);
708 if (recp
== NULL
|| rec_mtime
< sb
.st_mtime
) {
711 if ((recp
= strdup(recpath
)) == NULL
) {
712 msgq(sp
, M_SYSERR
, NULL
);
716 if ((pathp
= strdup(path
)) == NULL
) {
717 msgq(sp
, M_SYSERR
, NULL
);
727 rec_mtime
= sb
.st_mtime
;
732 next
: (void)close(fd
);
734 (void)closedir(dirp
);
737 msgq_str(sp
, M_INFO
, name
,
738 "068|No files named %s, readable by you, to recover");
744 "069|There are older versions of this file for you to recover");
745 if (found
> requested
)
747 "070|There are other files for you to recover");
751 * Create the FREF structure, start the btree file.
754 * file_init() is going to set ep->rcv_path.
756 if (file_init(sp
, frp
, pathp
+ sizeof(VI_PHEADER
) - 1, 0)) {
764 * We keep an open lock on the file so that the recover option can
765 * distinguish between files that are live and those that need to
766 * be recovered. The lock is already acquired, just copy it.
769 ep
->rcv_mpath
= recp
;
772 F_SET(frp
, FR_UNLOCKED
);
774 /* We believe the file is recoverable. */
782 * Copy a recovery file.
785 rcv_copy(SCR
*sp
, int wfd
, char *fname
)
787 int nr
, nw
, off
, rfd
;
790 if ((rfd
= open(fname
, O_RDONLY
, 0)) == -1)
792 while ((nr
= read(rfd
, buf
, sizeof(buf
))) > 0)
793 for (off
= 0; nr
; nr
-= nw
, off
+= nw
)
794 if ((nw
= write(wfd
, buf
+ off
, nr
)) < 0)
799 err
: msgq_str(sp
, M_SYSERR
, fname
, "%s");
805 * Fgets(3) for a file descriptor.
808 rcv_gets(char *buf
, size_t len
, int fd
)
813 if ((nr
= read(fd
, buf
, len
- 1)) == -1)
815 if ((p
= strchr(buf
, '\n')) == NULL
)
817 (void)lseek(fd
, (off_t
)((p
- buf
) + 1), SEEK_SET
);
823 * Paranoid make temporary file routine.
826 rcv_mktemp(SCR
*sp
, char *path
, const char *dname
, int perms
)
832 * We expect mkstemp(3) to set the permissions correctly. On
833 * historic System V systems, mkstemp didn't. Do it here, on
837 * The variable perms should really be a mode_t, and it would
838 * be nice to use fchmod(2) instead of chmod(2), here.
840 if ((fd
= mkstemp(path
)) == -1)
841 msgq_str(sp
, M_SYSERR
, dname
, "%s");
843 (void)chmod(path
, perms
);
852 rcv_email(SCR
*sp
, char *fname
)
855 char buf
[MAXPATHLEN
* 2 + 20];
857 if (_PATH_SENDMAIL
[0] != '/' || stat(_PATH_SENDMAIL
, &sb
))
858 msgq_str(sp
, M_SYSERR
,
859 _PATH_SENDMAIL
, "071|not sending email: %s");
863 * If you need to port this to a system that doesn't have
864 * sendmail, the -t flag causes sendmail to read the message
865 * for the recipients instead of specifying them some other
868 (void)snprintf(buf
, sizeof(buf
),
869 "%s -t < %s", _PATH_SENDMAIL
, fname
);