1 | /*- |
---|
2 | * Copyright (c) 1993, 1994 |
---|
3 | * The Regents of the University of California. All rights reserved. |
---|
4 | * Copyright (c) 1993, 1994, 1995, 1996 |
---|
5 | * Keith Bostic. All rights reserved. |
---|
6 | * |
---|
7 | * See the LICENSE file for redistribution information. |
---|
8 | */ |
---|
9 | |
---|
10 | #include "config.h" |
---|
11 | |
---|
12 | #ifndef lint |
---|
13 | static const char sccsid[] = "@(#)recover.c 10.21 (Berkeley) 9/15/96"; |
---|
14 | #endif /* not lint */ |
---|
15 | |
---|
16 | #include <sys/param.h> |
---|
17 | #include <sys/types.h> /* XXX: param.h may not have included types.h */ |
---|
18 | #include <sys/queue.h> |
---|
19 | #include <sys/stat.h> |
---|
20 | |
---|
21 | /* |
---|
22 | * We include <sys/file.h>, because the open #defines were found there |
---|
23 | * on historical systems. We also include <fcntl.h> because the open(2) |
---|
24 | * #defines are found there on newer systems. |
---|
25 | */ |
---|
26 | #include <sys/file.h> |
---|
27 | |
---|
28 | #include <bitstring.h> |
---|
29 | #include <dirent.h> |
---|
30 | #include <errno.h> |
---|
31 | #include <fcntl.h> |
---|
32 | #include <limits.h> |
---|
33 | #include <pwd.h> |
---|
34 | #include <stdio.h> |
---|
35 | #include <stdlib.h> |
---|
36 | #include <string.h> |
---|
37 | #include <time.h> |
---|
38 | #include <unistd.h> |
---|
39 | |
---|
40 | #include "common.h" |
---|
41 | #include "pathnames.h" |
---|
42 | |
---|
43 | /* |
---|
44 | * Recovery code. |
---|
45 | * |
---|
46 | * The basic scheme is as follows. In the EXF structure, we maintain full |
---|
47 | * paths of a b+tree file and a mail recovery file. The former is the file |
---|
48 | * used as backing store by the DB package. The latter is the file that |
---|
49 | * contains an email message to be sent to the user if we crash. The two |
---|
50 | * simple states of recovery are: |
---|
51 | * |
---|
52 | * + first starting the edit session: |
---|
53 | * the b+tree file exists and is mode 700, the mail recovery |
---|
54 | * file doesn't exist. |
---|
55 | * + after the file has been modified: |
---|
56 | * the b+tree file exists and is mode 600, the mail recovery |
---|
57 | * file exists, and is exclusively locked. |
---|
58 | * |
---|
59 | * In the EXF structure we maintain a file descriptor that is the locked |
---|
60 | * file descriptor for the mail recovery file. NOTE: we sometimes have to |
---|
61 | * do locking with fcntl(2). This is a problem because if you close(2) any |
---|
62 | * file descriptor associated with the file, ALL of the locks go away. Be |
---|
63 | * sure to remember that if you have to modify the recovery code. (It has |
---|
64 | * been rhetorically asked of what the designers could have been thinking |
---|
65 | * when they did that interface. The answer is simple: they weren't.) |
---|
66 | * |
---|
67 | * To find out if a recovery file/backing file pair are in use, try to get |
---|
68 | * a lock on the recovery file. |
---|
69 | * |
---|
70 | * To find out if a backing file can be deleted at boot time, check for an |
---|
71 | * owner execute bit. (Yes, I know it's ugly, but it's either that or put |
---|
72 | * special stuff into the backing file itself, or correlate the files at |
---|
73 | * boot time, neither of which looks like fun.) Note also that there's a |
---|
74 | * window between when the file is created and the X bit is set. It's small, |
---|
75 | * but it's there. To fix the window, check for 0 length files as well. |
---|
76 | * |
---|
77 | * To find out if a file can be recovered, check the F_RCV_ON bit. Note, |
---|
78 | * this DOES NOT mean that any initialization has been done, only that we |
---|
79 | * haven't yet failed at setting up or doing recovery. |
---|
80 | * |
---|
81 | * To preserve a recovery file/backing file pair, set the F_RCV_NORM bit. |
---|
82 | * If that bit is not set when ending a file session: |
---|
83 | * If the EXF structure paths (rcv_path and rcv_mpath) are not NULL, |
---|
84 | * they are unlink(2)'d, and free(3)'d. |
---|
85 | * If the EXF file descriptor (rcv_fd) is not -1, it is closed. |
---|
86 | * |
---|
87 | * The backing b+tree file is set up when a file is first edited, so that |
---|
88 | * the DB package can use it for on-disk caching and/or to snapshot the |
---|
89 | * file. When the file is first modified, the mail recovery file is created, |
---|
90 | * the backing file permissions are updated, the file is sync(2)'d to disk, |
---|
91 | * and the timer is started. Then, at RCV_PERIOD second intervals, the |
---|
92 | * b+tree file is synced to disk. RCV_PERIOD is measured using SIGALRM, which |
---|
93 | * means that the data structures (SCR, EXF, the underlying tree structures) |
---|
94 | * must be consistent when the signal arrives. |
---|
95 | * |
---|
96 | * The recovery mail file contains normal mail headers, with two additions, |
---|
97 | * which occur in THIS order, as the FIRST TWO headers: |
---|
98 | * |
---|
99 | * X-vi-recover-file: file_name |
---|
100 | * X-vi-recover-path: recover_path |
---|
101 | * |
---|
102 | * Since newlines delimit the headers, this means that file names cannot have |
---|
103 | * newlines in them, but that's probably okay. As these files aren't intended |
---|
104 | * to be long-lived, changing their format won't be too painful. |
---|
105 | * |
---|
106 | * Btree files are named "vi.XXXX" and recovery files are named "recover.XXXX". |
---|
107 | */ |
---|
108 | |
---|
109 | #define VI_FHEADER "X-vi-recover-file: " |
---|
110 | #define VI_PHEADER "X-vi-recover-path: " |
---|
111 | |
---|
112 | static int rcv_copy __P((SCR *, int, char *)); |
---|
113 | static void rcv_email __P((SCR *, char *)); |
---|
114 | static char *rcv_gets __P((char *, size_t, int)); |
---|
115 | static int rcv_mailfile __P((SCR *, int, char *)); |
---|
116 | static int rcv_mktemp __P((SCR *, char *, char *, int)); |
---|
117 | |
---|
118 | /* |
---|
119 | * rcv_tmp -- |
---|
120 | * Build a file name that will be used as the recovery file. |
---|
121 | * |
---|
122 | * PUBLIC: int rcv_tmp __P((SCR *, EXF *, char *)); |
---|
123 | */ |
---|
124 | int |
---|
125 | rcv_tmp(sp, ep, name) |
---|
126 | SCR *sp; |
---|
127 | EXF *ep; |
---|
128 | char *name; |
---|
129 | { |
---|
130 | struct stat sb; |
---|
131 | int fd; |
---|
132 | char *dp, *p, path[MAXPATHLEN]; |
---|
133 | |
---|
134 | /* |
---|
135 | * !!! |
---|
136 | * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER. |
---|
137 | * |
---|
138 | * |
---|
139 | * If the recovery directory doesn't exist, try and create it. As |
---|
140 | * the recovery files are themselves protected from reading/writing |
---|
141 | * by other than the owner, the worst that can happen is that a user |
---|
142 | * would have permission to remove other user's recovery files. If |
---|
143 | * the sticky bit has the BSD semantics, that too will be impossible. |
---|
144 | */ |
---|
145 | if (opts_empty(sp, O_RECDIR, 0)) |
---|
146 | goto err; |
---|
147 | dp = O_STR(sp, O_RECDIR); |
---|
148 | if (stat(dp, &sb)) { |
---|
149 | if (errno != ENOENT || mkdir(dp, 0)) { |
---|
150 | msgq(sp, M_SYSERR, "%s", dp); |
---|
151 | goto err; |
---|
152 | } |
---|
153 | (void)chmod(dp, S_IRWXU | S_IRWXG | S_IRWXO | S_ISVTX); |
---|
154 | } |
---|
155 | |
---|
156 | /* Newlines delimit the mail messages. */ |
---|
157 | for (p = name; *p; ++p) |
---|
158 | if (*p == '\n') { |
---|
159 | msgq(sp, M_ERR, |
---|
160 | "055|Files with newlines in the name are unrecoverable"); |
---|
161 | goto err; |
---|
162 | } |
---|
163 | |
---|
164 | (void)snprintf(path, sizeof(path), "%s/vi.XXXXXX", dp); |
---|
165 | if ((fd = rcv_mktemp(sp, path, dp, S_IRWXU)) == -1) |
---|
166 | goto err; |
---|
167 | (void)close(fd); |
---|
168 | |
---|
169 | if ((ep->rcv_path = strdup(path)) == NULL) { |
---|
170 | msgq(sp, M_SYSERR, NULL); |
---|
171 | (void)unlink(path); |
---|
172 | err: msgq(sp, M_ERR, |
---|
173 | "056|Modifications not recoverable if the session fails"); |
---|
174 | return (1); |
---|
175 | } |
---|
176 | |
---|
177 | /* We believe the file is recoverable. */ |
---|
178 | F_SET(ep, F_RCV_ON); |
---|
179 | return (0); |
---|
180 | } |
---|
181 | |
---|
182 | /* |
---|
183 | * rcv_init -- |
---|
184 | * Force the file to be snapshotted for recovery. |
---|
185 | * |
---|
186 | * PUBLIC: int rcv_init __P((SCR *)); |
---|
187 | */ |
---|
188 | int |
---|
189 | rcv_init(sp) |
---|
190 | SCR *sp; |
---|
191 | { |
---|
192 | EXF *ep; |
---|
193 | recno_t lno; |
---|
194 | |
---|
195 | ep = sp->ep; |
---|
196 | |
---|
197 | /* Only do this once. */ |
---|
198 | F_CLR(ep, F_FIRSTMODIFY); |
---|
199 | |
---|
200 | /* If we already know the file isn't recoverable, we're done. */ |
---|
201 | if (!F_ISSET(ep, F_RCV_ON)) |
---|
202 | return (0); |
---|
203 | |
---|
204 | /* Turn off recoverability until we figure out if this will work. */ |
---|
205 | F_CLR(ep, F_RCV_ON); |
---|
206 | |
---|
207 | /* Test if we're recovering a file, not editing one. */ |
---|
208 | if (ep->rcv_mpath == NULL) { |
---|
209 | /* Build a file to mail to the user. */ |
---|
210 | if (rcv_mailfile(sp, 0, NULL)) |
---|
211 | goto err; |
---|
212 | |
---|
213 | /* Force a read of the entire file. */ |
---|
214 | if (db_last(sp, &lno)) |
---|
215 | goto err; |
---|
216 | |
---|
217 | /* Turn on a busy message, and sync it to backing store. */ |
---|
218 | sp->gp->scr_busy(sp, |
---|
219 | "057|Copying file for recovery...", BUSY_ON); |
---|
220 | if (ep->db->sync(ep->db, R_RECNOSYNC)) { |
---|
221 | msgq_str(sp, M_SYSERR, ep->rcv_path, |
---|
222 | "058|Preservation failed: %s"); |
---|
223 | sp->gp->scr_busy(sp, NULL, BUSY_OFF); |
---|
224 | goto err; |
---|
225 | } |
---|
226 | sp->gp->scr_busy(sp, NULL, BUSY_OFF); |
---|
227 | } |
---|
228 | |
---|
229 | /* Turn off the owner execute bit. */ |
---|
230 | (void)chmod(ep->rcv_path, S_IRUSR | S_IWUSR); |
---|
231 | |
---|
232 | /* We believe the file is recoverable. */ |
---|
233 | F_SET(ep, F_RCV_ON); |
---|
234 | return (0); |
---|
235 | |
---|
236 | err: msgq(sp, M_ERR, |
---|
237 | "059|Modifications not recoverable if the session fails"); |
---|
238 | return (1); |
---|
239 | } |
---|
240 | |
---|
241 | /* |
---|
242 | * rcv_sync -- |
---|
243 | * Sync the file, optionally: |
---|
244 | * flagging the backup file to be preserved |
---|
245 | * snapshotting the backup file and send email to the user |
---|
246 | * sending email to the user if the file was modified |
---|
247 | * ending the file session |
---|
248 | * |
---|
249 | * PUBLIC: int rcv_sync __P((SCR *, u_int)); |
---|
250 | */ |
---|
251 | int |
---|
252 | rcv_sync(sp, flags) |
---|
253 | SCR *sp; |
---|
254 | u_int flags; |
---|
255 | { |
---|
256 | EXF *ep; |
---|
257 | int fd, rval; |
---|
258 | char *dp, buf[1024]; |
---|
259 | |
---|
260 | /* Make sure that there's something to recover/sync. */ |
---|
261 | ep = sp->ep; |
---|
262 | if (ep == NULL || !F_ISSET(ep, F_RCV_ON)) |
---|
263 | return (0); |
---|
264 | |
---|
265 | /* Sync the file if it's been modified. */ |
---|
266 | if (F_ISSET(ep, F_MODIFIED)) { |
---|
267 | SIGBLOCK; |
---|
268 | if (ep->db->sync(ep->db, R_RECNOSYNC)) { |
---|
269 | F_CLR(ep, F_RCV_ON | F_RCV_NORM); |
---|
270 | msgq_str(sp, M_SYSERR, |
---|
271 | ep->rcv_path, "060|File backup failed: %s"); |
---|
272 | SIGUNBLOCK; |
---|
273 | return (1); |
---|
274 | } |
---|
275 | SIGUNBLOCK; |
---|
276 | |
---|
277 | /* REQUEST: don't remove backing file on exit. */ |
---|
278 | if (LF_ISSET(RCV_PRESERVE)) |
---|
279 | F_SET(ep, F_RCV_NORM); |
---|
280 | |
---|
281 | /* REQUEST: send email. */ |
---|
282 | if (LF_ISSET(RCV_EMAIL)) |
---|
283 | rcv_email(sp, ep->rcv_mpath); |
---|
284 | } |
---|
285 | |
---|
286 | /* |
---|
287 | * !!! |
---|
288 | * Each time the user exec's :preserve, we have to snapshot all of |
---|
289 | * the recovery information, i.e. it's like the user re-edited the |
---|
290 | * file. We copy the DB(3) backing file, and then create a new mail |
---|
291 | * recovery file, it's simpler than exiting and reopening all of the |
---|
292 | * underlying files. |
---|
293 | * |
---|
294 | * REQUEST: snapshot the file. |
---|
295 | */ |
---|
296 | rval = 0; |
---|
297 | if (LF_ISSET(RCV_SNAPSHOT)) { |
---|
298 | if (opts_empty(sp, O_RECDIR, 0)) |
---|
299 | goto err; |
---|
300 | dp = O_STR(sp, O_RECDIR); |
---|
301 | (void)snprintf(buf, sizeof(buf), "%s/vi.XXXXXX", dp); |
---|
302 | if ((fd = rcv_mktemp(sp, buf, dp, S_IRUSR | S_IWUSR)) == -1) |
---|
303 | goto err; |
---|
304 | sp->gp->scr_busy(sp, |
---|
305 | "061|Copying file for recovery...", BUSY_ON); |
---|
306 | if (rcv_copy(sp, fd, ep->rcv_path) || |
---|
307 | close(fd) || rcv_mailfile(sp, 1, buf)) { |
---|
308 | (void)unlink(buf); |
---|
309 | (void)close(fd); |
---|
310 | rval = 1; |
---|
311 | } |
---|
312 | sp->gp->scr_busy(sp, NULL, BUSY_OFF); |
---|
313 | } |
---|
314 | if (0) { |
---|
315 | err: rval = 1; |
---|
316 | } |
---|
317 | |
---|
318 | /* REQUEST: end the file session. */ |
---|
319 | if (LF_ISSET(RCV_ENDSESSION) && file_end(sp, NULL, 1)) |
---|
320 | rval = 1; |
---|
321 | |
---|
322 | return (rval); |
---|
323 | } |
---|
324 | |
---|
325 | /* |
---|
326 | * rcv_mailfile -- |
---|
327 | * Build the file to mail to the user. |
---|
328 | */ |
---|
329 | static int |
---|
330 | rcv_mailfile(sp, issync, cp_path) |
---|
331 | SCR *sp; |
---|
332 | int issync; |
---|
333 | char *cp_path; |
---|
334 | { |
---|
335 | EXF *ep; |
---|
336 | GS *gp; |
---|
337 | struct passwd *pw; |
---|
338 | size_t len; |
---|
339 | time_t now; |
---|
340 | uid_t uid; |
---|
341 | int fd; |
---|
342 | char *dp, *p, *t, buf[4096], mpath[MAXPATHLEN]; |
---|
343 | char *t1, *t2, *t3; |
---|
344 | |
---|
345 | /* |
---|
346 | * XXX |
---|
347 | * MAXHOSTNAMELEN is in various places on various systems, including |
---|
348 | * <netdb.h> and <sys/socket.h>. If not found, use a large default. |
---|
349 | */ |
---|
350 | #ifndef MAXHOSTNAMELEN |
---|
351 | #define MAXHOSTNAMELEN 1024 |
---|
352 | #endif |
---|
353 | char host[MAXHOSTNAMELEN]; |
---|
354 | |
---|
355 | gp = sp->gp; |
---|
356 | if ((pw = getpwuid(uid = getuid())) == NULL) { |
---|
357 | msgq(sp, M_ERR, |
---|
358 | "062|Information on user id %u not found", uid); |
---|
359 | return (1); |
---|
360 | } |
---|
361 | |
---|
362 | if (opts_empty(sp, O_RECDIR, 0)) |
---|
363 | return (1); |
---|
364 | dp = O_STR(sp, O_RECDIR); |
---|
365 | (void)snprintf(mpath, sizeof(mpath), "%s/recover.XXXXXX", dp); |
---|
366 | if ((fd = rcv_mktemp(sp, mpath, dp, S_IRUSR | S_IWUSR)) == -1) |
---|
367 | return (1); |
---|
368 | |
---|
369 | /* |
---|
370 | * XXX |
---|
371 | * We keep an open lock on the file so that the recover option can |
---|
372 | * distinguish between files that are live and those that need to |
---|
373 | * be recovered. There's an obvious window between the mkstemp call |
---|
374 | * and the lock, but it's pretty small. |
---|
375 | */ |
---|
376 | ep = sp->ep; |
---|
377 | if (file_lock(sp, NULL, NULL, fd, 1) != LOCK_SUCCESS) |
---|
378 | msgq(sp, M_SYSERR, "063|Unable to lock recovery file"); |
---|
379 | if (!issync) { |
---|
380 | /* Save the recover file descriptor, and mail path. */ |
---|
381 | ep->rcv_fd = fd; |
---|
382 | if ((ep->rcv_mpath = strdup(mpath)) == NULL) { |
---|
383 | msgq(sp, M_SYSERR, NULL); |
---|
384 | goto err; |
---|
385 | } |
---|
386 | cp_path = ep->rcv_path; |
---|
387 | } |
---|
388 | |
---|
389 | /* |
---|
390 | * XXX |
---|
391 | * We can't use stdio(3) here. The problem is that we may be using |
---|
392 | * fcntl(2), so if ANY file descriptor into the file is closed, the |
---|
393 | * lock is lost. So, we could never close the FILE *, even if we |
---|
394 | * dup'd the fd first. |
---|
395 | */ |
---|
396 | t = sp->frp->name; |
---|
397 | if ((p = strrchr(t, '/')) == NULL) |
---|
398 | p = t; |
---|
399 | else |
---|
400 | ++p; |
---|
401 | (void)time(&now); |
---|
402 | (void)gethostname(host, sizeof(host)); |
---|
403 | len = snprintf(buf, sizeof(buf), |
---|
404 | "%s%s\n%s%s\n%s\n%s\n%s%s\n%s%s\n%s\n\n", |
---|
405 | VI_FHEADER, t, /* Non-standard. */ |
---|
406 | VI_PHEADER, cp_path, /* Non-standard. */ |
---|
407 | "Reply-To: root", |
---|
408 | "From: root (Nvi recovery program)", |
---|
409 | "To: ", pw->pw_name, |
---|
410 | "Subject: Nvi saved the file ", p, |
---|
411 | "Precedence: bulk"); /* For vacation(1). */ |
---|
412 | if (len > sizeof(buf) - 1) |
---|
413 | goto lerr; |
---|
414 | if (write(fd, buf, len) != len) |
---|
415 | goto werr; |
---|
416 | |
---|
417 | len = snprintf(buf, sizeof(buf), |
---|
418 | "%s%.24s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n\n", |
---|
419 | "On ", ctime(&now), ", the user ", pw->pw_name, |
---|
420 | " was editing a file named ", t, " on the machine ", |
---|
421 | host, ", when it was saved for recovery. ", |
---|
422 | "You can recover most, if not all, of the changes ", |
---|
423 | "to this file using the -r option to ", gp->progname, ":\n\n\t", |
---|
424 | gp->progname, " -r ", t); |
---|
425 | if (len > sizeof(buf) - 1) { |
---|
426 | lerr: msgq(sp, M_ERR, "064|Recovery file buffer overrun"); |
---|
427 | goto err; |
---|
428 | } |
---|
429 | |
---|
430 | /* |
---|
431 | * Format the message. (Yes, I know it's silly.) |
---|
432 | * Requires that the message end in a <newline>. |
---|
433 | */ |
---|
434 | #define FMTCOLS 60 |
---|
435 | for (t1 = buf; len > 0; len -= t2 - t1, t1 = t2) { |
---|
436 | /* Check for a short length. */ |
---|
437 | if (len <= FMTCOLS) { |
---|
438 | t2 = t1 + (len - 1); |
---|
439 | goto wout; |
---|
440 | } |
---|
441 | |
---|
442 | /* Check for a required <newline>. */ |
---|
443 | t2 = strchr(t1, '\n'); |
---|
444 | if (t2 - t1 <= FMTCOLS) |
---|
445 | goto wout; |
---|
446 | |
---|
447 | /* Find the closest space, if any. */ |
---|
448 | for (t3 = t2; t2 > t1; --t2) |
---|
449 | if (*t2 == ' ') { |
---|
450 | if (t2 - t1 <= FMTCOLS) |
---|
451 | goto wout; |
---|
452 | t3 = t2; |
---|
453 | } |
---|
454 | t2 = t3; |
---|
455 | |
---|
456 | /* t2 points to the last character to display. */ |
---|
457 | wout: *t2++ = '\n'; |
---|
458 | |
---|
459 | /* t2 points one after the last character to display. */ |
---|
460 | if (write(fd, t1, t2 - t1) != t2 - t1) |
---|
461 | goto werr; |
---|
462 | } |
---|
463 | |
---|
464 | if (issync) { |
---|
465 | rcv_email(sp, mpath); |
---|
466 | if (close(fd)) { |
---|
467 | werr: msgq(sp, M_SYSERR, "065|Recovery file"); |
---|
468 | goto err; |
---|
469 | } |
---|
470 | } |
---|
471 | return (0); |
---|
472 | |
---|
473 | err: if (!issync) |
---|
474 | ep->rcv_fd = -1; |
---|
475 | if (fd != -1) |
---|
476 | (void)close(fd); |
---|
477 | return (1); |
---|
478 | } |
---|
479 | |
---|
480 | /* |
---|
481 | * people making love |
---|
482 | * never exactly the same |
---|
483 | * just like a snowflake |
---|
484 | * |
---|
485 | * rcv_list -- |
---|
486 | * List the files that can be recovered by this user. |
---|
487 | * |
---|
488 | * PUBLIC: int rcv_list __P((SCR *)); |
---|
489 | */ |
---|
490 | int |
---|
491 | rcv_list(sp) |
---|
492 | SCR *sp; |
---|
493 | { |
---|
494 | struct dirent *dp; |
---|
495 | struct stat sb; |
---|
496 | DIR *dirp; |
---|
497 | FILE *fp; |
---|
498 | int found; |
---|
499 | char *p, *t, file[MAXPATHLEN], path[MAXPATHLEN]; |
---|
500 | |
---|
501 | /* Open the recovery directory for reading. */ |
---|
502 | if (opts_empty(sp, O_RECDIR, 0)) |
---|
503 | return (1); |
---|
504 | p = O_STR(sp, O_RECDIR); |
---|
505 | if (chdir(p) || (dirp = opendir(".")) == NULL) { |
---|
506 | msgq_str(sp, M_SYSERR, p, "recdir: %s"); |
---|
507 | return (1); |
---|
508 | } |
---|
509 | |
---|
510 | /* Read the directory. */ |
---|
511 | for (found = 0; (dp = readdir(dirp)) != NULL;) { |
---|
512 | if (strncmp(dp->d_name, "recover.", 8)) |
---|
513 | continue; |
---|
514 | |
---|
515 | /* |
---|
516 | * If it's readable, it's recoverable. |
---|
517 | * |
---|
518 | * XXX |
---|
519 | * Should be "r", we don't want to write the file. However, |
---|
520 | * if we're using fcntl(2), there's no way to lock a file |
---|
521 | * descriptor that's not open for writing. |
---|
522 | */ |
---|
523 | if ((fp = fopen(dp->d_name, "r+")) == NULL) |
---|
524 | continue; |
---|
525 | |
---|
526 | switch (file_lock(sp, NULL, NULL, fileno(fp), 1)) { |
---|
527 | case LOCK_FAILED: |
---|
528 | /* |
---|
529 | * XXX |
---|
530 | * Assume that a lock can't be acquired, but that we |
---|
531 | * should permit recovery anyway. If this is wrong, |
---|
532 | * and someone else is using the file, we're going to |
---|
533 | * die horribly. |
---|
534 | */ |
---|
535 | break; |
---|
536 | case LOCK_SUCCESS: |
---|
537 | break; |
---|
538 | case LOCK_UNAVAIL: |
---|
539 | /* If it's locked, it's live. */ |
---|
540 | (void)fclose(fp); |
---|
541 | continue; |
---|
542 | } |
---|
543 | |
---|
544 | /* Check the headers. */ |
---|
545 | if (fgets(file, sizeof(file), fp) == NULL || |
---|
546 | strncmp(file, VI_FHEADER, sizeof(VI_FHEADER) - 1) || |
---|
547 | (p = strchr(file, '\n')) == NULL || |
---|
548 | fgets(path, sizeof(path), fp) == NULL || |
---|
549 | strncmp(path, VI_PHEADER, sizeof(VI_PHEADER) - 1) || |
---|
550 | (t = strchr(path, '\n')) == NULL) { |
---|
551 | msgq_str(sp, M_ERR, dp->d_name, |
---|
552 | "066|%s: malformed recovery file"); |
---|
553 | goto next; |
---|
554 | } |
---|
555 | *p = *t = '\0'; |
---|
556 | |
---|
557 | /* |
---|
558 | * If the file doesn't exist, it's an orphaned recovery file, |
---|
559 | * toss it. |
---|
560 | * |
---|
561 | * XXX |
---|
562 | * This can occur if the backup file was deleted and we crashed |
---|
563 | * before deleting the email file. |
---|
564 | */ |
---|
565 | errno = 0; |
---|
566 | if (stat(path + sizeof(VI_PHEADER) - 1, &sb) && |
---|
567 | errno == ENOENT) { |
---|
568 | (void)unlink(dp->d_name); |
---|
569 | goto next; |
---|
570 | } |
---|
571 | |
---|
572 | /* Get the last modification time and display. */ |
---|
573 | (void)fstat(fileno(fp), &sb); |
---|
574 | (void)printf("%.24s: %s\n", |
---|
575 | ctime(&sb.st_mtime), file + sizeof(VI_FHEADER) - 1); |
---|
576 | found = 1; |
---|
577 | |
---|
578 | /* Close, discarding lock. */ |
---|
579 | next: (void)fclose(fp); |
---|
580 | } |
---|
581 | if (found == 0) |
---|
582 | (void)printf("vi: no files to recover.\n"); |
---|
583 | (void)closedir(dirp); |
---|
584 | return (0); |
---|
585 | } |
---|
586 | |
---|
587 | /* |
---|
588 | * rcv_read -- |
---|
589 | * Start a recovered file as the file to edit. |
---|
590 | * |
---|
591 | * PUBLIC: int rcv_read __P((SCR *, FREF *)); |
---|
592 | */ |
---|
593 | int |
---|
594 | rcv_read(sp, frp) |
---|
595 | SCR *sp; |
---|
596 | FREF *frp; |
---|
597 | { |
---|
598 | struct dirent *dp; |
---|
599 | struct stat sb; |
---|
600 | DIR *dirp; |
---|
601 | EXF *ep; |
---|
602 | time_t rec_mtime; |
---|
603 | int fd, found, locked, requested, sv_fd; |
---|
604 | char *name, *p, *t, *rp, *recp, *pathp; |
---|
605 | char file[MAXPATHLEN], path[MAXPATHLEN], recpath[MAXPATHLEN]; |
---|
606 | |
---|
607 | if (opts_empty(sp, O_RECDIR, 0)) |
---|
608 | return (1); |
---|
609 | rp = O_STR(sp, O_RECDIR); |
---|
610 | if ((dirp = opendir(rp)) == NULL) { |
---|
611 | msgq_str(sp, M_ERR, rp, "%s"); |
---|
612 | return (1); |
---|
613 | } |
---|
614 | |
---|
615 | name = frp->name; |
---|
616 | sv_fd = -1; |
---|
617 | rec_mtime = 0; |
---|
618 | recp = pathp = NULL; |
---|
619 | for (found = requested = 0; (dp = readdir(dirp)) != NULL;) { |
---|
620 | if (strncmp(dp->d_name, "recover.", 8)) |
---|
621 | continue; |
---|
622 | (void)snprintf(recpath, |
---|
623 | sizeof(recpath), "%s/%s", rp, dp->d_name); |
---|
624 | |
---|
625 | /* |
---|
626 | * If it's readable, it's recoverable. It would be very |
---|
627 | * nice to use stdio(3), but, we can't because that would |
---|
628 | * require closing and then reopening the file so that we |
---|
629 | * could have a lock and still close the FP. Another tip |
---|
630 | * of the hat to fcntl(2). |
---|
631 | * |
---|
632 | * XXX |
---|
633 | * Should be O_RDONLY, we don't want to write it. However, |
---|
634 | * if we're using fcntl(2), there's no way to lock a file |
---|
635 | * descriptor that's not open for writing. |
---|
636 | */ |
---|
637 | if ((fd = open(recpath, O_RDWR, 0)) == -1) |
---|
638 | continue; |
---|
639 | |
---|
640 | switch (file_lock(sp, NULL, NULL, fd, 1)) { |
---|
641 | case LOCK_FAILED: |
---|
642 | /* |
---|
643 | * XXX |
---|
644 | * Assume that a lock can't be acquired, but that we |
---|
645 | * should permit recovery anyway. If this is wrong, |
---|
646 | * and someone else is using the file, we're going to |
---|
647 | * die horribly. |
---|
648 | */ |
---|
649 | locked = 0; |
---|
650 | break; |
---|
651 | case LOCK_SUCCESS: |
---|
652 | locked = 1; |
---|
653 | break; |
---|
654 | case LOCK_UNAVAIL: |
---|
655 | /* If it's locked, it's live. */ |
---|
656 | (void)close(fd); |
---|
657 | continue; |
---|
658 | } |
---|
659 | |
---|
660 | /* Check the headers. */ |
---|
661 | if (rcv_gets(file, sizeof(file), fd) == NULL || |
---|
662 | strncmp(file, VI_FHEADER, sizeof(VI_FHEADER) - 1) || |
---|
663 | (p = strchr(file, '\n')) == NULL || |
---|
664 | rcv_gets(path, sizeof(path), fd) == NULL || |
---|
665 | strncmp(path, VI_PHEADER, sizeof(VI_PHEADER) - 1) || |
---|
666 | (t = strchr(path, '\n')) == NULL) { |
---|
667 | msgq_str(sp, M_ERR, recpath, |
---|
668 | "067|%s: malformed recovery file"); |
---|
669 | goto next; |
---|
670 | } |
---|
671 | *p = *t = '\0'; |
---|
672 | ++found; |
---|
673 | |
---|
674 | /* |
---|
675 | * If the file doesn't exist, it's an orphaned recovery file, |
---|
676 | * toss it. |
---|
677 | * |
---|
678 | * XXX |
---|
679 | * This can occur if the backup file was deleted and we crashed |
---|
680 | * before deleting the email file. |
---|
681 | */ |
---|
682 | errno = 0; |
---|
683 | if (stat(path + sizeof(VI_PHEADER) - 1, &sb) && |
---|
684 | errno == ENOENT) { |
---|
685 | (void)unlink(dp->d_name); |
---|
686 | goto next; |
---|
687 | } |
---|
688 | |
---|
689 | /* Check the file name. */ |
---|
690 | if (strcmp(file + sizeof(VI_FHEADER) - 1, name)) |
---|
691 | goto next; |
---|
692 | |
---|
693 | ++requested; |
---|
694 | |
---|
695 | /* |
---|
696 | * If we've found more than one, take the most recent. |
---|
697 | * |
---|
698 | * XXX |
---|
699 | * Since we're using st_mtime, for portability reasons, |
---|
700 | * we only get a single second granularity, instead of |
---|
701 | * getting it right. |
---|
702 | */ |
---|
703 | (void)fstat(fd, &sb); |
---|
704 | if (recp == NULL || rec_mtime < sb.st_mtime) { |
---|
705 | p = recp; |
---|
706 | t = pathp; |
---|
707 | if ((recp = strdup(recpath)) == NULL) { |
---|
708 | msgq(sp, M_SYSERR, NULL); |
---|
709 | recp = p; |
---|
710 | goto next; |
---|
711 | } |
---|
712 | if ((pathp = strdup(path)) == NULL) { |
---|
713 | msgq(sp, M_SYSERR, NULL); |
---|
714 | free(recp); |
---|
715 | recp = p; |
---|
716 | pathp = t; |
---|
717 | goto next; |
---|
718 | } |
---|
719 | if (p != NULL) { |
---|
720 | free(p); |
---|
721 | free(t); |
---|
722 | } |
---|
723 | rec_mtime = sb.st_mtime; |
---|
724 | if (sv_fd != -1) |
---|
725 | (void)close(sv_fd); |
---|
726 | sv_fd = fd; |
---|
727 | } else |
---|
728 | next: (void)close(fd); |
---|
729 | } |
---|
730 | (void)closedir(dirp); |
---|
731 | |
---|
732 | if (recp == NULL) { |
---|
733 | msgq_str(sp, M_INFO, name, |
---|
734 | "068|No files named %s, readable by you, to recover"); |
---|
735 | return (1); |
---|
736 | } |
---|
737 | if (found) { |
---|
738 | if (requested > 1) |
---|
739 | msgq(sp, M_INFO, |
---|
740 | "069|There are older versions of this file for you to recover"); |
---|
741 | if (found > requested) |
---|
742 | msgq(sp, M_INFO, |
---|
743 | "070|There are other files for you to recover"); |
---|
744 | } |
---|
745 | |
---|
746 | /* |
---|
747 | * Create the FREF structure, start the btree file. |
---|
748 | * |
---|
749 | * XXX |
---|
750 | * file_init() is going to set ep->rcv_path. |
---|
751 | */ |
---|
752 | if (file_init(sp, frp, pathp + sizeof(VI_PHEADER) - 1, 0)) { |
---|
753 | free(recp); |
---|
754 | free(pathp); |
---|
755 | (void)close(sv_fd); |
---|
756 | return (1); |
---|
757 | } |
---|
758 | |
---|
759 | /* |
---|
760 | * We keep an open lock on the file so that the recover option can |
---|
761 | * distinguish between files that are live and those that need to |
---|
762 | * be recovered. The lock is already acquired, just copy it. |
---|
763 | */ |
---|
764 | ep = sp->ep; |
---|
765 | ep->rcv_mpath = recp; |
---|
766 | ep->rcv_fd = sv_fd; |
---|
767 | if (!locked) |
---|
768 | F_SET(frp, FR_UNLOCKED); |
---|
769 | |
---|
770 | /* We believe the file is recoverable. */ |
---|
771 | F_SET(ep, F_RCV_ON); |
---|
772 | return (0); |
---|
773 | } |
---|
774 | |
---|
775 | /* |
---|
776 | * rcv_copy -- |
---|
777 | * Copy a recovery file. |
---|
778 | */ |
---|
779 | static int |
---|
780 | rcv_copy(sp, wfd, fname) |
---|
781 | SCR *sp; |
---|
782 | int wfd; |
---|
783 | char *fname; |
---|
784 | { |
---|
785 | int nr, nw, off, rfd; |
---|
786 | char buf[8 * 1024]; |
---|
787 | |
---|
788 | if ((rfd = open(fname, O_RDONLY, 0)) == -1) |
---|
789 | goto err; |
---|
790 | while ((nr = read(rfd, buf, sizeof(buf))) > 0) |
---|
791 | for (off = 0; nr; nr -= nw, off += nw) |
---|
792 | if ((nw = write(wfd, buf + off, nr)) < 0) |
---|
793 | goto err; |
---|
794 | if (nr == 0) |
---|
795 | return (0); |
---|
796 | |
---|
797 | err: msgq_str(sp, M_SYSERR, fname, "%s"); |
---|
798 | return (1); |
---|
799 | } |
---|
800 | |
---|
801 | /* |
---|
802 | * rcv_gets -- |
---|
803 | * Fgets(3) for a file descriptor. |
---|
804 | */ |
---|
805 | static char * |
---|
806 | rcv_gets(buf, len, fd) |
---|
807 | char *buf; |
---|
808 | size_t len; |
---|
809 | int fd; |
---|
810 | { |
---|
811 | int nr; |
---|
812 | char *p; |
---|
813 | |
---|
814 | if ((nr = read(fd, buf, len - 1)) == -1) |
---|
815 | return (NULL); |
---|
816 | if ((p = strchr(buf, '\n')) == NULL) |
---|
817 | return (NULL); |
---|
818 | (void)lseek(fd, (off_t)((p - buf) + 1), SEEK_SET); |
---|
819 | return (buf); |
---|
820 | } |
---|
821 | |
---|
822 | /* |
---|
823 | * rcv_mktemp -- |
---|
824 | * Paranoid make temporary file routine. |
---|
825 | */ |
---|
826 | static int |
---|
827 | rcv_mktemp(sp, path, dname, perms) |
---|
828 | SCR *sp; |
---|
829 | char *path, *dname; |
---|
830 | int perms; |
---|
831 | { |
---|
832 | int fd; |
---|
833 | |
---|
834 | /* |
---|
835 | * !!! |
---|
836 | * We expect mkstemp(3) to set the permissions correctly. On |
---|
837 | * historic System V systems, mkstemp didn't. Do it here, on |
---|
838 | * GP's. |
---|
839 | * |
---|
840 | * XXX |
---|
841 | * The variable perms should really be a mode_t, and it would |
---|
842 | * be nice to use fchmod(2) instead of chmod(2), here. |
---|
843 | */ |
---|
844 | if ((fd = mkstemp(path)) == -1) |
---|
845 | msgq_str(sp, M_SYSERR, dname, "%s"); |
---|
846 | else |
---|
847 | (void)chmod(path, perms); |
---|
848 | return (fd); |
---|
849 | } |
---|
850 | |
---|
851 | /* |
---|
852 | * rcv_email -- |
---|
853 | * Send email. |
---|
854 | */ |
---|
855 | static void |
---|
856 | rcv_email(sp, fname) |
---|
857 | SCR *sp; |
---|
858 | char *fname; |
---|
859 | { |
---|
860 | struct stat sb; |
---|
861 | char buf[MAXPATHLEN * 2 + 20]; |
---|
862 | |
---|
863 | if (_PATH_SENDMAIL[0] != '/' || stat(_PATH_SENDMAIL, &sb)) |
---|
864 | msgq_str(sp, M_SYSERR, |
---|
865 | _PATH_SENDMAIL, "071|not sending email: %s"); |
---|
866 | else { |
---|
867 | /* |
---|
868 | * !!! |
---|
869 | * If you need to port this to a system that doesn't have |
---|
870 | * sendmail, the -t flag causes sendmail to read the message |
---|
871 | * for the recipients instead of specifying them some other |
---|
872 | * way. |
---|
873 | */ |
---|
874 | (void)snprintf(buf, sizeof(buf), |
---|
875 | "%s -t < %s", _PATH_SENDMAIL, fname); |
---|
876 | (void)system(buf); |
---|
877 | } |
---|
878 | } |
---|