Skip to content
API Documentation
BastionGuard™
Snapshot.vala
Go to the documentation of this file.
1/*
2 * Snapshot.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
24
25using TeeJee.Logging;
30using TeeJee.System;
31using TeeJee.Misc;
32using Json;
33
34public class Snapshot : GLib.Object{
35
36 public string path = "";
37 public string name = "";
38 public DateTime date;
39 public string sys_uuid = "";
40 public string sys_distro = "";
41 public string app_version = "";
42 public string description = "";
43 public int64 file_count = 0;
44 public Gee.ArrayList<string> tags;
45 public Gee.ArrayList<string> exclude_list;
46 public Gee.HashMap<string,Subvolume> subvolumes;
47 public Gee.ArrayList<FsTabEntry> fstab_list;
48 public Gee.ArrayList<CryptTabEntry> cryttab_list;
49 public bool valid = true;
50 public bool live = false;
51 public bool marked_for_deletion = false;
54
55 //btrfs
56 public bool btrfs_mode = false;
57 public Gee.HashMap<string,string> paths; // for btrfs snapshots only
58 public string mount_path_root = "";
59 public string mount_path_home = "";
60
62
63 public Snapshot(string dir_path, bool btrfs_snapshot, SnapshotRepo _repo){
64
65 try{
66 var f = File.new_for_path(dir_path);
67 var info = f.query_info("*", FileQueryInfoFlags.NONE);
68
69 path = dir_path;
70 name = info.get_name();
71 description = "";
72 btrfs_mode = btrfs_snapshot;
73 repo = _repo;
74
75 date = new DateTime.from_unix_utc(0);
76 tags = new Gee.ArrayList<string>();
77 exclude_list = new Gee.ArrayList<string>();
78 fstab_list = new Gee.ArrayList<FsTabEntry>();
80 subvolumes = new Gee.HashMap<string,Subvolume>();
81 paths = new Gee.HashMap<string,string>();
82
87 }
88 catch(Error e){
89 log_error (e.message);
90 }
91 }
92
93 // properties
94
95 public string date_formatted{
96 owned get{
97 return date.format(App.date_format);//.format("%Y-%m-%d %H:%M:%S");
98 }
99 }
100
101 public string rsync_log_file{
102 owned get {
103 return path_combine(path, "rsync-log");
104 }
105 }
106
108 owned get {
109 return path_combine(path, "rsync-log-changes");
110 }
111 }
112
114 owned get {
115 return path_combine(path, "rsync-log-restore");
116 }
117 }
118
120 owned get {
121 return path_combine(path, "rsync-log-restore-changes");
122 }
123 }
124
126 owned get {
127 return path_combine(path, "exclude.list");
128 }
129 }
130
132 owned get {
133 return path_combine(path, "exclude-restore.list");
134 }
135 }
136
137 // manage tags
138
139 public string taglist{
140 owned get{
141 string str = "";
142 foreach(string tag in tags){
143 str += " " + tag;
144 }
145 return str.strip();
146 }
147 set{
148 tags.clear();
149 foreach(string tag in value.split(" ")){
150 if (!tags.contains(tag.strip())){
151 tags.add(tag.strip());
152 }
153 }
154 }
155 }
156
157 public string taglist_short{
158 owned get{
159 string str = "";
160 foreach(string tag in tags){
161 str += " " + tag.replace("ondemand","O").replace("boot","B").replace("hourly","H").replace("daily","D").replace("weekly","W").replace("monthly","M");
162 }
163 return str.strip();
164 }
165 }
166
167 public void add_tag(string tag){
168
169 if (!tags.contains(tag.strip())){
170 tags.add(tag.strip());
172 }
173 }
174
175 public void remove_tag(string tag){
176
177 if (tags.contains(tag.strip())){
178 tags.remove(tag.strip());
180 }
181 }
182
183 public bool has_tag(string tag){
184
185 return tags.contains(tag.strip());
186 }
187
188 // control files
189
190 public void read_control_file(){
191
192 //log_debug("read_control_file()");
193
194 string ctl_file = path + "/info.json";
195
196 var f = File.new_for_path(ctl_file);
197
198 if (f.query_exists()) {
199
200 var parser = new Json.Parser();
201
202 try{
203 parser.load_from_file(ctl_file);
204 } catch (Error e) {
205 log_error (e.message);
206 }
207
208 var node = parser.get_root();
209 var config = node.get_object();
210
211 if ((node == null)||(config == null)){
212 valid = false;
213 return;
214 }
215
216 string val = json_get_string(config,"created","");
217 if (val.length > 0) {
218 DateTime date_utc = new DateTime.from_unix_utc(int64.parse(val));
219 date = date_utc.to_local();
220 }
221
222 sys_uuid = json_get_string(config,"sys-uuid","");
223 sys_distro = json_get_string(config,"sys-distro","");
224 taglist = json_get_string(config,"tags","");
225 description = json_get_string(config,"comments","");
226 app_version = json_get_string(config,"app-version","");
227 file_count = (int64) json_get_uint64(config,"file_count",file_count);
228 live = json_get_bool(config,"live",false);
229 string type = config.get_string_member_with_default("type", "rsync");
230
231 string extension = (type == "btrfs") ? "@" : "localhost";
233
234 //log_debug("repo.mount_path: %s".printf(repo.mount_path));
235
236 if (config.has_member("subvolumes")){
237
238 var subvols = (Json.Object) config.get_object_member("subvolumes");
239
240 foreach(string subvol_name in subvols.get_members()){
241
242 if ((subvol_name != "@")&&(subvol_name != "@home")){ continue; }
243
244 paths[subvol_name] = path.replace(repo.mount_path, repo.mount_paths[subvol_name]);
245
246 var subvol_path = path_combine(paths[subvol_name], subvol_name);
247
248 if (!dir_exists(subvol_path)){ continue; }
249
250 //log_debug("subvol_path: %s".printf(subvol_path));
251
252 var subvolume = new Subvolume(subvol_name, subvol_path, "", repo); //subvolumes.get(subvol_name);
253 subvolumes.set(subvol_name, subvolume);
254
255 int index = -1;
256
257 foreach(Json.Node jnode in subvols.get_array_member(subvol_name).get_elements()) {
258
259 string item = jnode.get_string();
260 switch (++index){
261 case 0:
262 subvolume.name = item;
263 break;
264 case 1:
265 subvolume.id = long.parse(item);
266 break;
267 case 2:
268 subvolume.total_bytes = int64.parse(item);
269 break;
270 case 3:
271 subvolume.unshared_bytes = int64.parse(item);
272 break;
273 case 4:
274 subvolume.device_uuid = item.strip();
275 break;
276 }
277 }
278 }
279 }
280
281 string delete_trigger_file = path + "/delete";
282 if (file_exists(delete_trigger_file)){
283 marked_for_deletion = true;
284 }
285 }
286 else{
287 valid = false;
288 }
289
290 //log_debug("read_control_file(): exit");
291 }
292
293 public void read_exclude_list(){
294
295 string list_file = path + "/exclude.list";
296
297 exclude_list.clear();
298
299 var f = File.new_for_path(list_file);
300
301 if (f.query_exists()) {
302
303 foreach(string path in file_read(list_file).split("\n")){
304
305 path = path.strip();
306
307 if (!exclude_list.contains(path) && path.length > 0){
308 exclude_list.add(path);
309 }
310 }
311 }
312 else{
313 if (!btrfs_mode){
314 valid = false;
315 }
316 }
317 }
318
319 public void read_fstab_file(){
320
321 string fstab_path = path_combine(path, "/localhost/etc/fstab");
322
323 if (btrfs_mode){
324 fstab_path = path_combine(path, "/@/etc/fstab");
325 }
326
327 fstab_list = FsTabEntry.read_file(fstab_path);
328 }
329
330 public void read_crypttab_file(){
331
332 string crypttab_path = path_combine(path, "/localhost/etc/crypttab");
333
334 if (btrfs_mode){
335 crypttab_path = path_combine(path, "/@/etc/crypttab");
336 }
337
338 cryttab_list = CryptTabEntry.read_file(crypttab_path);
339 }
340
341 public void update_control_file(){
342 /* Updates tag and comments */
343
344 try{
345 string ctl_file = path + "/info.json";
346 var f = File.new_for_path(ctl_file);
347
348 if (f.query_exists()) {
349
350 var parser = new Json.Parser();
351 try{
352 parser.load_from_file(ctl_file);
353 } catch (Error e) {
354 log_error (e.message);
355 }
356 var node = parser.get_root();
357 var config = node.get_object();
358
359 config.set_string_member("tags", taglist);
360 config.set_string_member("comments", description);
361 config.set_string_member("live", live.to_string());
362
363 if (btrfs_mode){
364 var subvols = new Json.Object();
365 config.set_object_member("subvolumes",subvols);
366 foreach(var subvol in subvolumes.values){
367 Json.Array arr = new Json.Array();
368 arr.add_string_element(subvol.name);
369 arr.add_string_element(subvol.id.to_string());
370 arr.add_string_element(subvol.total_bytes.to_string());
371 arr.add_string_element(subvol.unshared_bytes.to_string());
372 arr.add_string_element(subvol.device_uuid);
373 subvols.set_array_member(subvol.name,arr);
374 }
375 }
376
377 var json = new Json.Generator();
378 json.pretty = true;
379 json.indent = 2;
380 node.set_object(config);
381 json.set_root(node);
382 f.delete();
383 json.to_file(ctl_file);
384 }
385 } catch (Error e) {
386 log_error (e.message);
387 }
388 }
389
390 public void remove_control_file(){
391
392 string ctl_file = path + "/info.json";
393 file_delete(ctl_file);
394 }
395
397 string snapshot_path, DateTime dt_created, string root_uuid, string distro_full_name,
398 string tag, string comments, int64 item_count, bool is_btrfs, bool is_live, SnapshotRepo repo, bool silent = false){
399
400 var ctl_path = snapshot_path + "/info.json";
401 var config = new Json.Object();
402
403 config.set_string_member("created", dt_created.to_utc().to_unix().to_string());
404 config.set_string_member("sys-uuid", root_uuid);
405 config.set_string_member("sys-distro", distro_full_name);
406 config.set_string_member("app-version", AppVersion);
407 config.set_string_member("file_count", item_count.to_string());
408 config.set_string_member("tags", tag);
409 config.set_string_member("comments", comments);
410 config.set_string_member("live", is_live.to_string());
411 config.set_string_member("type", (is_btrfs ? "btrfs" : "rsync"));
412
413 var json = new Json.Generator();
414 json.pretty = true;
415 json.indent = 2;
416 var node = new Json.Node(NodeType.OBJECT);
417 node.set_object(config);
418 json.set_root(node);
419
420 try{
421 var f = File.new_for_path(ctl_path);
422 if (f.query_exists()){
423 f.delete();
424 }
425
426 json.to_file(ctl_path);
427 } catch (Error e) {
428 log_error (e.message);
429 }
430
431 if (!silent){
432 log_msg(_("Created control file") + ": %s".printf(ctl_path));
433 }
434
435 return (new Snapshot(snapshot_path, is_btrfs, repo));
436 }
437
438 // check
439
440 public bool has_subvolumes(){
441 foreach(FsTabEntry en in fstab_list){
442 if (en.options.contains("subvol=@")){
443 return true;
444 }
445 }
446 return false;
447 }
448
449 public Gee.ArrayList<Subvolume> subvolumes_sorted {
450 owned get {
451 var list = new Gee.ArrayList<Subvolume>();
452 foreach(var subvol in subvolumes.values){
453 list.add(subvol);
454 }
455 list.sort((a,b)=>{
456 return strcmp(a.name, b.name);
457 });
458 return list;
459 }
460 }
461
462 // actions
463
464 public bool remove(bool wait){
465
466 if (!dir_exists(path)){
467 return true;
468 }
469
470 bool status = true;
471
472 if (btrfs_mode){
473 status = remove_btrfs();
474 }
475 else{
476 status = remove_rsync(wait);
477 }
478
479 return status;
480 }
481
482 public bool remove_rsync(bool wait){
483
484 log_msg(string.nfill(78, '-'));
485
486 var message = _("Removing") + " '%s'...".printf(name);
487 log_msg(message);
488
489 delete_file_task.dest_path = "%s/".printf(path);
490 delete_file_task.status_message = message;
491 delete_file_task.prg_count_total = Main.first_snapshot_count;
493
494 if (wait){
495
496 while (delete_file_task.status == AppStatus.RUNNING){
497
498 sleep(1000);
499 gtk_do_events ();
500
501 stdout.printf("%6.2f%% %s (%s %s)\r".printf(
502 delete_file_task.progress * 100.0, _("complete"),
503 delete_file_task.stat_time_remaining, _("remaining")));
504
505 stdout.flush();
506 }
507
508 stdout.printf(string.nfill(80, ' ') + "\r");
509 stdout.flush();
510
511 message = "%s '%s'".printf(_("Removed"), name);
512 log_msg(message);
513 log_msg(string.nfill(78, '-'));
514 }
515
516 return true;
517 }
518
519 public bool remove_btrfs(){
520
521 log_msg(string.nfill(78, '-'));
522
523 var message = _("Removing snapshot") + ": %s".printf(name);
524 log_msg(message);
525
526 // delete subvolumes
527
528 foreach(var subvol in subvolumes.values){
529
530 bool ok = subvol.remove();
531 if (!ok) {
532 log_error(_("Failed to remove snapshot") + ": %s".printf(name));
533 log_msg(string.nfill(78, '-'));
534 return false;
535 }
536 }
537
538 // delete directories after **all** subvolumes have been deleted
539
540 foreach(var subvol in subvolumes.values){
541
542 bool ok = dir_delete(paths[subvol.name], true);
543 if (!ok) {
544 log_error(_("Failed to remove snapshot") + ": %s".printf(name));
545 log_msg(string.nfill(78, '-'));
546 return false;
547 }
548 }
549
550 if (!dir_delete(path, true)){
551
552 log_error(_("Failed to remove snapshot") + ": %s".printf(name));
553 log_msg(string.nfill(78, '-'));
554 return false;
555 }
556
557 log_msg(_("Removed snapshot") + ": %s".printf(name));
558 log_msg(string.nfill(78, '-'));
559
560 return true;
561 }
562
563 public void mark_for_deletion(){
564
565 string delete_trigger_file = path + "/delete";
566
567 if (!file_exists(delete_trigger_file)){
568 file_write(delete_trigger_file, "");
569 marked_for_deletion = true;
570 } else {
571 file_delete(delete_trigger_file);
572 marked_for_deletion = false;
573 }
574 }
575
576 public void parse_log_file(){
577 /* Parses and archives rsync-log file, creates rsync-log-changes */
578 var task = new RsyncTask();
579 task.parse_log(rsync_log_file);
580 }
581}
Main App
AppStatus
nlohmann::json json
Definition Backend.cpp:2908
#define _(s)
string??? stat_time_remaining
virtual void execute()
AppStatus status
double progress
string options
static LinuxDistro get_dist_info(string root_path)
Definition Main.vala:39
static int64 first_snapshot_count
Definition Main.vala:124
string date_format
Definition Main.vala:130
Gee.HashMap< string, string > mount_paths
DeleteFileTask delete_file_task
Definition Snapshot.vala:61
Gee.ArrayList< CryptTabEntry > cryttab_list
Definition Snapshot.vala:48
DateTime date
Definition Snapshot.vala:38
void read_exclude_list()
LinuxDistro distro
Definition Snapshot.vala:52
string mount_path_home
Definition Snapshot.vala:59
string name
Definition Snapshot.vala:37
void parse_log_file()
string description
Definition Snapshot.vala:42
static Snapshot write_control_file(string snapshot_path, DateTime dt_created, string root_uuid, string distro_full_name, string tag, string comments, int64 item_count, bool is_btrfs, bool is_live, SnapshotRepo repo, bool silent=false)
void mark_for_deletion()
void remove_control_file()
string rsync_restore_changes_log_file
bool remove_btrfs()
string sys_distro
Definition Snapshot.vala:40
string exclude_file_for_restore
SnapshotRepo repo
Definition Snapshot.vala:53
bool has_subvolumes()
Gee.HashMap< string, Subvolume > subvolumes
Definition Snapshot.vala:46
void read_control_file()
string exclude_file_for_backup
string rsync_log_file
string rsync_restore_log_file
void read_fstab_file()
bool live
Definition Snapshot.vala:50
void remove_tag(string tag)
void update_control_file()
bool has_tag(string tag)
string path
Definition Snapshot.vala:36
bool valid
Definition Snapshot.vala:49
string app_version
Definition Snapshot.vala:41
string mount_path_root
Definition Snapshot.vala:58
string taglist
string sys_uuid
Definition Snapshot.vala:39
void read_crypttab_file()
void add_tag(string tag)
string rsync_changes_log_file
int64 file_count
Definition Snapshot.vala:43
bool btrfs_mode
Definition Snapshot.vala:56
Gee.ArrayList< string > exclude_list
Definition Snapshot.vala:45
Gee.ArrayList< Subvolume > subvolumes_sorted
string taglist_short
Gee.ArrayList< string > tags
Definition Snapshot.vala:44
bool remove_rsync(bool wait)
Gee.HashMap< string, string > paths
Definition Snapshot.vala:57
Gee.ArrayList< FsTabEntry > fstab_list
Definition Snapshot.vala:47
string date_formatted
Definition Snapshot.vala:95
Snapshot(string dir_path, bool btrfs_snapshot, SnapshotRepo _repo)
Definition Snapshot.vala:63
bool marked_for_deletion
Definition Snapshot.vala:51
Subvolume(string name, string path, string parent_dev_uuid, SnapshotRepo? parent_repo)
bool file_exists(string file_path)
string? file_read(string file_path)
bool file_write(string file_path, string contents)
string path_combine(string path1, string path2)
bool file_delete(string file_path)
bool dir_exists(string dir_path)
uint64 json_get_uint64(Json.Object jobj, string member, uint64 def_value)
string json_get_string(Json.Object jobj, string member, string def_value)
bool json_get_bool(Json.Object jobj, string member, bool def_value)
void log_error(string message, bool highlight=false, bool is_warning=false)
void log_msg(string message, bool highlight=false)
void sleep(int milliseconds)