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