1 /*********************************************************************/
2 // dar - disk archive - a backup/restoration program
3 // Copyright (C) 2002-2052 Denis Corbin
4 //
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software
17 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18 //
19 // to contact the author : http://dar.linux.free.fr/email.html
20 /*********************************************************************/
21
22 #include "../my_config.h"
23
24 extern "C"
25 {
26 #if STDC_HEADERS
27 #include <stdlib.h>
28 #endif
29
30 #if HAVE_ERRNO_H
31 #include <errno.h>
32 #endif
33 } // end extern "C"
34
35 #include <iomanip>
36 #include <iostream>
37 #include <deque>
38 #include "database.hpp"
39 #include "user_interaction.hpp"
40 #include "deci.hpp"
41 #include "tools.hpp"
42 #include "storage.hpp"
43 #include "database_header.hpp"
44
45 using namespace libdar;
46 using namespace std;
47
48 static storage *file2storage(generic_file &f, memory_pool *pool);
49 static void memory2file(storage &s, generic_file &f);
50
51 namespace libdar
52 {
53
database()54 database::database()
55 {
56 archive_data dat;
57
58 dat.chemin = "";
59 dat.basename = "";
60 coordinate.clear();
61 coordinate.push_back(dat); // coordinate[0] is never used, but must exist
62 options_to_dar.clear();
63 dar_path = "";
64 files = new (get_pool()) data_dir("."); // "." or whaterver else (this name is not used)
65 if(files == nullptr)
66 throw Ememory("database::database");
67 data_files = nullptr;
68 check_order_asked = true;
69 cur_db_version = database_header_get_supported_version();
70 }
71
database(user_interaction & dialog,const string & base,const database_open_options & opt)72 database::database(user_interaction & dialog, const string & base, const database_open_options & opt)
73 {
74 generic_file *f = database_header_open(dialog, get_pool(), base, cur_db_version);
75
76 if(f == nullptr)
77 throw Ememory("database::database");
78 try
79 {
80 check_order_asked = opt.get_warn_order();
81 build(dialog, *f, opt.get_partial(), opt.get_partial_read_only(), cur_db_version);
82 }
83 catch(...)
84 {
85 delete f;
86 throw;
87 }
88 delete f;
89 }
90
build(user_interaction & dialog,generic_file & f,bool partial,bool read_only,const unsigned char db_version)91 void database::build(user_interaction & dialog, generic_file & f, bool partial, bool read_only, const unsigned char db_version)
92 {
93 NLS_SWAP_IN;
94 try
95 {
96 struct archive_data dat;
97
98 if(db_version > database_header_get_supported_version())
99 throw SRC_BUG; // we should not get there if the database is more recent than what that software can handle. this is necessary if we do not want to destroy the database or loose data.
100 coordinate.clear();
101 infinint tmp = infinint(f); // number of archive to read
102 while(!tmp.is_zero())
103 {
104 tools_read_string(f, dat.chemin);
105 tools_read_string(f, dat.basename);
106 if(db_version >= 3)
107 dat.root_last_mod.read(f, db2archive_version(db_version));
108 else
109 dat.root_last_mod = datetime(0);
110 coordinate.push_back(dat);
111 --tmp;
112 }
113 if(coordinate.empty())
114 throw Erange("database::database", gettext("Badly formatted database"));
115 tools_read_vector(f, options_to_dar);
116 tools_read_string(f, dar_path);
117 if(db_version < database_header_get_supported_version())
118 partial = false;
119
120 if(!partial)
121 {
122 files = data_tree_read(f, db_version, get_pool());
123 if(files == nullptr)
124 throw Ememory("database::database");
125 if(files->get_name() != ".")
126 files->set_name(".");
127 data_files = nullptr;
128 }
129 else
130 {
131 if(!read_only)
132 {
133 files = nullptr;
134 data_files = file2storage(f, get_pool());
135 }
136 else
137 {
138 files = nullptr;
139 data_files = nullptr;
140 }
141 }
142 }
143 catch(...)
144 {
145 NLS_SWAP_OUT;
146 throw;
147 }
148 NLS_SWAP_OUT;
149 }
150
~database()151 database::~database()
152 {
153 if(files != nullptr)
154 delete files;
155 if(data_files != nullptr)
156 delete data_files;
157 }
158
dump(user_interaction & dialog,const std::string & filename,const database_dump_options & opt) const159 void database::dump(user_interaction & dialog, const std::string & filename, const database_dump_options & opt) const
160 {
161 if(files == nullptr && data_files == nullptr)
162 throw Erange("database::dump", gettext("Cannot write down a read-only database"));
163
164 generic_file *f = database_header_create(dialog, get_pool(), filename, opt.get_overwrite());
165 if(f == nullptr)
166 throw Ememory("database::dump");
167
168 try
169 {
170 archive_num tmp = coordinate.size();
171
172 infinint(tmp).dump(*f);
173 for(archive_num i = 0; i < tmp; ++i)
174 {
175 tools_write_string(*f, coordinate[i].chemin);
176 tools_write_string(*f, coordinate[i].basename);
177 coordinate[i].root_last_mod.dump(*f);
178 }
179 tools_write_vector(*f, options_to_dar);
180 tools_write_string(*f, dar_path);
181 if(files != nullptr)
182 files->dump(*f);
183 else
184 if(data_files != nullptr)
185 memory2file(*data_files, *f);
186 else
187 throw SRC_BUG;
188 }
189 catch(...)
190 {
191 if(f != nullptr)
192 delete f;
193 throw;
194 }
195 if(f != nullptr)
196 delete f;
197 }
198
add_archive(const archive & arch,const string & chemin,const string & basename,const database_add_options & opt)199 void database::add_archive(const archive & arch, const string & chemin, const string & basename, const database_add_options & opt)
200 {
201 NLS_SWAP_IN;
202 try
203 {
204 struct archive_data dat;
205 archive_num number = coordinate.size();
206
207 if(files == nullptr)
208 throw SRC_BUG;
209 if(basename == "")
210 throw Erange("database::add_archive", gettext("Empty string is an invalid archive basename"));
211 if(number >= ARCHIVE_NUM_MAX)
212 throw Erange("database::add_archive", gettext("Cannot add another archive, database is full"));
213
214 dat.chemin = chemin;
215 dat.basename = basename;
216 dat.root_last_mod = arch.get_catalogue().get_root_dir_last_modif();
217 coordinate.push_back(dat);
218 data_tree_update_with(arch.get_catalogue().get_contenu(), number, files);
219 if(number > 1)
220 files->finalize_except_self(number, get_root_last_mod(number), 0);
221 }
222 catch(...)
223 {
224 NLS_SWAP_OUT;
225 throw;
226 }
227 NLS_SWAP_OUT;
228 }
229
remove_archive(archive_num min,archive_num max,const database_remove_options & opt)230 void database::remove_archive(archive_num min, archive_num max, const database_remove_options & opt)
231 {
232 NLS_SWAP_IN;
233 try
234 {
235 min = get_real_archive_num(min, opt.get_revert_archive_numbering());
236 max = get_real_archive_num(max, opt.get_revert_archive_numbering());
237 if(min > max)
238 throw Erange("database::remove_archive", gettext("Incorrect archive range in database"));
239 if(min == 0 || max >= coordinate.size())
240 throw Erange("database::remove_archive", gettext("Incorrect archive range in database"));
241 for(U_I i = max ; i >= min ; --i)
242 {
243 if(files == nullptr)
244 throw SRC_BUG;
245 files->remove_all_from(i, coordinate.size() - 1);
246 files->skip_out(i);
247 coordinate.erase(coordinate.begin() + i);
248 }
249 }
250 catch(...)
251 {
252 NLS_SWAP_OUT;
253 throw;
254 }
255 NLS_SWAP_OUT;
256 }
257
change_name(archive_num num,const string & basename,const database_change_basename_options & opt)258 void database::change_name(archive_num num, const string & basename, const database_change_basename_options &opt)
259 {
260 NLS_SWAP_IN;
261 try
262 {
263 num = get_real_archive_num(num, opt.get_revert_archive_numbering());
264 if(num < coordinate.size() && num != 0)
265 coordinate[num].basename = basename;
266 else
267 throw Erange("database::change_name", gettext("Non existent archive in database"));
268 }
269 catch(...)
270 {
271 NLS_SWAP_OUT;
272 throw;
273 }
274 NLS_SWAP_OUT;
275 }
276
set_path(archive_num num,const string & chemin,const database_change_path_options & opt)277 void database::set_path(archive_num num, const string & chemin, const database_change_path_options & opt)
278 {
279 NLS_SWAP_IN;
280 try
281 {
282 num = get_real_archive_num(num, opt.get_revert_archive_numbering());
283 if(num < coordinate.size() && coordinate[num].basename != "")
284 coordinate[num].chemin = chemin;
285 else
286 throw Erange("database::change_name", gettext("Non existent archive in database"));
287 }
288 catch(...)
289 {
290 NLS_SWAP_OUT;
291 throw;
292 }
293 NLS_SWAP_OUT;
294 }
295
set_permutation(archive_num src,archive_num dst)296 void database::set_permutation(archive_num src, archive_num dst)
297 {
298 NLS_SWAP_IN;
299 try
300 {
301 struct archive_data moved;
302
303 if(files == nullptr)
304 throw SRC_BUG;
305 if(src >= coordinate.size() || src <= 0)
306 throw Erange("database::set_permutation", string(gettext("Invalid archive number: ")) + tools_int2str(src));
307 if(dst >= coordinate.size() || dst <= 0)
308 throw Erange("database::set_permutation", string(gettext("Invalid archive number: ")) + tools_int2str(dst));
309
310 moved = coordinate[src];
311 coordinate.erase(coordinate.begin()+src);
312 coordinate.insert(coordinate.begin()+dst, moved);
313 files->apply_permutation(src, dst);
314
315 // update et_absent dates
316
317 set<archive_num> re_finalize;
318 set<archive_num>::iterator re_it;
319
320 if(src < dst)
321 {
322 re_finalize.insert(src);
323 re_finalize.insert(dst);
324 if(dst+1 < (archive_num)coordinate.size())
325 re_finalize.insert(dst+1);
326 }
327 else // src >= dst
328 {
329 if(src+1 < (archive_num)coordinate.size())
330 re_finalize.insert(src+1);
331 re_finalize.insert(dst);
332 if(dst+1 < (archive_num)coordinate.size())
333 re_finalize.insert(dst+1);
334
335 // if src == dst the set still contains on entry (src or dst).
336 // this is intended to let the user have the possibility
337 // to ask dates recompilation, even if in theory this is useless
338 }
339
340 re_it = re_finalize.begin();
341 while(re_it != re_finalize.end())
342 {
343 files->finalize_except_self(*re_it, get_root_last_mod(*re_it), *re_it + 1);
344 ++re_it;
345 }
346
347 }
348 catch(...)
349 {
350 NLS_SWAP_OUT;
351 throw;
352 }
353 NLS_SWAP_OUT;
354 }
355
show_contents(user_interaction & dialog) const356 void database::show_contents(user_interaction & dialog) const
357 {
358 NLS_SWAP_IN;
359 try
360 {
361 string opt = tools_concat_vector(" ", options_to_dar);
362
363 if(!dialog.get_use_dar_manager_contents())
364 {
365 dialog.warning("\n");
366 dialog.printf(gettext("dar path : %S\n"), &dar_path);
367 dialog.printf(gettext("dar options : %S\n"), &opt);
368 dialog.printf(gettext("database version: %d\n"), cur_db_version);
369 dialog.warning("\n");
370 dialog.printf(gettext("archive # | path | basename\n"));
371 dialog.printf("------------+--------------+---------------\n");
372 }
373
374 for(archive_num i = 1; i < coordinate.size(); ++i)
375 {
376 if(dialog.get_use_dar_manager_contents())
377 dialog.dar_manager_contents(i, coordinate[i].chemin, coordinate[i].basename);
378 else
379 {
380 opt = (coordinate[i].chemin == "") ? gettext("<empty>") : coordinate[i].chemin;
381 dialog.printf(" \t%u\t%S\t%S\n", i, &opt, &coordinate[i].basename);
382 }
383 }
384 }
385 catch(...)
386 {
387 NLS_SWAP_OUT;
388 throw;
389 }
390 NLS_SWAP_OUT;
391 }
392
show_files(user_interaction & dialog,archive_num num,const database_used_options & opt) const393 void database::show_files(user_interaction & dialog, archive_num num, const database_used_options & opt) const
394 {
395 NLS_SWAP_IN;
396 try
397 {
398 if(num != 0)
399 num = get_real_archive_num(num, opt.get_revert_archive_numbering());
400 if(files == nullptr)
401 throw SRC_BUG;
402
403 if(num < coordinate.size())
404 files->show(dialog, num);
405 else
406 throw Erange("database::show_files", gettext("Non existent archive in database"));
407 }
408 catch(...)
409 {
410 NLS_SWAP_OUT;
411 throw;
412 }
413 NLS_SWAP_OUT;
414 }
415
416
show_version(user_interaction & dialog,path chemin) const417 void database::show_version(user_interaction & dialog, path chemin) const
418 {
419 NLS_SWAP_IN;
420 try
421 {
422 const data_tree *ptr = nullptr;
423 const data_dir *ptr_dir = files;
424 string tmp;
425
426 if(files == nullptr)
427 throw SRC_BUG;
428
429 if(!chemin.is_relative())
430 throw Erange("database::show_version", gettext("Invalid path, path must be relative"));
431
432 while(chemin.pop_front(tmp) && ptr_dir != nullptr)
433 {
434 ptr = ptr_dir->read_child(tmp);
435 if(ptr == nullptr)
436 throw Erange("database::show_version", gettext("Non existent file in database"));
437 ptr_dir = dynamic_cast<const data_dir *>(ptr);
438 }
439
440 if(ptr_dir == nullptr)
441 throw Erange("database::show_version", gettext("Non existent file in database"));
442
443 ptr = ptr_dir->read_child(chemin.display());
444 if(ptr == nullptr)
445 throw Erange("database::show_version", gettext("Non existent file in database"));
446 else
447 ptr->listing(dialog);
448 }
449 catch(...)
450 {
451 NLS_SWAP_OUT;
452 throw;
453 }
454 NLS_SWAP_OUT;
455 }
456
show_most_recent_stats(user_interaction & dialog) const457 void database::show_most_recent_stats(user_interaction & dialog) const
458 {
459 NLS_SWAP_IN;
460 try
461 {
462 vector<infinint> stats_data(coordinate.size(), 0);
463 vector<infinint> stats_ea(coordinate.size(), 0);
464 vector<infinint> total_data(coordinate.size(), 0);
465 vector<infinint> total_ea(coordinate.size(), 0);
466 if(files == nullptr)
467 throw SRC_BUG;
468 files->compute_most_recent_stats(stats_data, stats_ea, total_data, total_ea);
469
470 if(!dialog.get_use_dar_manager_statistics())
471 {
472 dialog.printf(gettext(" archive # | most recent/total data | most recent/total EA\n"));
473 dialog.printf(gettext("--------------+-------------------------+-----------------------\n")); // having it with gettext let the translater adjust columns width
474 }
475 for(archive_num i = 1; i < coordinate.size(); ++i)
476 if(dialog.get_use_dar_manager_statistics())
477 dialog.dar_manager_statistics(i, stats_data[i], total_data[i], stats_ea[i], total_ea[i]);
478 else
479 dialog.printf("\t%u %i/%i \t\t\t %i/%i\n", i, &stats_data[i], &total_data[i], &stats_ea[i], &total_ea[i]);
480 }
481 catch(...)
482 {
483 NLS_SWAP_OUT;
484 throw;
485 }
486 NLS_SWAP_OUT;
487 }
488
489
restore(user_interaction & dialog,const vector<string> & filename,const database_restore_options & opt)490 void database::restore(user_interaction & dialog,
491 const vector<string> & filename,
492 const database_restore_options & opt)
493 {
494 NLS_SWAP_IN;
495 try
496 {
497 map<archive_num, vector<string> > command_line;
498 deque<string> anneau;
499 const data_tree *ptr;
500
501 anneau.assign(filename.begin(), filename.end());
502 if(files == nullptr)
503 throw SRC_BUG;
504
505 if(opt.get_info_details())
506 dialog.warning(gettext("Checking chronological ordering of files between the archives..."));
507 check_order(dialog);
508
509 // determination of the archive to restore and files to restore for each selected file
510 while(!anneau.empty())
511 {
512 if(data_tree_find(anneau.front(), *files, ptr))
513 {
514 const data_dir *ptr_dir = dynamic_cast<const data_dir *>(ptr);
515 archive_num num_data = 0;
516 archive_num num_ea = 0;
517 data_tree::lookup look_ea, look_data;
518
519 look_data = ptr->get_data(num_data, opt.get_date(), opt.get_even_when_removed());
520 look_ea = ptr->get_EA(num_ea, opt.get_date(), opt.get_even_when_removed());
521
522 switch(look_data)
523 {
524 case data_tree::found_present:
525 break;
526 case data_tree::found_removed:
527 num_data = 0; // we do not restore it
528 if(opt.get_info_details())
529 dialog.warning(string(gettext("File recorded as removed at this date in database: ")) + anneau.front());
530 break;
531 case data_tree::not_found:
532 num_data = 0;
533 dialog.warning(string(gettext("File not found in database: ")) + anneau.front());
534 break;
535 case data_tree::not_restorable:
536 num_data = 0;
537 dialog.warning(string(gettext("File found in database but impossible to restore (only found \"unchanged\" in differential backups): ")) + anneau.front());
538 break;
539 default:
540 throw SRC_BUG;
541 }
542
543 switch(look_ea)
544 {
545 case data_tree::found_present:
546 if(opt.get_even_when_removed()
547 && look_data == data_tree::found_present
548 && num_data > num_ea)
549 num_ea = num_data;
550 break;
551 case data_tree::found_removed:
552 num_ea = 0; // we do not restore it
553 break;
554 case data_tree::not_found:
555 num_ea = 0;
556 break;
557 case data_tree::not_restorable:
558 num_ea = 0;
559 dialog.warning(string(gettext("Extended Attribute of file found in database but impossible to restore (only found \"unchanged\" in differential backups): ")) + anneau.front());
560 break;
561 default:
562 throw SRC_BUG;
563 }
564
565 // if there is something to restore for that file
566
567 if(look_ea == data_tree::found_present || look_data == data_tree::found_present)
568 {
569 if(num_data == num_ea) // both EA and data are located in the same archive
570 {
571 if(num_data != 0) // archive is not zero (so it is a real archive)
572 {
573 command_line[num_data].push_back("-g");
574 command_line[num_data].push_back(anneau.front());
575 }
576 else // archive number is zero (not a valid archive number)
577 if(!opt.get_date().is_zero()) // a date was specified
578 {
579 string fic = anneau.front();
580 if(opt.get_info_details())
581 dialog.printf(gettext("%S did not exist before specified date and cannot be restored"), &fic);
582 }
583 else
584 throw SRC_BUG; // no date limitation, the file's data should have been found
585 }
586 else // num_data != num_ea
587 {
588 if(num_data != 0)
589 {
590 command_line[num_data].push_back("-g");
591 command_line[num_data].push_back(anneau.front());
592 }
593 if(num_ea != 0)
594 {
595 command_line[num_ea].push_back("-g");
596 command_line[num_ea].push_back(anneau.front());
597 }
598 if(num_data != 0 && num_ea != 0)
599 if(num_data > num_ea) // will restore "EA only" then "data + old EA"
600 {
601 string fic = anneau.front();
602 if(!opt.get_even_when_removed())
603 dialog.printf(gettext("Either archives in database are not properly tidied, or file last modification date has been artificially set to an more ancient date. This may lead improper Extended Attribute restoration for inode %S"), &fic);
604 }
605 }
606 }
607
608 if(ptr_dir != nullptr)
609 { // adding current directory children in the list of files
610 vector<string> fils;
611 vector<string>::iterator fit;
612 path base = anneau.front();
613 ptr_dir->read_all_children(fils);
614
615 fit = fils.begin();
616 while(fit != fils.end())
617 anneau.push_back((base + *(fit++)).display());
618 }
619 }
620 else
621 {
622 string fic = anneau.front();
623 dialog.printf(gettext("Cannot restore file %S : non existent file in database"), &fic);
624 }
625 anneau.pop_front();
626 }
627
628 //freeing memory if early_release is set
629
630 if(opt.get_early_release())
631 {
632 if(files != nullptr)
633 {
634 delete files;
635 files = nullptr;
636 }
637 }
638
639 // calling dar for each archive
640
641 if(!command_line.empty())
642 {
643 string dar_cmd = dar_path != "" ? dar_path : "dar";
644 map<archive_num, vector<string> >::iterator ut = command_line.begin();
645 vector<string> argvector_init = vector<string>(1, dar_cmd);
646
647 while(ut != command_line.end())
648 {
649 try
650 {
651 string archive_name;
652 vector<string> argvpipe;
653
654 // building the argument list sent through anonymous pipe
655
656 if(coordinate[ut->first].chemin != "")
657 archive_name = coordinate[ut->first].chemin + "/";
658 else
659 archive_name = "";
660 archive_name += coordinate[ut->first].basename;
661 argvpipe.push_back(dar_cmd); // just to fill the argv[0] by the command even when transmitted through anonymous pipe
662 argvpipe.push_back("-x");
663 argvpipe.push_back(archive_name);
664 if(!opt.get_ignore_dar_options_in_database())
665 argvpipe += options_to_dar;
666 argvpipe += opt.get_extra_options_for_dar();
667 argvpipe += ut->second;
668
669 dialog.printf("CALLING DAR: restoring %d files from archive %S using anonymous pipe to transmit configuration to the dar process", ut->second.size()/2, &archive_name);
670 if(opt.get_info_details())
671 {
672 dialog.printf("Arguments sent through anonymous pipe are:");
673 dialog.warning(tools_concat_vector(" ", argvpipe));
674 }
675 tools_system_with_pipe(dialog, dar_cmd, argvpipe, get_pool());
676 }
677 catch(Erange & e)
678 {
679 dialog.warning(string(gettext("Error while restoring the following files: "))
680 + tools_concat_vector( " ", ut->second)
681 + " : "
682 + e.get_message());
683 }
684 ut++;
685 }
686 }
687 else
688 dialog.warning(gettext("Cannot restore any file, nothing done"));
689 }
690 catch(...)
691 {
692 NLS_SWAP_OUT;
693 throw;
694 }
695 NLS_SWAP_OUT;
696 }
697
get_real_archive_num(archive_num num,bool revert) const698 archive_num database::get_real_archive_num(archive_num num, bool revert) const
699 {
700 if(num == 0)
701 throw Erange("database::get_real_archive_num", tools_printf(dar_gettext("Invalid archive number: %d"), num));
702
703 if(revert)
704 {
705 U_I size = coordinate.size(); // size is +1 because of record zero that is never used but must exist
706 if(size > num)
707 return size - num;
708 else
709 throw Erange("database::get_real_archive_num", tools_printf(dar_gettext("Invalid archive number: %d"), -num));
710 }
711 else
712 return num;
713 }
714
get_root_last_mod(const archive_num & num) const715 const datetime & database::get_root_last_mod(const archive_num & num) const
716 {
717 if(num >= coordinate.size())
718 throw SRC_BUG;
719
720 return coordinate[num].root_last_mod;
721 }
722
723 } // end of namespace
724
file2storage(generic_file & f,memory_pool * pool)725 static storage *file2storage(generic_file &f, memory_pool *pool)
726 {
727 storage *st = new (pool) storage(0);
728 const U_I taille = 102400;
729 unsigned char buffer[taille];
730 S_I lu;
731
732 if(st == nullptr)
733 throw Ememory("dar_manager:file2storage");
734
735 do
736 {
737 lu = f.read((char *)buffer, taille);
738 if(lu > 0)
739 st->insert_bytes_at_iterator(st->end(), buffer, lu);
740 }
741 while(lu > 0);
742
743 return st;
744 }
745
memory2file(storage & s,generic_file & f)746 static void memory2file(storage &s, generic_file &f)
747 {
748 s.dump(f);
749 }
750