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