1 #include "config.h"
2
3 #include <iostream>
4 #include <vector>
5 #include <map>
6 #include <string>
7 #include <cassert>
8
9 #include "asserts.h"
10 #include "error.h"
11 #include "rmath.h"
12 #include "fs.h"
13 #include "rconfig.h"
14 #include "logger.h"
15 #include "timer.h"
16 #include "estat.h"
17 #include "reporter.h"
18 #include "strfmt.h"
19
20 #include "vaulter.h"
21
22 /** C'tor */
vault_manager()23 vault_manager::vault_manager()
24 {
25 if (this != &vaulter)
26 throw(INTERNAL_ERROR(0,"Attempt to allocate multiple vault managers"));
27 clear();
28 }
29
30 /** Clear the vault manager */
clear(void)31 void vault_manager::clear(void)
32 {
33 m_lock.clear();
34 m_selected_vault.erase();
35 m_deleted_archives.clear();
36 m_da_err = false;
37 m_initialized = false;
38 }
39
40 /** Initialize the vault manager */
init(void)41 void vault_manager::init(void)
42 {
43 clear();
44 m_initialized = true;
45 }
46
47 /** Return the initialized status of the vault manager */
initialized(void) const48 const bool vault_manager::initialized(void) const
49 {
50 return(m_initialized);
51 }
52
53 /** Select a vault
54
55 If an archive of the same timestamp for the given timestamp resolution
56 already exists on any vault, then that vault is selected. Otherwise select
57 a vault according to vault-selection-behavior.
58
59 */
select(void)60 void vault_manager::select(void)
61 {
62 std::string es;
63 std::string ts;
64 configuration_manager::vaults_type::const_iterator vi;
65 std::string lockfile;
66 std::string lstr;
67
68 if (!initialized())
69 throw(INTERNAL_ERROR(0,"Vault manager not initialized"));
70
71 TRY_nomem(es = "Could not select a vault");
72 try {
73 // If an archive exists in any vault by the same timestamp as conf.stamp(),
74 // then use that vault regardless of the selection behavior.
75 for (
76 vi = config.vaults().begin();
77 vi != config.vaults().end();
78 vi++
79 )
80 {
81 subdirectory subdir;
82 subdirectory::iterator sdi;
83
84 subdir.path(*vi);
85 for (sdi = subdir.begin(); sdi != subdir.end(); sdi++) {
86 TRY_nomem(ts = config.timestamp().str());
87
88 if (*sdi == ts) {
89 TRY_nomem(lstr = "Existing archive directory found in vault: \"");
90 TRY_nomem(lstr += *vi);
91 TRY_nomem(lstr += "\"\n");
92 logger.write(lstr);
93
94 if (config.vault_locking()) {
95 TRY_nomem(lockfile = *vi);
96 TRY_nomem(lockfile += "/.rvm_lock");
97 m_lock.clear();
98 m_lock.lockfile(lockfile);
99 if (!m_lock.lock()) {
100 error e(ENOLCK);
101 e.push_back(ERROR_INSTANCE(es));
102 TRY_nomem(es = "Could not lock vault: \"");
103 TRY_nomem(es += *vi);
104 TRY_nomem(es += "\"");
105 e.push_back(ERROR_INSTANCE(es));
106 if (m_lock.is_locked()) {
107 TRY_nomem(es = "Vault is locked by PID: ");
108 TRY_nomem(es += estring(m_lock.locked_by()));
109 e.push_back(ERROR_INSTANCE(es));
110 }
111 throw(e);
112 }
113 }
114 TRY_nomem(m_selected_vault = *vi);
115 return;
116 }
117
118 TRY_nomem(ts += ".incomplete");
119
120 if (*sdi == ts) {
121 TRY_nomem(lstr = "Existing archive directory found in vault: \"");
122 TRY_nomem(lstr += *vi);
123 TRY_nomem(lstr += "\"\n");
124 logger.write(lstr);
125
126 if (config.vault_locking()) {
127 TRY_nomem(lockfile = *vi);
128 TRY_nomem(lockfile += "/.rvm_lock");
129 m_lock.clear();
130 m_lock.lockfile(lockfile);
131 if (!m_lock.lock()) {
132 error e(ENOLCK);
133 e.push_back(ERROR_INSTANCE(es));
134 TRY_nomem(es = "Could not lock vault: \"");
135 TRY_nomem(es += *vi);
136 TRY_nomem(es += "\"");
137 e.push_back(ERROR_INSTANCE(es));
138 if (m_lock.is_locked()) {
139 TRY_nomem(es = "Vault is locked by PID: ");
140 TRY_nomem(es += estring(m_lock.locked_by()));
141 e.push_back(ERROR_INSTANCE(es));
142 }
143 throw(e);
144 }
145 }
146 TRY_nomem(m_selected_vault = *vi);
147 return;
148 }
149 }
150 }
151
152 // If an archive by the same timestamp does not already exist, then select
153 // a vault.
154 if (config.vault_selection_behavior()
155 == configuration_manager::selection_round_robin)
156 {
157 std::pair<std::string,std::string> youngest;
158
159 for (
160 vi = config.vaults().begin();
161 vi != config.vaults().end();
162 vi++
163 )
164 {
165 subdirectory subdir;
166 subdirectory::iterator sdi;
167
168 if (config.vault_locking()) {
169 TRY_nomem(lockfile = *vi);
170 TRY_nomem(lockfile += "/.rvm_lock");
171 m_lock.clear();
172 m_lock.lockfile(lockfile);
173 if (m_lock.is_locked()) {
174 std::string lstr;
175
176 TRY_nomem(lstr = "Skipping locked vault: \"");
177 TRY_nomem(lstr += *vi);
178 TRY_nomem(lstr += "\"\n");
179 logger.write(lstr);
180
181 continue;
182 }
183 }
184
185 subdir.path(*vi);
186 for (sdi = subdir.begin(); sdi != subdir.end(); sdi++) {
187 if ((youngest.first < *sdi) || (youngest.first.size() == 0)) {
188 TRY_nomem(youngest.first = *sdi);
189 TRY_nomem(youngest.second = *vi);
190 }
191 }
192 }
193
194 TRY_nomem(m_selected_vault = "");
195 if (youngest.second.size() == 0) {
196 TRY_nomem(m_selected_vault = config.vaults()[0]);
197 }
198 else {
199 for (
200 vi = config.vaults().begin();
201 vi != config.vaults().end();
202 vi++
203 )
204 {
205 if (*vi == youngest.second) {
206 if ((vi+1) == config.vaults().end()) {
207 TRY_nomem(m_selected_vault = config.vaults()[0]);
208 }
209 else {
210 TRY_nomem(m_selected_vault = *(vi+1));
211 }
212 }
213 }
214 }
215 }
216 else {
217 std::pair<std::string,filesystem::size_type> most_space;
218 filesystem fsys;
219
220 TRY_nomem(most_space.first = config.vaults()[0]);
221 fsys.path(config.vaults()[0]);
222 most_space.second = fsys.free_blocks();
223 for (
224 vi = (config.vaults().begin()+1);
225 vi != config.vaults().end();
226 vi++
227 )
228 {
229 fsys.path(*vi);
230 if (most_space.second < fsys.free_blocks()) {
231 TRY_nomem(most_space.first = *vi);
232 most_space.second = fsys.free_blocks();
233 }
234 }
235 TRY_nomem(m_selected_vault = most_space.first);
236 }
237 }
238 catch(error e) {
239 e.push_back(es);
240 throw(e);
241 }
242 catch(...) {
243 error e(0);
244 if (errno == ENOMEM) {
245 e = err_nomem;
246 e.push_back(es);
247 throw(e);
248 }
249 e = err_unknown;
250 e.push_back(es);
251 throw(e);
252 }
253
254 if (m_selected_vault.size() == 0)
255 throw(ERROR(0,es));
256 }
257
258 /** Return a list of archive directories in the selected vault
259
260 Return a list of archive directories in the selected vault, including
261 incomplete archives. Ignore all other directories.
262 */
get_archive_list(void)263 const subdirectory vault_manager::get_archive_list(void)
264 {
265 subdirectory subdir;
266 subdirectory::iterator sdi;
267
268 if (!initialized())
269 throw(INTERNAL_ERROR(0,"Vault manager not initialized"));
270
271 if (!selected())
272 throw(INTERNAL_ERROR(0,"No vault selected"));
273
274 subdir.path(vault());
275
276 sdi = subdir.begin();
277 while (sdi != subdir.end()) {
278 if (!is_timestamp((*sdi).substr(0,(*sdi).find(".incomplete"))))
279 {
280 subdir.erase(sdi);
281 sdi = subdir.begin();
282 }
283 else {
284 sdi++;
285 }
286 }
287
288 return(subdir);
289 }
290
291 /** Return the path to the selected vault */
vault(void) const292 const std::string vault_manager::vault(void) const
293 {
294 if (!initialized())
295 throw(INTERNAL_ERROR(0,"Vault manager not initialized"));
296
297 if (!selected())
298 throw(INTERNAL_ERROR(0,"No vault selected"));
299
300 return(m_selected_vault);
301 }
302
303 /** Return whether or not a vault has been selected yet */
selected(void) const304 const bool vault_manager::selected(void) const
305 {
306 bool value = true;
307
308 if (!initialized())
309 throw(INTERNAL_ERROR(0,"Vault manager not initialized"));
310
311 if (m_selected_vault.size() == 0)
312 value = false;
313
314 return(value);
315 }
316
317 /** Return the percent of used blocks and used inodes in the selected vault */
usage(uint16 & a_blocks,uint16 & a_inodes) const318 void vault_manager::usage(uint16 &a_blocks, uint16 &a_inodes) const
319 {
320 filesystem fsys;
321 safe_num<uint64> blocks, inodes;
322
323 if (!initialized())
324 throw(INTERNAL_ERROR(0,"Vault manager not initialized"));
325
326 if (!selected())
327 throw(INTERNAL_ERROR(0,"No vault selected"));
328
329 fsys.path(vault());
330
331 if (fsys.total_blocks() == 0)
332 blocks = 0;
333 else {
334 try {
335 blocks = fsys.free_blocks();
336 // blocks *= 100;
337 // blocks /= fsys.total_blocks();
338 blocks /= fsys.total_blocks() / 100;
339 }
340 catch(error e) {
341 logger.write("*** ERROR: Overflow error detected in vault_manager::usage() while calculating percent blocks used\n");
342 logger.write(e.str());
343 blocks = 0;
344 }
345 catch(...) {
346 logger.write("*** ERROR: Unknown error detected in vault_manager::usage() while calculating percent blocks used\n");
347 blocks = 0;
348 }
349 }
350
351 if (fsys.free_inodes() == 0)
352 inodes = 0;
353 else {
354 try {
355 inodes = fsys.free_inodes();
356 // inodes *= 100;
357 // inodes /= fsys.total_inodes();
358 inodes /= fsys.total_inodes() / 100;
359 }
360 catch(error e) {
361 logger.write("*** ERROR: Overflow error detected in vault_manager::usage() while calculating percent inodes used\n");
362 logger.write(e.str());
363 blocks = 0;
364 }
365 catch(...) {
366 logger.write("*** ERROR: Unknown error detected in vault_manager::usage() while calculating percent inodes used\n");
367 blocks = 0;
368 }
369 }
370
371 ASSERT(blocks <= max_limit(blocks));
372 ASSERT(inodes <= max_limit(inodes));
373
374 a_blocks = blocks.value();
375 a_inodes = inodes.value();
376 }
377
378 /** Test to see if a vault has exceeded it's overflow threshold */
overflow(bool a_report)379 const bool vault_manager::overflow(bool a_report)
380 {
381 uint16 free_blocks;
382 uint16 free_inodes;
383 bool value = false;
384 std::string lstr;
385
386 if (!initialized())
387 throw(INTERNAL_ERROR(0,"Vault manager not initialized"));
388
389 if (!selected())
390 throw(INTERNAL_ERROR(0,"No vault selected"));
391
392 usage(free_blocks, free_inodes);
393 if (free_blocks < config.vault_overflow_blocks())
394 value = true;
395 if (free_inodes < config.vault_overflow_inodes())
396 value = true;
397
398 if (value && a_report) {
399 TRY_nomem(lstr = "Vault overflow detected: ");
400 TRY_nomem(lstr += vault());
401 TRY_nomem(lstr += "\n");
402 logger.write(lstr);
403
404 TRY_nomem(lstr = " Threshold: ");
405 TRY_nomem(lstr +=
406 percent_string(config.vault_overflow_blocks(),
407 static_cast<uint16>(100)));
408 TRY_nomem(lstr += " free blocks, ");
409 TRY_nomem(lstr +=
410 percent_string(config.vault_overflow_inodes(),
411 static_cast<uint16>(100)));
412 TRY_nomem(lstr += " free inodes");
413 TRY_nomem(lstr += "\n");
414 logger.write(lstr);
415
416 TRY_nomem(lstr = "Vault capacity: ");
417 TRY_nomem(lstr += percent_string(free_blocks,static_cast<uint16>(100)));
418 TRY_nomem(lstr += " free blocks, ");
419 TRY_nomem(lstr += percent_string(free_inodes,static_cast<uint16>(100)));
420 TRY_nomem(lstr += " free inodes");
421 TRY_nomem(lstr += "\n");
422 logger.write(lstr);
423
424 estring __e = estring("Overflow Detected:");
425 reporter.vault().add_report(
426 vault_stats_report(estring("Overflow Detected:"),filesystem(vault()))
427 );
428 }
429
430 return(value);
431 }
432
433 /** Find the oldest archive in the vault and delete it */
delete_oldest_archive(void)434 void vault_manager::delete_oldest_archive(void)
435 {
436 std::string es;
437 estring ts;
438 estring tsi;
439 std::string lstr;
440 subdirectory archive_list;
441 std::string oldest;
442 std::string dir;
443 std::string delete_dir;
444 estring estr;
445 timer t;
446 uint16 free_blocks, free_inodes;
447 subdirectory logfile_list;
448 subdirectory::const_iterator ilf;
449
450 if (!initialized())
451 throw(INTERNAL_ERROR(0,"Vault manager not initialized"));
452
453 if (!selected())
454 throw(INTERNAL_ERROR(0,"No vault selected"));
455
456 TRY_nomem(ts = config.timestamp().str());
457 TRY_nomem(tsi = ts + ".incomplete");
458
459 archive_list = get_archive_list();
460 if (
461 (archive_list.size() == 0)
462 || (
463 (archive_list.size() == 1)
464 && (archive_list[0] == ts || archive_list[0] == tsi)
465 )
466 )
467 {
468 TRY_nomem(es = "Vault has insufficient space: \"");
469 TRY_nomem(es += vault());
470 TRY_nomem(es += "\"");
471 exit_manager.assign(exitstat::vault_full);
472 throw(ERROR(0,es));
473 }
474
475 TRY_nomem(oldest = archive_list[0]);
476 if (oldest == ts || oldest == tsi) {
477 TRY_nomem(lstr = "Oldest is actually archive in use: \"");
478 TRY_nomem(lstr += oldest);
479 TRY_nomem(lstr += "\" Skipping to next archive...\n");
480 logger.write(lstr);
481 TRY_nomem(oldest = archive_list[1]);
482 }
483
484 if (oldest.find(".incomplete") != std::string::npos) {
485 TRY_nomem(lstr = "WARNING: Oldest archive found is incomplete.\n");
486 logger.write(lstr);
487 }
488
489 TRY_nomem(lstr = "Deleting oldest archive: \"");
490 TRY_nomem(lstr += oldest);
491 TRY_nomem(lstr += "\"\n");
492 logger.write(lstr);
493
494 TRY_nomem(dir = vault());
495 TRY_nomem(dir += "/");
496 TRY_nomem(dir += oldest);
497 t.start();
498 try {
499 m_deleted_archives.push_back(oldest);
500 }
501 catch(...) {
502 m_da_err = true;
503 }
504
505 delete_dir = dir;
506 if (delete_dir.find(".deleting") == std::string::npos)
507 delete_dir += ".deleting";
508 rename_file(dir, delete_dir);
509 if (config.delete_command_path().size() == 0)
510 rm_recursive(delete_dir);
511 else {
512 std::string es;
513 std::string cmdline;
514
515 TRY_nomem(es = "Delete command returned non-zero exit value: \"");
516 TRY_nomem(es += oldest);
517 TRY_nomem(es += "\"");
518 cmdline = config.delete_command_path();
519 cmdline += " \"";
520 cmdline += delete_dir;
521 cmdline += "\"";
522 if (system(cmdline.c_str()) != 0)
523 throw(ERROR(1,es));
524 }
525 t.stop();
526
527 TRY_nomem(lstr = "Deletion complete, duration: ");
528 TRY_nomem(lstr += t.duration());
529 TRY_nomem(lstr += "\n");
530 logger.write(lstr);
531
532 usage(free_blocks, free_inodes);
533 TRY_nomem(lstr = "Vault capacity: ");
534 TRY_nomem(lstr += percent_string(free_blocks,static_cast<uint16>(100)));
535 TRY_nomem(lstr += " free blocks, ");
536 TRY_nomem(lstr += percent_string(free_inodes,static_cast<uint16>(100)));
537 TRY_nomem(lstr += " free inodes");
538 TRY_nomem(lstr += "\n");
539 logger.write(lstr);
540
541 estr = "Deleted ";
542 estr += oldest;
543 estr += ":";
544 reporter.vault().add_report(vault_stats_report(estr,filesystem(vault())));
545
546 // This is a grey area for me: Should log/report file removal be managed by
547 // log_manager/report_manager, or is it OK to do it here? For simplicity
548 // sake, I do it here.
549 //
550 if (config.delete_old_log_files()) {
551 estring wildcard;
552
553 logger.write("Searching for old log files to delete...\n");
554 wildcard = oldest;
555 wildcard += ".log*";
556 logfile_list.path(config.log_dir(), wildcard);
557 for (ilf = logfile_list.begin(); ilf != logfile_list.end(); ilf++) {
558 estring file;
559
560 file = config.log_dir();
561 file += "/";
562 file += *ilf;
563
564 try {
565 rm_file(file);
566 }
567 catch(error e) {
568 estring es;
569
570 es = "*** ERROR: Could not remove log file: ";
571 es += *ilf;
572
573 logger.write(es);
574 logger.write(e.str());
575
576 // Should I throw an error here, or should deletion of old log files
577 // not be considered important enough to interrupt archiving?
578 }
579 catch(...) {
580 estring es;
581
582 es = "*** ERROR: Unknown error detected in vault_manager::delete_oldest_archive() while deleting old log file: ";
583 es += *ilf;
584 es += '\n';
585
586 logger.write(es);
587
588 // Should I throw an error here, or should deletion of old log files
589 // not be considered important enough to interrupt archiving?
590 }
591 }
592
593 wildcard = oldest;
594 wildcard += ".relink*";
595 logfile_list.path(config.log_dir(), wildcard);
596 for (ilf = logfile_list.begin(); ilf != logfile_list.end(); ilf++) {
597 estring file;
598
599 file = config.log_dir();
600 file += "/";
601 file += *ilf;
602
603 try {
604 rm_file(file);
605 }
606 catch(error e) {
607 estring es;
608
609 es = "*** ERROR: Could not remove relink file: ";
610 es += *ilf;
611
612 logger.write(es);
613 logger.write(e.str());
614
615 // Should I throw an error here, or should deletion of old log files
616 // not be considered important enough to interrupt archiving?
617 }
618 catch(...) {
619 estring es;
620
621 es = "*** ERROR: Unknown error detected in vault_manager::delete_oldest_archive() while deleting old relink file: ";
622 es += *ilf;
623 es += '\n';
624
625 logger.write(es);
626
627 // Should I throw an error here, or should deletion of old log files
628 // not be considered important enough to interrupt archiving?
629 }
630 }
631 }
632
633 if (config.delete_old_report_files()) {
634 estring wildcard;
635
636 logger.write("Searching for old report files to delete...\n");
637 wildcard = oldest;
638 wildcard += ".report*";
639 logfile_list.path(config.log_dir(), wildcard);
640 for (ilf = logfile_list.begin(); ilf != logfile_list.end(); ilf++) {
641 estring file;
642
643 file = config.log_dir();
644 file += "/";
645 file += *ilf;
646
647 try {
648 rm_file(file);
649 }
650 catch(error e) {
651 estring es;
652
653 es = "*** ERROR: Could not remove report file: ";
654 es += *ilf;
655
656 logger.write(es);
657 logger.write(e.str());
658
659 // Should I throw an error here, or should deletion of old log files
660 // not be considered important enough to interrupt archiving?
661 }
662 catch(...) {
663 estring es;
664
665 es = "*** ERROR: Unknown error detected in vault_manager::delete_oldest_archive() while deleting old log file: ";
666 es += *ilf;
667 es += '\n';
668
669 logger.write(es);
670
671 // Should I throw an error here, or should deletion of old log files
672 // not be considered important enough to interrupt archiving?
673 }
674 }
675 }
676 }
677
678 /** Prepare the selected vault
679
680 Check the selected vault for overflow. If the overflow threshold has been
681 exceeded and the vault-overflow-behavior setting is not quit, then delete
682 the oldest archive in the vault. Depending on vault-overflow-behavior,
683 possibly repeat this action until either there is space free or there are no
684 archives left in the vault.
685
686 */
prepare(bool a_assume_overflow)687 void vault_manager::prepare(bool a_assume_overflow)
688 {
689 std::string es;
690 std::string lstr;
691
692 /*
693 estring debug_estr;
694
695 debug_estr = "[DEBUG]: vault_manager::prepare() - a_assume_overflow = ";
696 debug_estr += estring(a_assume_overflow);
697 debug_estr += "\n";
698 logger.write(debug_estr);
699 */
700
701 if (!initialized())
702 throw(INTERNAL_ERROR(0,"Vault manager not initialized"));
703
704 if (!selected())
705 throw(INTERNAL_ERROR(0,"No vault selected"));
706
707 if (!overflow() && !a_assume_overflow)
708 return;
709
710 // logger.write("Vault has insufficient space\n");
711 // logger.write("[DEBUG]: vault_manager::prepare() - Cleaning vault...\n");
712
713 if (config.vault_overflow_behavior()
714 == configuration_manager::overflow_quit)
715 {
716 TRY_nomem(es = "Vault has insufficient space: \"");
717 TRY_nomem(es += vault());
718 TRY_nomem(es += "\"");
719 throw(ERROR(0,es));
720 }
721
722 if (config.vault_overflow_behavior()
723 == configuration_manager::overflow_delete_oldest)
724 {
725 if (m_deleted_archives.size() == 0) {
726 TRY(delete_oldest_archive(),"Failure preparing vault");
727 a_assume_overflow = false;
728 }
729 else {
730 logger.write("Vault has insufficient space\n");
731 throw(ERROR(0,"Vault has insufficient space"));
732 }
733 }
734
735 if (!overflow() && !a_assume_overflow)
736 return;
737
738 if (config.vault_overflow_behavior()
739 == configuration_manager::overflow_delete_until_free)
740 {
741 while (overflow() || a_assume_overflow) {
742 // logger.write("Vault has insufficient space\n");
743 TRY(delete_oldest_archive(),"Failure preparing vault");
744 a_assume_overflow = false;
745 }
746 }
747 else {
748 // logger.write("Vault has insufficient space\n");
749 exit_manager.assign(exitstat::vault_full);
750 // throw(ERROR(0,"Vault has insufficient space"));
751 }
752 }
753
754 /** Return a list of deleted archives */
deleted_archives(void) const755 const std::vector<std::string>& vault_manager::deleted_archives(void) const
756 {
757 if (!initialized())
758 throw(INTERNAL_ERROR(0,"Vault manager not initialized"));
759
760 return(m_deleted_archives);
761 }
762
763 /** Return whether or not there was an error deleting archives */
err_deleted_archives(void) const764 const bool vault_manager::err_deleted_archives(void) const
765 {
766 if (!initialized())
767 throw(INTERNAL_ERROR(0,"Vault manager not initialized"));
768
769 return(m_da_err);
770 }
771
772 /** The global vault manager */
773 vault_manager vaulter;
774
775