1 /*
2    Bacula(R) - The Network Backup Solution
3 
4    Copyright (C) 2000-2020 Kern Sibbald
5 
6    The original author of Bacula is Kern Sibbald, with contributions
7    from many others, a complete list can be found in the file AUTHORS.
8 
9    You may use this file and others of this release according to the
10    license defined in the LICENSE file, which includes the Affero General
11    Public License, v3.0 ("AGPLv3") and some additional permissions and
12    terms pursuant to its AGPLv3 Section 7.
13 
14    This notice must be preserved when any source code is
15    conveyed and/or propagated.
16 
17    Bacula(R) is a registered trademark of Kern Sibbald.
18  */
19 
20 #include "journal.h"
21 
22 static int DBGLVL = 90;
23 
beginTransaction(const char * mode)24 bool Journal::beginTransaction(const char *mode)
25 {
26    if (hasTransaction) {
27       return true;
28    }
29 
30    bool hasLock = false;
31    int timeout = 1800;
32 
33    for (int time = 0; time < timeout; time++) {
34       _fp = bfopen(_jPath, mode);
35 
36       if (!_fp) {
37          Dmsg0(0, "Tried to start transaction but Journal File was not found.\n");
38          return false;
39       }
40 
41       _fd = fileno(_fp);
42       int rc = flock(_fd, LOCK_EX | LOCK_NB);
43 
44       //Success. Lock acquired.
45       if (rc == 0) {
46          hasLock = true;
47          break;
48       }
49 
50       fclose(_fp);
51       sleep(1);
52    }
53 
54    if (hasLock) {
55       hasTransaction = true;
56       return true;
57    } else {
58       Dmsg0(0, "Tried to start transaction but could not lock Journal File.\n");
59       return false;
60    }
61 }
62 
endTransaction()63 void Journal::endTransaction()
64 {
65    if (!hasTransaction) {
66       return;
67    }
68 
69    if (_fp != NULL) {
70       int rc = flock(_fd, LOCK_UN);
71 
72       if (rc != 0) {
73          Dmsg0(0, "could not release flock\n");
74       }
75 
76       fclose(_fp);
77       _fp = NULL;
78    }
79 
80    _fd = -1;
81    hasTransaction = false;
82 }
83 
84 /**
85  * Given a string formatted as 'key=val\n',
86  * this function tries to return 'val'
87  */
extract_val(const char * key_val)88 char *Journal::extract_val(const char *key_val)
89 {
90    const int SANITY_CHECK = 10000;
91    int max_idx = cstrlen(key_val) - 1;
92    char *val = (char *) malloc(SANITY_CHECK * sizeof(char));
93 
94    int idx_keyend = 0;
95 
96    while(key_val[idx_keyend] != '=') {
97       idx_keyend++;
98 
99       if(idx_keyend > max_idx) {
100          free(val);
101          return NULL;
102       }
103    }
104 
105    int i;
106    int j = 0;
107    for(i = idx_keyend + 1; key_val[i] != '\n' ; i++) {
108       val[j] = key_val[i];
109       j++;
110 
111       if(i > max_idx) {
112          free(val);
113          return NULL;
114       }
115    }
116 
117    val[j] = '\0';
118    return val;
119 }
120 
setJournalPath(const char * path)121 bool Journal::setJournalPath(const char *path)
122 {
123    _jPath = bstrdup(path);
124    FILE *jfile = bfopen(_jPath, "r");
125 
126    if (!jfile) {
127       if (this->beginTransaction("w")) {
128          SettingsRecord rec;
129          rec.journalVersion = JOURNAL_VERSION;
130          this->writeSettings(rec);
131       } else {
132          Dmsg1(0, "(ERROR) Could not create Journal File: %s\n", path);
133          return false;
134       }
135    } else {
136       fclose(jfile);
137    }
138 
139    return true;
140 }
141 
setJournalPath(const char * path,const char * spoolDir)142 bool Journal::setJournalPath(const char *path, const char *spoolDir)
143 {
144    _jPath = bstrdup(path);
145    FILE *jfile = fopen(_jPath, "r");
146 
147    if (!jfile) {
148       if (this->beginTransaction("w")) {
149          SettingsRecord rec;
150          rec.journalVersion = JOURNAL_VERSION;
151          rec.setSpoolDir(spoolDir);
152          this->writeSettings(rec);
153       } else {
154          Dmsg1(0, "(ERROR) Could not create Journal File: %s\n", path);
155          return false;
156       }
157    } else {
158       fclose(jfile);
159    }
160 
161    return true;
162 }
163 
164 
writeSettings(SettingsRecord & rec)165 bool Journal::writeSettings(SettingsRecord &rec)
166 {
167    int rc;
168    bool success = true;
169    const char *spoolDir;
170    char jversion[50];
171    char heartbeat[50];
172 
173    if(!this->beginTransaction("r+")) {
174       Dmsg0(50, "Could not start transaction for writeSettings()\n");
175       success = false;
176       goto bail_out;
177    }
178 
179    spoolDir = rec.getSpoolDir();
180 
181    if (spoolDir == NULL) {
182       spoolDir = "<NULL>";
183    }
184 
185    edit_int64(rec.heartbeat, heartbeat);
186    edit_int64(rec.journalVersion, jversion);
187    rc = fprintf(_fp,
188          "Settings {\n"
189          "spooldir=%s\n"
190          "heartbeat=%s\n"
191          "jversion=%s\n"
192          "}\n",
193          spoolDir,
194          heartbeat,
195          jversion);
196 
197    if(rc < 0) {
198       success = false;
199       Dmsg1(50, "(ERROR) Could not write SettingsRecord. RC=%d\n", rc);
200       goto bail_out;
201    }
202 
203    Dmsg3(DBGLVL,
204          "WROTE RECORD:\n"
205          " Settings {\n"
206          "  spooldir=%s\n"
207          "  heartbeat=%s\n"
208          "  jversion=%s\n"
209          " }\n",
210          spoolDir,
211          heartbeat,
212          jversion);
213 
214 bail_out:
215    this->endTransaction();
216    return success;
217 }
218 
readSettings()219 SettingsRecord *Journal::readSettings()
220 {
221    const int SANITY_CHECK = 10000;
222    bool corrupted = false;
223    char tmp[SANITY_CHECK];
224    char jversion[SANITY_CHECK];
225    char heartbeat[SANITY_CHECK];
226    char spoolpath[SANITY_CHECK];
227    char *jvstr = NULL;
228    char *hbstr = NULL;
229    SettingsRecord *rec = NULL;
230 
231    if(!this->beginTransaction("r+")) {
232       Dmsg0(0, "Could not start transaction for readSettings()\n");
233       goto bail_out;
234    }
235 
236    //reads line "Settings {\n"
237    if(!bfgets(tmp, SANITY_CHECK, _fp)) {
238       corrupted = true;
239       goto bail_out;
240    }
241 
242    rec = new SettingsRecord();
243 
244    //reads Spool Dir
245    if(!bfgets(spoolpath, SANITY_CHECK, _fp)) {
246       corrupted = true;
247       goto bail_out;
248    }
249    rec->setSpoolDir(extract_val(spoolpath));
250    if(rec->getSpoolDir() == NULL) {
251       corrupted = true;
252       goto bail_out;
253    }
254 
255    //reads Heartbeat
256    if(!bfgets(heartbeat, SANITY_CHECK, _fp)) {
257       corrupted = true;
258       goto bail_out;
259    }
260    hbstr = extract_val(heartbeat);
261    if(hbstr == NULL) {
262       corrupted = true;
263       goto bail_out;
264    }
265    rec->heartbeat = atoi(hbstr);
266 
267    //reads Journal Version
268    if(!bfgets(jversion, SANITY_CHECK, _fp)) {
269       corrupted = true;
270       goto bail_out;
271    }
272    jvstr = extract_val(jversion);
273    if(jvstr == NULL) {
274       corrupted = true;
275       goto bail_out;
276    }
277    rec->journalVersion = atoi(jvstr);
278 
279    //reads line "}\n"
280    if(!bfgets(tmp, SANITY_CHECK, _fp)) {
281       corrupted = true;
282       goto bail_out;
283    }
284 
285    Dmsg3(DBGLVL,
286          "READ RECORD:\n"
287          " Settings {\n"
288          "  spooldir=%s\n"
289          "  heartbeat=%s\n"
290          "  jversion=%s\n"
291          " }\n",
292          rec->getSpoolDir(),
293          hbstr,
294          jvstr);
295 
296 bail_out:
297    if(jvstr != NULL) {
298       free(jvstr);
299    }
300 
301    if(hbstr != NULL) {
302       free(hbstr);
303    }
304 
305    if(rec != NULL && rec->getSpoolDir() != NULL && strcmp(rec->getSpoolDir(), "<NULL>") == 0) {
306       free(rec->getSpoolDir());
307       rec->setSpoolDir(NULL);
308    }
309 
310    if(corrupted) {
311       Dmsg0(0, "Could not read Settings Record. Journal is Corrupted.\n");
312 
313       if(rec != NULL) {
314          delete rec;
315          rec = NULL;
316       }
317    }
318 
319    this->endTransaction();
320    return rec;
321 }
322 
writeFileRecord(const FileRecord & record)323 bool Journal::writeFileRecord(const FileRecord &record)
324 {
325    int rc;
326    bool success = true;
327    char mtime_str[50];
328 
329    if(!this->beginTransaction("a")) {
330       success = false;
331       Dmsg0(0, "Could not start transaction for writeFileRecord()\n");
332       goto bail_out;
333    }
334 
335    edit_int64(record.mtime, mtime_str);
336    rc = fprintf(_fp,
337          "File {\n"
338          "name=%s\n"
339          "sname=%s\n"
340          "mtime=%s\n"
341          "attrs=%s\n"
342          "}\n",
343          record.name,
344          record.sname,
345          mtime_str,
346          record.fattrs);
347 
348    if(rc < 0) {
349       success = false;
350       Dmsg1(50, "(ERROR) Could not write FileRecord. RC=%d\n", rc);
351       goto bail_out;
352    }
353 
354    Dmsg4(DBGLVL,
355          "NEW RECORD:\n"
356          " File {\n"
357          "  name=%s\n"
358          "  sname=%s\n"
359          "  mtime=%s"
360          "  attrs=%s\n"
361          " }\n",
362          record.name,
363          record.sname,
364          mtime_str,
365          record.fattrs);
366 
367 bail_out:
368    this->endTransaction();
369    return success;
370 }
371 
readFileRecord()372 FileRecord *Journal::readFileRecord()
373 {
374    const int SANITY_CHECK = 10000;
375    bool corrupted = false;
376    char tmp[SANITY_CHECK];
377    char fname[SANITY_CHECK];
378    char sname[SANITY_CHECK];
379    char fattrs[SANITY_CHECK];
380    char mtime[SANITY_CHECK];
381    char *mstr = NULL;
382    FileRecord *rec = NULL;
383 
384    if(!hasTransaction) {
385       Dmsg0(0, "(ERROR) Journal::readFileRecord() called without any transaction\n");
386       goto bail_out;
387    }
388 
389    //Reads lines until it finds a FileRecord, or until EOF
390    for(;;) {
391       if(!bfgets(tmp, SANITY_CHECK, _fp)) {
392          goto bail_out;
393       }
394 
395       if(strstr(tmp, "File {\n") != NULL) {
396          break;
397       }
398    }
399 
400    rec = new FileRecord();
401 
402    //reads filename
403    if(!bfgets(fname, SANITY_CHECK, _fp)) {
404       corrupted = true;
405       goto bail_out;
406    }
407    rec->name = extract_val(fname);
408    if(rec->name == NULL) {
409       corrupted = true;
410       goto bail_out;
411    }
412 
413    //reads spoolname
414    if(!bfgets(sname, SANITY_CHECK, _fp)) {
415       corrupted = true;
416       goto bail_out;
417    }
418    rec->sname = extract_val(sname);
419    if(rec->sname == NULL) {
420       corrupted = true;
421       goto bail_out;
422    }
423 
424    //reads mtime
425    if(!bfgets(mtime, SANITY_CHECK, _fp)) {
426       corrupted = true;
427       goto bail_out;
428    }
429    mstr = extract_val(mtime);
430    if(mstr == NULL) {
431       corrupted = true;
432       goto bail_out;
433    }
434    rec->mtime = atoi(mstr);
435 
436    //reads encoded attributes
437    if(!bfgets(fattrs, SANITY_CHECK, _fp)) {
438       corrupted = true;
439       goto bail_out;
440    }
441    rec->fattrs = extract_val(fattrs);
442    if(rec->fattrs == NULL) {
443       corrupted = true;
444       goto bail_out;
445    }
446 
447    Dmsg4(DBGLVL,
448          "READ RECORD:\n"
449          " File {\n"
450          "  name=%s\n"
451          "  sname=%s\n"
452          "  mtime=%s\n"
453          "  attrs=%s\n"
454          " }\n",
455          rec->name,
456          rec->sname,
457          mstr,
458          rec->fattrs);
459 
460    //reads line "}\n"
461    if(!bfgets(tmp, SANITY_CHECK, _fp)) {
462       corrupted = true;
463       goto bail_out;
464    }
465 
466 bail_out:
467    if(mstr != NULL) {
468       free(mstr);
469    }
470 
471    if(corrupted) {
472       Dmsg0(0, "Could not read File Record. Journal is Corrupted.\n");
473 
474       if(rec != NULL) {
475          delete rec;
476          rec = NULL;
477       }
478    }
479 
480    return rec;
481 }
482 
writeFolderRecord(const FolderRecord & record)483 bool Journal::writeFolderRecord(const FolderRecord &record)
484 {
485    int rc;
486    bool success = true;
487 
488    if(!this->beginTransaction("a")) {
489       success = false;
490       Dmsg0(0, "Could not start transaction for writeFileRecord()\n");
491       goto bail_out;
492    }
493 
494    rc = fprintf(_fp,
495          "Folder {\n"
496          "path=%s\n"
497          "}\n",
498          record.path);
499 
500    if(rc < 0) {
501       success = false;
502       Dmsg1(0, "(ERROR) Could not write FolderRecord. RC=%d\n", rc);
503       goto bail_out;
504    }
505 
506    Dmsg1(DBGLVL,
507          "NEW RECORD:\n"
508          " Folder {\n"
509          "  path=%s\n"
510          " }\n",
511          record.path);
512 
513 bail_out:
514    this->endTransaction();
515    return success;
516 }
517 
readFolderRecord()518 FolderRecord *Journal::readFolderRecord()
519 {
520    const int SANITY_CHECK = 10000;
521    bool corrupted = false;
522    char tmp[SANITY_CHECK];
523    char path[SANITY_CHECK];
524    FolderRecord *rec = NULL;
525 
526    if(!hasTransaction) {
527       Dmsg0(0, "(ERROR) Journal::readFolderRecord() called without any transaction\n");
528       goto bail_out;
529    }
530 
531    //Reads lines until it finds a FolderRecord, or until EOF
532    for(;;) {
533       if(!bfgets(tmp, SANITY_CHECK, _fp)) {
534          //No need to set 'corrupted = true' here
535          goto bail_out;
536       }
537 
538       if(strstr(tmp, "Folder {\n") != NULL) {
539          break;
540       }
541    }
542 
543    rec = new FolderRecord();
544 
545    //reads folder path
546    if(!bfgets(path, SANITY_CHECK, _fp)) {
547       corrupted = true;
548       goto bail_out;
549    }
550    rec->path = extract_val(path);
551    if(rec->path == NULL) {
552       corrupted = true;
553       goto bail_out;
554    }
555 
556    Dmsg1(DBGLVL,
557          "READ RECORD:\n"
558          " Folder {\n"
559          "  path=%s\n"
560          " }\n",
561          rec->path);
562 
563    //reads line "}\n"
564    if(!bfgets(tmp, SANITY_CHECK, _fp)) {
565       corrupted = true;
566       goto bail_out;
567    }
568 
569 bail_out:
570    if(corrupted) {
571       Dmsg0(0, "Could not read FolderRecord. Journal is Corrupted.\n");
572 
573       if(rec != NULL) {
574          delete rec;
575          rec = NULL;
576       }
577    }
578 
579    return rec;
580 }
581 
removeFolderRecord(const char * folder)582 bool Journal::removeFolderRecord(const char* folder)
583 {
584    bool success = false;
585    const int SANITY_CHECK = 10000;
586    char path[SANITY_CHECK];
587    char tmp[SANITY_CHECK];
588    char *recPath;
589    FILE *tmpFp = NULL;
590    int rc;
591 
592    POOL_MEM tmp_jPath;
593    Mmsg(tmp_jPath, "%s.temp", _jPath);
594 
595    if(!this->beginTransaction("r")) {
596       goto bail_out;
597    }
598 
599    tmpFp = bfopen(tmp_jPath.c_str(), "w");
600 
601    if(tmpFp == NULL) {
602       goto bail_out;
603    }
604 
605    for(;;) {
606       if(!bfgets(tmp, SANITY_CHECK, _fp)) {
607          goto bail_out;
608       }
609 
610       if(strstr(tmp, "Folder {\n") != NULL) {
611          //reads folder path
612          if(!bfgets(path, SANITY_CHECK, _fp)) {
613             goto bail_out;
614          }
615 
616          recPath = extract_val(path);
617 
618          if(recPath == NULL) {
619             goto bail_out;
620          }
621 
622          //reads line "}\n"
623          if(!bfgets(tmp, SANITY_CHECK, _fp)) {
624             goto bail_out;
625          }
626 
627          if(bstrcmp(folder, recPath) == 0) {
628             //Didn't found the Record that needs to be removed
629             rc = fprintf(tmpFp,
630                   "Folder {\n"
631                   "path=%s\n"
632                   "}\n",
633                   recPath);
634 
635             if(rc < 0) {
636                goto bail_out;
637             }
638          } else {
639             //Found the Folder Record that needs to be removed
640             success = true;
641          }
642 
643       } else {
644          fprintf(tmpFp, "%s", tmp);
645       }
646    }
647 
648 bail_out:
649 
650    if(tmpFp != NULL) {
651       fclose(tmpFp);
652    }
653 
654    if(success) {
655       fclose(_fp);
656       _fp = NULL;
657       unlink(_jPath);
658       rc = rename(tmp_jPath.c_str(), _jPath);
659 
660       if(rc != 0) {
661          Dmsg0(0, "Could not rename TMP Journal\n");
662       }
663    }
664 
665    this->endTransaction();
666    return success;
667 }
668 
migrateTo(const char * newPath)669 bool Journal::migrateTo(const char *newPath)
670 {
671    bool success = true;
672    const int SANITY_CHECK = 10000;
673    char tmp[SANITY_CHECK];
674    FILE *tmpFp = NULL;
675    FILE *newFp = NULL;
676    int rc;
677 
678    POOLMEM *tmp_jPath = get_pool_memory(PM_FNAME);
679    Mmsg(tmp_jPath, "%s.temp", newPath);
680 
681    if(!this->beginTransaction("r")) {
682       success = false;
683       goto bail_out;
684    }
685 
686    Dmsg2(DBGLVL, "Migrating Journal %s to %s...\n", _jPath, newPath);
687    tmpFp = bfopen(tmp_jPath, "w");
688    newFp = bfopen(newPath, "w");
689 
690    if (tmpFp == NULL) {
691       Dmsg1(0, "Could not bfopen %s. Aborting migration.\n", tmp_jPath);
692       success = false;
693       goto bail_out;
694    }
695 
696    if (newFp == NULL) {
697       Dmsg1(0, "Could not bfopen %s. Aborting migration.\n", newPath);
698       success = false;
699       goto bail_out;
700    }
701 
702    // Migrate everything to the new Journal ('newFp')
703    // Remove FileRecords from the old Journal
704    // by not saving them in 'tmpFp'
705    for(;;) {
706       if(!bfgets(tmp, SANITY_CHECK, _fp)) {
707          break;
708       }
709 
710       if(strstr(tmp, "File {") != NULL) {
711          //Found a FileRecord
712          //Write it only in the New Jornal
713          fprintf(newFp, "%s", tmp);
714 
715          for(int i = 0; i < 5; i++) {
716             if(!bfgets(tmp, SANITY_CHECK, _fp)) {
717                //Found a corrupted FileRecord
718                Dmsg0(0, "Found a corrupt FileRecord. Canceling Migration");
719                success = false;
720                goto bail_out;
721             }
722 
723             fprintf(newFp, "%s", tmp);
724          }
725 
726       } else {
727          fprintf(newFp, "%s", tmp);
728          fprintf(tmpFp, "%s", tmp);
729       }
730    }
731 
732 bail_out:
733 
734    if(newFp != NULL) {
735       fclose(newFp);
736    }
737 
738    if(tmpFp != NULL) {
739       fclose(tmpFp);
740    }
741 
742    if(success) {
743       fclose(_fp);
744       _fp = NULL;
745       unlink(_jPath);
746       rc = rename(tmp_jPath, _jPath);
747 
748       if(rc != 0) {
749          Dmsg0(0, "Could not rename TMP Journal\n");
750       }
751 
752       free(_jPath);
753       _jPath = bstrdup(newPath);
754       Dmsg0(DBGLVL, "Journal migration completed\n");
755    }
756 
757    free_and_null_pool_memory(tmp_jPath);
758    this->endTransaction();
759    return success;
760 }
761