File: | src/usr.bin/vi/build/../common/recover.c |
Warning: | line 330, column 2 Value stored to 'gp' is never read |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
1 | /* $OpenBSD: recover.c,v 1.32 2022/02/20 19:45:51 tb 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 | /* Clear recovery sync flag. */ |
256 | F_CLR(ep, F_RCV_SYNC)(((ep)->flags) &= ~((0x100))); |
257 | if (ep->db->sync(ep->db, R_RECNOSYNC11)) { |
258 | F_CLR(ep, F_RCV_ON | F_RCV_NORM)(((ep)->flags) &= ~((0x040 | 0x020))); |
259 | msgq_str(sp, M_SYSERR, |
260 | ep->rcv_path, "File backup failed: %s"); |
261 | return (1); |
262 | } |
263 | |
264 | /* REQUEST: don't remove backing file on exit. */ |
265 | if (LF_ISSET(RCV_PRESERVE)((flags) & ((0x04)))) |
266 | F_SET(ep, F_RCV_NORM)(((ep)->flags) |= ((0x020))); |
267 | |
268 | /* REQUEST: send email. */ |
269 | if (LF_ISSET(RCV_EMAIL)((flags) & ((0x01)))) |
270 | rcv_email(sp, ep->rcv_fd); |
271 | } |
272 | |
273 | /* |
274 | * !!! |
275 | * Each time the user exec's :preserve, we have to snapshot all of |
276 | * the recovery information, i.e. it's like the user re-edited the |
277 | * file. We copy the DB(3) backing file, and then create a new mail |
278 | * recovery file, it's simpler than exiting and reopening all of the |
279 | * underlying files. |
280 | * |
281 | * REQUEST: snapshot the file. |
282 | */ |
283 | rval = 0; |
284 | if (LF_ISSET(RCV_SNAPSHOT)((flags) & ((0x08)))) { |
285 | if (opts_empty(sp, O_RECDIR, 0)) |
286 | goto err; |
287 | 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); |
288 | (void)snprintf(buf, sizeof(buf), "%s/vi.XXXXXXXXXX", dp); |
289 | if ((fd = rcv_mktemp(sp, buf, dp, S_IRUSR0000400 | S_IWUSR0000200)) == -1) |
290 | goto err; |
291 | sp->gp->scr_busy(sp, |
292 | "Copying file for recovery...", BUSY_ON); |
293 | if (rcv_copy(sp, fd, ep->rcv_path) || |
294 | close(fd) || rcv_mailfile(sp, 1, buf)) { |
295 | (void)unlink(buf); |
296 | (void)close(fd); |
297 | rval = 1; |
298 | } |
299 | sp->gp->scr_busy(sp, NULL((void *)0), BUSY_OFF); |
300 | } |
301 | if (0) { |
302 | err: rval = 1; |
303 | } |
304 | |
305 | /* REQUEST: end the file session. */ |
306 | if (LF_ISSET(RCV_ENDSESSION)((flags) & ((0x02)))) |
307 | F_SET(sp, SC_EXIT_FORCE)(((sp)->flags) |= ((0x00000400))); |
308 | |
309 | return (rval); |
310 | } |
311 | |
312 | /* |
313 | * rcv_mailfile -- |
314 | * Build the file to mail to the user. |
315 | */ |
316 | static int |
317 | rcv_mailfile(SCR *sp, int issync, char *cp_path) |
318 | { |
319 | EXF *ep; |
320 | GS *gp; |
321 | struct passwd *pw; |
322 | size_t len; |
323 | time_t now; |
324 | uid_t uid; |
325 | int fd; |
326 | char *dp, *p, *t, buf[4096], mpath[PATH_MAX1024]; |
327 | char *t1, *t2, *t3; |
328 | char host[HOST_NAME_MAX255+1]; |
329 | |
330 | gp = sp->gp; |
Value stored to 'gp' is never read | |
331 | if ((pw = getpwuid(uid = getuid())) == NULL((void *)0)) { |
332 | msgq(sp, M_ERR, |
333 | "Information on user id %u not found", uid); |
334 | return (1); |
335 | } |
336 | |
337 | if (opts_empty(sp, O_RECDIR, 0)) |
338 | return (1); |
339 | 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); |
340 | (void)snprintf(mpath, sizeof(mpath), "%s/recover.XXXXXXXXXX", dp); |
341 | if ((fd = rcv_mktemp(sp, mpath, dp, S_IRUSR0000400 | S_IWUSR0000200)) == -1) |
342 | return (1); |
343 | |
344 | /* |
345 | * XXX |
346 | * We keep an open lock on the file so that the recover option can |
347 | * distinguish between files that are live and those that need to |
348 | * be recovered. There's an obvious window between the mkstemp call |
349 | * and the lock, but it's pretty small. |
350 | */ |
351 | ep = sp->ep; |
352 | if (file_lock(sp, NULL((void *)0), NULL((void *)0), fd, 1) != LOCK_SUCCESS) |
353 | msgq(sp, M_SYSERR, "Unable to lock recovery file"); |
354 | if (!issync) { |
355 | /* Save the recover file descriptor, and mail path. */ |
356 | ep->rcv_fd = fd; |
357 | if ((ep->rcv_mpath = strdup(mpath)) == NULL((void *)0)) { |
358 | msgq(sp, M_SYSERR, NULL((void *)0)); |
359 | goto err; |
360 | } |
361 | cp_path = ep->rcv_path; |
362 | } |
363 | |
364 | /* |
365 | * XXX |
366 | * We can't use stdio(3) here. The problem is that we may be using |
367 | * fcntl(2), so if ANY file descriptor into the file is closed, the |
368 | * lock is lost. So, we could never close the FILE *, even if we |
369 | * dup'd the fd first. |
370 | */ |
371 | t = sp->frp->name; |
372 | if ((p = strrchr(t, '/')) == NULL((void *)0)) |
373 | p = t; |
374 | else |
375 | ++p; |
376 | (void)time(&now); |
377 | (void)gethostname(host, sizeof(host)); |
378 | len = snprintf(buf, sizeof(buf), |
379 | "%s%s\n%s%s\n%s\n%s\n%s%s\n%s%s\n%s\n%s\n\n", |
380 | VI_FHEADER"X-vi-recover-file: ", t, /* Non-standard. */ |
381 | VI_PHEADER"X-vi-recover-path: ", cp_path, /* Non-standard. */ |
382 | "Reply-To: root", |
383 | "From: root (Nvi recovery program)", |
384 | "To: ", pw->pw_name, |
385 | "Subject: Nvi saved the file ", p, |
386 | "Precedence: bulk", /* For vacation(1). */ |
387 | "Auto-Submitted: auto-generated"); |
388 | if (len > sizeof(buf) - 1) |
389 | goto lerr; |
390 | if (write(fd, buf, len) != len) |
391 | goto werr; |
392 | |
393 | len = snprintf(buf, sizeof(buf), |
394 | "%s%.24s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n\n", |
395 | "On ", ctime(&now), ", the user ", pw->pw_name, |
396 | " was editing a file named ", t, " on the machine ", |
397 | host, ", when it was saved for recovery. ", |
398 | "You can recover most, if not all, of the changes ", |
399 | "to this file using the -r option to ", getprogname(), ":\n\n\t", |
400 | getprogname(), " -r ", t); |
401 | if (len > sizeof(buf) - 1) { |
402 | lerr: msgq(sp, M_ERR, "Recovery file buffer overrun"); |
403 | goto err; |
404 | } |
405 | |
406 | /* |
407 | * Format the message. (Yes, I know it's silly.) |
408 | * Requires that the message end in a <newline>. |
409 | */ |
410 | #define FMTCOLS60 60 |
411 | for (t1 = buf; len > 0; len -= t2 - t1, t1 = t2) { |
412 | /* Check for a short length. */ |
413 | if (len <= FMTCOLS60) { |
414 | t2 = t1 + (len - 1); |
415 | goto wout; |
416 | } |
417 | |
418 | /* Check for a required <newline>. */ |
419 | t2 = strchr(t1, '\n'); |
420 | if (t2 - t1 <= FMTCOLS60) |
421 | goto wout; |
422 | |
423 | /* Find the closest space, if any. */ |
424 | for (t3 = t2; t2 > t1; --t2) |
425 | if (*t2 == ' ') { |
426 | if (t2 - t1 <= FMTCOLS60) |
427 | goto wout; |
428 | t3 = t2; |
429 | } |
430 | t2 = t3; |
431 | |
432 | /* t2 points to the last character to display. */ |
433 | wout: *t2++ = '\n'; |
434 | |
435 | /* t2 points one after the last character to display. */ |
436 | if (write(fd, t1, t2 - t1) != t2 - t1) |
437 | goto werr; |
438 | } |
439 | |
440 | if (issync) { |
441 | rcv_email(sp, fd); |
442 | if (close(fd)) { |
443 | werr: msgq(sp, M_SYSERR, "Recovery file"); |
444 | goto err; |
445 | } |
446 | } |
447 | return (0); |
448 | |
449 | err: if (!issync) |
450 | ep->rcv_fd = -1; |
451 | if (fd != -1) |
452 | (void)close(fd); |
453 | return (1); |
454 | } |
455 | |
456 | /* |
457 | * rcv_openat -- |
458 | * Open a recovery file in the specified dir and lock it. |
459 | * |
460 | * PUBLIC: int rcv_openat(SCR *, int, const char *, int *) |
461 | */ |
462 | static int |
463 | rcv_openat(SCR *sp, int dfd, const char *name, int *locked) |
464 | { |
465 | struct stat sb; |
466 | int fd, dummy; |
467 | |
468 | /* |
469 | * If it's readable, it's recoverable. |
470 | * Note: file_lock() sets the close on exec flag for us. |
471 | */ |
472 | fd = openat(dfd, name, O_RDONLY0x0000|O_NOFOLLOW0x0100|O_NONBLOCK0x0004); |
473 | if (fd == -1) |
474 | goto bad; |
475 | |
476 | /* |
477 | * Real vi recovery files are created with mode 0600. |
478 | * If not a regular file or the mode has changed, skip it. |
479 | */ |
480 | if (fstat(fd, &sb) == -1 || !S_ISREG(sb.st_mode)((sb.st_mode & 0170000) == 0100000) || |
481 | (sb.st_mode & ALLPERMS(0004000|0002000|0001000|0000700|0000070|0000007)) != (S_IRUSR0000400 | S_IWUSR0000200)) |
482 | goto bad; |
483 | |
484 | if (locked == NULL((void *)0)) |
485 | locked = &dummy; |
486 | switch ((*locked = file_lock(sp, NULL((void *)0), NULL((void *)0), fd, 0))) { |
487 | case LOCK_FAILED: |
488 | /* |
489 | * XXX |
490 | * Assume that a lock can't be acquired, but that we |
491 | * should permit recovery anyway. If this is wrong, |
492 | * and someone else is using the file, we're going to |
493 | * die horribly. |
494 | */ |
495 | break; |
496 | case LOCK_SUCCESS: |
497 | break; |
498 | case LOCK_UNAVAIL: |
499 | /* If it's locked, it's live. */ |
500 | goto bad; |
501 | } |
502 | return fd; |
503 | bad: |
504 | if (fd != -1) |
505 | close(fd); |
506 | return -1; |
507 | } |
508 | |
509 | /* |
510 | * people making love |
511 | * never exactly the same |
512 | * just like a snowflake |
513 | * |
514 | * rcv_list -- |
515 | * List the files that can be recovered by this user. |
516 | * |
517 | * PUBLIC: int rcv_list(SCR *); |
518 | */ |
519 | int |
520 | rcv_list(SCR *sp) |
521 | { |
522 | struct dirent *dp; |
523 | struct stat sb; |
524 | DIR *dirp; |
525 | int fd; |
526 | FILE *fp; |
527 | int found; |
528 | char *p, *t, file[PATH_MAX1024], path[PATH_MAX1024]; |
529 | |
530 | /* Open the recovery directory for reading. */ |
531 | if (opts_empty(sp, O_RECDIR, 0)) |
532 | return (1); |
533 | 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); |
534 | if ((dirp = opendir(p)) == NULL((void *)0)) { |
535 | msgq_str(sp, M_SYSERR, p, "recdir: %s"); |
536 | return (1); |
537 | } |
538 | |
539 | /* Read the directory. */ |
540 | for (found = 0; (dp = readdir(dirp)) != NULL((void *)0);) { |
541 | if (strncmp(dp->d_name, "recover.", 8)) |
542 | continue; |
543 | |
544 | if ((fd = rcv_openat(sp, dirfd(dirp), dp->d_name, NULL((void *)0))) == -1) |
545 | continue; |
546 | |
547 | /* Check the headers. */ |
548 | if ((fp = fdopen(fd, "r")) == NULL((void *)0)) { |
549 | close(fd); |
550 | continue; |
551 | } |
552 | if (fgets(file, sizeof(file), fp) == NULL((void *)0) || |
553 | strncmp(file, VI_FHEADER"X-vi-recover-file: ", sizeof(VI_FHEADER"X-vi-recover-file: ") - 1) || |
554 | (p = strchr(file, '\n')) == NULL((void *)0) || |
555 | fgets(path, sizeof(path), fp) == NULL((void *)0) || |
556 | strncmp(path, VI_PHEADER"X-vi-recover-path: ", sizeof(VI_PHEADER"X-vi-recover-path: ") - 1) || |
557 | (t = strchr(path, '\n')) == NULL((void *)0)) { |
558 | msgq_str(sp, M_ERR, dp->d_name, |
559 | "%s: malformed recovery file"); |
560 | goto next; |
561 | } |
562 | *p = *t = '\0'; |
563 | |
564 | /* |
565 | * If the file doesn't exist, it's an orphaned recovery file, |
566 | * toss it. |
567 | * |
568 | * XXX |
569 | * This can occur if the backup file was deleted and we crashed |
570 | * before deleting the email file. |
571 | */ |
572 | errno(*__errno()) = 0; |
573 | if (stat(path + sizeof(VI_PHEADER"X-vi-recover-path: ") - 1, &sb) && |
574 | errno(*__errno()) == ENOENT2) { |
575 | (void)unlinkat(dirfd(dirp), dp->d_name, 0); |
576 | goto next; |
577 | } |
578 | |
579 | /* Get the last modification time and display. */ |
580 | (void)fstat(fd, &sb); |
581 | (void)printf("%.24s: %s\n", |
582 | ctime(&sb.st_mtimest_mtim.tv_sec), file + sizeof(VI_FHEADER"X-vi-recover-file: ") - 1); |
583 | found = 1; |
584 | |
585 | /* Close, discarding lock. */ |
586 | next: (void)fclose(fp); |
587 | } |
588 | if (found == 0) |
589 | (void)printf("%s: No files to recover\n", getprogname()); |
590 | (void)closedir(dirp); |
591 | return (0); |
592 | } |
593 | |
594 | /* |
595 | * rcv_read -- |
596 | * Start a recovered file as the file to edit. |
597 | * |
598 | * PUBLIC: int rcv_read(SCR *, FREF *); |
599 | */ |
600 | int |
601 | rcv_read(SCR *sp, FREF *frp) |
602 | { |
603 | struct dirent *dp; |
604 | struct stat sb; |
605 | DIR *dirp; |
606 | EXF *ep; |
607 | struct timespec rec_mtim; |
608 | int fd, found, lck, requested, sv_fd; |
609 | char *name, *p, *t, *rp, *recp, *pathp; |
610 | char file[PATH_MAX1024], path[PATH_MAX1024], recpath[PATH_MAX1024]; |
611 | |
612 | if (opts_empty(sp, O_RECDIR, 0)) |
613 | return (1); |
614 | 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); |
615 | if ((dirp = opendir(rp)) == NULL((void *)0)) { |
616 | msgq_str(sp, M_SYSERR, rp, "%s"); |
617 | return (1); |
618 | } |
619 | |
620 | name = frp->name; |
621 | sv_fd = -1; |
622 | rec_mtim.tv_sec = rec_mtim.tv_nsec = 0; |
623 | recp = pathp = NULL((void *)0); |
624 | for (found = requested = 0; (dp = readdir(dirp)) != NULL((void *)0);) { |
625 | if (strncmp(dp->d_name, "recover.", 8)) |
626 | continue; |
627 | if ((size_t)snprintf(recpath, sizeof(recpath), "%s/%s", |
628 | rp, dp->d_name) >= sizeof(recpath)) |
629 | continue; |
630 | |
631 | if ((fd = rcv_openat(sp, dirfd(dirp), dp->d_name, &lck)) == -1) |
632 | continue; |
633 | |
634 | /* Check the headers. */ |
635 | if (rcv_gets(file, sizeof(file), fd) == NULL((void *)0) || |
636 | strncmp(file, VI_FHEADER"X-vi-recover-file: ", sizeof(VI_FHEADER"X-vi-recover-file: ") - 1) || |
637 | (p = strchr(file, '\n')) == NULL((void *)0) || |
638 | rcv_gets(path, sizeof(path), fd) == NULL((void *)0) || |
639 | strncmp(path, VI_PHEADER"X-vi-recover-path: ", sizeof(VI_PHEADER"X-vi-recover-path: ") - 1) || |
640 | (t = strchr(path, '\n')) == NULL((void *)0)) { |
641 | msgq_str(sp, M_ERR, recpath, |
642 | "%s: malformed recovery file"); |
643 | goto next; |
644 | } |
645 | *p = *t = '\0'; |
646 | ++found; |
647 | |
648 | /* |
649 | * If the file doesn't exist, it's an orphaned recovery file, |
650 | * toss it. |
651 | * |
652 | * XXX |
653 | * This can occur if the backup file was deleted and we crashed |
654 | * before deleting the email file. |
655 | */ |
656 | errno(*__errno()) = 0; |
657 | if (stat(path + sizeof(VI_PHEADER"X-vi-recover-path: ") - 1, &sb) && |
658 | errno(*__errno()) == ENOENT2) { |
659 | (void)unlink(dp->d_name); |
660 | goto next; |
661 | } |
662 | |
663 | /* Check the file name. */ |
664 | if (strcmp(file + sizeof(VI_FHEADER"X-vi-recover-file: ") - 1, name)) |
665 | goto next; |
666 | |
667 | ++requested; |
668 | |
669 | /* |
670 | * If we've found more than one, take the most recent. |
671 | */ |
672 | (void)fstat(fd, &sb); |
673 | if (recp == NULL((void *)0) || |
674 | 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 ))) { |
675 | p = recp; |
676 | t = pathp; |
677 | if ((recp = strdup(recpath)) == NULL((void *)0)) { |
678 | msgq(sp, M_SYSERR, NULL((void *)0)); |
679 | recp = p; |
680 | goto next; |
681 | } |
682 | if ((pathp = strdup(path)) == NULL((void *)0)) { |
683 | msgq(sp, M_SYSERR, NULL((void *)0)); |
684 | free(recp); |
685 | recp = p; |
686 | pathp = t; |
687 | goto next; |
688 | } |
689 | if (p != NULL((void *)0)) { |
690 | free(p); |
691 | free(t); |
692 | } |
693 | rec_mtim = sb.st_mtim; |
694 | if (sv_fd != -1) |
695 | (void)close(sv_fd); |
696 | sv_fd = fd; |
697 | } else |
698 | next: (void)close(fd); |
699 | } |
700 | (void)closedir(dirp); |
701 | |
702 | if (recp == NULL((void *)0)) { |
703 | msgq_str(sp, M_INFO, name, |
704 | "No files named %s, readable by you, to recover"); |
705 | return (1); |
706 | } |
707 | if (found) { |
708 | if (requested > 1) |
709 | msgq(sp, M_INFO, |
710 | "There are older versions of this file for you to recover"); |
711 | if (found > requested) |
712 | msgq(sp, M_INFO, |
713 | "There are other files for you to recover"); |
714 | } |
715 | |
716 | /* |
717 | * Create the FREF structure, start the btree file. |
718 | * |
719 | * XXX |
720 | * file_init() is going to set ep->rcv_path. |
721 | */ |
722 | if (file_init(sp, frp, pathp + sizeof(VI_PHEADER"X-vi-recover-path: ") - 1, 0)) { |
723 | free(recp); |
724 | free(pathp); |
725 | (void)close(sv_fd); |
726 | return (1); |
727 | } |
728 | |
729 | /* |
730 | * We keep an open lock on the file so that the recover option can |
731 | * distinguish between files that are live and those that need to |
732 | * be recovered. The lock is already acquired, just copy it. |
733 | */ |
734 | ep = sp->ep; |
735 | ep->rcv_mpath = recp; |
736 | ep->rcv_fd = sv_fd; |
737 | if (lck != LOCK_SUCCESS) |
738 | F_SET(frp, FR_UNLOCKED)(((frp)->flags) |= ((0x0100))); |
739 | |
740 | /* We believe the file is recoverable. */ |
741 | F_SET(ep, F_RCV_ON)(((ep)->flags) |= ((0x040))); |
742 | return (0); |
743 | } |
744 | |
745 | /* |
746 | * rcv_copy -- |
747 | * Copy a recovery file. |
748 | */ |
749 | static int |
750 | rcv_copy(SCR *sp, int wfd, char *fname) |
751 | { |
752 | int nr, nw, off, rfd; |
753 | char buf[8 * 1024]; |
754 | |
755 | if ((rfd = open(fname, O_RDONLY0x0000)) == -1) |
756 | goto err; |
757 | while ((nr = read(rfd, buf, sizeof(buf))) > 0) |
758 | for (off = 0; nr; nr -= nw, off += nw) |
759 | if ((nw = write(wfd, buf + off, nr)) < 0) |
760 | goto err; |
761 | if (nr == 0) |
762 | return (0); |
763 | |
764 | err: msgq_str(sp, M_SYSERR, fname, "%s"); |
765 | return (1); |
766 | } |
767 | |
768 | /* |
769 | * rcv_gets -- |
770 | * Fgets(3) for a file descriptor. |
771 | */ |
772 | static char * |
773 | rcv_gets(char *buf, size_t len, int fd) |
774 | { |
775 | int nr; |
776 | char *p; |
777 | |
778 | if ((nr = read(fd, buf, len - 1)) == -1) |
779 | return (NULL((void *)0)); |
780 | buf[nr] = '\0'; |
781 | if ((p = strchr(buf, '\n')) == NULL((void *)0)) |
782 | return (NULL((void *)0)); |
783 | (void)lseek(fd, (off_t)((p - buf) + 1), SEEK_SET0); |
784 | return (buf); |
785 | } |
786 | |
787 | /* |
788 | * rcv_mktemp -- |
789 | * Paranoid make temporary file routine. |
790 | */ |
791 | static int |
792 | rcv_mktemp(SCR *sp, char *path, char *dname, int perms) |
793 | { |
794 | int fd; |
795 | |
796 | /* |
797 | * !!! |
798 | * We expect mkstemp(3) to set the permissions correctly. On |
799 | * historic System V systems, mkstemp didn't. Do it here, on |
800 | * GP's. This also protects us from users with stupid umasks. |
801 | * |
802 | * XXX |
803 | * The variable perms should really be a mode_t. |
804 | */ |
805 | if ((fd = mkstemp(path)) == -1 || fchmod(fd, perms) == -1) { |
806 | msgq_str(sp, M_SYSERR, dname, "%s"); |
807 | if (fd != -1) { |
808 | close(fd); |
809 | unlink(path); |
810 | fd = -1; |
811 | } |
812 | } |
813 | return (fd); |
814 | } |
815 | |
816 | /* |
817 | * rcv_email -- |
818 | * Send email. |
819 | */ |
820 | static void |
821 | rcv_email(SCR *sp, int fd) |
822 | { |
823 | struct stat sb; |
824 | pid_t pid; |
825 | |
826 | /* |
827 | * In secure mode, our pledge(2) includes neither "proc" |
828 | * nor "exec". So simply skip sending the mail. |
829 | * Later vi -r still works because rcv_mailfile() |
830 | * already did all the necessary setup. |
831 | */ |
832 | 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)) |
833 | return; |
834 | |
835 | if (_PATH_SENDMAIL"/usr/sbin/sendmail"[0] != '/' || stat(_PATH_SENDMAIL"/usr/sbin/sendmail", &sb) == -1) |
836 | msgq_str(sp, M_SYSERR, |
837 | _PATH_SENDMAIL"/usr/sbin/sendmail", "not sending email: %s"); |
838 | else { |
839 | /* |
840 | * !!! |
841 | * If you need to port this to a system that doesn't have |
842 | * sendmail, the -t flag causes sendmail to read the message |
843 | * for the recipients instead of specifying them some other |
844 | * way. |
845 | */ |
846 | switch (pid = fork()) { |
847 | case -1: /* Error. */ |
848 | msgq(sp, M_SYSERR, "fork"); |
849 | break; |
850 | case 0: /* Sendmail. */ |
851 | if (lseek(fd, 0, SEEK_SET0) == -1) { |
852 | msgq(sp, M_SYSERR, "lseek"); |
853 | _exit(127); |
854 | } |
855 | if (fd != STDIN_FILENO0) { |
856 | if (dup2(fd, STDIN_FILENO0) == -1) { |
857 | msgq(sp, M_SYSERR, "dup2"); |
858 | _exit(127); |
859 | } |
860 | close(fd); |
861 | } |
862 | execl(_PATH_SENDMAIL"/usr/sbin/sendmail", "sendmail", "-t", (char *)NULL((void *)0)); |
863 | msgq(sp, M_SYSERR, _PATH_SENDMAIL"/usr/sbin/sendmail"); |
864 | _exit(127); |
865 | default: /* Parent. */ |
866 | while (waitpid(pid, NULL((void *)0), 0) == -1 && errno(*__errno()) == EINTR4) |
867 | continue; |
868 | break; |
869 | } |
870 | |
871 | } |
872 | } |