Skip to content
API Documentation
BastionGuard™
SnapshotRepo.vala
Go to the documentation of this file.
1/*
2 * SnapshotRepo.vala
3 *
4 * Copyright 2012-2018 Tony George <teejeetech@gmail.com>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 * MA 02110-1301, USA.
20 *
21 *
22 */
23
24using TeeJee.Logging;
29using TeeJee.System;
30using TeeJee.Misc;
31
32public class SnapshotRepo : GLib.Object{
33
34 public Device device = null;
35 public Device device_home = null; // used for btrfs mode only
36 public string mount_path = "";
37 public Gee.HashMap<string,string> mount_paths;
38 public bool btrfs_mode = false;
39
40 public Gee.ArrayList<Snapshot?> snapshots;
41 public Gee.ArrayList<Snapshot?> invalid_snapshots;
42
43 public string status_message = "";
44 public string status_details = "";
46 public bool last_snapshot_failed_space = false;
47
48 // private
49 private Gtk.Window? parent_window = null;
50 private bool thr_success = false;
51 private bool thr_running = false;
52 private string thr_args1 = "";
53
54 public SnapshotRepo.from_path(string path, Gtk.Window? parent_win, bool _btrfs_mode){
55
56 log_debug("SnapshotRepo: from_path()");
57
58 this.mount_path = path;
59 this.parent_window = parent_win;
60 this.btrfs_mode = _btrfs_mode;
61
62 snapshots = new Gee.ArrayList<Snapshot>();
63 invalid_snapshots = new Gee.ArrayList<Snapshot>();
64 mount_paths = new Gee.HashMap<string,string>();
65
66 //log_debug("Selected snapshot repo path: %s".printf(path));
67
68 var list = Device.get_disk_space_using_df(path);
69
70 if (list.size > 0){
71
72 device = list[0];
73
74 log_debug(_("Device") + ": %s".printf(device.device));
75 log_debug(_("Free space") + ": %s".printf(format_file_size(device.free_bytes)));
76 }
77 else {
78 // Fall back for filesystems that have no block-device UUID
79 // (CIFS/SMB, NFS, sshfs, …). We synthesise a Device so the
80 // rest of the code can carry on uniformly.
82 if (device != null){
83 log_debug("SnapshotRepo: from_path(): synthetic device for path %s".printf(path));
84 }
85 }
86
88 }
89
90 private static Device? build_synthetic_device_for_path(string path){
91 string std_out, std_err;
92 int rc = exec_sync("df -T -B1 \"%s\"".printf(path), out std_out, out std_err);
93 if (rc != 0){ return null; }
94
95 string[] lines = std_out.split("\n");
96 if (lines.length < 2){ return null; }
97
98 // Take the last non-empty line — this is the data row
99 string data_line = "";
100 for (int i = lines.length - 1; i >= 1; i--){
101 if (lines[i].strip().length > 0){ data_line = lines[i]; break; }
102 }
103 if (data_line.length == 0){ return null; }
104
105 var dev = new Device();
106 int k = 1;
107 foreach (string val in data_line.split(" ")){
108 if (val.strip().length == 0){ continue; }
109 switch (k++){
110 case 1: dev.device = val.strip(); break;
111 case 2: dev.fstype = val.strip(); break;
112 case 3: dev.size_bytes = uint64.parse(val.strip()); break;
113 case 4: dev.used_bytes = uint64.parse(val.strip()); break;
114 case 5: dev.available_bytes = uint64.parse(val.strip()); break;
115 case 6: dev.used_percent = val.strip(); break;
116 }
117 }
118 // Synthetic non-empty UUID so downstream code that checks
119 // `device != null && device.uuid.length > 0` keeps working.
120 // We deliberately use a "sha:" prefix that no real UUID can have.
121 dev.uuid = "smb:%s".printf(path);
122 dev.type = "remote";
123 return dev;
124 }
125
126 public SnapshotRepo.from_device(Device dev, Gtk.Window? parent_win, bool btrfs_repo){
127
128 log_debug("SnapshotRepo: from_device(): %s".printf(btrfs_repo ? "BTRFS" : "RSYNC"));
129
130 this.device = dev;
131 //this.use_snapshot_path_custom = false;
132 this.parent_window = parent_win;
133 this.btrfs_mode = btrfs_repo;
134
135 snapshots = new Gee.ArrayList<Snapshot>();
136 invalid_snapshots = new Gee.ArrayList<Snapshot>();
137 mount_paths = new Gee.HashMap<string,string>();
138
140 }
141
142 public SnapshotRepo.from_uuid(string uuid, Gtk.Window? parent_win, bool btrfs_repo){
143
144 log_debug("SnapshotRepo: from_uuid(): %s".printf(btrfs_repo ? "BTRFS" : "RSYNC"));
145 log_debug("uuid=%s".printf(uuid));
146
148 if (device == null){
149 device = new Device();
150 device.uuid = uuid;
151 }
152
153 //this.use_snapshot_path_custom = false;
154 this.parent_window = parent_win;
155 this.btrfs_mode = btrfs_repo;
156
157 snapshots = new Gee.ArrayList<Snapshot>();
158 invalid_snapshots = new Gee.ArrayList<Snapshot>();
159 mount_paths = new Gee.HashMap<string,string>();
160
162
163 log_debug("SnapshotRepo: from_uuid(): exit");
164 }
165
167
168 log_debug("SnapshotRepo: from_null()");
169 log_debug("device not set");
170
171 snapshots = new Gee.ArrayList<Snapshot>();
172 invalid_snapshots = new Gee.ArrayList<Snapshot>();
173 mount_paths = new Gee.HashMap<string,string>();
174
175 log_debug("SnapshotRepo: from_null(): exit");
176 }
177
178 private void init_from_device(){
179
180 log_debug("SnapshotRepo: init_from_device()");
181
182 if ((device != null) && (device.uuid.length > 0) && (Device.get_device_by_uuid(device.uuid) != null)){
183
184 log_debug("");
186
187 if ((device != null) && (device.device.length > 0)){
188
189 log_debug(_("Selected snapshot device") + ": %s".printf(device.device));
190 log_debug(_("Free space") + ": %s".printf(format_file_size(device.free_bytes)));
191 }
192 }
193
194 if ((device != null) && (device.device.length > 0)){
195
196 check_status();
197 }
198
199 log_debug("SnapshotRepo: init_from_device(): exit");
200 }
201
202 // properties
203
204 public string timeshift_path {
205 owned get {
206 if (btrfs_mode) {
207 string bg_btrfs = path_combine(mount_path, "bastionguard-backup-btrfs");
208 if (dir_exists(bg_btrfs)) {
209 return bg_btrfs;
210 }
211
212 string legacy_btrfs = path_combine(mount_path, "timeshift-btrfs");
213 if (dir_exists(legacy_btrfs)) {
214 return legacy_btrfs;
215 }
216
217 /* Nessun repo esistente: usa il nuovo nome */
218 return bg_btrfs;
219 }
220 else {
221 string bg_path = path_combine(mount_path, "bastionguard-backup");
222 if (dir_exists(bg_path)) {
223 return bg_path;
224 }
225
226 string legacy_path = path_combine(mount_path, "timeshift");
227 if (dir_exists(legacy_path)) {
228 return legacy_path;
229 }
230
231 /* Nessun repo esistente: usa il nuovo nome */
232 return bg_path;
233 }
234 }
235 }
236
237 public string snapshots_path {
238 owned get{
239 return path_combine(timeshift_path, "snapshots");
240 }
241 }
242
243 // load ---------------------------------------
244
246
247 log_debug("SnapshotRepo: unlock_and_mount_devices()");
248
249 if (device == null){
250 log_debug("device=null");
251 }
252 else{
253 log_debug("device=%s".printf(device.device));
254 }
255
257
258 if (mount_path.length == 0){
259 return false;
260 }
261
262 // rsync
263 mount_paths["@"] = "";
264 mount_paths["@home"] = "";
265
266 if (btrfs_mode){
267
268 mount_paths["@"] = mount_path;
269 mount_paths["@home"] = mount_path; //default
270 device_home = device; //default
271
272 // mount @home if on different disk -------
273
275
276 if (repo_subvolumes.has_key("@home")){
277
278 var subvol = repo_subvolumes["@home"];
279
280 if (subvol.device_uuid != device.uuid){
281
282 // @home is on a separate device
283 device_home = subvol.get_device();
284
286
287 if (mount_paths["@home"].length == 0){
288 return false;
289 }
290 }
291 }
292 }
293
295
296 log_debug("SnapshotRepo: unlock_and_mount_device(): exit");
297
298 return true;
299 }
300
301 public string unlock_and_mount_device(Device device_to_mount, string path_to_mount){
302
303 // mounts the device and returns mount path
304
305 log_debug("SnapshotRepo: unlock_and_mount_device()");
306
307 Device dev = device_to_mount;
308
309 if (dev == null){
310 log_debug("device=null");
311 }
312 else{
313 log_debug("device=%s".printf(dev.device));
314 }
315
316 // unlock encrypted device
317 if (dev.is_encrypted_partition()){
318
319 dev = unlock_encrypted_device(dev);
320 device = dev; // set repo device to unlocked child disk
321
322 if (dev == null){
323 log_debug("device is null");
324 log_debug("SnapshotRepo: unlock_and_mount_device(): exit");
325 return "";
326 }
327 }
328
329 // mount
330 var mount_options = "";
331 if (btrfs_mode){
332 mount_options = "subvolid=0";
333 }
334 bool ok = Device.mount(dev.uuid, path_to_mount, mount_options); // TODO: check if already mounted
335
336 if (ok){
337 return path_to_mount;
338 }
339 else{
340 return "";
341 }
342 }
343
345
346 log_debug("SnapshotRepo: unlock_encrypted_device()");
347
348 if (luks_device == null){
349 log_debug("luks_device=null");
350 return null;
351 }
352 else{
353 log_debug("luks_device=%s".printf(luks_device.device));
354 }
355
356 string msg_out, msg_err;
357 var luks_unlocked = Device.luks_unlock(
358 luks_device, "", "", parent_window, out msg_out, out msg_err);
359
360 return luks_unlocked;
361 }
362
363 public bool load_snapshots(){
364
365 log_debug("SnapshotRepo: load_snapshots()");
366
367 snapshots.clear();
368 invalid_snapshots.clear();
369
370 if ((device == null) || !dir_exists(snapshots_path)){
371 return false;
372 }
373
374 try{
375 var dir = File.new_for_path(snapshots_path);
376 var enumerator = dir.enumerate_children("*", 0);
377
378 var info = enumerator.next_file ();
379 while (info != null) {
380 if (info.get_file_type() == FileType.DIRECTORY) {
381 if (info.get_name() != ".sync") {
382
383 //log_debug("load_snapshots():" + snapshots_path + "/" + info.get_name());
384
385 Snapshot bak = new Snapshot(snapshots_path + "/" + info.get_name(), btrfs_mode, this);
386 if (bak.valid){
387 snapshots.add(bak);
388 }
389 else{
390 invalid_snapshots.add(bak);
391 }
392 }
393 }
394 info = enumerator.next_file ();
395 }
396 }
397 catch(Error e){
398 log_error (e.message);
399 return false;
400 }
401
402 snapshots.sort((a,b) => {
403 Snapshot t1 = (Snapshot) a;
404 Snapshot t2 = (Snapshot) b;
405 return t1.date.compare(t2.date);
406 });
407
408 // reset the 'live' flag ------------
409
410 DateTime dt_boot = new DateTime.now_local();
411 dt_boot = dt_boot.add_seconds(-1.0 * get_system_uptime_seconds());
412 foreach(var bak in snapshots){
413 if (bak.live){
414 if ((App.sys_root == null) || (App.sys_root.uuid != bak.sys_uuid)){
415 // we are accessing the snapshot from a live system or another system
416 bak.live = false;
417 bak.update_control_file();
418 }
419 else{
420 if (bak.date.difference(dt_boot) < 0){
421 // snapshot was created before the last reboot
422 bak.live = false;
423 bak.update_control_file();
424 }
425 else{
426 // do nothing, snapshot is still in use by system
427 }
428 }
429 }
430 }
431
432 if (btrfs_mode){
434 }
435
436 log_debug("loading snapshots from '%s': %d found".printf(snapshots_path, snapshots.size));
437
438 return true;
439 }
440
441 // get tagged snapshots ----------------------------------
442
443 public Gee.ArrayList<Snapshot?> get_snapshots_by_tag(string tag = ""){
444
445 var list = new Gee.ArrayList<Snapshot?>();
446
447 foreach(Snapshot bak in snapshots){
448 if (bak.valid && (tag.length == 0) || bak.has_tag(tag)){
449 list.add(bak);
450 }
451 }
452 list.sort((a,b) => {
453 Snapshot t1 = (Snapshot) a;
454 Snapshot t2 = (Snapshot) b;
455 return (t1.date.compare(t2.date));
456 });
457
458 return list;
459 }
460
461 public Snapshot? get_latest_snapshot(string tag, string sys_uuid){
462
463 var list = get_snapshots_by_tag(tag);
464
465 for(int i = list.size - 1; i >= 0; i--){
466 var bak = list[i];
467 if (bak.sys_uuid == sys_uuid){
468 return bak;
469 }
470 }
471
472 return null;
473 }
474
475 public Snapshot? get_oldest_snapshot(string tag, string sys_uuid){
476
477 var list = get_snapshots_by_tag(tag);
478
479 for(int i = 0; i < list.size; i++){
480 var bak = list[i];
481 if (bak.sys_uuid == sys_uuid){
482 return bak;
483 }
484 }
485
486 return null;
487 }
488
489 // status check ---------------------------
490
491 public void check_status(){
492
493 log_debug("SnapshotRepo: check_status()");
494
496 {
497 status_code = SnapshotLocationStatus.HAS_SNAPSHOTS_HAS_SPACE;
498 status_message = "";
499 status_details = "";
500 }
501
502 if (available()){
505 {
506 has_space();
507 }
508 }
509
510 if ((App != null) && (App.app_mode.length == 0)){
511
512 log_debug("%s: '%s'".printf(
513 _("Snapshot device"),
514 (device == null) ? " UNKNOWN" : device.device));
515
516 log_debug("%s: %s".printf(
517 _("Snapshot location"), mount_path));
518
521
522 log_debug("%s: %s".printf(
523 _("Status"),
524 status_code.to_string().replace("SNAPSHOT_LOCATION_STATUS_","")));
525
526 log_debug("");
527 }
528
530 log_debug("SnapshotRepo: check_status(): exit");
531 }
532
533 public bool available(){
534
535 log_debug("SnapshotRepo: available()");
536
537 //log_debug("checking selected device");
538
539 if (device == null){
540 if (App.backup_uuid == null || App.backup_uuid.length == 0){
541 log_debug("device is null");
542 status_message = _("Snapshot device not selected");
543 status_details = _("Select the snapshot device");
544 status_code = SnapshotLocationStatus.NOT_SELECTED;
545 log_debug("is_available: false");
546 return false;
547 }
548 else{
549 status_message = _("Snapshot device not available");
550 status_details = _("Device not found") + ": UUID='%s'".printf(App.backup_uuid);
551 status_code = SnapshotLocationStatus.NOT_AVAILABLE;
552 log_debug("is_available: false");
553 return false;
554 }
555 }
556 else{
557 if (btrfs_mode){
558 bool ok = has_btrfs_system();
559 if (ok){
560 log_debug("is_available: ok");
561 }
562 return ok;
563 }
564 else{
565 log_debug("is_available: ok");
566 return true;
567 }
568 }
569 }
570
571 public bool has_btrfs_system(){
572
573 log_debug("SnapshotRepo: has_btrfs_system()");
574
575 var root_path = path_combine(mount_paths["@"],"@");
576 log_debug("root_path=%s".printf(root_path));
577 log_debug("btrfs_mode=%s".printf(btrfs_mode.to_string()));
578 if (btrfs_mode){
579 if (!dir_exists(root_path)){
580 status_message = _("Selected snapshot device is not a system disk");
581 status_details = _("Select BTRFS system disk with root subvolume (@)");
582 status_code = SnapshotLocationStatus.NO_BTRFS_SYSTEM;
584 return false;
585 }
586 }
587
588 return true;
589 }
590
591 public bool has_snapshots(){
592
593 log_debug("SnapshotRepo: has_snapshots()");
594
595 //load_snapshots();
596 return (snapshots.size > 0);
597 }
598
599 public bool has_space(uint64 needed = 0) {
600 log_debug("SnapshotRepo: has_space() - %llu required (%s)".printf(needed, format_file_size(needed)));
601
602 if ((device != null) && (device.device.length > 0)){
604 }
605 else{
606 log_debug("device is NULL");
607 return false;
608 }
609
610 if (snapshots.size > 0){
611 // has snapshots, check minimum space
612
613 if (device.free_bytes < (needed > 0 ? needed : Main.MIN_FREE_SPACE)) {
614 status_message = _("Not enough disk space");
615 status_message += " (< %s)".printf(format_file_size((needed > 0 ? needed : Main.MIN_FREE_SPACE), false, "", true, 0));
616
617 status_details = _("Select another device or free up some space");
618
619 status_code = SnapshotLocationStatus.HAS_SNAPSHOTS_NO_SPACE;
621 return false;
622 }
623 else{
624 //ok
625 status_message = _("OK");
626
627 status_details = _("%d snapshots, %s free").printf(
629
631 status_code = SnapshotLocationStatus.HAS_SNAPSHOTS_HAS_SPACE;
632 return true;
633 }
634 }
635 else {
636
637 // no snapshots, check estimated space
638 log_debug("no snapshots");
639
640 var required_space = Main.first_snapshot_size;
641
642 if (device.free_bytes < required_space){
643 status_message = _("Not enough disk space");
644 status_message += " (< %s)".printf(format_file_size(required_space));
645
646 status_details = _("Select another device or free up some space");
647
648 status_code = SnapshotLocationStatus.NO_SNAPSHOTS_NO_SPACE;
649 return false;
650 }
651 else{
652 status_message = _("No snapshots on this device");
653
654 status_details = _("First snapshot requires:");
655 status_details += " %s".printf(format_file_size(required_space));
656
657 status_code = SnapshotLocationStatus.NO_SNAPSHOTS_HAS_SPACE;
658 return true;
659 }
660 }
661 }
662
663 public void print_status(){
664
665 check_status();
666
667 //log_msg("");
668
669 if (device == null){
670 log_msg("%-6s : %s".printf(_("Device"), _("Not Selected")));
671 }
672 else{
673 log_msg("%-6s : %s".printf(_("Device"), device.device_name_with_parent));
674 log_msg("%-6s : %s".printf("UUID", device.uuid));
675 log_msg("%-6s : %s".printf(_("Path"), mount_path));
676 log_msg("%-6s : %s".printf(_("Mode"), btrfs_mode ? "BTRFS" : "RSYNC"));
677 log_msg("%-6s : %s".printf(_("Status"), status_message));
679 }
680
681 log_msg("");
682 }
683
684 // actions -------------------------------------
685
686 public void auto_remove(){
687
688 log_debug("SnapshotRepo: auto_remove()");
690 DateTime now = new DateTime.now_local();
691 DateTime dt_limit;
692 int count_limit;
693
694 // remove tags from older backups - boot ---------------
695
696 var list = get_snapshots_by_tag("boot");
697
698 if (list.size > App.count_boot){
699 log_msg(_("Maximum backups exceeded for backup level") + " '%s'".printf("boot"));
700 while (list.size > App.count_boot){
701 list[0].remove_tag("boot");
702 log_msg(_("Snapshot") + " '%s' ".printf(list[0].name) + _("un-tagged") + " '%s'".printf("boot"));
703 list = get_snapshots_by_tag("boot");
704 }
705 }
706
707 // remove tags from older backups - hourly, daily, weekly, monthly ---------
708
709 string[] levels = { "hourly","daily","weekly","monthly" };
710
711 foreach(string level in levels){
712
713 list = get_snapshots_by_tag(level);
714
715 if (list.size == 0) { continue; }
716
717 switch (level){
718 case "hourly":
719 dt_limit = now.add_hours(-1 * App.count_hourly);
720 count_limit = App.count_hourly;
721 break;
722 case "daily":
723 dt_limit = now.add_days(-1 * App.count_daily);
724 count_limit = App.count_daily;
725 break;
726 case "weekly":
727 dt_limit = now.add_weeks(-1 * App.count_weekly);
728 count_limit = App.count_weekly;
729 break;
730 case "monthly":
731 dt_limit = now.add_months(-1 * App.count_monthly);
732 count_limit = App.count_monthly;
733 break;
734 default:
735 dt_limit = now.add_years(-1 * 10);
736 count_limit = 100000;
737 break;
738 }
739
740 if (list.size > count_limit){
741
742 log_msg(_("Maximum backups exceeded for backup level") + " '%s'".printf(level));
743
744 int snaps_count = list.size;
745
746 foreach(var snap in list){
747
748 if (snap.description.strip().length > 0){ continue; } // don't delete snapshots with comments
749
750 if ((snap.date.compare(dt_limit) < 0) && (snaps_count > count_limit)){
751
752 snap.remove_tag(level);
753 snaps_count--;
754
755 log_msg(_("Snapshot") + " '%s' ".printf(list[0].name) + _("un-tagged") + " '%s'".printf(level));
756 }
757 }
758 }
759 }
760
761 // remove tags from older backups - max days -------
762
763 /*show_msg = true;
764 count = 0;
765 foreach(var bak in snapshots){
766 if (bak.date.compare(now.add_days(-1 * App.retain_snapshots_max_days)) < 0){
767 if (!bak.has_tag("ondemand")){
768
769 if (show_msg){
770 log_msg(_("Removing backups older than") + " %d ".printf(
771 App.retain_snapshots_max_days) + _("days..."));
772 show_msg = false;
773 }
774
775 log_msg(_("Snapshot") + " '%s' ".printf(bak.name) + _("un-tagged"));
776 bak.tags.clear();
777 count++;
778 }
779 }
780 }*/
781
782 // delete untagged snapshots
783
785
786 // delete older backups - minimum space -------
787
788 /*
789 device.query_disk_space();
790
791 show_msg = true;
792 count = 0;
793 while ((device.size_bytes - device.used_bytes) < App.minimum_free_disk_space){
794
795 load_snapshots();
796
797 if (snapshots.size > 0){
798 if (!snapshots[0].has_tag("ondemand")){
799
800 if (show_msg){
801 log_msg(_("Free space is less than") + " %lld GB".printf(
802 Main.MIN_FREE_SPACE / GB));
803 log_msg(_("Removing older backups to free disk space"));
804 show_msg = false;
805 }
806
807 snapshots[0].remove();
808 }
809 }
810
811 device.query_disk_space();
812 }
813 * */
814
815 // delete snapshots marked for deletion
816
818
819 // delete invalid snapshots
820
822 }
823
824 public void remove_untagged(){
825
826 log_debug("SnapshotRepo: remove_untagged()");
827
828 bool show_msg = true;
829
830 foreach(Snapshot bak in snapshots){
831 if (bak.tags.size == 0){
832
833 if (show_msg){
834 log_msg("%s (%s):".printf(_("Removing snapshots"), _("un-tagged")));
835 show_msg = false;
836 }
837
838 bak.remove(true);
839 }
840 }
841
842 load_snapshots(); // update the list
843 }
844
846
847 bool show_msg = true;
848
849 foreach(var bak in snapshots){
850 if (bak.marked_for_deletion){
851
852 if (show_msg){
853 log_msg("%s (%s):".printf(_("Removing snapshots"), _("marked for deletion")));
854 show_msg = false;
855 }
856
857 bak.remove(true);
858 }
859 }
860
861 load_snapshots(); // update the list
862 }
863
864 public void remove_invalid(){
865
866 bool show_msg = true;
867
868 foreach(var bak in invalid_snapshots){
869
870 if (show_msg){
871 log_msg("%s (%s):".printf(_("Removing snapshots"), _("incomplete")));
872 show_msg = false;
873 }
874
875 bak.remove(true);
876 }
877
878 load_snapshots(); // update the list
879 }
880
881 public bool remove_all(){
882
884
885 log_msg(_("Removing snapshots") + " > " + _("all") + "...");
886
887 //delete snapshots
888 foreach(var bak in snapshots){
889 bak.remove(true);
890 }
891
893
895
896 load_snapshots(); // update the list
897
898 return ok;
899 }
900 else{
901 log_msg(_("No snapshots found") + " '%s'".printf(mount_path));
902 return true;
903 }
904 }
905
906 public bool remove_sync_dir(){
907 string sync_dir = mount_path + "/bastionguard-backup/snapshots/.sync";
908
909 //delete .sync
910 if (dir_exists(sync_dir)){
911 if (!delete_directory(sync_dir)){
912 return false;
913 }
914 }
915
916 return true;
917 }
918
919 public bool remove_timeshift_dir(){
920
921 // delete /bastionguard-backup
924 return false;
925 }
926 }
927
928 return true;
929 }
930
931 // private -------------------------------------------
932
933 private bool delete_directory(string dir_path){
934 thr_args1 = dir_path;
935
936 try {
937 thr_running = true;
938 thr_success = false;
939 new Thread<void>.try ("delete-directory", () => {delete_directory_thread();});
940 } catch (Error e) {
941 thr_running = false;
942 thr_success = false;
943 log_error (e.message);
944 }
945
946 while (thr_running){
947 gtk_do_events ();
948 Thread.usleep((ulong) GLib.TimeSpan.MILLISECOND * 100);
949 }
950
951 thr_args1 = null;
952
953 return thr_success;
954 }
955
957 thr_success = TeeJee.FileSystem.dir_delete_recursive(thr_args1);
958
959 if (thr_success) {
960 log_msg(_("Removed") + ": '%s'".printf(thr_args1));
961 } else{
962 log_error(_("Failed to remove") + ": '%s'".printf(thr_args1));
963 }
964
965 thr_running = false;
966 }
967
968 // symlinks ----------------------------------------
969
970 public void create_symlinks(){
971 cleanup_symlink_dir("boot");
972 cleanup_symlink_dir("hourly");
973 cleanup_symlink_dir("daily");
974 cleanup_symlink_dir("weekly");
975 cleanup_symlink_dir("monthly");
976 cleanup_symlink_dir("ondemand");
977
978
979 foreach(Snapshot bak in snapshots){
980 foreach(string tag in bak.tags) {
981 string linkTarget = "%s-%s/%s".printf(snapshots_path, tag, bak.name);
982 string linkValue = "../snapshots/" + bak.name;
983 try {
984 File f = File.new_for_path(linkTarget);
985 if (!f.make_symbolic_link(linkValue)) {
986 log_error(_("Failed to create symlinks") + ": %s".printf(linkTarget));
987 }
988 } catch(Error e) {
989 log_debug(e.message);
990 log_error(_("Failed to create symlinks") + ": %s".printf(linkTarget));
991 }
992 }
993 }
994
995 log_debug (_("Symlinks updated"));
996 }
997
998 public void cleanup_symlink_dir(string tag){
999 try{
1000 string path = "%s-%s".printf(snapshots_path, tag);
1001 if(!TeeJee.FileSystem.dir_delete_recursive(path)) {
1002 log_error(_("Failed to delete symlinks") + ": '%s'".printf(path));
1003 }
1004
1005 File f = File.new_for_path(path);
1006 f.make_directory_with_parents();
1007 }
1008 catch (Error e) {
1009 log_error (e.message);
1010 }
1011 }
1012
1013}
1014
1016 /*
1017 -1 - device un-available, path does not exist
1018 0 - first snapshot taken, disk space sufficient
1019 1 - first snapshot taken, disk space not sufficient
1020 2 - first snapshot not taken, disk space not sufficient
1021 3 - first snapshot not taken, disk space sufficient
1022 4 - path is readonly
1023 5 - hardlinks not supported
1024 6 - btrfs device does not have @ subvolume
1025 */
1035}
Main App
#define _(s)
SnapshotLocationStatus
static Gee.ArrayList< Device > get_disk_space_using_df(string dev_name_or_mount_point="")
Definition Device.vala:822
Device()
Definition Device.vala:94
string uuid
Definition Device.vala:50
uint64 free_bytes
Definition Device.vala:120
bool is_encrypted_partition()
Definition Device.vala:216
void query_disk_space()
Definition Device.vala:1285
static bool mount(string dev_name_or_uuid, string mount_point, string mount_options="", bool silent=false)
Definition Device.vala:1571
static ? Device luks_unlock(Device luks_device, string mapped_name, string passphrase, Gtk.Window? parent_window, out string message, out string details)
Definition Device.vala:1387
static ? Device get_device_by_uuid(string uuid)
Definition Device.vala:1081
string device
Definition Device.vala:44
string device_name_with_parent
Definition Device.vala:1813
Definition Main.vala:39
static uint64 first_snapshot_size
Definition Main.vala:123
Device sys_root
Definition Main.vala:75
void query_subvolume_info(SnapshotRepo parent_repo)
Definition Main.vala:4222
string app_mode
Definition Main.vala:98
int count_monthly
Definition Main.vala:92
int count_hourly
Definition Main.vala:95
string backup_uuid
Definition Main.vala:49
int count_boot
Definition Main.vala:96
string mount_point_app
Definition Main.vala:82
int count_weekly
Definition Main.vala:93
const uint64 MIN_FREE_SPACE
Definition Main.vala:122
int count_daily
Definition Main.vala:94
void delete_directory_thread()
void cleanup_symlink_dir(string tag)
Gee.ArrayList< Snapshot?> snapshots
SnapshotRepo. from_device(Device dev, Gtk.Window? parent_win, bool btrfs_repo)
void init_from_device()
bool has_btrfs_system()
bool remove_sync_dir()
Gee.ArrayList< Snapshot?> invalid_snapshots
Gtk.? Window parent_window
Gee.HashMap< string, string > mount_paths
bool last_snapshot_failed_space
bool load_snapshots()
void remove_marked_for_deletion()
Snapshot? get_oldest_snapshot(string tag, string sys_uuid)
bool remove_timeshift_dir()
bool unlock_and_mount_devices()
string unlock_and_mount_device(Device device_to_mount, string path_to_mount)
Snapshot? get_latest_snapshot(string tag, string sys_uuid)
string status_details
bool has_space(uint64 needed=0)
SnapshotRepo. from_path(string path, Gtk.Window? parent_win, bool _btrfs_mode)
string status_message
bool delete_directory(string dir_path)
string snapshots_path
SnapshotRepo. from_null()
void create_symlinks()
static ? Device build_synthetic_device_for_path(string path)
SnapshotRepo. from_uuid(string uuid, Gtk.Window? parent_win, bool btrfs_repo)
SnapshotLocationStatus status_code
void remove_untagged()
string timeshift_path
Gee.ArrayList< Snapshot?> get_snapshots_by_tag(string tag="")
void remove_invalid()
Device? unlock_encrypted_device(Device luks_device)
DateTime date
Definition Snapshot.vala:38
bool valid
Definition Snapshot.vala:49
Snapshot(string dir_path, bool btrfs_snapshot, SnapshotRepo _repo)
Definition Snapshot.vala:63
static Gee.HashMap< string, Subvolume > detect_subvolumes_for_system_by_path(string system_path, SnapshotRepo? repo, Gtk.Window? parent_window)
string path_combine(string path1, string path2)
string format_file_size(uint64 size, bool binary_units=false, string unit="", bool show_units=true, int decimals=1)
bool dir_exists(string dir_path)
void log_debug(string message)
void log_error(string message, bool highlight=false, bool is_warning=false)
void log_msg(string message, bool highlight=false)
int exec_sync(string cmd, out string? std_out=null, out string? std_err=null)
double get_system_uptime_seconds()