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