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