Bug Summary

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

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple amd64-unknown-openbsd7.4 -analyze -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name recover.c -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 -ffp-contract=on -fno-rounding-math -mconstructor-aliases -funwind-tables=2 -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/llvm16/lib/clang/16 -I /usr/src/usr.bin/vi/build -I /usr/src/usr.bin/vi/build/../include -I . -internal-isystem /usr/local/llvm16/lib/clang/16/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 -fcf-protection=branch -fno-jump-tables -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/scan/2024-01-11-140451-98009-1 -x c /usr/src/usr.bin/vi/build/../common/recover.c
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
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 /* 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) {
302err: 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 */
316static int
317rcv_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) {
402lerr: 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. */
433wout: *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)) {
443werr: msgq(sp, M_SYSERR, "Recovery file");
444 goto err;
445 }
446 }
447 return (0);
448
449err: 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 */
462static int
463rcv_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;
503bad:
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 */
519int
520rcv_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. */
586next: (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 */
600int
601rcv_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
698next: (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 */
749static int
750rcv_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
764err: msgq_str(sp, M_SYSERR, fname, "%s");
765 return (1);
766}
767
768/*
769 * rcv_gets --
770 * Fgets(3) for a file descriptor.
771 */
772static char *
773rcv_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 */
791static int
792rcv_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 */
820static void
821rcv_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}