3 * The Regents of the University of California. All rights reserved.
5 * %sccs.include.redist.c%
9 static char sccsid
[] = "$Id: recover.c,v 8.39 1993/11/20 10:05:20 bostic Exp $ (Berkeley) $Date: 1993/11/20 10:05:20 $";
12 #include <sys/param.h>
17 * We include <sys/file.h>, because the flock(2) #defines were
18 * found there on historical systems. We also include <fcntl.h>
19 * because the open(2) #defines are found there on newer systems.
23 #include <netdb.h> /* MAXHOSTNAMELEN on some systems. */
34 #include "pathnames.h"
39 * The basic scheme is there's a btree file, whose name we specify. The first
40 * time a file is modified, and then at RCV_PERIOD intervals after that, the
41 * btree file is synced to disk. Each time a keystroke is requested for a file
42 * the terminal routines check to see if the file needs to be synced. This, of
43 * course means that the data structures had better be consistent each time the
44 * key routines are called.
46 * We don't use timers other than to flag that the file should be synced. This
47 * would require that the SCR and EXF data structures be locked, the dbopen(3)
48 * routines lock out the timers for each update, etc. It's just not worth it.
49 * The only way we can lose in the current scheme is if the file is saved, then
50 * the user types furiously for RCV_PERIOD - 1 seconds, and types nothing more.
53 * When a file is first modified, a file which can be handed off to the mailer
54 * is created. The file contains normal headers, with two additions, which
55 * occur in THIS order, as the FIRST TWO headers:
57 * Vi-recover-file: file_name
58 * Vi-recover-path: recover_path
60 * Since newlines delimit the headers, this means that file names cannot
61 * have newlines in them, but that's probably okay.
63 * Btree files are named "vi.XXXX" and recovery files are named "recover.XXXX".
66 #define VI_FHEADER "Vi-recover-file: "
67 #define VI_PHEADER "Vi-recover-path: "
69 static void rcv_alrm
__P((int));
70 static int rcv_mailfile
__P((SCR
*, EXF
*));
71 static void rcv_syncit
__P((SCR
*, int));
75 * Build a file name that will be used as the recovery file.
85 char *dp
, *p
, path
[MAXPATHLEN
];
88 * If the recovery directory doesn't exist, try and create it. As
89 * the recovery files are themselves protected from reading/writing
90 * by other than the owner, the worst that can happen is that a user
91 * would have permission to remove other users recovery files. If
92 * the sticky bit has the BSD semantics, that too will be impossible.
94 dp
= O_STR(sp
, O_DIRECTORY
);
96 if (errno
!= ENOENT
|| mkdir(dp
, 0)) {
97 msgq(sp
, M_ERR
, "Error: %s: %s", dp
, strerror(errno
));
100 (void)chmod(dp
, S_IRWXU
| S_IRWXG
| S_IRWXO
| S_ISVTX
);
103 /* Newlines delimit the mail messages. */
104 for (p
= name
; *p
; ++p
)
107 "Files with newlines in the name are unrecoverable.");
111 (void)snprintf(path
, sizeof(path
), "%s/vi.XXXXXX", dp
);
115 * We depend on mkstemp(3) setting the permissions correctly.
116 * GP's, we do it ourselves, to keep the window as small as
119 if ((fd
= mkstemp(path
)) == -1) {
120 msgq(sp
, M_ERR
, "Error: %s: %s", dp
, strerror(errno
));
123 (void)chmod(path
, S_IRUSR
| S_IWUSR
);
126 if ((ep
->rcv_path
= strdup(path
)) == NULL
) {
127 msgq(sp
, M_SYSERR
, NULL
);
132 /* We believe the file is recoverable. */
139 * Force the file to be snapshotted for recovery.
146 struct itimerval value
;
147 struct sigaction act
;
150 F_CLR(ep
, F_FIRSTMODIFY
| F_RCV_ON
);
152 /* Build file to mail to the user. */
153 if (rcv_mailfile(sp
, ep
))
156 /* Force read of entire file. */
157 if (file_lline(sp
, ep
, &lno
))
160 /* Turn on a busy message, and sync it to backing store. */
161 busy_on(sp
, 1, "Copying file for recovery...");
162 if (ep
->db
->sync(ep
->db
, R_RECNOSYNC
)) {
163 msgq(sp
, M_ERR
, "Preservation failed: %s: %s",
164 ep
->rcv_path
, strerror(errno
));
170 if (!F_ISSET(sp
->gp
, G_RECOVER_SET
)) {
171 /* Install the recovery timer handler. */
172 act
.sa_handler
= rcv_alrm
;
173 sigemptyset(&act
.sa_mask
);
175 (void)sigaction(SIGALRM
, &act
, NULL
);
177 /* Start the recovery timer. */
178 value
.it_interval
.tv_sec
= value
.it_value
.tv_sec
= RCV_PERIOD
;
179 value
.it_interval
.tv_usec
= value
.it_value
.tv_usec
= 0;
180 if (setitimer(ITIMER_REAL
, &value
, NULL
)) {
182 "Error: setitimer: %s", strerror(errno
));
184 "Recovery after system crash not possible.");
189 /* We believe the file is recoverable. */
196 * Recovery timer interrupt handler.
202 F_SET(__global_list
, G_SIGALRM
);
207 * Build the file to mail to the user.
219 char *p
, *t
, host
[MAXHOSTNAMELEN
], path
[MAXPATHLEN
];
221 if ((pw
= getpwuid(uid
= getuid())) == NULL
) {
222 msgq(sp
, M_ERR
, "Information on user id %u not found.", uid
);
226 (void)snprintf(path
, sizeof(path
),
227 "%s/recover.XXXXXX", O_STR(sp
, O_DIRECTORY
));
228 if ((fd
= mkstemp(path
)) == -1 || (fp
= fdopen(fd
, "w")) == NULL
) {
230 "Error: %s: %s", O_STR(sp
, O_DIRECTORY
), strerror(errno
));
237 * We keep an open lock on the file so that the recover option can
238 * distinguish between files that are live and those that need to
239 * be recovered. There's an obvious window between the mkstemp call
240 * and the lock, but it's pretty small.
242 if ((ep
->rcv_fd
= dup(fd
)) != -1)
243 (void)flock(ep
->rcv_fd
, LOCK_EX
| LOCK_NB
);
245 if ((ep
->rcv_mpath
= strdup(path
)) == NULL
) {
246 msgq(sp
, M_SYSERR
, NULL
);
251 t
= FILENAME(sp
->frp
);
252 if ((p
= strrchr(t
, '/')) == NULL
)
257 (void)gethostname(host
, sizeof(host
));
258 (void)fprintf(fp
, "%s%s\n%s%s\n%s\n%s\n%s%s\n%s%s\n%s\n\n",
259 VI_FHEADER
, p
, /* Non-standard. */
260 VI_PHEADER
, ep
->rcv_path
, /* Non-standard. */
262 "From: root (Nvi recovery program)",
264 "Subject: Nvi saved the file ", p
,
265 "Precedence: bulk"); /* For vacation(1). */
266 (void)fprintf(fp
, "%s%.24s%s%s\n%s%s",
268 ", the user ", pw
->pw_name
,
269 "was editing a file named ", p
);
271 (void)fprintf(fp
, " (%s)", t
);
272 (void)fprintf(fp
, "\n%s%s%s\n",
273 "on the machine ", host
, ", when it was saved for\nrecovery.");
274 (void)fprintf(fp
, "\n%s\n%s\n%s\n\n",
275 "You can recover most, if not all, of the changes",
276 "to this file using the -l and -r options to nvi(1)",
279 if (fflush(fp
) || ferror(fp
)) {
280 msgq(sp
, M_SYSERR
, NULL
);
289 * Sync the backing file.
296 struct itimerval value
;
298 if (ep
->db
->sync(ep
->db
, R_RECNOSYNC
)) {
299 msgq(sp
, M_ERR
, "Automatic file backup failed: %s: %s",
300 ep
->rcv_path
, strerror(errno
));
301 value
.it_interval
.tv_sec
= value
.it_interval
.tv_usec
= 0;
302 value
.it_value
.tv_sec
= value
.it_value
.tv_usec
= 0;
303 (void)setitimer(ITIMER_REAL
, &value
, NULL
);
312 * Recovery SIGHUP interrupt handler. (Modem line dropped, or
313 * xterm window closed.)
321 * Walk the lists of screens, sync'ing the files; only sync
322 * each file once. Send email to the user for each file saved.
324 for (sp
= __global_list
->dq
.cqh_first
;
325 sp
!= (void *)&__global_list
->dq
; sp
= sp
->q
.cqe_next
)
327 for (sp
= __global_list
->hq
.cqh_first
;
328 sp
!= (void *)&__global_list
->hq
; sp
= sp
->q
.cqe_next
)
332 * Die with the proper exit status. Don't bother using
333 * sigaction(2) 'cause we want the default behavior.
335 (void)signal(SIGHUP
, SIG_DFL
);
336 (void)kill(0, SIGHUP
);
344 * Recovery SIGTERM interrupt handler. (Reboot or halt is running.)
352 * Walk the lists of screens, sync'ing the files; only sync
355 for (sp
= __global_list
->dq
.cqh_first
;
356 sp
!= (void *)&__global_list
->dq
; sp
= sp
->q
.cqe_next
)
358 for (sp
= __global_list
->hq
.cqh_first
;
359 sp
!= (void *)&__global_list
->hq
; sp
= sp
->q
.cqe_next
)
363 * Die with the proper exit status. Don't bother using
364 * sigaction(2) 'cause we want the default behavior.
366 (void)signal(SIGTERM
, SIG_DFL
);
367 (void)kill(0, SIGTERM
);
375 * Sync the file, optionally send mail.
378 rcv_syncit(sp
, email
)
385 if ((ep
= sp
->ep
) == NULL
||
386 !F_ISSET(ep
, F_MODIFIED
) || !F_ISSET(ep
, F_RCV_ON
))
389 (void)ep
->db
->sync(ep
->db
, R_RECNOSYNC
);
390 F_SET(ep
, F_RCV_NORM
);
394 * If you need to port this to a system that doesn't have sendmail,
395 * the -t flag being used causes sendmail to read the message for
396 * the recipients instead of us specifying them some other way.
399 (void)snprintf(comm
, sizeof(comm
),
400 "%s -t < %s", _PATH_SENDMAIL
, ep
->rcv_mpath
);
403 (void)file_end(sp
, ep
, 1);
408 * never exactly the same
409 * just like a snowflake
412 * List the files that can be recovered by this user.
425 if (chdir(O_STR(sp
, O_DIRECTORY
)) || (dirp
= opendir(".")) == NULL
) {
426 (void)fprintf(stderr
,
427 "vi: %s: %s\n", O_STR(sp
, O_DIRECTORY
), strerror(errno
));
431 for (found
= 0; (dp
= readdir(dirp
)) != NULL
;) {
432 if (strncmp(dp
->d_name
, "recover.", 8))
435 /* If it's readable, it's recoverable. */
436 if ((fp
= fopen(dp
->d_name
, "r")) == NULL
)
439 /* If it's locked, it's live. */
440 if (flock(fileno(fp
), LOCK_EX
| LOCK_NB
)) {
445 /* Check the header, get the file name. */
446 if (fgets(file
, sizeof(file
), fp
) == NULL
||
447 strncmp(file
, VI_FHEADER
, sizeof(VI_FHEADER
) - 1) ||
448 (p
= strchr(file
, '\n')) == NULL
) {
449 (void)fprintf(stderr
,
450 "vi: %s: malformed recovery file.\n", dp
->d_name
);
455 /* Get the last modification time. */
456 if (fstat(fileno(fp
), &sb
)) {
457 (void)fprintf(stderr
,
458 "vi: %s: %s\n", dp
->d_name
, strerror(errno
));
463 (void)printf("%s: %s",
464 file
+ sizeof(VI_FHEADER
) - 1, ctime(&sb
.st_mtime
));
467 next
: (void)fclose(fp
);
470 (void)printf("vi: no files to recover.\n");
471 (void)closedir(dirp
);
477 * Start a recovered file as the file to edit.
490 int found
, requested
;
491 char *p
, *t
, *recp
, *pathp
;
492 char recpath
[MAXPATHLEN
], file
[MAXPATHLEN
], path
[MAXPATHLEN
];
494 if ((dirp
= opendir(O_STR(sp
, O_DIRECTORY
))) == NULL
) {
496 "%s: %s", O_STR(sp
, O_DIRECTORY
), strerror(errno
));
501 for (found
= requested
= 0; (dp
= readdir(dirp
)) != NULL
;) {
502 if (strncmp(dp
->d_name
, "recover.", 8))
505 /* If it's readable, it's recoverable. */
506 (void)snprintf(recpath
, sizeof(recpath
),
507 "%s/%s", O_STR(sp
, O_DIRECTORY
), dp
->d_name
);
508 if ((fp
= fopen(recpath
, "r")) == NULL
)
511 /* If it's locked, it's live. */
512 if (flock(fileno(fp
), LOCK_EX
| LOCK_NB
)) {
517 /* Check the headers. */
518 if (fgets(file
, sizeof(file
), fp
) == NULL
||
519 strncmp(file
, VI_FHEADER
, sizeof(VI_FHEADER
) - 1) ||
520 (p
= strchr(file
, '\n')) == NULL
||
521 fgets(path
, sizeof(path
), fp
) == NULL
||
522 strncmp(path
, VI_PHEADER
, sizeof(VI_PHEADER
) - 1) ||
523 (t
= strchr(path
, '\n')) == NULL
) {
525 "%s: malformed recovery file.", recpath
);
531 /* Get the last modification time. */
532 if (fstat(fileno(fp
), &sb
)) {
534 "vi: %s: %s", dp
->d_name
, strerror(errno
));
538 /* Check the file name. */
539 if (strcmp(file
+ sizeof(VI_FHEADER
) - 1, name
))
544 /* If we've found more than one, take the most recent. */
545 if (recp
== NULL
|| rec_mtime
< sb
.st_mtime
) {
548 if ((recp
= strdup(recpath
)) == NULL
) {
550 "vi: Error: %s.\n", strerror(errno
));
554 if ((pathp
= strdup(path
)) == NULL
) {
556 "vi: Error: %s.\n", strerror(errno
));
557 FREE(recp
, strlen(recp
) + 1);
563 FREE(p
, strlen(p
) + 1);
564 FREE(t
, strlen(t
) + 1);
566 rec_mtime
= sb
.st_mtime
;
569 next
: (void)fclose(fp
);
571 (void)closedir(dirp
);
575 "No files named %s, owned by you, to edit.", name
);
581 "There are older versions of this file for you to recover.");
582 if (found
> requested
)
584 "There are other files that you can recover.");
587 /* Create the FREF structure, start the btree file. */
588 if ((frp
= file_add(sp
, NULL
, name
, 0)) == NULL
||
589 file_init(sp
, frp
, pathp
+ sizeof(VI_PHEADER
) - 1, 0)) {
590 FREE(recp
, strlen(recp
) + 1);
591 FREE(pathp
, strlen(pathp
) + 1);
594 sp
->ep
->rcv_mpath
= recp
;