1 // Copyright 2020 Michael Reilly (mreilly@resiliware.com).
2 //
3 // Redistribution and use in source and binary forms, with or without
4 // modification, are permitted provided that the following conditions
5 // are met:
6 // 1. Redistributions of source code must retain the above copyright
7 // notice, this list of conditions and the following disclaimer.
8 // 2. Redistributions in binary form must reproduce the above copyright
9 // notice, this list of conditions and the following disclaimer in the
10 // documentation and/or other materials provided with the distribution.
11 // 3. Neither the names of the copyright holders nor the names of the
12 // contributors may be used to endorse or promote products derived from
13 // this software without specific prior written permission.
14 //
15 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16 // ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
18 // PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS
19 // OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20 // EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22 // OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23 // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24 // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 // ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
27 #define SRCNAME "hexpeek_backup.c"
28
29 #include <hexpeek.h>
30
31 #include <limits.h>
32 #include <string.h>
33 #include <errno.h>
34
35 //-------------------------------- Constants ---------------------------------//
36
37 #define HDR_MAGIC_SZ 0x10
38 #define HDR_MAGIC_DATA PRGNM " bk v0\0\0\0"
39
40 #define OPINFO_MAGIC_SZ 0xF
41 #define OPINFO_MAGIC_DATA "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\0\0\0"
42
43 #define OP_STATUS_BACKUP_START 0xB0
44 #define OP_STATUS_BACKUP_DONE 0xBD
45 #define OP_STATUS_RECOVERY_DONE 0xDD
46
47 #define OP_SZ 0x100
48 #define OP_MID (OPINFO_MAGIC_SZ + sizeof(uint8_t) + 6 * sizeof(hoff_t))
49
50 #define OP_CMD_TRUNCATED '~'
51
52 // saved_from and saved_at should be set even if saved_len is zero
53 typedef struct
54 {
55 // Basic information
56 uint8_t magic[OPINFO_MAGIC_SZ];
57 uint8_t status;
58 // Offsets in original data file
59 hoff_t size_orig;
60 hoff_t size_adj;
61 hoff_t last_at;
62 hoff_t saved_from;
63 // Offsets in backup file
64 hoff_t saved_at;
65 hoff_t saved_len;
66 // Nul-terminated string in local encoding
67 char origcmd[OP_SZ - OP_MID];
68 } __attribute__((packed)) BackupOp;
69
70 #define LAST_ADJ_OPIDX MAX_BACKUP_DEPTH
71
72 typedef struct
73 {
74 uint8_t magic[HDR_MAGIC_SZ];
75 uint64_t firstop;
76 uint8_t reserved[sizeof(BackupOp) - HDR_MAGIC_SZ - sizeof(uint64_t)];
77 BackupOp ops[MAX_BACKUP_DEPTH + 1];
78 uint8_t reserved2[0x4000 - (MAX_BACKUP_DEPTH + 2) * sizeof(BackupOp)];
79 } __attribute__((packed)) BackupHeader;
80
81 //---------------------------------- Macros ----------------------------------//
82
83 // Offset in backup file of OpHeader for operation index oi
84 #define BKFL_OPINFO_OFF(oi) ( (1 + (oi)) * sizeof(BackupOp) )
85
86 // Backup completion octet
87 #define BKFL_BFIN_OFF(oi) (BKFL_OPINFO_OFF((oi)) + OPINFO_MAGIC_SZ)
88 #define BKFL_BFIN_PTR(poh) (&((poh)->status))
89 #define BKFL_BFIN_LEN (sizeof(uint8_t))
90
91 // Recovery completion octet
92 #define BKFL_RFIN_OFF(oi) BKFL_BFIN_OFF(oi)
93 #define BKFL_RFIN_PTR(poh) BKFL_BFIN_PTR(poh)
94 #define BKFL_RFIN_LEN BKFL_BFIN_LEN
95
96 #define sync(d) \
97 if(Params.backup_sync && (rc = hexpeek_sync(d)) != RC_OK) \
98 goto end;
99
100 //-------------------------------- Functions ---------------------------------//
101
backupFd(int data_fi)102 int backupFd(int data_fi)
103 {
104 if(BackupDepth <= 0)
105 return -1;
106 else
107 return BK_FD(data_fi,
108 (DT_OPCNT(data_fi) / BackupDepth) % BACKUP_FILE_COUNT);
109 }
110
111 // First non-zero status that is not against
mostRecentOp(BackupHeader const * ph)112 static int mostRecentOp(BackupHeader const *ph)
113 {
114 int max_op = MAX_BACKUP_DEPTH - 1;
115 for( ; max_op >= 0; max_op--)
116 {
117 if(ph->ops[max_op].status)
118 break;
119 }
120 return max_op;
121 }
122
checkOp(BackupHeader const * ph,int cur,int prv)123 static int checkOp(BackupHeader const *ph, int cur, int prv)
124 {
125 if(memcmp(ph->ops[cur].magic, OPINFO_MAGIC_DATA, OPINFO_MAGIC_SZ))
126 return 2;
127 else if(ph->ops[cur].size_orig < 0)
128 return 3;
129 else if(ph->ops[cur].saved_from < 0)
130 return 4;
131 else if(ph->ops[cur].saved_at < (hoff_t)sizeof *ph)
132 return 5;
133 else if(ph->ops[cur].saved_len < 0)
134 return 6;
135 else if(ph->ops[cur].saved_len > 0 && cur > 0 && prv >= 0 &&
136 ph->ops[cur].saved_at <
137 ph->ops[prv].saved_at + ph->ops[prv].saved_len)
138 return 7;
139 else if(ph->ops[cur].origcmd[sizeof ph->ops[cur].origcmd - 1] != '\0')
140 return 8;
141 return 0;
142 }
143
checkHeader(BackupHeader const * ph)144 static rc_t checkHeader(BackupHeader const *ph)
145 {
146 rc_t rc = RC_UNSPEC;
147 int mark = 0, ix = -1, most_recent = -1;
148
149 traceEntry();
150
151 if(memcmp(ph->magic, HDR_MAGIC_DATA, HDR_MAGIC_SZ))
152 {
153 mark = 1;
154 rc = RC_CRIT;
155 goto end;
156 }
157
158 most_recent = mostRecentOp(ph);
159
160 ix = LAST_ADJ_OPIDX;
161 if(ph->ops[ix].status)
162 {
163 mark = checkOp(ph, ix, most_recent);
164 if(mark)
165 {
166 rc = RC_CRIT;
167 goto end;
168 }
169 }
170
171 for(ix = most_recent; ix >= 0; ix--)
172 {
173 mark = checkOp(ph, ix, ix - 1);
174 if(mark)
175 {
176 rc = RC_CRIT;
177 goto end;
178 }
179 }
180
181 rc = RC_OK;
182
183 end:
184 traceExit(TRC_rc " [mark=%d] [ix=%d]", rc, mark, ix);
185 return rc;
186 }
187
readHeader(int backup_fd,BackupHeader * p_hdr)188 static rc_t readHeader(int backup_fd, BackupHeader *p_hdr)
189 {
190 rc_t rc = RC_UNSPEC;
191
192 rc = readat(backup_fd, 0, p_hdr, sizeof *p_hdr);
193 checkrc(rc);
194
195 rc = checkHeader(p_hdr);
196 if(rc)
197 {
198 prerr("%s header is malformed!\n", fdname(backup_fd));
199 goto end;
200 }
201
202 rc = RC_OK;
203
204 end:
205 return rc;
206 }
207
getHeader(int backup_fd,BackupHeader * p_hdr,hoff_t * next_at)208 static rc_t getHeader(int backup_fd, BackupHeader *p_hdr, hoff_t *next_at)
209 {
210 rc_t rc = RC_UNSPEC;
211 int max_op = -1;
212
213 rc = readHeader(backup_fd, p_hdr);
214 checkrc(rc);
215
216 if(p_hdr->ops[LAST_ADJ_OPIDX].status)
217 {
218 rc = RC_CRIT;
219 prerr("cannot make backup with incomplete file adjustment\n");
220 goto end;
221 }
222
223 max_op = mostRecentOp(p_hdr);
224 if(max_op < 0)
225 *next_at = ceilbound(sizeof *p_hdr, PAGESZ);
226 else
227 *next_at = ceilbound(p_hdr->ops[max_op].saved_at +
228 p_hdr->ops[max_op].saved_len, PAGESZ);
229
230 rc = RC_OK;
231
232 end:
233 return rc;
234 }
235
writeOp(int data_fi,int backup_fd,int opix,BackupOp * p_op)236 static rc_t writeOp(int data_fi, int backup_fd, int opix, BackupOp *p_op)
237 {
238 rc_t rc = RC_UNSPEC;
239
240 plugin(3, (void*)0);
241
242 rc = writeat(backup_fd, BKFL_OPINFO_OFF(opix), p_op, sizeof *p_op);
243 checkrc(rc);
244
245 rc = filecpy(DT_FD(data_fi), p_op->saved_from, p_op->saved_len,
246 backup_fd, p_op->saved_at, p_op->saved_len);
247 checkrc(rc);
248
249 sync(backup_fd);
250 p_op->status = OP_STATUS_BACKUP_DONE;
251 rc = writeat(backup_fd, BKFL_BFIN_OFF(opix),
252 BKFL_BFIN_PTR(p_op), BKFL_BFIN_LEN);
253 checkrc(rc);
254 sync(backup_fd);
255
256 rc = RC_OK;
257
258 end:
259 plugin(3, (void*)1);
260 return rc;
261 }
262
makeBackup(ParsedCommand const * ppc)263 rc_t makeBackup(ParsedCommand const *ppc)
264 {
265 rc_t rc = RC_UNSPEC;
266 int opix = 0, backup_fd = 0;
267 hoff_t sv_at = HOFF_NIL;
268 BackupHeader header;
269 BackupOp *p_op = NULL;
270
271 if(BackupDepth <= 0)
272 {
273 rc = RC_OK;
274 goto end;
275 }
276
277 traceEntry("%" PRIu64, DT_OPCNT(ppc->fz.fi));
278
279 assert(ppc);
280 assert(ppc->fz.start >= 0);
281 assert(ppc->fz.len >= 0);
282 memset(&header, 0, sizeof header);
283
284 if(DT_OPCNT(ppc->fz.fi) == UINT64_MAX)
285 {
286 rc = RC_CRIT;
287 prerr("64 bit operation counter would overflow, aborting.\n");
288 goto end;
289 }
290
291 opix = DT_OPCNT(ppc->fz.fi) % BackupDepth;
292 p_op = &header.ops[opix];
293 backup_fd = backupFd(ppc->fz.fi);
294 assert(backup_fd >= 0);
295
296 // If start of round, need to truncate backup file and write a new Header
297 if(opix == 0)
298 {
299 memcpy(header.magic, HDR_MAGIC_DATA, HDR_MAGIC_SZ);
300 header.firstop = DT_OPCNT(ppc->fz.fi);
301 rc = hexpeek_truncate(backup_fd, 0);
302 checkrc(rc);
303 rc = writeat(backup_fd, 0, &header, sizeof header);
304 checkrc(rc);
305 sv_at = ceilbound(sizeof header, PAGESZ);
306 }
307 else
308 {
309 rc = getHeader(backup_fd, &header, &sv_at);
310 checkrc(rc);
311 if(p_op->status && p_op->status != OP_STATUS_RECOVERY_DONE)
312 {
313 prerr("%s header is malformed: unexpected operation present!\n",
314 fdname(backup_fd));
315 goto end;
316 }
317 }
318
319 memset(p_op, 0, sizeof *p_op);
320 memcpy(p_op->magic, OPINFO_MAGIC_DATA, OPINFO_MAGIC_SZ);
321 p_op->status = OP_STATUS_BACKUP_START;
322 p_op->size_orig = filesize(ppc->fz.fi);
323 p_op->last_at = Params.infiles[ppc->fz.fi].last_at;
324 p_op->saved_from = ppc->fz.start;
325 p_op->saved_at = sv_at;
326 switch(ppc->cmd)
327 {
328 case CMD_REPLACE:
329 p_op->size_adj = 0;
330 p_op->saved_len = ppc->fz.len;
331 break;
332 case CMD_INSERT:
333 p_op->size_adj = ppc->fz.len;
334 p_op->saved_len = 0;
335 break;
336 case CMD_KILL:
337 p_op->size_adj = -ppc->fz.len;
338 p_op->saved_len = ppc->fz.len;
339 break;
340 default:
341 die();
342 }
343 if(p_op->saved_from + p_op->saved_len > p_op->size_orig)
344 p_op->saved_len = max(0, p_op->size_orig - p_op->saved_from);
345
346 if(ppc->origcmd)
347 {
348 strncpy(p_op->origcmd, ppc->origcmd, sizeof p_op->origcmd - 1);
349 if(strlen(ppc->origcmd) > sizeof p_op->origcmd - 1)
350 {
351 p_op->origcmd[sizeof p_op->origcmd - 3] = '\0';
352 p_op->origcmd[sizeof p_op->origcmd - 2] = OP_CMD_TRUNCATED;
353 }
354 }
355
356 rc = writeOp(ppc->fz.fi, backup_fd, opix, p_op);
357 checkrc(rc);
358
359 rc = RC_OK;
360
361 end:
362 if(rc)
363 prerr("backup failed\n");
364 traceExit(TRC_rc, rc);
365 return rc;
366 }
367
makeAdjBackup(int data_fi,int backup_fd,hoff_t sv_from)368 rc_t makeAdjBackup(int data_fi, int backup_fd, hoff_t sv_from)
369 {
370 rc_t rc = RC_UNSPEC;
371 hoff_t sv_at = -1;
372 BackupHeader header;
373 BackupOp *p_op = &header.ops[LAST_ADJ_OPIDX];
374
375 if(backup_fd < 0)
376 {
377 rc = RC_OK;
378 goto end;
379 }
380
381 traceEntry("%d, %d, " TRC_hoff, data_fi, backup_fd, trchoff(sv_from));
382
383 rc = getHeader(backup_fd, &header, &sv_at);
384 checkrc(rc);
385
386 memset(p_op, 0, sizeof *p_op);
387 memcpy(p_op->magic, OPINFO_MAGIC_DATA, OPINFO_MAGIC_SZ);
388 p_op->status = OP_STATUS_BACKUP_START;
389 p_op->saved_from = sv_from;
390 p_op->saved_at = sv_at;
391 p_op->saved_len = max(0, filesize(data_fi) - sv_from);
392
393 rc = writeOp(data_fi, backup_fd, LAST_ADJ_OPIDX, p_op);
394 checkrc(rc);
395
396 rc = RC_OK;
397
398 end:
399 if(rc)
400 prerr("backup failed\n");
401 traceExit(TRC_rc, rc);
402 return rc;
403 }
404
clearAdjBackup(int backup_fd,void * vp)405 rc_t clearAdjBackup(int backup_fd, void *vp)
406 {
407 rc_t rc = RC_UNSPEC;
408 hoff_t sv_at = HOFF_NIL;
409 BackupHeader storage;
410 BackupOp *p_adj = (BackupOp*)vp;
411
412 if(backup_fd < 0)
413 {
414 rc = RC_OK;
415 goto end;
416 }
417
418 traceEntry("%d, %p", backup_fd, p_adj);
419
420 if( ! p_adj)
421 {
422 rc = readHeader(backup_fd, &storage);
423 checkrc(rc);
424 p_adj = &storage.ops[LAST_ADJ_OPIDX];
425 }
426
427 if(p_adj->status && p_adj->saved_len)
428 sv_at = p_adj->saved_at;
429
430 memset(p_adj, 0, sizeof *p_adj);
431 rc = writeat(backup_fd, BKFL_OPINFO_OFF(LAST_ADJ_OPIDX),
432 p_adj, sizeof *p_adj);
433 checkrc(rc);
434
435 if(sv_at != HOFF_NIL)
436 {
437 rc = hexpeek_truncate(backup_fd, sv_at);
438 checkrc(rc);
439 }
440
441 sync(backup_fd);
442
443 rc = RC_OK;
444
445 end:
446 traceExit(TRC_rc, rc);
447 return rc;
448 }
449
450 typedef struct
451 {
452 int total;
453 int prev;
454 int reverted;
455 int noncompl;
456 int failed;
457 } RecoveryCounts;
458
459 #define OPFMT "#x%" PRIX64
460 #define OPNUM ((p_hdr)->firstop + (opix))
461 #define OPTRUNC(s) ( ( (s)[sizeof (s) - 3] == '\0' && \
462 (s)[sizeof (s) - 2] == OP_CMD_TRUNCATED ) ? \
463 " (truncated)" : "")
464
recoverOp(int data_fi,int backup_fd,bool ask,int opix,BackupHeader * p_hdr,RecoveryCounts * p_cnt)465 static rc_t recoverOp(int data_fi, int backup_fd, bool ask, int opix,
466 BackupHeader *p_hdr, RecoveryCounts *p_cnt)
467 {
468 rc_t rc = RC_UNSPEC;
469 hoff_t f_sz = HOFF_NIL, post_sz = HOFF_NIL;
470
471 traceEntry("%d, %d, %d, %p, %p", data_fi, backup_fd, opix, p_hdr, p_cnt);
472
473 BackupOp *p_op = &p_hdr->ops[opix];
474
475 switch(p_op->status)
476 {
477 case OP_STATUS_BACKUP_START:
478 console(" Backup record " OPFMT " incomplete, skipping.\n", OPNUM);
479 if(p_cnt) p_cnt->noncompl++;
480 break;
481 case OP_STATUS_BACKUP_DONE:
482 if(p_op->size_adj < 0 && p_op->saved_len == 0)
483 {
484 rc = RC_CRIT;
485 prerr("Backup record " OPFMT " has no data!\n", OPNUM);
486 goto end;
487 }
488
489 if(ask && consoleAsk(" Revert operation " OPFMT " '%s'%s",
490 OPNUM, p_op->origcmd, OPTRUNC(p_op->origcmd)))
491 {
492 rc = RC_DONE;
493 goto end;
494 }
495
496 f_sz = filesize(data_fi);
497 post_sz = p_op->size_orig + p_op->size_adj;
498 if(f_sz == p_op->size_orig)
499 {
500 // Nothing to do, file size is same as original.
501 }
502 else if(f_sz == post_sz)
503 {
504 // Previous file size adjustment completed, so just reverse it.
505 rc = adjustSize(data_fi, p_op->saved_from, -p_op->size_adj,
506 backup_fd);
507 checkrc(rc);
508 }
509 else if(p_op->size_adj >= 0 &&
510 p_op->saved_from + p_op->saved_len >= p_op->size_orig &&
511 f_sz > p_op->size_orig)
512 {
513 // An append operation did not complete, truncate it.
514 rc = hexpeek_truncate(DT_FD(data_fi), p_op->size_orig);
515 checkrc(rc);
516 }
517 else
518 {
519 // The interruption of a file size adjustment should be handled by
520 // recoverAdjOp() - it should not be possible to reach this case.
521 rc = RC_CRIT;
522 prerr("data file size is wrong!\n");
523 goto end;
524 }
525
526 rc = filecpy(backup_fd, p_op->saved_at, p_op->saved_len,
527 DT_FD(data_fi), p_op->saved_from, p_op->saved_len);
528 checkrc(rc);
529
530 p_op->status = OP_STATUS_RECOVERY_DONE;
531 rc = writeat(backup_fd, BKFL_RFIN_OFF(opix),
532 BKFL_RFIN_PTR(p_op), BKFL_RFIN_LEN);
533 checkrc(rc);
534
535 sync(backup_fd);
536
537 Params.infiles[data_fi].at = p_op->last_at;
538 if(DT_OPCNT(data_fi) > 0)
539 DT_OPCNT(data_fi)--;
540
541 if(p_cnt) p_cnt->reverted++;
542 break;
543 case OP_STATUS_RECOVERY_DONE:
544 console(" Backup record " OPFMT " previously recovered, skipping.\n",
545 OPNUM);
546 if(p_cnt) p_cnt->prev++;
547 break;
548 default:
549 rc = RC_CRIT;
550 prerr("Backup record " OPFMT " has unknown status!\n", OPNUM);
551 goto end;
552 }
553
554 rc = RC_OK;
555
556 end:
557 traceExit(TRC_rc, rc);
558 return rc;
559 }
560
recoverAdjOp(int data_fi,int backup_fd,bool ask,BackupOp * p_adj,RecoveryCounts * p_cnt)561 static rc_t recoverAdjOp(int data_fi, int backup_fd, bool ask, BackupOp *p_adj,
562 RecoveryCounts *p_cnt)
563 {
564 rc_t rc = RC_UNSPEC;
565
566 traceEntry("%d, %d, %p, %p", data_fi, backup_fd, p_adj, p_cnt);
567
568 if(p_adj->status == 0)
569 {
570 rc = RC_OK;
571 goto end;
572 }
573
574 if(p_cnt) p_cnt->total++;
575
576 switch(p_adj->status)
577 {
578 case OP_STATUS_BACKUP_START:
579 console(" Backup record for file size adjustment incomplete, "
580 "skipping.\n");
581 if(p_cnt) p_cnt->noncompl++;
582 break;
583 case OP_STATUS_BACKUP_DONE:
584 if(ask &&
585 consoleAsk(" A file size adjustment was interrupted, revert"))
586 {
587 rc = RC_DONE;
588 goto end;
589 }
590 rc = hexpeek_truncate(DT_FD(data_fi),
591 p_adj->saved_from + p_adj->saved_len);
592 checkrc(rc);
593 rc = filecpy(backup_fd, p_adj->saved_at, p_adj->saved_len,
594 DT_FD(data_fi), p_adj->saved_from, p_adj->saved_len);
595 checkrc(rc);
596 sync(backup_fd);
597 rc = clearAdjBackup(backup_fd, p_adj);
598 checkrc(rc);
599 console(" File size adjustment successfully reverted.\n");
600 if(p_cnt) p_cnt->reverted++;
601 break;
602 case OP_STATUS_RECOVERY_DONE:
603 console(" Backup record for file size adjustment previously "
604 "recovered, skipping.\n");
605 if(p_cnt) p_cnt->prev++;
606 default:
607 rc = RC_CRIT;
608 prerr("backup record for file size adjustment has unknown status!\n");
609 goto end;
610 }
611
612 rc = RC_OK;
613
614 end:
615 traceExit(TRC_rc, rc);
616 return rc;
617 }
618
recoverBackupFile(int data_fi,int backup_fd,BackupHeader * p_hdr,bool * uncompleted)619 static rc_t recoverBackupFile(int data_fi,
620 int backup_fd,
621 BackupHeader *p_hdr,
622 bool *uncompleted)
623 {
624 rc_t rc = RC_UNSPEC;
625 int max_op = 0;
626 RecoveryCounts counts;
627
628 memset(&counts, 0, sizeof counts);
629
630 console("\nRecovery from %s starting.\n", fdname(backup_fd));
631
632 max_op = mostRecentOp(p_hdr);
633 counts.total += max_op;
634
635 rc = recoverAdjOp(data_fi, backup_fd, ! Params.recover_auto,
636 p_hdr->ops + LAST_ADJ_OPIDX, &counts);
637 checkrc(rc);
638
639 // Note that this code will process, without error, a backup file that
640 // contains non-complete backup records between complete backup records
641 // even though such a file can never be generated by operation of this
642 // program. Consider adding a pre-check that such a gap does not exist.
643 for(int cur_op = max_op; cur_op >= 0; cur_op--)
644 {
645 rc = recoverOp(data_fi, backup_fd, ! Params.recover_auto, cur_op, p_hdr,
646 &counts);
647 checkrc(rc);
648 }
649
650 rc = RC_OK;
651
652 end:
653 console("\n");
654 if(rc == RC_DONE)
655 {
656 console("Recovery from %s was terminated by user:\n",fdname(backup_fd));
657 }
658 else if(rc)
659 {
660 counts.failed = 1;
661 console("Recovery from %s failed:\n", fdname(backup_fd));
662 }
663 else if(counts.reverted)
664 {
665 console("Recovery from %s was successful:\n", fdname(backup_fd));
666 }
667 else
668 {
669 console("No recovery from %s was attempted:\n", fdname(backup_fd));
670 }
671 int nonproc = counts.total + 1 - counts.prev - counts.reverted
672 - counts.noncompl - counts.failed;
673 console(" x%X backup record%s previously recovered\n",
674 plrztn(counts.prev));
675 console(" x%X backup record%s successfully reverted\n",
676 plrztn(counts.reverted));
677 console(" x%X backup record%s skipped due to incompletion\n",
678 plrztn(counts.noncompl));
679 console(" x%X backup record%s failed recovery attempt\n",
680 plrztn(counts.failed));
681 console(" x%X backup record%s not processed due to early termination\n",
682 plrztn(nonproc));
683 if(counts.noncompl)
684 {
685 console(
686 "When a backup record is skipped during recovery due to incompletion, this\n"
687 "usually indicates the backup for that operation was interrupted, meaning the\n"
688 "operation in question never modified the data file. It is also possible the\n"
689 "backup file has been corrupted since it was written.\n");
690 }
691 if(rc || counts.noncompl || counts.failed || nonproc)
692 *uncompleted = true;
693 return rc;
694 }
695
696 // what: -1 -- print recoverable operations
697 // INT_MAX -- prompt for recovery or auto recover
698 // <DEPTH> -- recover up to DEPTH operations
recoverBackup(int data_fi,int what)699 rc_t recoverBackup(int data_fi, int what)
700 {
701 rc_t rc = RC_UNSPEC;
702 int *backup_fds = Params.infiles[data_fi].bk_fds;
703 int files_count = 0, files_successful = 0;
704 bool ops_uncompleted = false;
705 BackupHeader hrs[BACKUP_FILE_COUNT];
706 int sorted[BACKUP_FILE_COUNT];
707
708 memset(&hrs, 0, sizeof hrs);
709 for(int bidx = 0; bidx < BACKUP_FILE_COUNT; bidx++)
710 sorted[bidx] = -1;
711
712 if(what == INT_MAX)
713 console("\nRecovery starting.\n");
714
715 // Read and validate the Header of each backup file
716 for(int bidx = 0; bidx < BACKUP_FILE_COUNT; bidx++)
717 {
718 ssize_t rdsz = -1;
719 if(backup_fds[bidx] < 0)
720 continue;
721 files_count++;
722 rc = seekto(backup_fds[bidx], 0);
723 checkrc(rc);
724 rdsz = readfull(backup_fds[bidx], &hrs[bidx], sizeof hrs[bidx]);
725 if(rdsz == 0)
726 {
727 if(what == INT_MAX)
728 console("\n%s is empty, skipping.\n", fdname(backup_fds[bidx]));
729 files_count--;
730 continue;
731 }
732 else if(rdsz != sizeof hrs[bidx])
733 {
734 rc = RC_CRIT;
735 if(errno)
736 {
737 prerr("error reading from %s: %s\n", fdname(backup_fds[bidx]),
738 strerror(errno));
739 }
740 else
741 {
742 prerr(EofErrString , fdname(backup_fds[bidx]));
743 }
744 goto end;
745 }
746 if(checkHeader(&hrs[bidx]))
747 {
748 rc = RC_CRIT;
749 prerr("%s header is malformed!\n", fdname(backup_fds[bidx]));
750 goto end;
751 }
752 sorted[bidx] = bidx;
753 }
754
755 // Sort so backup files are processed newest to oldest
756 #if (BACKUP_FILE_COUNT == 2)
757 if(files_count == 2 && hrs[1].firstop > hrs[0].firstop)
758 {
759 sorted[0] = 1;
760 sorted[1] = 0;
761 }
762 #else
763 #error
764 #endif
765
766 if(what == INT_MAX)
767 {
768 for(int st_idx = 0; st_idx < files_count; st_idx++)
769 {
770 rc = recoverBackupFile(data_fi, BK_FD(data_fi, sorted[st_idx]),
771 &hrs[sorted[st_idx]], &ops_uncompleted);
772 if(rc == RC_DONE)
773 {
774 rc = RC_OK;
775 goto end;
776 }
777 else if(rc)
778 {
779 goto end;
780 }
781 files_successful++;
782 }
783 console("\nSyncing data file...\n");
784 rc = hexpeek_sync(DT_FD(data_fi));
785 checkrc(rc);
786 console("Sync complete.\n");
787 }
788 else
789 {
790 for(int st_idx = 0, counter = 0; st_idx < files_count; st_idx++)
791 {
792 BackupHeader *p_hdr = &hrs[sorted[st_idx]];
793 for(int opix = mostRecentOp(p_hdr); opix >= 0; opix--)
794 {
795 if(p_hdr->ops[opix].status != OP_STATUS_RECOVERY_DONE)
796 {
797 counter++; // 1 based index
798 if(what == -1)
799 {
800 console("%X, operation " OPFMT ", command '%s'%s\n",
801 counter, OPNUM,
802 p_hdr->ops[opix].origcmd,
803 OPTRUNC(p_hdr->ops[opix].origcmd));
804 }
805 else if(counter <= what)
806 {
807 rc = recoverOp(data_fi, BK_FD(data_fi, sorted[st_idx]),
808 false, opix, &hrs[sorted[st_idx]], NULL);
809 if(rc)
810 goto end;
811 }
812 else
813 {
814 goto done;
815 }
816 }
817 }
818 }
819 /*
820 if(what > 0)
821 {
822 // Getting here means the undo depth was greater than the number of
823 // operations available to recover. Currently this is not treated as
824 // an error or warning condition.
825 }
826 */
827 }
828
829 done:
830 rc = RC_OK;
831
832 end:
833 if(what == INT_MAX)
834 {
835 if(rc)
836 console("\nRecovery FAILED.\n");
837 else if(files_count != files_successful)
838 console("\nRecovery skipped.\n");
839 else
840 console("\nRecovery complete.\n");
841 if(rc || ops_uncompleted)
842 BackupUnlinkAllowed = false;
843 }
844 return rc;
845 }
846