1 |
|
|
/* $OpenBSD: recover.c,v 1.29 2017/11/10 18:25:48 martijn 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.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: " |
108 |
|
|
#define VI_PHEADER "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_MAX]; |
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); |
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_IRWXU)) == -1) |
157 |
|
|
goto err; |
158 |
|
|
(void)close(fd); |
159 |
|
|
|
160 |
|
|
if ((ep->rcv_path = strdup(path)) == NULL) { |
161 |
|
|
msgq(sp, M_SYSERR, NULL); |
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); |
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); |
189 |
|
|
|
190 |
|
|
/* If we already know the file isn't recoverable, we're done. */ |
191 |
|
|
if (!F_ISSET(ep, F_RCV_ON)) |
192 |
|
|
return (0); |
193 |
|
|
|
194 |
|
|
/* Turn off recoverability until we figure out if this will work. */ |
195 |
|
|
F_CLR(ep, F_RCV_ON); |
196 |
|
|
|
197 |
|
|
/* Test if we're recovering a file, not editing one. */ |
198 |
|
|
if (ep->rcv_mpath == NULL) { |
199 |
|
|
/* Build a file to mail to the user. */ |
200 |
|
|
if (rcv_mailfile(sp, 0, NULL)) |
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_RECNOSYNC)) { |
211 |
|
|
msgq_str(sp, M_SYSERR, ep->rcv_path, |
212 |
|
|
"Preservation failed: %s"); |
213 |
|
|
sp->gp->scr_busy(sp, NULL, BUSY_OFF); |
214 |
|
|
goto err; |
215 |
|
|
} |
216 |
|
|
sp->gp->scr_busy(sp, NULL, BUSY_OFF); |
217 |
|
|
} |
218 |
|
|
|
219 |
|
|
/* Turn off the owner execute bit. */ |
220 |
|
|
(void)chmod(ep->rcv_path, S_IRUSR | S_IWUSR); |
221 |
|
|
|
222 |
|
|
/* We believe the file is recoverable. */ |
223 |
|
|
F_SET(ep, F_RCV_ON); |
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 || !F_ISSET(ep, F_RCV_ON)) |
251 |
|
|
return (0); |
252 |
|
|
|
253 |
|
|
/* Sync the file if it's been modified. */ |
254 |
|
|
if (F_ISSET(ep, F_MODIFIED)) { |
255 |
|
|
if (ep->db->sync(ep->db, R_RECNOSYNC)) { |
256 |
|
|
F_CLR(ep, F_RCV_ON | F_RCV_NORM); |
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)) |
264 |
|
|
F_SET(ep, F_RCV_NORM); |
265 |
|
|
|
266 |
|
|
/* REQUEST: send email. */ |
267 |
|
|
if (LF_ISSET(RCV_EMAIL)) |
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)) { |
283 |
|
|
if (opts_empty(sp, O_RECDIR, 0)) |
284 |
|
|
goto err; |
285 |
|
|
dp = O_STR(sp, O_RECDIR); |
286 |
|
|
(void)snprintf(buf, sizeof(buf), "%s/vi.XXXXXXXXXX", dp); |
287 |
|
|
if ((fd = rcv_mktemp(sp, buf, dp, S_IRUSR | S_IWUSR)) == -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, BUSY_OFF); |
298 |
|
|
} |
299 |
|
|
if (0) { |
300 |
|
|
err: rval = 1; |
301 |
|
|
} |
302 |
|
|
|
303 |
|
|
/* REQUEST: end the file session. */ |
304 |
|
|
if (LF_ISSET(RCV_ENDSESSION)) |
305 |
|
|
F_SET(sp, SC_EXIT_FORCE); |
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_MAX]; |
325 |
|
|
char *t1, *t2, *t3; |
326 |
|
|
char host[HOST_NAME_MAX+1]; |
327 |
|
|
|
328 |
|
|
gp = sp->gp; |
329 |
|
|
if ((pw = getpwuid(uid = getuid())) == NULL) { |
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); |
338 |
|
|
(void)snprintf(mpath, sizeof(mpath), "%s/recover.XXXXXXXXXX", dp); |
339 |
|
|
if ((fd = rcv_mktemp(sp, mpath, dp, S_IRUSR | S_IWUSR)) == -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, NULL, 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) { |
356 |
|
|
msgq(sp, M_SYSERR, NULL); |
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) |
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, t, /* Non-standard. */ |
379 |
|
|
VI_PHEADER, 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 FMTCOLS 60 |
409 |
|
|
for (t1 = buf; len > 0; len -= t2 - t1, t1 = t2) { |
410 |
|
|
/* Check for a short length. */ |
411 |
|
|
if (len <= FMTCOLS) { |
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 <= FMTCOLS) |
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 <= FMTCOLS) |
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_RDONLY|O_NOFOLLOW|O_NONBLOCK); |
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) || |
479 |
|
|
(sb.st_mode & ALLPERMS) != (S_IRUSR | S_IWUSR)) |
480 |
|
|
goto bad; |
481 |
|
|
|
482 |
|
|
if (locked == NULL) |
483 |
|
|
locked = &dummy; |
484 |
|
|
switch ((*locked = file_lock(sp, NULL, NULL, 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_MAX], path[PATH_MAX]; |
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); |
532 |
|
|
if ((dirp = opendir(p)) == NULL) { |
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;) { |
539 |
|
|
if (strncmp(dp->d_name, "recover.", 8)) |
540 |
|
|
continue; |
541 |
|
|
|
542 |
|
|
if ((fd = rcv_openat(sp, dirfd(dirp), dp->d_name, NULL)) == -1) |
543 |
|
|
continue; |
544 |
|
|
|
545 |
|
|
/* Check the headers. */ |
546 |
|
|
if ((fp = fdopen(fd, "r")) == NULL) { |
547 |
|
|
close(fd); |
548 |
|
|
continue; |
549 |
|
|
} |
550 |
|
|
if (fgets(file, sizeof(file), fp) == NULL || |
551 |
|
|
strncmp(file, VI_FHEADER, sizeof(VI_FHEADER) - 1) || |
552 |
|
|
(p = strchr(file, '\n')) == NULL || |
553 |
|
|
fgets(path, sizeof(path), fp) == NULL || |
554 |
|
|
strncmp(path, VI_PHEADER, sizeof(VI_PHEADER) - 1) || |
555 |
|
|
(t = strchr(path, '\n')) == NULL) { |
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 = 0; |
571 |
|
|
if (stat(path + sizeof(VI_PHEADER) - 1, &sb) && |
572 |
|
|
errno == ENOENT) { |
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_mtime), file + sizeof(VI_FHEADER) - 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_MAX], path[PATH_MAX], recpath[PATH_MAX]; |
609 |
|
|
|
610 |
|
|
if (opts_empty(sp, O_RECDIR, 0)) |
611 |
|
|
return (1); |
612 |
|
|
rp = O_STR(sp, O_RECDIR); |
613 |
|
|
if ((dirp = opendir(rp)) == NULL) { |
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; |
622 |
|
|
for (found = requested = 0; (dp = readdir(dirp)) != NULL;) { |
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 || |
634 |
|
|
strncmp(file, VI_FHEADER, sizeof(VI_FHEADER) - 1) || |
635 |
|
|
(p = strchr(file, '\n')) == NULL || |
636 |
|
|
rcv_gets(path, sizeof(path), fd) == NULL || |
637 |
|
|
strncmp(path, VI_PHEADER, sizeof(VI_PHEADER) - 1) || |
638 |
|
|
(t = strchr(path, '\n')) == NULL) { |
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 = 0; |
655 |
|
|
if (stat(path + sizeof(VI_PHEADER) - 1, &sb) && |
656 |
|
|
errno == ENOENT) { |
657 |
|
|
(void)unlink(dp->d_name); |
658 |
|
|
goto next; |
659 |
|
|
} |
660 |
|
|
|
661 |
|
|
/* Check the file name. */ |
662 |
|
|
if (strcmp(file + sizeof(VI_FHEADER) - 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 || |
672 |
|
|
timespeccmp(&rec_mtim, &sb.st_mtim, <)) { |
673 |
|
|
p = recp; |
674 |
|
|
t = pathp; |
675 |
|
|
if ((recp = strdup(recpath)) == NULL) { |
676 |
|
|
msgq(sp, M_SYSERR, NULL); |
677 |
|
|
recp = p; |
678 |
|
|
goto next; |
679 |
|
|
} |
680 |
|
|
if ((pathp = strdup(path)) == NULL) { |
681 |
|
|
msgq(sp, M_SYSERR, NULL); |
682 |
|
|
free(recp); |
683 |
|
|
recp = p; |
684 |
|
|
pathp = t; |
685 |
|
|
goto next; |
686 |
|
|
} |
687 |
|
|
if (p != NULL) { |
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) { |
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) - 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); |
737 |
|
|
|
738 |
|
|
/* We believe the file is recoverable. */ |
739 |
|
|
F_SET(ep, F_RCV_ON); |
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_RDONLY, 0)) == -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); |
778 |
|
|
buf[nr] = '\0'; |
779 |
|
|
if ((p = strchr(buf, '\n')) == NULL) |
780 |
|
|
return (NULL); |
781 |
|
|
(void)lseek(fd, (off_t)((p - buf) + 1), SEEK_SET); |
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 |
|
|
if (_PATH_SENDMAIL[0] != '/' || stat(_PATH_SENDMAIL, &sb) == -1) |
825 |
|
|
msgq_str(sp, M_SYSERR, |
826 |
|
|
_PATH_SENDMAIL, "not sending email: %s"); |
827 |
|
|
else { |
828 |
|
|
/* |
829 |
|
|
* !!! |
830 |
|
|
* If you need to port this to a system that doesn't have |
831 |
|
|
* sendmail, the -t flag causes sendmail to read the message |
832 |
|
|
* for the recipients instead of specifying them some other |
833 |
|
|
* way. |
834 |
|
|
*/ |
835 |
|
|
switch (pid = fork()) { |
836 |
|
|
case -1: /* Error. */ |
837 |
|
|
msgq(sp, M_SYSERR, "fork"); |
838 |
|
|
break; |
839 |
|
|
case 0: /* Sendmail. */ |
840 |
|
|
if (lseek(fd, 0, SEEK_SET) == -1) { |
841 |
|
|
msgq(sp, M_SYSERR, "lseek"); |
842 |
|
|
_exit(127); |
843 |
|
|
} |
844 |
|
|
if (fd != STDIN_FILENO) { |
845 |
|
|
if (dup2(fd, STDIN_FILENO) == -1) { |
846 |
|
|
msgq(sp, M_SYSERR, "dup2"); |
847 |
|
|
_exit(127); |
848 |
|
|
} |
849 |
|
|
close(fd); |
850 |
|
|
} |
851 |
|
|
execl(_PATH_SENDMAIL, "sendmail", "-t", (char *)NULL); |
852 |
|
|
msgq(sp, M_SYSERR, _PATH_SENDMAIL); |
853 |
|
|
_exit(127); |
854 |
|
|
default: /* Parent. */ |
855 |
|
|
while (waitpid(pid, NULL, 0) == -1 && errno == EINTR) |
856 |
|
|
continue; |
857 |
|
|
break; |
858 |
|
|
} |
859 |
|
|
|
860 |
|
|
} |
861 |
|
|
} |