Bug Summary

File:src/usr.bin/vi/build/../common/recover.c
Warning:line 328, column 2
Value stored to 'gp' is never read

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple amd64-unknown-openbsd7.0 -analyze -disable-free -disable-llvm-verifier -discard-value-names -main-file-name recover.c -analyzer-store=region -analyzer-opt-analyze-nested-blocks -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -mrelocation-model pic -pic-level 1 -pic-is-pie -mframe-pointer=all -relaxed-aliasing -fno-rounding-math -mconstructor-aliases -munwind-tables -target-cpu x86-64 -target-feature +retpoline-indirect-calls -target-feature +retpoline-indirect-branches -tune-cpu generic -debugger-tuning=gdb -fcoverage-compilation-dir=/usr/src/usr.bin/vi/build/obj -resource-dir /usr/local/lib/clang/13.0.0 -I /usr/src/usr.bin/vi/build -I /usr/src/usr.bin/vi/build/../include -I . -internal-isystem /usr/local/lib/clang/13.0.0/include -internal-externc-isystem /usr/include -O2 -fdebug-compilation-dir=/usr/src/usr.bin/vi/build/obj -ferror-limit 19 -fwrapv -D_RET_PROTECTOR -ret-protector -fgnuc-version=4.2.1 -vectorize-loops -vectorize-slp -fno-builtin-malloc -fno-builtin-calloc -fno-builtin-realloc -fno-builtin-valloc -fno-builtin-free -fno-builtin-strdup -fno-builtin-strndup -analyzer-output=html -faddrsig -D__GCC_HAVE_DWARF2_CFI_ASM=1 -o /home/ben/Projects/vmm/scan-build/2022-01-12-194120-40624-1 -x c /usr/src/usr.bin/vi/build/../common/recover.c
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
110static int rcv_copy(SCR *, int, char *);
111static void rcv_email(SCR *, int);
112static char *rcv_gets(char *, size_t, int);
113static int rcv_mailfile(SCR *, int, char *);
114static int rcv_mktemp(SCR *, char *, char *, int);
115static 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 */
123int
124rcv_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);
163err: 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 */
179int
180rcv_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
226err: 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 */
241int
242rcv_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) {
300err: 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 */
314static int
315rcv_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) {
400lerr: 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. */
431wout: *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)) {
441werr: msgq(sp, M_SYSERR, "Recovery file");
442 goto err;
443 }
444 }
445 return (0);
446
447err: 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 */
460static int
461rcv_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;
501bad:
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 */
517int
518rcv_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. */
584next: (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 */
598int
599rcv_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
696next: (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 */
747static int
748rcv_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
762err: msgq_str(sp, M_SYSERR, fname, "%s");
763 return (1);
764}
765
766/*
767 * rcv_gets --
768 * Fgets(3) for a file descriptor.
769 */
770static char *
771rcv_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 */
789static int
790rcv_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 */
818static void
819rcv_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}