This commit was generated by cvs2svn to track changes on a CVS vendor
[unix-history] / usr.bin / vi / recover.c
CommitLineData
1e64b3ba
JH
1/*-
2 * Copyright (c) 1993
3 * The Regents of the University of California. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. All advertising materials mentioning features or use of this software
14 * must display the following acknowledgement:
15 * This product includes software developed by the University of
16 * California, Berkeley and its contributors.
17 * 4. Neither the name of the University nor the names of its contributors
18 * may be used to endorse or promote products derived from this software
19 * without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 */
33
34#ifndef lint
35static char sccsid[] = "@(#)recover.c 8.40 (Berkeley) 12/21/93";
36#endif /* not lint */
37
38#include <sys/param.h>
39#include <sys/stat.h>
40#include <sys/time.h>
41
42/*
43 * We include <sys/file.h>, because the flock(2) #defines were
44 * found there on historical systems. We also include <fcntl.h>
45 * because the open(2) #defines are found there on newer systems.
46 */
47#include <sys/file.h>
48
49#include <netdb.h> /* MAXHOSTNAMELEN on some systems. */
50
51#include <dirent.h>
52#include <errno.h>
53#include <fcntl.h>
54#include <pwd.h>
55#include <stdlib.h>
56#include <string.h>
57#include <unistd.h>
58
59#include "vi.h"
60#include "pathnames.h"
61
62/*
63 * Recovery code.
64 *
65 * The basic scheme is there's a btree file, whose name we specify. The first
66 * time a file is modified, and then at RCV_PERIOD intervals after that, the
67 * btree file is synced to disk. Each time a keystroke is requested for a file
68 * the terminal routines check to see if the file needs to be synced. This, of
69 * course means that the data structures had better be consistent each time the
70 * key routines are called.
71 *
72 * We don't use timers other than to flag that the file should be synced. This
73 * would require that the SCR and EXF data structures be locked, the dbopen(3)
74 * routines lock out the timers for each update, etc. It's just not worth it.
75 * The only way we can lose in the current scheme is if the file is saved, then
76 * the user types furiously for RCV_PERIOD - 1 seconds, and types nothing more.
77 * Not likely.
78 *
79 * When a file is first modified, a file which can be handed off to the mailer
80 * is created. The file contains normal headers, with two additions, which
81 * occur in THIS order, as the FIRST TWO headers:
82 *
83 * Vi-recover-file: file_name
84 * Vi-recover-path: recover_path
85 *
86 * Since newlines delimit the headers, this means that file names cannot
87 * have newlines in them, but that's probably okay.
88 *
89 * Btree files are named "vi.XXXX" and recovery files are named "recover.XXXX".
90 */
91
92#define VI_FHEADER "Vi-recover-file: "
93#define VI_PHEADER "Vi-recover-path: "
94
95static void rcv_alrm __P((int));
96static int rcv_mailfile __P((SCR *, EXF *));
97static void rcv_syncit __P((SCR *, int));
98
99/*
100 * rcv_tmp --
101 * Build a file name that will be used as the recovery file.
102 */
103int
104rcv_tmp(sp, ep, name)
105 SCR *sp;
106 EXF *ep;
107 char *name;
108{
109 struct stat sb;
110 int fd;
111 char *dp, *p, path[MAXPATHLEN];
112
113 /*
114 * If the recovery directory doesn't exist, try and create it. As
115 * the recovery files are themselves protected from reading/writing
116 * by other than the owner, the worst that can happen is that a user
117 * would have permission to remove other users recovery files. If
118 * the sticky bit has the BSD semantics, that too will be impossible.
119 */
120 dp = O_STR(sp, O_RECDIR);
121 if (stat(dp, &sb)) {
122 if (errno != ENOENT || mkdir(dp, 0)) {
123 msgq(sp, M_ERR, "Error: %s: %s", dp, strerror(errno));
124 return (1);
125 }
126 (void)chmod(dp, S_IRWXU | S_IRWXG | S_IRWXO | S_ISVTX);
127 }
128
129 /* Newlines delimit the mail messages. */
130 for (p = name; *p; ++p)
131 if (*p == '\n') {
132 msgq(sp, M_ERR,
133 "Files with newlines in the name are unrecoverable.");
134 return (1);
135 }
136
137 (void)snprintf(path, sizeof(path), "%s/vi.XXXXXX", dp);
138
139 /*
140 * !!!
141 * We depend on mkstemp(3) setting the permissions correctly.
142 * GP's, we do it ourselves, to keep the window as small as
143 * possible.
144 */
145 if ((fd = mkstemp(path)) == -1) {
146 msgq(sp, M_ERR, "Error: %s: %s", dp, strerror(errno));
147 return (1);
148 }
149 (void)chmod(path, S_IRUSR | S_IWUSR);
150 (void)close(fd);
151
152 if ((ep->rcv_path = strdup(path)) == NULL) {
153 msgq(sp, M_SYSERR, NULL);
154 (void)unlink(path);
155 return (1);
156 }
157
158 /* We believe the file is recoverable. */
159 F_SET(ep, F_RCV_ON);
160 return (0);
161}
162
163/*
164 * rcv_init --
165 * Force the file to be snapshotted for recovery.
166 */
167int
168rcv_init(sp, ep)
169 SCR *sp;
170 EXF *ep;
171{
172 struct itimerval value;
173 struct sigaction act;
174 recno_t lno;
175
176 F_CLR(ep, F_FIRSTMODIFY | F_RCV_ON);
177
178 /* Build file to mail to the user. */
179 if (rcv_mailfile(sp, ep))
180 goto err;
181
182 /* Force read of entire file. */
183 if (file_lline(sp, ep, &lno))
184 goto err;
185
186 /* Turn on a busy message, and sync it to backing store. */
187 busy_on(sp, 1, "Copying file for recovery...");
188 if (ep->db->sync(ep->db, R_RECNOSYNC)) {
189 msgq(sp, M_ERR, "Preservation failed: %s: %s",
190 ep->rcv_path, strerror(errno));
191 busy_off(sp);
192 goto err;
193 }
194 busy_off(sp);
195
196 if (!F_ISSET(sp->gp, G_RECOVER_SET)) {
197 /* Install the recovery timer handler. */
198 act.sa_handler = rcv_alrm;
199 sigemptyset(&act.sa_mask);
200 act.sa_flags = 0;
201 (void)sigaction(SIGALRM, &act, NULL);
202
203 /* Start the recovery timer. */
204 value.it_interval.tv_sec = value.it_value.tv_sec = RCV_PERIOD;
205 value.it_interval.tv_usec = value.it_value.tv_usec = 0;
206 if (setitimer(ITIMER_REAL, &value, NULL)) {
207 msgq(sp, M_ERR,
208 "Error: setitimer: %s", strerror(errno));
209err: msgq(sp, M_ERR,
210 "Recovery after system crash not possible.");
211 return (1);
212 }
213 }
214
215 /* We believe the file is recoverable. */
216 F_SET(ep, F_RCV_ON);
217 return (0);
218}
219
220/*
221 * rcv_alrm --
222 * Recovery timer interrupt handler.
223 */
224static void
225rcv_alrm(signo)
226 int signo;
227{
228 F_SET(__global_list, G_SIGALRM);
229}
230
231/*
232 * rcv_mailfile --
233 * Build the file to mail to the user.
234 */
235static int
236rcv_mailfile(sp, ep)
237 SCR *sp;
238 EXF *ep;
239{
240 struct passwd *pw;
241 uid_t uid;
242 FILE *fp;
243 time_t now;
244 int fd;
245 char *p, *t, host[MAXHOSTNAMELEN], path[MAXPATHLEN];
246
247 if ((pw = getpwuid(uid = getuid())) == NULL) {
248 msgq(sp, M_ERR, "Information on user id %u not found.", uid);
249 return (1);
250 }
251
252 (void)snprintf(path, sizeof(path),
253 "%s/recover.XXXXXX", O_STR(sp, O_RECDIR));
254 if ((fd = mkstemp(path)) == -1 || (fp = fdopen(fd, "w")) == NULL) {
255 msgq(sp, M_ERR,
256 "Error: %s: %s", O_STR(sp, O_RECDIR), strerror(errno));
257 if (fd != -1)
258 (void)close(fd);
259 return (1);
260 }
261
262 /*
263 * We keep an open lock on the file so that the recover option can
264 * distinguish between files that are live and those that need to
265 * be recovered. There's an obvious window between the mkstemp call
266 * and the lock, but it's pretty small.
267 */
268 if ((ep->rcv_fd = dup(fd)) != -1)
269 (void)flock(ep->rcv_fd, LOCK_EX | LOCK_NB);
270
271 if ((ep->rcv_mpath = strdup(path)) == NULL) {
272 msgq(sp, M_SYSERR, NULL);
273 (void)fclose(fp);
274 return (1);
275 }
276
277 t = FILENAME(sp->frp);
278 if ((p = strrchr(t, '/')) == NULL)
279 p = t;
280 else
281 ++p;
282 (void)time(&now);
283 (void)gethostname(host, sizeof(host));
284 (void)fprintf(fp, "%s%s\n%s%s\n%s\n%s\n%s%s\n%s%s\n%s\n\n",
285 VI_FHEADER, p, /* Non-standard. */
286 VI_PHEADER, ep->rcv_path, /* Non-standard. */
287 "Reply-To: root",
288 "From: root (Nvi recovery program)",
289 "To: ", pw->pw_name,
290 "Subject: Nvi saved the file ", p,
291 "Precedence: bulk"); /* For vacation(1). */
292 (void)fprintf(fp, "%s%.24s%s%s\n%s%s",
293 "On ", ctime(&now),
294 ", the user ", pw->pw_name,
295 "was editing a file named ", p);
296 if (p != t)
297 (void)fprintf(fp, " (%s)", t);
298 (void)fprintf(fp, "\n%s%s%s\n",
299 "on the machine ", host, ", when it was saved for\nrecovery.");
300 (void)fprintf(fp, "\n%s\n%s\n%s\n\n",
301 "You can recover most, if not all, of the changes",
302 "to this file using the -l and -r options to nvi(1)",
303 "or nex(1).");
304
305 if (fflush(fp) || ferror(fp)) {
306 msgq(sp, M_SYSERR, NULL);
307 (void)fclose(fp);
308 return (1);
309 }
310 return (0);
311}
312
313/*
314 * rcv_sync --
315 * Sync the backing file.
316 */
317int
318rcv_sync(sp, ep)
319 SCR *sp;
320 EXF *ep;
321{
322 struct itimerval value;
323
324 if (ep->db->sync(ep->db, R_RECNOSYNC)) {
325 msgq(sp, M_ERR, "Automatic file backup failed: %s: %s",
326 ep->rcv_path, strerror(errno));
327 value.it_interval.tv_sec = value.it_interval.tv_usec = 0;
328 value.it_value.tv_sec = value.it_value.tv_usec = 0;
329 (void)setitimer(ITIMER_REAL, &value, NULL);
330 F_CLR(ep, F_RCV_ON);
331 return (1);
332 }
333 return (0);
334}
335
336/*
337 * rcv_hup --
338 * Recovery SIGHUP interrupt handler. (Modem line dropped, or
339 * xterm window closed.)
340 */
341void
342rcv_hup()
343{
344 SCR *sp;
345
346 /*
347 * Walk the lists of screens, sync'ing the files; only sync
348 * each file once. Send email to the user for each file saved.
349 */
350 for (sp = __global_list->dq.cqh_first;
351 sp != (void *)&__global_list->dq; sp = sp->q.cqe_next)
352 rcv_syncit(sp, 1);
353 for (sp = __global_list->hq.cqh_first;
354 sp != (void *)&__global_list->hq; sp = sp->q.cqe_next)
355 rcv_syncit(sp, 1);
356
357 /*
358 * Die with the proper exit status. Don't bother using
359 * sigaction(2) 'cause we want the default behavior.
360 */
361 (void)signal(SIGHUP, SIG_DFL);
362 (void)kill(0, SIGHUP);
363
364 /* NOTREACHED */
365 exit (1);
366}
367
368/*
369 * rcv_term --
370 * Recovery SIGTERM interrupt handler. (Reboot or halt is running.)
371 */
372void
373rcv_term()
374{
375 SCR *sp;
376
377 /*
378 * Walk the lists of screens, sync'ing the files; only sync
379 * each file once.
380 */
381 for (sp = __global_list->dq.cqh_first;
382 sp != (void *)&__global_list->dq; sp = sp->q.cqe_next)
383 rcv_syncit(sp, 0);
384 for (sp = __global_list->hq.cqh_first;
385 sp != (void *)&__global_list->hq; sp = sp->q.cqe_next)
386 rcv_syncit(sp, 0);
387
388 /*
389 * Die with the proper exit status. Don't bother using
390 * sigaction(2) 'cause we want the default behavior.
391 */
392 (void)signal(SIGTERM, SIG_DFL);
393 (void)kill(0, SIGTERM);
394
395 /* NOTREACHED */
396 exit (1);
397}
398
399/*
400 * rcv_syncit --
401 * Sync the file, optionally send mail.
402 */
403static void
404rcv_syncit(sp, email)
405 SCR *sp;
406 int email;
407{
408 EXF *ep;
409 char comm[1024];
410
411 if ((ep = sp->ep) == NULL ||
412 !F_ISSET(ep, F_MODIFIED) || !F_ISSET(ep, F_RCV_ON))
413 return;
414
415 (void)ep->db->sync(ep->db, R_RECNOSYNC);
416 F_SET(ep, F_RCV_NORM);
417
418 /*
419 * !!!
420 * If you need to port this to a system that doesn't have sendmail,
421 * the -t flag being used causes sendmail to read the message for
422 * the recipients instead of us specifying them some other way.
423 */
424 if (email) {
425 (void)snprintf(comm, sizeof(comm),
426 "%s -t < %s", _PATH_SENDMAIL, ep->rcv_mpath);
427 (void)system(comm);
428 }
429 (void)file_end(sp, ep, 1);
430}
431
432/*
433 * people making love
434 * never exactly the same
435 * just like a snowflake
436 *
437 * rcv_list --
438 * List the files that can be recovered by this user.
439 */
440int
441rcv_list(sp)
442 SCR *sp;
443{
444 struct dirent *dp;
445 struct stat sb;
446 DIR *dirp;
447 FILE *fp;
448 int found;
449 char *p, file[1024];
450
451 if (chdir(O_STR(sp, O_RECDIR)) || (dirp = opendir(".")) == NULL) {
452 (void)fprintf(stderr,
453 "vi: %s: %s\n", O_STR(sp, O_RECDIR), strerror(errno));
454 return (1);
455 }
456
457 for (found = 0; (dp = readdir(dirp)) != NULL;) {
458 if (strncmp(dp->d_name, "recover.", 8))
459 continue;
460
461 /* If it's readable, it's recoverable. */
462 if ((fp = fopen(dp->d_name, "r")) == NULL)
463 continue;
464
465 /* If it's locked, it's live. */
466 if (flock(fileno(fp), LOCK_EX | LOCK_NB)) {
467 (void)fclose(fp);
468 continue;
469 }
470
471 /* Check the header, get the file name. */
472 if (fgets(file, sizeof(file), fp) == NULL ||
473 strncmp(file, VI_FHEADER, sizeof(VI_FHEADER) - 1) ||
474 (p = strchr(file, '\n')) == NULL) {
475 (void)fprintf(stderr,
476 "vi: %s: malformed recovery file.\n", dp->d_name);
477 goto next;
478 }
479 *p = '\0';
480
481 /* Get the last modification time. */
482 if (fstat(fileno(fp), &sb)) {
483 (void)fprintf(stderr,
484 "vi: %s: %s\n", dp->d_name, strerror(errno));
485 goto next;
486 }
487
488 /* Display. */
489 (void)printf("%s: %s",
490 file + sizeof(VI_FHEADER) - 1, ctime(&sb.st_mtime));
491 found = 1;
492
493next: (void)fclose(fp);
494 }
495 if (found == 0)
496 (void)printf("vi: no files to recover.\n");
497 (void)closedir(dirp);
498 return (0);
499}
500
501/*
502 * rcv_read --
503 * Start a recovered file as the file to edit.
504 */
505int
506rcv_read(sp, name)
507 SCR *sp;
508 char *name;
509{
510 struct dirent *dp;
511 struct stat sb;
512 DIR *dirp;
513 FREF *frp;
514 FILE *fp;
515 time_t rec_mtime;
516 int found, requested;
517 char *p, *t, *recp, *pathp;
518 char recpath[MAXPATHLEN], file[MAXPATHLEN], path[MAXPATHLEN];
519
520 if ((dirp = opendir(O_STR(sp, O_RECDIR))) == NULL) {
521 msgq(sp, M_ERR,
522 "%s: %s", O_STR(sp, O_RECDIR), strerror(errno));
523 return (1);
524 }
525
526 recp = pathp = NULL;
527 for (found = requested = 0; (dp = readdir(dirp)) != NULL;) {
528 if (strncmp(dp->d_name, "recover.", 8))
529 continue;
530
531 /* If it's readable, it's recoverable. */
532 (void)snprintf(recpath, sizeof(recpath),
533 "%s/%s", O_STR(sp, O_RECDIR), dp->d_name);
534 if ((fp = fopen(recpath, "r")) == NULL)
535 continue;
536
537 /* If it's locked, it's live. */
538 if (flock(fileno(fp), LOCK_EX | LOCK_NB)) {
539 (void)fclose(fp);
540 continue;
541 }
542
543 /* Check the headers. */
544 if (fgets(file, sizeof(file), fp) == NULL ||
545 strncmp(file, VI_FHEADER, sizeof(VI_FHEADER) - 1) ||
546 (p = strchr(file, '\n')) == NULL ||
547 fgets(path, sizeof(path), fp) == NULL ||
548 strncmp(path, VI_PHEADER, sizeof(VI_PHEADER) - 1) ||
549 (t = strchr(path, '\n')) == NULL) {
550 msgq(sp, M_ERR,
551 "%s: malformed recovery file.", recpath);
552 goto next;
553 }
554 ++found;
555 *t = *p = '\0';
556
557 /* Get the last modification time. */
558 if (fstat(fileno(fp), &sb)) {
559 msgq(sp, M_ERR,
560 "vi: %s: %s", dp->d_name, strerror(errno));
561 goto next;
562 }
563
564 /* Check the file name. */
565 if (strcmp(file + sizeof(VI_FHEADER) - 1, name))
566 goto next;
567
568 ++requested;
569
570 /* If we've found more than one, take the most recent. */
571 if (recp == NULL || rec_mtime < sb.st_mtime) {
572 p = recp;
573 t = pathp;
574 if ((recp = strdup(recpath)) == NULL) {
575 msgq(sp, M_ERR,
576 "vi: Error: %s.\n", strerror(errno));
577 recp = p;
578 goto next;
579 }
580 if ((pathp = strdup(path)) == NULL) {
581 msgq(sp, M_ERR,
582 "vi: Error: %s.\n", strerror(errno));
583 FREE(recp, strlen(recp) + 1);
584 recp = p;
585 pathp = t;
586 goto next;
587 }
588 if (p != NULL) {
589 FREE(p, strlen(p) + 1);
590 FREE(t, strlen(t) + 1);
591 }
592 rec_mtime = sb.st_mtime;
593 }
594
595next: (void)fclose(fp);
596 }
597 (void)closedir(dirp);
598
599 if (recp == NULL) {
600 msgq(sp, M_INFO,
601 "No files named %s, owned by you, to edit.", name);
602 return (1);
603 }
604 if (found) {
605 if (requested > 1)
606 msgq(sp, M_INFO,
607 "There are older versions of this file for you to recover.");
608 if (found > requested)
609 msgq(sp, M_INFO,
610 "There are other files that you can recover.");
611 }
612
613 /* Create the FREF structure, start the btree file. */
614 if ((frp = file_add(sp, NULL, name, 0)) == NULL ||
615 file_init(sp, frp, pathp + sizeof(VI_PHEADER) - 1, 0)) {
616 FREE(recp, strlen(recp) + 1);
617 FREE(pathp, strlen(pathp) + 1);
618 return (1);
619 }
620 sp->ep->rcv_mpath = recp;
621 return (0);
622}