~teejee2008/timeshift/trunk

« back to all changes in this revision

Viewing changes to src/Main.vala

  • Committer: Tony George
  • Date: 2016-10-29 11:17:41 UTC
  • Revision ID: tony.george.kol@gmail.com-20161029111741-u2kir9dawb1pz176
Moved console and Gtk code to separate utilities: timeshift and timeshift-gtk

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/*
2
 
 * Main.vala
3
 
 *
4
 
 * Copyright 2016 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
 
using GLib;
25
 
using Gtk;
26
 
using Gee;
27
 
using Json;
28
 
 
29
 
using TeeJee.Logging;
30
 
using TeeJee.FileSystem;
31
 
using TeeJee.JsonHelper;
32
 
using TeeJee.ProcessHelper;
33
 
using TeeJee.GtkHelper;
34
 
using TeeJee.System;
35
 
using TeeJee.Misc;
36
 
 
37
 
public Main App;
38
 
public const string AppName = "Timeshift RSYNC";
39
 
public const string AppShortName = "timeshift";
40
 
public const string AppVersion = "16.10.6";
41
 
public const string AppAuthor = "Tony George";
42
 
public const string AppAuthorEmail = "teejeetech@gmail.com";
43
 
 
44
 
const string GETTEXT_PACKAGE = "";
45
 
const string LOCALE_DIR = "/usr/share/locale";
46
 
 
47
 
extern void exit(int exit_code);
48
 
 
49
 
public class Main : GLib.Object{
50
 
        public string app_path = "";
51
 
        public string share_folder = "";
52
 
        public string rsnapshot_conf_path = "";
53
 
        public string app_conf_path = "";
54
 
        public bool first_run = false;
55
 
 
56
 
        public string backup_uuid = "";
57
 
        public string backup_parent_uuid = "";
58
 
        
59
 
        public Gee.ArrayList<Device> partitions;
60
 
 
61
 
        public Gee.ArrayList<string> exclude_list_user;
62
 
        public Gee.ArrayList<string> exclude_list_default;
63
 
        public Gee.ArrayList<string> exclude_list_default_extra;
64
 
        public Gee.ArrayList<string> exclude_list_home;
65
 
        public Gee.ArrayList<string> exclude_list_restore;
66
 
        public Gee.ArrayList<AppExcludeEntry> exclude_list_apps;
67
 
        public Gee.ArrayList<MountEntry> mount_list;
68
 
        public Gee.ArrayList<string> exclude_app_names;
69
 
        
70
 
        public SnapshotRepo repo; 
71
 
 
72
 
        //temp
73
 
        //private Gee.ArrayList<Device> grub_device_list;
74
 
 
75
 
        public Device sys_root;
76
 
        public Device sys_boot;
77
 
        public Device sys_efi;
78
 
        public Device sys_home;
79
 
 
80
 
        public string mount_point_restore = "";
81
 
        public string mount_point_app = "/mnt/timeshift";
82
 
 
83
 
        public LinuxDistro current_distro;
84
 
        public bool mirror_system = false;
85
 
 
86
 
        public bool schedule_monthly = false;
87
 
        public bool schedule_weekly = false;
88
 
        public bool schedule_daily = true;
89
 
        public bool schedule_hourly = false;
90
 
        public bool schedule_boot = true;
91
 
        public int count_monthly = 2;
92
 
        public int count_weekly = 3;
93
 
        public int count_daily = 5;
94
 
        public int count_hourly = 6;
95
 
        public int count_boot = 5;
96
 
 
97
 
        public string app_mode = "";
98
 
 
99
 
        //global vars for controlling threads
100
 
        public bool thr_success = false;
101
 
        
102
 
        public bool thread_estimate_running = false;
103
 
        public bool thread_estimate_success = false;
104
 
        
105
 
        public bool thread_restore_running = false;
106
 
        public bool thread_restore_success = false;
107
 
 
108
 
        public bool thread_delete_running = false;
109
 
        public bool thread_delete_success = false;
110
 
                        
111
 
        public int thr_retval = -1;
112
 
        public string thr_arg1 = "";
113
 
        public bool thr_timeout_active = false;
114
 
        public string thr_timeout_cmd = "";
115
 
 
116
 
        public int startup_delay_interval_mins = 10;
117
 
        public int retain_snapshots_max_days = 200;
118
 
        
119
 
        public int64 snapshot_location_free_space = 0;
120
 
 
121
 
        public const int SHIELD_ICON_SIZE = 64;
122
 
        public const int64 MIN_FREE_SPACE = 1 * GB;
123
 
        public static int64 first_snapshot_size = 0;
124
 
        public static int64 first_snapshot_count = 0;
125
 
        
126
 
        public string log_dir = "";
127
 
        public string log_file = "";
128
 
        public AppLock app_lock;
129
 
 
130
 
        public Gee.ArrayList<Snapshot> delete_list;
131
 
        
132
 
        public Snapshot snapshot_to_delete;
133
 
        public Snapshot snapshot_to_restore;
134
 
        public Device restore_target;
135
 
        public bool reinstall_grub2 = false;
136
 
        public string grub_device = "";
137
 
 
138
 
        public bool cmd_skip_grub = false;
139
 
        public string cmd_grub_device = "";
140
 
        public string cmd_target_device = "";
141
 
        public string cmd_backup_device = "";
142
 
        public string cmd_snapshot = "";
143
 
        public bool cmd_confirm = false;
144
 
        public bool cmd_verbose = true;
145
 
 
146
 
        public string progress_text = "";
147
 
        public int snapshot_list_start_index = 0;
148
 
 
149
 
        public RsyncTask task;
150
 
        public DeleteFileTask delete_file_task;
151
 
 
152
 
        //initialization
153
 
 
154
 
        public static int main (string[] args) {
155
 
                set_locale();
156
 
 
157
 
                LOG_TIMESTAMP = false;
158
 
                
159
 
                //show help and exit
160
 
                if (args.length > 1) {
161
 
                        switch (args[1].down()) {
162
 
                                case "--help":
163
 
                                case "-h":
164
 
                                        stdout.printf (Main.help_message ());
165
 
                                        return 0;
166
 
                        }
167
 
                }
168
 
 
169
 
                //init TMP
170
 
                LOG_ENABLE = false;
171
 
                init_tmp(AppShortName);
172
 
                LOG_ENABLE = true;
173
 
 
174
 
                /*
175
 
                 * Note:
176
 
                 * init_tmp() will fail if timeshift is run as normal user
177
 
                 * logging will be disabled temporarily so that the error is not displayed to user
178
 
                 */
179
 
 
180
 
                /*
181
 
                var map = Device.get_mounted_filesystems_using_mtab();
182
 
                foreach(Device pi in map.values){
183
 
                        log_msg(pi.description_full());
184
 
                }
185
 
                exit(0);
186
 
                */
187
 
 
188
 
                App = new Main(args);
189
 
 
190
 
                bool success = App.start_application(args);
191
 
                App.exit_app();
192
 
 
193
 
                return (success) ? 0 : 1;
194
 
        }
195
 
 
196
 
        private static void set_locale(){
197
 
                log_debug("setting locale...");
198
 
                Intl.setlocale(GLib.LocaleCategory.MESSAGES, "timeshift");
199
 
                Intl.textdomain(GETTEXT_PACKAGE);
200
 
                Intl.bind_textdomain_codeset(GETTEXT_PACKAGE, "utf-8");
201
 
                Intl.bindtextdomain(GETTEXT_PACKAGE, LOCALE_DIR);
202
 
        }
203
 
 
204
 
        public Main(string[] args){
205
 
 
206
 
                log_debug("Main()");
207
 
                
208
 
                string msg = "";
209
 
 
210
 
                //parse arguments (initial) ------------
211
 
 
212
 
                parse_arguments(args);
213
 
 
214
 
                //check for admin access before logging is initialized
215
 
                //since writing to log directory requires admin access
216
 
 
217
 
                if (!user_is_admin()){
218
 
                        msg = _("TimeShift needs admin access to backup and restore system files.") + "\n";
219
 
                        msg += _("Please run the application as admin (using 'sudo' or 'su')");
220
 
 
221
 
                        log_error(msg);
222
 
 
223
 
                        if (app_mode == ""){
224
 
                                string title = _("Admin Access Required");
225
 
                                gtk_messagebox(title, msg, null, true);
226
 
                        }
227
 
 
228
 
                        exit(0);
229
 
                }
230
 
 
231
 
                //init log ------------------
232
 
 
233
 
                try {
234
 
                        string suffix = (app_mode.length == 0) ? "_gui" : "_" + app_mode;
235
 
                        
236
 
                        DateTime now = new DateTime.now_local();
237
 
                        log_dir = "/var/log/timeshift";
238
 
                        log_file = path_combine(log_dir,
239
 
                                "%s_%s.log".printf(now.format("%Y-%m-%d_%H-%M-%S"), suffix));
240
 
 
241
 
                        var file = File.new_for_path (log_dir);
242
 
                        if (!file.query_exists ()) {
243
 
                                file.make_directory_with_parents();
244
 
                        }
245
 
 
246
 
                        file = File.new_for_path (log_file);
247
 
                        if (file.query_exists ()) {
248
 
                                file.delete ();
249
 
                        }
250
 
 
251
 
                        dos_log = new DataOutputStream (file.create(FileCreateFlags.REPLACE_DESTINATION));
252
 
                        if ((app_mode == "")||(LOG_DEBUG)){
253
 
                                log_msg(_("Session log file") + ": %s".printf(log_file));
254
 
                        }
255
 
                }
256
 
                catch (Error e) {
257
 
                        log_error (e.message);
258
 
                }
259
 
 
260
 
                log_msg("");
261
 
                log_msg(_("Running") + " %s v%s".printf(AppName, AppVersion));
262
 
                
263
 
                //get Linux distribution info -----------------------
264
 
 
265
 
                this.current_distro = LinuxDistro.get_dist_info("/");
266
 
                log_msg(_("Distribution") + ": " + current_distro.full_name());
267
 
                log_msg("DIST_ID" + ": " + current_distro.dist_id);
268
 
 
269
 
                //check dependencies ---------------------
270
 
 
271
 
                string message;
272
 
                if (!check_dependencies(out message)){
273
 
                        if (app_mode == ""){
274
 
                                string title = _("Missing Dependencies");
275
 
                                gtk_messagebox(title, message, null, true);
276
 
                        }
277
 
                        exit(0);
278
 
                }
279
 
 
280
 
                //check and create lock ------------------
281
 
 
282
 
                app_lock = new AppLock();
283
 
                
284
 
                if (!app_lock.create("timeshift", app_mode)){
285
 
                        if (app_mode == ""){
286
 
                                if (app_lock.lock_message == "backup"){
287
 
                                        msg = _("Another instance of Timeshift is creating a snapshot.") + "\n";
288
 
                                        msg += _("Please wait a few minutes and try again.");
289
 
                                }
290
 
                                else{
291
 
                                        msg = _("Another instance of timeshift is currently running!") + "\n";
292
 
                                        msg += _("Please check if you have multiple windows open.") + "\n";
293
 
                                }
294
 
 
295
 
                                string title = _("Scheduled snapshot in progress...");
296
 
                                gtk_messagebox(title, msg, null, true);
297
 
                        }
298
 
                        else{
299
 
                                //already logged - do nothing
300
 
                        }
301
 
                        exit(0);
302
 
                }
303
 
 
304
 
                //initialize variables ------------------
305
 
 
306
 
                this.app_path = (File.new_for_path (args[0])).get_parent().get_path ();
307
 
                this.share_folder = "/usr/share";
308
 
                this.app_conf_path = "/etc/timeshift.json";
309
 
                //sys_root and sys_home will be initalized by update_partition_list()
310
 
 
311
 
                //check if running locally -------------
312
 
 
313
 
                string local_exec = args[0];
314
 
                string local_conf = app_path + "/timeshift.json";
315
 
                string local_share = app_path + "/share";
316
 
 
317
 
                var f_local_exec = File.new_for_path(local_exec);
318
 
                if (f_local_exec.query_exists()){
319
 
 
320
 
                        var f_local_conf = File.new_for_path(local_conf);
321
 
                        if (f_local_conf.query_exists()){
322
 
                                this.app_conf_path = local_conf;
323
 
                        }
324
 
 
325
 
                        var f_local_share = File.new_for_path(local_share);
326
 
                        if (f_local_share.query_exists()){
327
 
                                this.share_folder = local_share;
328
 
                        }
329
 
                }
330
 
                else{
331
 
                        //timeshift is running from system directory - update app_path
332
 
                        this.app_path = get_cmd_path("timeshift");
333
 
                }
334
 
 
335
 
                //initialize lists -------------------------
336
 
 
337
 
                repo = new SnapshotRepo();
338
 
 
339
 
                mount_list = new Gee.ArrayList<MountEntry>();
340
 
                delete_list = new Gee.ArrayList<Snapshot>();
341
 
 
342
 
                exclude_app_names = new Gee.ArrayList<string>();
343
 
                add_default_exclude_entries();
344
 
                //add_app_exclude_entries();
345
 
 
346
 
                //initialize app --------------------
347
 
 
348
 
                update_partitions();
349
 
                detect_system_devices();
350
 
 
351
 
                //finish initialization --------------
352
 
 
353
 
                load_app_config();
354
 
 
355
 
                task = new RsyncTask();
356
 
                delete_file_task = new DeleteFileTask();
357
 
 
358
 
                log_debug("Main(): ok");
359
 
        }
360
 
 
361
 
        public bool start_application(string[] args){
362
 
                bool is_success = true;
363
 
 
364
 
                log_debug("start_application()");
365
 
 
366
 
                if (live_system()){
367
 
                        switch(app_mode){
368
 
                        case "backup":
369
 
                        case "ondemand":
370
 
                                log_error(_("Snapshots cannot be created in Live CD mode"));
371
 
                                return false;
372
 
                        }
373
 
                }
374
 
                
375
 
                switch(app_mode){
376
 
                        case "backup":
377
 
                        case "ondemand":
378
 
                        case "restore":
379
 
                        case "delete":
380
 
                        case "delete-all":
381
 
                        case "list-snapshots":
382
 
                                //set backup device from commandline argument if available or prompt user if device is null
383
 
                                if (!mirror_system){
384
 
                                        get_backup_device_from_cmd(false, null);
385
 
                                }
386
 
                                break;
387
 
                }
388
 
 
389
 
                switch(app_mode){
390
 
                        case "backup":
391
 
                                is_success = take_snapshot(false, "", null);
392
 
                                return is_success;
393
 
 
394
 
                        case "restore":
395
 
                                is_success = restore_snapshot(null);
396
 
                                return is_success;
397
 
 
398
 
                        case "delete":
399
 
                                delete_snapshot();
400
 
                                return true;
401
 
 
402
 
                        case "delete-all":
403
 
                                is_success = delete_all_snapshots();
404
 
                                return is_success;
405
 
 
406
 
                        case "ondemand":
407
 
                                is_success = take_snapshot(true,"",null);
408
 
                                return is_success;
409
 
 
410
 
                        case "list-snapshots":
411
 
                                LOG_ENABLE = true;
412
 
                                if (App.repo.has_snapshots()){
413
 
                                        log_msg(_("Snapshots on device %s").printf(
414
 
                                                repo.device.full_name_with_alias) + ":\n");
415
 
                                        list_snapshots(false);
416
 
                                        log_msg("");
417
 
                                        return true;
418
 
                                }
419
 
                                else{
420
 
                                        log_msg(_("No snapshots found on device") + " '%s'".printf(repo.device.device));
421
 
                                        return false;
422
 
                                }
423
 
 
424
 
                        case "list-devices":
425
 
                                LOG_ENABLE = true;
426
 
                                log_msg(_("Devices with Linux file systems") + ":\n");
427
 
                                list_all_devices();
428
 
                                log_msg("");
429
 
                                return true;
430
 
 
431
 
                        default:
432
 
                                log_debug("Creating MainWindow");
433
 
                                //Initialize main window
434
 
                                var window = new MainWindow ();
435
 
                                window.destroy.connect(Gtk.main_quit);
436
 
                                window.show_all();
437
 
 
438
 
                                //start event loop
439
 
                                Gtk.main();
440
 
 
441
 
                                return true;
442
 
                }
443
 
        }
444
 
 
445
 
        public bool check_dependencies(out string msg){
446
 
                msg = "";
447
 
 
448
 
                log_debug("check_dependencies()");
449
 
                
450
 
                string[] dependencies = { "rsync","/sbin/blkid","df","mount","umount","fuser","crontab","cp","rm","touch","ln","sync"}; //"shutdown","chroot",
451
 
 
452
 
                string path;
453
 
                foreach(string cmd_tool in dependencies){
454
 
                        path = get_cmd_path (cmd_tool);
455
 
                        if ((path == null) || (path.length == 0)){
456
 
                                msg += " * " + cmd_tool + "\n";
457
 
                        }
458
 
                }
459
 
 
460
 
                if (msg.length > 0){
461
 
                        msg = _("Commands listed below are not available on this system") + ":\n\n" + msg + "\n";
462
 
                        msg += _("Please install required packages and try running TimeShift again");
463
 
                        log_error(msg);
464
 
                        return false;
465
 
                }
466
 
                else{
467
 
                        return true;
468
 
                }
469
 
        }
470
 
 
471
 
        public bool check_btrfs_layout_system(Gtk.Window? win = null){
472
 
 
473
 
                log_debug("check_btrfs_layout_system()");
474
 
 
475
 
                bool supported = check_btrfs_layout(sys_root, sys_home);
476
 
 
477
 
                if (!supported){
478
 
                        string msg = _("The system partition has an unsupported subvolume layout.") + " ";
479
 
                        msg += _("Only ubuntu-type layouts with @ and @home subvolumes are currently supported.") + "\n\n";
480
 
                        msg += _("Application will exit.") + "\n\n";
481
 
                        string title = _("Not Supported");
482
 
                        
483
 
                        if (app_mode == ""){
484
 
                                gtk_set_busy(false, win);
485
 
                                gtk_messagebox(title, msg, win, true);
486
 
                        }
487
 
                        else{
488
 
                                log_error(msg);
489
 
                        }
490
 
                }
491
 
 
492
 
                return supported;
493
 
        }
494
 
 
495
 
        public bool check_btrfs_layout(Device? dev_root, Device? dev_home){
496
 
                
497
 
                bool supported = true; // keep true for non-btrfs systems
498
 
 
499
 
                if ((dev_root != null) && (dev_root.fstype == "btrfs")){
500
 
                        
501
 
                        if ((dev_home != null) && (dev_home.fstype == "btrfs")){
502
 
                                supported = supported && check_btrfs_volume(dev_root, "@");
503
 
                                supported = supported && check_btrfs_volume(dev_home, "@home");
504
 
                        }
505
 
                        else{
506
 
                                supported = supported && check_btrfs_volume(dev_root, "@,@home");
507
 
                        }
508
 
                }
509
 
 
510
 
                return supported;
511
 
        }
512
 
 
513
 
        // exclude lists
514
 
        
515
 
        public void add_default_exclude_entries(){
516
 
 
517
 
                exclude_list_user = new Gee.ArrayList<string>();
518
 
                exclude_list_default = new Gee.ArrayList<string>();
519
 
                exclude_list_default_extra = new Gee.ArrayList<string>();
520
 
                exclude_list_home = new Gee.ArrayList<string>();
521
 
                exclude_list_restore = new Gee.ArrayList<string>();
522
 
                exclude_list_apps = new Gee.ArrayList<AppExcludeEntry>();
523
 
                
524
 
                partitions = new Gee.ArrayList<Device>();
525
 
 
526
 
                //default exclude entries -------------------
527
 
 
528
 
                exclude_list_default.add("/dev/*");
529
 
                exclude_list_default.add("/proc/*");
530
 
                exclude_list_default.add("/sys/*");
531
 
                exclude_list_default.add("/media/*");
532
 
                exclude_list_default.add("/mnt/*");
533
 
                exclude_list_default.add("/tmp/*");
534
 
                exclude_list_default.add("/run/*");
535
 
                exclude_list_default.add("/var/run/*");
536
 
                exclude_list_default.add("/var/lock/*");
537
 
                exclude_list_default.add("/lost+found");
538
 
                exclude_list_default.add("/timeshift/*");
539
 
                exclude_list_default.add("/data/*");
540
 
                exclude_list_default.add("/cdrom/*");
541
 
 
542
 
                exclude_list_default.add("/root/.thumbnails");
543
 
                exclude_list_default.add("/root/.cache");
544
 
                exclude_list_default.add("/root/.gvfs");
545
 
                exclude_list_default.add("/root/.local/share/Trash");
546
 
 
547
 
                exclude_list_default.add("/home/*/.thumbnails");
548
 
                exclude_list_default.add("/home/*/.cache");
549
 
                exclude_list_default.add("/home/*/.gvfs");
550
 
                exclude_list_default.add("/home/*/.local/share/Trash");
551
 
 
552
 
                //default extra ------------------
553
 
 
554
 
                exclude_list_default_extra.add("/root/.mozilla/firefox/*.default/Cache");
555
 
                exclude_list_default_extra.add("/root/.mozilla/firefox/*.default/OfflineCache");
556
 
                exclude_list_default_extra.add("/root/.opera/cache");
557
 
                exclude_list_default_extra.add("/root/.kde/share/apps/kio_http/cache");
558
 
                exclude_list_default_extra.add("/root/.kde/share/cache/http");
559
 
 
560
 
                exclude_list_default_extra.add("/home/*/.mozilla/firefox/*.default/Cache");
561
 
                exclude_list_default_extra.add("/home/*/.mozilla/firefox/*.default/OfflineCache");
562
 
                exclude_list_default_extra.add("/home/*/.opera/cache");
563
 
                exclude_list_default_extra.add("/home/*/.kde/share/apps/kio_http/cache");
564
 
                exclude_list_default_extra.add("/home/*/.kde/share/cache/http");
565
 
 
566
 
                //default home ----------------
567
 
 
568
 
                exclude_list_home.add("+ /root/.**");
569
 
                exclude_list_home.add("/root/**");
570
 
                exclude_list_home.add("+ /home/*/.**");
571
 
                exclude_list_home.add("/home/*/**");
572
 
 
573
 
                /*
574
 
                Most web browsers store their cache under ~/.cache and /tmp
575
 
                These files will be excluded by the entries for ~/.cache and /tmp
576
 
                There is no need to add special entries.
577
 
 
578
 
                ~/.cache/google-chrome                  -- Google Chrome
579
 
                ~/.cache/chromium                               -- Chromium
580
 
                ~/.cache/epiphany-browser               -- Epiphany
581
 
                ~/.cache/midori/web                             -- Midori
582
 
                /var/tmp/kdecache-$USER/http    -- Rekonq
583
 
                */
584
 
 
585
 
        }
586
 
 
587
 
        public void add_app_exclude_entries(){
588
 
                AppExcludeEntry.clear();
589
 
 
590
 
                string home;
591
 
                string user_name;
592
 
 
593
 
                string cmd = "echo ${SUDO_USER:-$(whoami)}";
594
 
                string std_out;
595
 
                string std_err;
596
 
                int ret_val;
597
 
                ret_val = exec_script_sync(cmd, out std_out, out std_err);
598
 
 
599
 
                if ((std_out == null) || (std_out.length == 0)){
600
 
                        user_name = "root";
601
 
                        home = "/root";
602
 
                }
603
 
                else{
604
 
                        user_name = std_out.strip();
605
 
                        home = "/home/%s".printf(user_name);
606
 
                }
607
 
 
608
 
                if ((sys_root == null)
609
 
                        || ((restore_target.device != sys_root.device)
610
 
                                && (restore_target.uuid != sys_root.uuid))){
611
 
 
612
 
                        home = mount_point_restore + home;
613
 
                }
614
 
 
615
 
                if ((sys_root == null)
616
 
                        || ((restore_target.device != sys_root.device)
617
 
                                && (restore_target.uuid != sys_root.uuid))){
618
 
 
619
 
                        home = mount_point_restore + home;
620
 
                }
621
 
 
622
 
                AppExcludeEntry.add_app_exclude_entries_from_path(home);
623
 
 
624
 
                exclude_list_apps = AppExcludeEntry.get_apps_list(exclude_app_names);
625
 
        }
626
 
 
627
 
        public Gee.ArrayList<string> create_exclude_list_for_backup(){
628
 
                var list = new Gee.ArrayList<string>();
629
 
 
630
 
                //add default entries
631
 
                foreach(string path in exclude_list_default){
632
 
                        if (!list.contains(path)){
633
 
                                list.add(path);
634
 
                        }
635
 
                }
636
 
 
637
 
                //add default extra entries
638
 
                foreach(string path in exclude_list_default_extra){
639
 
                        if (!list.contains(path)){
640
 
                                list.add(path);
641
 
                        }
642
 
                }
643
 
 
644
 
                //add user entries from current settings
645
 
                foreach(string path in exclude_list_user){
646
 
                        if (!list.contains(path)){
647
 
                                list.add(path);
648
 
                        }
649
 
                }
650
 
 
651
 
                //add home entries
652
 
                foreach(string path in exclude_list_home){
653
 
                        if (!list.contains(path)){
654
 
                                list.add(path);
655
 
                        }
656
 
                }
657
 
 
658
 
                string timeshift_path = "/timeshift/*";
659
 
                if (!list.contains(timeshift_path)){
660
 
                        list.add(timeshift_path);
661
 
                }
662
 
 
663
 
                return list;
664
 
        }
665
 
        
666
 
        public Gee.ArrayList<string> create_exclude_list_for_restore(){
667
 
 
668
 
                exclude_list_restore.clear();
669
 
                
670
 
                //add default entries
671
 
                foreach(string path in exclude_list_default){
672
 
                        if (!exclude_list_restore.contains(path)){
673
 
                                exclude_list_restore.add(path);
674
 
                        }
675
 
                }
676
 
 
677
 
                if (!mirror_system){
678
 
                        //add default_extra entries
679
 
                        foreach(string path in exclude_list_default_extra){
680
 
                                if (!exclude_list_restore.contains(path)){
681
 
                                        exclude_list_restore.add(path);
682
 
                                }
683
 
                        }
684
 
                }
685
 
 
686
 
                //add app entries
687
 
                foreach(var entry in exclude_list_apps){
688
 
                        if (entry.enabled){
689
 
                                foreach(var pattern in entry.patterns){
690
 
                                        if (!exclude_list_restore.contains(pattern)){
691
 
                                                exclude_list_restore.add(pattern);
692
 
                                        }
693
 
                                }
694
 
                        }
695
 
                }
696
 
 
697
 
                //add user entries from current settings
698
 
                foreach(string path in exclude_list_user){
699
 
                        if (!exclude_list_restore.contains(path) && !exclude_list_home.contains(path)){
700
 
                                exclude_list_restore.add(path);
701
 
                        }
702
 
                }
703
 
 
704
 
                //add user entries from snapshot exclude list
705
 
                if (snapshot_to_restore != null){
706
 
                        string list_file = path_combine(snapshot_to_restore.path, "exclude.list");
707
 
                        if (file_exists(list_file)){
708
 
                                foreach(string path in file_read(list_file).split("\n")){
709
 
                                        if (!exclude_list_restore.contains(path) && !exclude_list_home.contains(path)){
710
 
                                                exclude_list_restore.add(path);
711
 
                                        }
712
 
                                }
713
 
                        }
714
 
                }
715
 
 
716
 
                //add home entries
717
 
                foreach(string path in exclude_list_home){
718
 
                        if (!exclude_list_restore.contains(path)){
719
 
                                exclude_list_restore.add(path);
720
 
                        }
721
 
                }
722
 
 
723
 
                string timeshift_path = "/timeshift/*";
724
 
                if (!exclude_list_restore.contains(timeshift_path)){
725
 
                        exclude_list_restore.add(timeshift_path);
726
 
                }
727
 
        
728
 
                return exclude_list_restore;
729
 
        }
730
 
 
731
 
        public bool save_exclude_list_for_backup(string output_path){
732
 
 
733
 
                var list = create_exclude_list_for_backup();
734
 
                
735
 
                var txt = "";
736
 
                foreach(var pattern in list){
737
 
                        if (pattern.strip().length > 0){
738
 
                                txt += "%s\n".printf(pattern);
739
 
                        }
740
 
                }
741
 
                
742
 
                string list_file = path_combine(output_path, "exclude.list");
743
 
                return file_write(list_file, txt);
744
 
        }
745
 
 
746
 
        public bool save_exclude_list_for_restore(string output_path){
747
 
 
748
 
                var list = create_exclude_list_for_restore();
749
 
 
750
 
                log_debug("Exclude list -------------");
751
 
                
752
 
                var txt = "";
753
 
                foreach(var pattern in list){
754
 
                        if (pattern.strip().length > 0){
755
 
                                txt += "%s\n".printf(pattern);
756
 
                                log_debug(pattern);
757
 
                        }
758
 
                }
759
 
                
760
 
                string list_file = path_combine(output_path, "exclude-restore.list");
761
 
                return file_write(list_file, txt);
762
 
        }
763
 
 
764
 
        public void save_exclude_list_selections(){
765
 
                
766
 
                // add new selected items
767
 
                foreach(var entry in App.exclude_list_apps){
768
 
                        if (entry.enabled && !App.exclude_app_names.contains(entry.name)){
769
 
                                App.exclude_app_names.add(entry.name);
770
 
                                log_debug("add app name: %s".printf(entry.name));
771
 
                        }
772
 
                }
773
 
 
774
 
                // remove item only if present in current list and un-selected
775
 
                foreach(var entry in App.exclude_list_apps){
776
 
                        if (!entry.enabled && App.exclude_app_names.contains(entry.name)){
777
 
                                App.exclude_app_names.remove(entry.name);
778
 
                                log_debug("remove app name: %s".printf(entry.name));
779
 
                        }
780
 
                }
781
 
 
782
 
                App.exclude_app_names.sort((a,b) => {
783
 
                        return Posix.strcmp(a,b);
784
 
                });
785
 
        }
786
 
 
787
 
        //console functions
788
 
 
789
 
        public static string help_message (){
790
 
                string msg = "\n" + AppName + " v" + AppVersion + " by Tony George (teejeetech@gmail.com)" + "\n";
791
 
                msg += "\n";
792
 
                msg += "Syntax:\n";
793
 
                msg += "\n";
794
 
                msg += "  timeshift --list-{snapshots|devices} [OPTIONS]\n";
795
 
                msg += "  timeshift --backup[-now] [OPTIONS]\n";
796
 
                msg += "  timeshift --restore [OPTIONS]\n";
797
 
                msg += "  timeshift --delete-[all] [OPTIONS]\n";
798
 
                msg += "\n";
799
 
                msg += _("Options") + ":\n";
800
 
                msg += "\n";
801
 
                msg += _("List") + ":\n";
802
 
                msg += "  --list[-snapshots]         " + _("List snapshots") + "\n";
803
 
                msg += "  --list-devices             " + _("List devices") + "\n";
804
 
                msg += "\n";
805
 
                msg += _("Backup") + ":\n";
806
 
                msg += "  --backup                   " + _("Take scheduled backup") + "\n";
807
 
                msg += "  --backup-now               " + _("Take on-demand backup") + "\n";
808
 
                msg += "\n";
809
 
                msg += _("Restore") + ":\n";
810
 
                msg += "  --restore                  " + _("Restore snapshot") + "\n";
811
 
                msg += "  --clone                    " + _("Clone current system") + "\n";
812
 
                msg += "  --snapshot <name>          " + _("Specify snapshot to restore") + "\n";
813
 
                msg += "  --target[-device] <device> " + _("Specify target device") + "\n";
814
 
                msg += "  --grub[-device] <device>   " + _("Specify device for installing GRUB2 bootloader") + "\n";
815
 
                msg += "  --skip-grub                " + _("Skip GRUB2 reinstall") + "\n";
816
 
                msg += "\n";
817
 
                msg += _("Delete") + ":\n";
818
 
                msg += "  --delete                   " + _("Delete snapshot") + "\n";
819
 
                msg += "  --delete-all               " + _("Delete all snapshots") + "\n";
820
 
                msg += "\n";
821
 
                msg += _("Global") + ":\n";
822
 
                msg += "  --backup-device <device>   " + _("Specify backup device") + "\n";
823
 
                msg += "  --yes                      " + _("Answer YES to all confirmation prompts") + "\n";
824
 
                msg += "  --debug                    " + _("Show additional debug messages") + "\n";
825
 
                msg += "  --verbose                  " + _("Show rsync output (default)") + "\n";
826
 
                msg += "  --quiet                    " + _("Hide rsync output") + "\n";
827
 
                msg += "  --help                     " + _("Show all options") + "\n";
828
 
                msg += "\n";
829
 
 
830
 
                msg += _("Examples") + ":\n";
831
 
                msg += "\n";
832
 
                msg += "timeshift --list\n";
833
 
                msg += "timeshift --list --backup-device /dev/sda1\n";
834
 
                msg += "timeshift --backup-now \n";
835
 
                msg += "timeshift --restore \n";
836
 
                msg += "timeshift --restore --snapshot '2014-10-12_16-29-08' --target /dev/sda1 --skip-grub\n";
837
 
                msg += "timeshift --delete  --snapshot '2014-10-12_16-29-08'\n";
838
 
                msg += "timeshift --delete-all \n";
839
 
                msg += "\n";
840
 
 
841
 
                msg += _("Notes") + ":\n";
842
 
                msg += "\n";
843
 
                msg += "  1) --backup will take a snapshot only if a scheduled snapshot is due\n";
844
 
                msg += "  2) --backup-now will take an immediate (forced) snapshot\n";
845
 
                msg += "  3) --backup will not take snapshots till first snapshot is taken with --backup-now\n";
846
 
                msg += "  4) Use --restore without other options to select options interactively\n";
847
 
                msg += "  5) UUID can be specified instead of device name\n";
848
 
                msg += "\n";
849
 
                return msg;
850
 
        }
851
 
 
852
 
        private void parse_arguments(string[] args){
853
 
 
854
 
                log_debug("parse_arguments()");
855
 
                
856
 
                for (int k = 1; k < args.length; k++) // Oth arg is app path
857
 
                {
858
 
                        switch (args[k].down()){
859
 
                                case "--backup":
860
 
                                        LOG_TIMESTAMP = false;
861
 
                                        LOG_DEBUG = false;
862
 
                                        app_mode = "backup";
863
 
                                        break;
864
 
 
865
 
                                case "--delete":
866
 
                                        LOG_TIMESTAMP = false;
867
 
                                        LOG_DEBUG = false;
868
 
                                        app_mode = "delete";
869
 
                                        break;
870
 
 
871
 
                                case "--delete-all":
872
 
                                        LOG_TIMESTAMP = false;
873
 
                                        LOG_DEBUG = false;
874
 
                                        app_mode = "delete-all";
875
 
                                        break;
876
 
 
877
 
                                case "--restore":
878
 
                                        LOG_TIMESTAMP = false;
879
 
                                        LOG_DEBUG = false;
880
 
                                        mirror_system = false;
881
 
                                        app_mode = "restore";
882
 
                                        break;
883
 
 
884
 
                                case "--clone":
885
 
                                        LOG_TIMESTAMP = false;
886
 
                                        LOG_DEBUG = false;
887
 
                                        mirror_system = true;
888
 
                                        app_mode = "restore";
889
 
                                        break;
890
 
 
891
 
                                case "--backup-now":
892
 
                                        LOG_TIMESTAMP = false;
893
 
                                        LOG_DEBUG = false;
894
 
                                        app_mode = "ondemand";
895
 
                                        break;
896
 
 
897
 
                                case "--skip-grub":
898
 
                                        cmd_skip_grub = true;
899
 
                                        break;
900
 
 
901
 
                                case "--verbose":
902
 
                                        cmd_verbose = true;
903
 
                                        break;
904
 
 
905
 
                                case "--quiet":
906
 
                                        cmd_verbose = false;
907
 
                                        break;
908
 
 
909
 
                                case "--yes":
910
 
                                        cmd_confirm = true;
911
 
                                        break;
912
 
 
913
 
                                case "--grub":
914
 
                                case "--grub-device":
915
 
                                        reinstall_grub2 = true;
916
 
                                        cmd_grub_device = args[++k];
917
 
                                        break;
918
 
 
919
 
                                case "--target":
920
 
                                case "--target-device":
921
 
                                        cmd_target_device = args[++k];
922
 
                                        break;
923
 
 
924
 
                                case "--backup-device":
925
 
                                        cmd_backup_device = args[++k];
926
 
                                        break;
927
 
 
928
 
                                case "--snapshot":
929
 
                                case "--snapshot-name":
930
 
                                        cmd_snapshot = args[++k];
931
 
                                        break;
932
 
 
933
 
                                case "--debug":
934
 
                                        LOG_COMMANDS = true;
935
 
                                        LOG_DEBUG = true;
936
 
                                        break;
937
 
 
938
 
                                case "--list":
939
 
                                case "--list-snapshots":
940
 
                                        app_mode = "list-snapshots";
941
 
                                        LOG_TIMESTAMP = false;
942
 
                                        LOG_DEBUG = false;
943
 
                                        break;
944
 
 
945
 
                                case "--list-devices":
946
 
                                        app_mode = "list-devices";
947
 
                                        LOG_TIMESTAMP = false;
948
 
                                        LOG_DEBUG = false;
949
 
                                        break;
950
 
 
951
 
                                default:
952
 
                                        LOG_TIMESTAMP = false;
953
 
                                        log_error(_("Invalid command line arguments") + ": %s".printf(args[k]), true);
954
 
                                        log_msg(Main.help_message());
955
 
                                        exit(1);
956
 
                                        break;
957
 
                        }
958
 
                }
959
 
 
960
 
                /* LOG_ENABLE = false;          disables all console output
961
 
                 * LOG_TIMESTAMP = false;       disables the timestamp prepended to every line in terminal output
962
 
                 * LOG_DEBUG = false;           disables additional console messages
963
 
                 * LOG_COMMANDS = true;         enables printing of all commands on terminal
964
 
                 * */
965
 
 
966
 
                if (app_mode == ""){
967
 
                        //Initialize GTK
968
 
                        LOG_TIMESTAMP = true;
969
 
                        Gtk.init(ref args);
970
 
                }
971
 
 
972
 
                //X.init_threads();
973
 
        }
974
 
 
975
 
        private void list_snapshots(bool paginate){
976
 
                int count = 0;
977
 
                for(int index = 0; index < repo.snapshots.size; index++){
978
 
                        if (!paginate || ((index >= snapshot_list_start_index) && (index < snapshot_list_start_index + 10))){
979
 
                                count++;
980
 
                        }
981
 
                }
982
 
 
983
 
                string[,] grid = new string[count+1,5];
984
 
                bool[] right_align = { false, false, false, false, false};
985
 
 
986
 
                int row = 0;
987
 
                int col = -1;
988
 
                grid[row, ++col] = _("Num");
989
 
                grid[row, ++col] = "";
990
 
                grid[row, ++col] = _("Name");
991
 
                grid[row, ++col] = _("Tags");
992
 
                grid[row, ++col] = _("Description");
993
 
                row++;
994
 
 
995
 
                for(int index = 0; index < repo.snapshots.size; index++){
996
 
                        Snapshot bak = repo.snapshots[index];
997
 
                        if (!paginate || ((index >= snapshot_list_start_index) && (index < snapshot_list_start_index + 10))){
998
 
                                col = -1;
999
 
                                grid[row, ++col] = "%d".printf(index);
1000
 
                                grid[row, ++col] = ">";
1001
 
                                grid[row, ++col] = "%s".printf(bak.name);
1002
 
                                grid[row, ++col] = "%s".printf(bak.taglist_short);
1003
 
                                grid[row, ++col] = "%s".printf(bak.description);
1004
 
                                row++;
1005
 
                        }
1006
 
                }
1007
 
 
1008
 
                print_grid(grid, right_align);
1009
 
        }
1010
 
 
1011
 
        private void list_devices(Gee.ArrayList<Device> device_list){
1012
 
                string[,] grid = new string[device_list.size+1,6];
1013
 
                bool[] right_align = { false, false, false, true, true, false};
1014
 
 
1015
 
                int row = 0;
1016
 
                int col = -1;
1017
 
                grid[row, ++col] = _("Num");
1018
 
                grid[row, ++col] = "";
1019
 
                grid[row, ++col] = _("Device");
1020
 
                //grid[row, ++col] = _("UUID");
1021
 
                grid[row, ++col] = _("Size");
1022
 
                grid[row, ++col] = _("Type");
1023
 
                grid[row, ++col] = _("Label");
1024
 
                row++;
1025
 
 
1026
 
                foreach(var pi in device_list) {
1027
 
                        col = -1;
1028
 
                        grid[row, ++col] = "%d".printf(row - 1);
1029
 
                        grid[row, ++col] = ">";
1030
 
                        grid[row, ++col] = "%s".printf(pi.full_name_with_alias);
1031
 
                        //grid[row, ++col] = "%s".printf(pi.uuid);
1032
 
                        grid[row, ++col] = "%s".printf((pi.size_bytes > 0) ? "%s".printf(pi.size) : "?? GB");
1033
 
                        grid[row, ++col] = "%s".printf(pi.fstype);
1034
 
                        grid[row, ++col] = "%s".printf(pi.label);
1035
 
                        row++;
1036
 
                }
1037
 
 
1038
 
                print_grid(grid, right_align);
1039
 
        }
1040
 
 
1041
 
        private Gee.ArrayList<Device> list_all_devices(){
1042
 
 
1043
 
                //add devices
1044
 
                var device_list = new Gee.ArrayList<Device>();
1045
 
                foreach(var dev in Device.get_block_devices_using_lsblk()) {
1046
 
                        if (dev.has_linux_filesystem()){
1047
 
                                device_list.add(dev);
1048
 
                        }
1049
 
                }
1050
 
 
1051
 
                string[,] grid = new string[device_list.size+1,6];
1052
 
                bool[] right_align = { false, false, false, true, true, false};
1053
 
 
1054
 
                int row = 0;
1055
 
                int col = -1;
1056
 
                grid[row, ++col] = _("Num");
1057
 
                grid[row, ++col] = "";
1058
 
                grid[row, ++col] = _("Device");
1059
 
                //grid[row, ++col] = _("UUID");
1060
 
                grid[row, ++col] = _("Size");
1061
 
                grid[row, ++col] = _("Type");
1062
 
                grid[row, ++col] = _("Label");
1063
 
                row++;
1064
 
 
1065
 
                foreach(var pi in device_list) {
1066
 
                        col = -1;
1067
 
                        grid[row, ++col] = "%d".printf(row - 1);
1068
 
                        grid[row, ++col] = ">";
1069
 
                        grid[row, ++col] = "%s".printf(pi.full_name_with_alias);
1070
 
                        //grid[row, ++col] = "%s".printf(pi.uuid);
1071
 
                        grid[row, ++col] = "%s".printf((pi.size_bytes > 0) ? "%s GB".printf(pi.size) : "?? GB");
1072
 
                        grid[row, ++col] = "%s".printf(pi.fstype);
1073
 
                        grid[row, ++col] = "%s".printf(pi.label);
1074
 
                        row++;
1075
 
                }
1076
 
 
1077
 
                print_grid(grid, right_align);
1078
 
 
1079
 
                return device_list;
1080
 
        }
1081
 
 
1082
 
        private Gee.ArrayList<Device> list_grub_devices(bool print_to_console = true){
1083
 
                //add devices
1084
 
                var grub_device_list = new Gee.ArrayList<Device>();
1085
 
                foreach(var dev in Device.get_block_devices_using_lsblk()) {
1086
 
                        if (dev.type == "disk"){
1087
 
                                grub_device_list.add(dev);
1088
 
                        }
1089
 
                        else if (dev.type == "part"){ 
1090
 
                                if (dev.has_linux_filesystem()){
1091
 
                                        grub_device_list.add(dev);
1092
 
                                }
1093
 
                        }
1094
 
                        // skip crypt/loop
1095
 
                }
1096
 
 
1097
 
                /*Note: Lists are already sorted. No need to sort again */
1098
 
 
1099
 
                string[,] grid = new string[grub_device_list.size+1,4];
1100
 
                bool[] right_align = { false, false, false, false };
1101
 
 
1102
 
                int row = 0;
1103
 
                int col = -1;
1104
 
                grid[row, ++col] = _("Num");
1105
 
                grid[row, ++col] = "";
1106
 
                grid[row, ++col] = _("Device");
1107
 
                grid[row, ++col] = _("Description");
1108
 
                row++;
1109
 
 
1110
 
                string desc = "";
1111
 
                foreach(Device pi in grub_device_list) {
1112
 
                        col = -1;
1113
 
                        grid[row, ++col] = "%d".printf(row - 1);
1114
 
                        grid[row, ++col] = ">";
1115
 
                        grid[row, ++col] = "%s".printf(pi.short_name_with_alias);
1116
 
 
1117
 
                        if (pi.type == "disk"){
1118
 
                                desc = "%s".printf(((pi.vendor.length > 0)||(pi.model.length > 0)) ? (pi.vendor + " " + pi.model  + " [MBR]") : "");
1119
 
                        }
1120
 
                        else{
1121
 
                                desc = "%5s, ".printf(pi.fstype);
1122
 
                                desc += "%10s".printf((pi.size_bytes > 0) ? "%s GB".printf(pi.size) : "?? GB");
1123
 
                                desc += "%s".printf((pi.label.length > 0) ? ", " + pi.label : "");
1124
 
                        }
1125
 
                        grid[row, ++col] = "%s".printf(desc);
1126
 
                        row++;
1127
 
                }
1128
 
 
1129
 
                print_grid(grid, right_align);
1130
 
 
1131
 
                return grub_device_list;
1132
 
        }
1133
 
 
1134
 
        private void print_grid(string[,] grid, bool[] right_align, bool has_header = true){
1135
 
                int[] col_width = new int[grid.length[1]];
1136
 
 
1137
 
                for(int col=0; col<grid.length[1]; col++){
1138
 
                        for(int row=0; row<grid.length[0]; row++){
1139
 
                                if (grid[row,col].length > col_width[col]){
1140
 
                                        col_width[col] = grid[row,col].length;
1141
 
                                }
1142
 
                        }
1143
 
                }
1144
 
 
1145
 
                for(int row=0; row<grid.length[0]; row++){
1146
 
                        for(int col=0; col<grid.length[1]; col++){
1147
 
                                string fmt = "%" + (right_align[col] ? "+" : "-") + col_width[col].to_string() + "s  ";
1148
 
                                stdout.printf(fmt.printf(grid[row,col]));
1149
 
                        }
1150
 
                        stdout.printf("\n");
1151
 
 
1152
 
                        if (has_header && (row == 0)){
1153
 
                                stdout.printf(string.nfill(78, '-'));
1154
 
                                stdout.printf("\n");
1155
 
                        }
1156
 
                }
1157
 
        }
1158
 
 
1159
 
 
1160
 
        //prompt for input
1161
 
 
1162
 
        public void get_backup_device_from_cmd(bool prompt_if_empty, Gtk.Window? parent_win){
1163
 
 
1164
 
                var list = new Gee.ArrayList<Device>();
1165
 
                foreach(var pi in partitions){
1166
 
                        if (pi.has_linux_filesystem()){
1167
 
                                list.add(pi);
1168
 
                        }
1169
 
                }
1170
 
                                        
1171
 
                if (cmd_backup_device.length > 0){
1172
 
                        //set backup device from command line argument
1173
 
                        var cmd_dev = Device.get_device_by_name(cmd_backup_device);
1174
 
                        if (cmd_dev != null){
1175
 
                                repo = new SnapshotRepo.from_device(cmd_dev, null);
1176
 
                                if (!repo.available()){
1177
 
                                        exit_app();
1178
 
                                        exit(1);
1179
 
                                }
1180
 
                        }
1181
 
                        else{
1182
 
                                log_error(_("Could not find device") + ": '%s'".printf(cmd_backup_device));
1183
 
                                exit_app();
1184
 
                                exit(1);
1185
 
                        }
1186
 
                }
1187
 
                else{
1188
 
                        if ((repo.device == null) || (prompt_if_empty && (repo.snapshots.size == 0))){
1189
 
                                //prompt user for backup device
1190
 
                                log_msg("");
1191
 
 
1192
 
                                log_msg(_("Select backup device") + ":\n");
1193
 
                                list_devices(list);
1194
 
                                log_msg("");
1195
 
 
1196
 
                                Device dev = null;
1197
 
                                int attempts = 0;
1198
 
                                while (dev == null){
1199
 
                                        attempts++;
1200
 
                                        if (attempts > 3) { break; }
1201
 
                                        stdout.printf("" +
1202
 
                                                _("Enter device name or number (a=Abort)") + ": ");
1203
 
                                        stdout.flush();
1204
 
 
1205
 
                                        dev = read_stdin_device(list);
1206
 
                                }
1207
 
 
1208
 
                                log_msg("");
1209
 
                                
1210
 
                                if (dev == null){
1211
 
                                        log_error(_("Failed to get input from user in 3 attempts"));
1212
 
                                        log_msg(_("Aborted."));
1213
 
                                        exit_app();
1214
 
                                        exit(0);
1215
 
                                }
1216
 
 
1217
 
                                repo = new SnapshotRepo.from_device(dev, null);
1218
 
                                if (!repo.available()){
1219
 
                                        exit_app();
1220
 
                                        exit(1);
1221
 
                                }
1222
 
                        }
1223
 
                }
1224
 
        }
1225
 
 
1226
 
        private Device? read_stdin_device(Gee.ArrayList<Device> device_list){
1227
 
                var counter = new TimeoutCounter();
1228
 
                counter.exit_on_timeout();
1229
 
                string? line = stdin.read_line();
1230
 
                counter.stop();
1231
 
 
1232
 
                line = (line != null) ? line.strip() : "";
1233
 
 
1234
 
                Device selected_device = null;
1235
 
 
1236
 
                if (line.down() == "a"){
1237
 
                        log_msg(_("Aborted."));
1238
 
                        exit_app();
1239
 
                        exit(0);
1240
 
                }
1241
 
                else if ((line == null)||(line.length == 0)){
1242
 
                        log_error("Invalid input");
1243
 
                }
1244
 
                else if (line.contains("/")){
1245
 
                        selected_device = Device.get_device_by_name(line);
1246
 
                        if (selected_device == null){
1247
 
                                log_error("Invalid input");
1248
 
                        }
1249
 
                }
1250
 
                else{
1251
 
                        selected_device = get_device_from_index(device_list, line);
1252
 
                        if (selected_device == null){
1253
 
                                log_error("Invalid input");
1254
 
                        }
1255
 
                }
1256
 
 
1257
 
                return selected_device;
1258
 
        }
1259
 
 
1260
 
        private Device? read_stdin_device_mounts(Gee.ArrayList<Device> device_list, MountEntry mnt){
1261
 
                var counter = new TimeoutCounter();
1262
 
                counter.exit_on_timeout();
1263
 
                string? line = stdin.read_line();
1264
 
                counter.stop();
1265
 
 
1266
 
                line = (line != null) ? line.strip() : "";
1267
 
 
1268
 
                Device selected_device = null;
1269
 
 
1270
 
                if ((line == null)||(line.length == 0)||(line.down() == "c")||(line.down() == "d")){
1271
 
                        //set default
1272
 
                        if (mirror_system){
1273
 
                                return restore_target; //root device
1274
 
                        }
1275
 
                        else{
1276
 
                                return mnt.device; //keep current
1277
 
                        }
1278
 
                }
1279
 
                else if (line.down() == "a"){
1280
 
                        log_msg("Aborted.");
1281
 
                        exit_app();
1282
 
                        exit(0);
1283
 
                }
1284
 
                else if ((line.down() == "n")||(line.down() == "r")){
1285
 
                        return restore_target; //root device
1286
 
                }
1287
 
                else if (line.contains("/")){
1288
 
                        selected_device = Device.get_device_by_name(line);
1289
 
                        if (selected_device == null){
1290
 
                                log_error("Invalid input");
1291
 
                        }
1292
 
                }
1293
 
                else{
1294
 
                        selected_device = get_device_from_index(device_list, line);
1295
 
                        if (selected_device == null){
1296
 
                                log_error("Invalid input");
1297
 
                        }
1298
 
                }
1299
 
 
1300
 
                return selected_device;
1301
 
        }
1302
 
 
1303
 
        private Device? get_device_from_index(Gee.ArrayList<Device> device_list, string index_string){
1304
 
                int64 index;
1305
 
                if (int64.try_parse(index_string, out index)){
1306
 
                        int i = -1;
1307
 
                        foreach(Device pi in device_list) {
1308
 
                                if (++i == index){
1309
 
                                        return pi;
1310
 
                                }
1311
 
                        }
1312
 
                }
1313
 
 
1314
 
                return null;
1315
 
        }
1316
 
 
1317
 
        private Snapshot read_stdin_snapshot(){
1318
 
                var counter = new TimeoutCounter();
1319
 
                counter.exit_on_timeout();
1320
 
                string? line = stdin.read_line();
1321
 
                counter.stop();
1322
 
 
1323
 
                line = (line != null) ? line.strip() : "";
1324
 
 
1325
 
                Snapshot selected_snapshot = null;
1326
 
 
1327
 
                if (line.down() == "a"){
1328
 
                        log_msg("Aborted.");
1329
 
                        exit_app();
1330
 
                        exit(0);
1331
 
                }
1332
 
                else if (line.down() == "p"){
1333
 
                        snapshot_list_start_index -= 10;
1334
 
                        if (snapshot_list_start_index < 0){
1335
 
                                snapshot_list_start_index = 0;
1336
 
                        }
1337
 
                        log_msg("");
1338
 
                        list_snapshots(true);
1339
 
                        log_msg("");
1340
 
                }
1341
 
                else if (line.down() == "n"){
1342
 
                        if ((snapshot_list_start_index + 10) < repo.snapshots.size){
1343
 
                                snapshot_list_start_index += 10;
1344
 
                        }
1345
 
                        log_msg("");
1346
 
                        list_snapshots(true);
1347
 
                        log_msg("");
1348
 
                }
1349
 
                else if (line.contains("_")||line.contains("-")){
1350
 
                        //TODO: read name
1351
 
                        log_error("Invalid input");
1352
 
                }
1353
 
                else if ((line == null)||(line.length == 0)){
1354
 
                        log_error("Invalid input");
1355
 
                }
1356
 
                else{
1357
 
                        int64 index;
1358
 
                        if (int64.try_parse(line, out index)){
1359
 
                                if (index < repo.snapshots.size){
1360
 
                                        selected_snapshot = repo.snapshots[(int) index];
1361
 
                                }
1362
 
                                else{
1363
 
                                        log_error("Invalid input");
1364
 
                                }
1365
 
                        }
1366
 
                        else{
1367
 
                                log_error("Invalid input");
1368
 
                        }
1369
 
                }
1370
 
 
1371
 
                return selected_snapshot;
1372
 
        }
1373
 
 
1374
 
        private bool read_stdin_grub_install(){
1375
 
                var counter = new TimeoutCounter();
1376
 
                counter.exit_on_timeout();
1377
 
                string? line = stdin.read_line();
1378
 
                counter.stop();
1379
 
 
1380
 
                line = (line != null) ? line.strip() : line;
1381
 
 
1382
 
                if ((line == null)||(line.length == 0)){
1383
 
                        log_error("Invalid input");
1384
 
                        return false;
1385
 
                }
1386
 
                else if (line.down() == "a"){
1387
 
                        log_msg("Aborted.");
1388
 
                        exit_app();
1389
 
                        exit(0);
1390
 
                        return true;
1391
 
                }
1392
 
                else if (line.down() == "y"){
1393
 
                        cmd_skip_grub = false;
1394
 
                        reinstall_grub2 = true;
1395
 
                        return true;
1396
 
                }
1397
 
                else if (line.down() == "n"){
1398
 
                        cmd_skip_grub = true;
1399
 
                        reinstall_grub2 = false;
1400
 
                        return true;
1401
 
                }
1402
 
                else if ((line == null)||(line.length == 0)){
1403
 
                        log_error("Invalid input");
1404
 
                        return false;
1405
 
                }
1406
 
                else{
1407
 
                        log_error("Invalid input");
1408
 
                        return false;
1409
 
                }
1410
 
        }
1411
 
 
1412
 
        private bool read_stdin_restore_confirm(){
1413
 
                var counter = new TimeoutCounter();
1414
 
                counter.exit_on_timeout();
1415
 
                
1416
 
                string? line = stdin.read_line();
1417
 
                counter.stop();
1418
 
 
1419
 
                line = (line != null) ? line.strip() : "";
1420
 
 
1421
 
                if ((line.down() == "a")||(line.down() == "n")){
1422
 
                        log_msg("Aborted.");
1423
 
                        exit_app();
1424
 
                        exit(0);
1425
 
                        return true;
1426
 
                }
1427
 
                else if ((line == null)||(line.length == 0)){
1428
 
                        log_error("Invalid input");
1429
 
                        return false;
1430
 
                }
1431
 
                else if (line.down() == "y"){
1432
 
                        cmd_confirm = true;
1433
 
                        return true;
1434
 
                }
1435
 
                else if ((line == null)||(line.length == 0)){
1436
 
                        log_error("Invalid input");
1437
 
                        return false;
1438
 
                }
1439
 
                else{
1440
 
                        log_error("Invalid input");
1441
 
                        return false;
1442
 
                }
1443
 
        }
1444
 
 
1445
 
        //properties
1446
 
        
1447
 
        public bool scheduled{
1448
 
                get{
1449
 
                        return !live_system()
1450
 
                        && (schedule_boot || schedule_hourly || schedule_daily ||
1451
 
                                schedule_weekly || schedule_monthly);
1452
 
                }
1453
 
        }
1454
 
 
1455
 
        public bool live_system(){
1456
 
                //return true;
1457
 
                return (sys_root == null);
1458
 
        }
1459
 
 
1460
 
        // backup
1461
 
 
1462
 
        public bool take_snapshot (
1463
 
                bool is_ondemand, string snapshot_comments, Gtk.Window? parent_win){
1464
 
 
1465
 
                bool status;
1466
 
                bool update_symlinks = false;
1467
 
 
1468
 
                string sys_uuid = (sys_root == null) ? "" : sys_root.uuid;
1469
 
                
1470
 
                try
1471
 
                {
1472
 
                        log_debug("checking btrfs volumes on root device...");
1473
 
                        
1474
 
                        if (App.check_btrfs_layout_system() == false){
1475
 
                                return false;
1476
 
                        }
1477
 
                
1478
 
                        // create a timestamp
1479
 
                        DateTime now = new DateTime.now_local();
1480
 
 
1481
 
                        log_debug("checking if snapshot device is mounted...");
1482
 
                        
1483
 
                        log_debug("checking snapshot device...");
1484
 
                        
1485
 
                        // check space
1486
 
                        if (!repo.has_space()){
1487
 
 
1488
 
                                log_error(repo.status_message);
1489
 
                                log_error(repo.status_details + "\n");
1490
 
                                
1491
 
                                // remove invalid snapshots
1492
 
                                if (app_mode.length != 0){
1493
 
                                        repo.auto_remove();
1494
 
                                }
1495
 
 
1496
 
                                // check again ------------
1497
 
 
1498
 
                                if (!repo.has_space()){
1499
 
                                        log_error(repo.status_message);
1500
 
                                        log_error(repo.status_details + "\n");
1501
 
                                        return false;
1502
 
                                }
1503
 
                        }
1504
 
 
1505
 
                        string snapshot_dir = path_combine(repo.snapshot_location, "timeshift/snapshots");
1506
 
                        
1507
 
                        // create snapshot root if missing
1508
 
                        var f = File.new_for_path(snapshot_dir);
1509
 
                        if (!f.query_exists()){
1510
 
                                log_debug("mkdir: %s".printf(snapshot_dir));
1511
 
                                f.make_directory_with_parents();
1512
 
                        }
1513
 
 
1514
 
                        // ondemand
1515
 
                        if (is_ondemand){
1516
 
                                bool ok = backup_and_rotate ("ondemand",now);
1517
 
                                if(!ok){
1518
 
                                        return false;
1519
 
                                }
1520
 
                                else{
1521
 
                                        update_symlinks = true;
1522
 
                                }
1523
 
                        }
1524
 
                        else if (scheduled){
1525
 
                                Snapshot last_snapshot_boot = repo.get_latest_snapshot("boot", sys_uuid);
1526
 
                                Snapshot last_snapshot_hourly = repo.get_latest_snapshot("hourly", sys_uuid);
1527
 
                                Snapshot last_snapshot_daily = repo.get_latest_snapshot("daily", sys_uuid);
1528
 
                                Snapshot last_snapshot_weekly = repo.get_latest_snapshot("weekly", sys_uuid);
1529
 
                                Snapshot last_snapshot_monthly = repo.get_latest_snapshot("monthly", sys_uuid);
1530
 
 
1531
 
                                DateTime dt_sys_boot = now.add_seconds((-1) * get_system_uptime_seconds());
1532
 
                                bool take_new = false;
1533
 
 
1534
 
                                if (schedule_boot){
1535
 
 
1536
 
                                        log_msg(_("Boot snapshots are enabled"));
1537
 
 
1538
 
                                        if (last_snapshot_boot == null){
1539
 
                                                log_msg(_("Last boot snapshot not found"));
1540
 
                                                take_new = true;
1541
 
                                        }
1542
 
                                        else if (last_snapshot_boot.date.compare(dt_sys_boot) < 0){
1543
 
                                                log_msg(_("Last boot snapshot is older than system start time"));
1544
 
                                                take_new = true;
1545
 
                                        }
1546
 
                                        else{
1547
 
                                                int hours = (int) ((float) now.difference(last_snapshot_boot.date) / TimeSpan.HOUR);
1548
 
                                                log_msg(_("Last boot snapshot is %d hours old").printf(hours));
1549
 
                                                take_new = false;
1550
 
                                        }
1551
 
 
1552
 
                                        if (take_new){
1553
 
                                                status = backup_and_rotate ("boot",now);
1554
 
                                                if(!status){
1555
 
                                                        log_error(_("Boot snapshot failed!"));
1556
 
                                                        return false;
1557
 
                                                }
1558
 
                                                else{
1559
 
                                                        update_symlinks = true;
1560
 
                                                }
1561
 
                                        }
1562
 
                                }
1563
 
 
1564
 
                                if (schedule_hourly){
1565
 
 
1566
 
                                        log_msg(_("Hourly snapshots are enabled"));
1567
 
 
1568
 
                                        if (last_snapshot_hourly == null){
1569
 
                                                log_msg(_("Last hourly snapshot not found"));
1570
 
                                                take_new = true;
1571
 
                                        }
1572
 
                                        else if (last_snapshot_hourly.date.compare(now.add_hours(-1).add_minutes(1)) < 0){
1573
 
                                                log_msg(_("Last hourly snapshot is more than 1 hour old"));
1574
 
                                                take_new = true;
1575
 
                                        }
1576
 
                                        else{
1577
 
                                                int mins = (int) ((float) now.difference(last_snapshot_hourly.date) / TimeSpan.MINUTE);
1578
 
                                                log_msg(_("Last hourly snapshot is %d minutes old").printf(mins));
1579
 
                                                take_new = false;
1580
 
                                        }
1581
 
 
1582
 
                                        if (take_new){
1583
 
                                                status = backup_and_rotate ("hourly",now);
1584
 
                                                if(!status){
1585
 
                                                        log_error(_("Hourly snapshot failed!"));
1586
 
                                                        return false;
1587
 
                                                }
1588
 
                                                else{
1589
 
                                                        update_symlinks = true;
1590
 
                                                }
1591
 
                                        }
1592
 
                                }
1593
 
 
1594
 
                                if (schedule_daily){
1595
 
 
1596
 
                                        log_msg(_("Daily snapshots are enabled"));
1597
 
 
1598
 
                                        if (last_snapshot_daily == null){
1599
 
                                                log_msg(_("Last daily snapshot not found"));
1600
 
                                                take_new = true;
1601
 
                                        }
1602
 
                                        else if (last_snapshot_daily.date.compare(now.add_days(-1).add_minutes(1)) < 0){
1603
 
                                                log_msg(_("Last daily snapshot is more than 1 day old"));
1604
 
                                                take_new = true;
1605
 
                                        }
1606
 
                                        else{
1607
 
                                                int hours = (int) ((float) now.difference(last_snapshot_daily.date) / TimeSpan.HOUR);
1608
 
                                                log_msg(_("Last daily snapshot is %d hours old").printf(hours));
1609
 
                                                take_new = false;
1610
 
                                        }
1611
 
 
1612
 
                                        if (take_new){
1613
 
                                                status = backup_and_rotate ("daily",now);
1614
 
                                                if(!status){
1615
 
                                                        log_error(_("Daily snapshot failed!"));
1616
 
                                                        return false;
1617
 
                                                }
1618
 
                                                else{
1619
 
                                                        update_symlinks = true;
1620
 
                                                }
1621
 
                                        }
1622
 
                                }
1623
 
 
1624
 
                                if (schedule_weekly){
1625
 
 
1626
 
                                        log_msg(_("Weekly snapshots are enabled"));
1627
 
 
1628
 
                                        if (last_snapshot_weekly == null){
1629
 
                                                log_msg(_("Last weekly snapshot not found"));
1630
 
                                                take_new = true;
1631
 
                                        }
1632
 
                                        else if (last_snapshot_weekly.date.compare(now.add_weeks(-1).add_minutes(1)) < 0){
1633
 
                                                log_msg(_("Last weekly snapshot is more than 1 week old"));
1634
 
                                                take_new = true;
1635
 
                                        }
1636
 
                                        else{
1637
 
                                                int days = (int) ((float) now.difference(last_snapshot_weekly.date) / TimeSpan.DAY);
1638
 
                                                log_msg(_("Last weekly snapshot is %d days old").printf(days));
1639
 
                                                take_new = false;
1640
 
                                        }
1641
 
 
1642
 
                                        if (take_new){
1643
 
                                                status = backup_and_rotate ("weekly",now);
1644
 
                                                if(!status){
1645
 
                                                        log_error(_("Weekly snapshot failed!"));
1646
 
                                                        return false;
1647
 
                                                }
1648
 
                                                else{
1649
 
                                                        update_symlinks = true;
1650
 
                                                }
1651
 
                                        }
1652
 
                                }
1653
 
 
1654
 
                                if (schedule_monthly){
1655
 
 
1656
 
                                        log_msg(_("Monthly snapshot are enabled"));
1657
 
 
1658
 
                                        if (last_snapshot_monthly == null){
1659
 
                                                log_msg(_("Last monthly snapshot not found"));
1660
 
                                                take_new = true;
1661
 
                                        }
1662
 
                                        else if (last_snapshot_monthly.date.compare(now.add_months(-1).add_minutes(1)) < 0){
1663
 
                                                log_msg(_("Last monthly snapshot is more than 1 month old"));
1664
 
                                                take_new = true;
1665
 
                                        }
1666
 
                                        else{
1667
 
                                                int days = (int) ((float) now.difference(last_snapshot_monthly.date) / TimeSpan.DAY);
1668
 
                                                log_msg(_("Last monthly snapshot is %d days old").printf(days));
1669
 
                                                take_new = false;
1670
 
                                        }
1671
 
 
1672
 
                                        if (take_new){
1673
 
                                                status = backup_and_rotate ("monthly",now);
1674
 
                                                if(!status){
1675
 
                                                        log_error(_("Monthly snapshot failed!"));
1676
 
                                                        return false;
1677
 
                                                }
1678
 
                                                else{
1679
 
                                                        update_symlinks = true;
1680
 
                                                }
1681
 
                                        }
1682
 
                                }
1683
 
                        }
1684
 
                        else{
1685
 
                                log_msg(_("Scheduled snapshots are disabled") + " - " + _("Nothing to do!"));
1686
 
                                cron_job_update();
1687
 
                        }
1688
 
 
1689
 
                        if (app_mode.length != 0){
1690
 
                                repo.auto_remove();
1691
 
                        }
1692
 
 
1693
 
                        if (update_symlinks){
1694
 
                                repo.load_snapshots();
1695
 
                                repo.create_symlinks();
1696
 
                        }
1697
 
                        
1698
 
                        log_msg("ok");
1699
 
                }
1700
 
                catch(Error e){
1701
 
                        log_error (e.message);
1702
 
                        return false;
1703
 
                }
1704
 
 
1705
 
                return true;
1706
 
        }
1707
 
 
1708
 
        public bool backup_and_rotate(string tag, DateTime dt_created){
1709
 
                //string msg;
1710
 
                File f;
1711
 
 
1712
 
                bool backup_taken = false;
1713
 
 
1714
 
                // save start time
1715
 
                var dt_begin = new DateTime.now_local();
1716
 
 
1717
 
                string sys_uuid = (sys_root == null) ? "" : sys_root.uuid;
1718
 
                
1719
 
                try{
1720
 
                        // get system boot time
1721
 
                        DateTime now = new DateTime.now_local();
1722
 
                        DateTime dt_sys_boot = now.add_seconds((-1) * get_system_uptime_seconds());
1723
 
 
1724
 
                        // check if we can rotate an existing backup -------------
1725
 
 
1726
 
                        DateTime dt_filter = null;
1727
 
 
1728
 
                        if (tag != "ondemand"){
1729
 
                                switch(tag){
1730
 
                                        case "boot":
1731
 
                                                dt_filter = dt_sys_boot;
1732
 
                                                break;
1733
 
                                        case "hourly":
1734
 
                                        case "daily":
1735
 
                                        case "weekly":
1736
 
                                        case "monthly":
1737
 
                                                dt_filter = now.add_hours(-1);
1738
 
                                                break;
1739
 
                                        default:
1740
 
                                                log_error(_("Unknown snapshot type") + ": %s".printf(tag));
1741
 
                                                return false;
1742
 
                                }
1743
 
 
1744
 
                                // find a recent backup that can be used
1745
 
                                Snapshot backup_to_rotate = null;
1746
 
                                foreach(var bak in repo.snapshots){
1747
 
                                        if (bak.date.compare(dt_filter) > 0){
1748
 
                                                backup_to_rotate = bak;
1749
 
                                                break;
1750
 
                                        }
1751
 
                                }
1752
 
 
1753
 
                                if (backup_to_rotate != null){
1754
 
                                        
1755
 
                                        // tag the backup
1756
 
                                        backup_to_rotate.add_tag(tag);
1757
 
                                        
1758
 
                                        backup_taken = true;
1759
 
                                        var message = "%s '%s' %s '%s'".printf(
1760
 
                                                _("Snapshot"), backup_to_rotate.name, _("tagged"), tag);
1761
 
                                        log_msg(message);
1762
 
                                }
1763
 
                        }
1764
 
 
1765
 
                        if (!backup_taken){
1766
 
 
1767
 
                                log_msg("Creating new backup...");
1768
 
                                
1769
 
                                // take new backup ---------------------------------
1770
 
 
1771
 
                                if (repo.snapshot_location.length == 0){
1772
 
                                        log_error("Backup location not mounted");
1773
 
                                        exit_app();
1774
 
                                }
1775
 
 
1776
 
                                string time_stamp = dt_created.format("%Y-%m-%d_%H-%M-%S");
1777
 
                                string snapshot_dir = path_combine(repo.snapshot_location, "timeshift/snapshots");
1778
 
                                string snapshot_name = time_stamp;
1779
 
                                string snapshot_path = path_combine(snapshot_dir, snapshot_name);
1780
 
 
1781
 
                                Snapshot snapshot_to_link = null;
1782
 
 
1783
 
                                dir_create(path_combine(snapshot_path, "/localhost"));
1784
 
 
1785
 
                                // check if a snapshot was restored recently and use it for linking ---------
1786
 
                                
1787
 
                                string ctl_path = path_combine(snapshot_dir, ".sync-restore");
1788
 
                                f = File.new_for_path(ctl_path);
1789
 
                                
1790
 
                                if (f.query_exists()){
1791
 
 
1792
 
                                        // read snapshot name from file
1793
 
                                        string snap_path = file_read(ctl_path);
1794
 
                                        string snap_name = file_basename(snap_path);
1795
 
                                        
1796
 
                                        // find the snapshot that was restored
1797
 
                                        foreach(var bak in repo.snapshots){
1798
 
                                                if ((bak.name == snap_name) && (bak.sys_uuid == sys_uuid)){
1799
 
                                                        // use for linking
1800
 
                                                        snapshot_to_link = bak;
1801
 
                                                        // delete the restore-control-file
1802
 
                                                        f.delete();
1803
 
                                                        break;
1804
 
                                                }
1805
 
                                        }
1806
 
                                }
1807
 
 
1808
 
                                // get latest snapshot to link if not set -------
1809
 
 
1810
 
                                if (snapshot_to_link == null){
1811
 
                                        snapshot_to_link = repo.get_latest_snapshot("", sys_uuid);
1812
 
                                }
1813
 
 
1814
 
                                string link_from_path = "";
1815
 
                                if (snapshot_to_link != null){
1816
 
                                        log_msg("%s: %s".printf(_("Linking from snapshot"), snapshot_to_link.name));
1817
 
                                        link_from_path = "%s/localhost/".printf(snapshot_to_link.path);
1818
 
                                }
1819
 
 
1820
 
                                // save exclude list ----------------
1821
 
 
1822
 
                                bool ok = save_exclude_list_for_backup(snapshot_path);
1823
 
                                
1824
 
                                string exclude_from_file = path_combine(snapshot_path, "exclude.list");
1825
 
 
1826
 
                                if (!ok){
1827
 
                                        log_error(_("Failed to save exclude list"));
1828
 
                                        return false;
1829
 
                                }
1830
 
                                
1831
 
                                // rsync file system -------------------
1832
 
                                
1833
 
                                progress_text = _("Synching files with rsync...");
1834
 
                                log_msg(progress_text);
1835
 
 
1836
 
                                var log_file = snapshot_path + "/rsync-log";
1837
 
                                file_delete(log_file);
1838
 
 
1839
 
                                task = new RsyncTask();
1840
 
 
1841
 
                                task.source_path = "";
1842
 
                                task.dest_path = snapshot_path + "/localhost/";
1843
 
                                task.link_from_path = link_from_path;
1844
 
                                task.exclude_from_file = exclude_from_file;
1845
 
                                task.rsync_log_file = log_file;
1846
 
                                task.prg_count_total = Main.first_snapshot_count;
1847
 
 
1848
 
                                task.relative = true;
1849
 
                                task.verbose = true;
1850
 
                                task.delete_extra = true;
1851
 
                                task.delete_excluded = true;
1852
 
                                task.delete_after = false;
1853
 
                                        
1854
 
                                if (app_mode.length > 0){
1855
 
                                        // console mode
1856
 
                                        task.io_nice = true;
1857
 
                                }
1858
 
 
1859
 
                                task.execute();
1860
 
 
1861
 
                                while (task.status == AppStatus.RUNNING){
1862
 
                                        sleep(1000);
1863
 
                                        gtk_do_events();
1864
 
                                }
1865
 
 
1866
 
                                if (task.total_size == 0){
1867
 
                                        log_error(_("rsync returned an error"));
1868
 
                                        log_error(_("Failed to create new snapshot"));
1869
 
                                        return false;
1870
 
                                }
1871
 
 
1872
 
                                // write control file
1873
 
                                write_snapshot_control_file(snapshot_path, dt_created, tag);
1874
 
 
1875
 
                                // parse log file
1876
 
                                progress_text = _("Parsing log file...");
1877
 
                                log_msg(progress_text);
1878
 
                                var task = new RsyncTask();
1879
 
                                task.parse_log(log_file);
1880
 
 
1881
 
                                // finish ------------------------------
1882
 
                                
1883
 
                                var dt_end = new DateTime.now_local();
1884
 
                                TimeSpan elapsed = dt_end.difference(dt_begin);
1885
 
                                long seconds = (long)(elapsed * 1.0 / TimeSpan.SECOND);
1886
 
                                
1887
 
                                var message = "%s (%lds)".printf(_("Snapshot saved successfully"), seconds);
1888
 
                                log_msg(message);
1889
 
                                
1890
 
                                OSDNotify.notify_send("TimeShift", message, 10000, "low");
1891
 
 
1892
 
                                message = "%s '%s' %s '%s'".printf(
1893
 
                                                _("Snapshot"), snapshot_name, _("tagged"), tag);
1894
 
                                log_msg(message);
1895
 
        
1896
 
                                repo.load_snapshots();
1897
 
                        }
1898
 
                }
1899
 
                catch(Error e){
1900
 
                        log_error (e.message);
1901
 
                        return false;
1902
 
                }
1903
 
 
1904
 
                return true;
1905
 
        }
1906
 
        
1907
 
        public Snapshot write_snapshot_control_file(
1908
 
                string snapshot_path, DateTime dt_created, string tag){
1909
 
                        
1910
 
                var ctl_path = snapshot_path + "/info.json";
1911
 
                var config = new Json.Object();
1912
 
 
1913
 
                config.set_string_member("created", dt_created.to_utc().to_unix().to_string());
1914
 
                config.set_string_member("sys-uuid", sys_root.uuid);
1915
 
                config.set_string_member("sys-distro", current_distro.full_name());
1916
 
                config.set_string_member("app-version", AppVersion);
1917
 
                config.set_string_member("tags", tag);
1918
 
                config.set_string_member("comments", "");
1919
 
 
1920
 
                var json = new Json.Generator();
1921
 
                json.pretty = true;
1922
 
                json.indent = 2;
1923
 
                var node = new Json.Node(NodeType.OBJECT);
1924
 
                node.set_object(config);
1925
 
                json.set_root(node);
1926
 
 
1927
 
                try{
1928
 
                        var f = File.new_for_path(ctl_path);
1929
 
                        if (f.query_exists()){
1930
 
                                f.delete();
1931
 
                        }
1932
 
 
1933
 
                        json.to_file(ctl_path);
1934
 
                } catch (Error e) {
1935
 
                log_error (e.message);
1936
 
            }
1937
 
 
1938
 
            return (new Snapshot(snapshot_path));
1939
 
        }
1940
 
 
1941
 
        // delete from terminal
1942
 
 
1943
 
        public void delete_snapshot(Snapshot? snapshot = null){
1944
 
 
1945
 
                bool found = false;
1946
 
                
1947
 
                // set snapshot -----------------------------------------------
1948
 
 
1949
 
                if (app_mode != ""){ //command-line mode
1950
 
 
1951
 
                        if (cmd_snapshot.length > 0){
1952
 
 
1953
 
                                //check command line arguments
1954
 
                                found = false;
1955
 
                                foreach(var bak in repo.snapshots) {
1956
 
                                        if (bak.name == cmd_snapshot){
1957
 
                                                snapshot_to_delete = bak;
1958
 
                                                found = true;
1959
 
                                                break;
1960
 
                                        }
1961
 
                                }
1962
 
 
1963
 
                                //check if found
1964
 
                                if (!found){
1965
 
                                        log_error(_("Could not find snapshot") + ": '%s'".printf(cmd_snapshot));
1966
 
                                        return;
1967
 
                                }
1968
 
                        }
1969
 
 
1970
 
                        //prompt user for snapshot
1971
 
                        if (snapshot_to_delete == null){
1972
 
 
1973
 
                                if (repo.snapshots.size == 0){
1974
 
                                        log_msg(_("No snapshots found on device") +
1975
 
                                                " '%s'".printf(repo.device.device));
1976
 
                                        return;
1977
 
                                }
1978
 
 
1979
 
                                log_msg("");
1980
 
                                log_msg(_("Select snapshot to delete") + ":\n");
1981
 
                                list_snapshots(true);
1982
 
                                log_msg("");
1983
 
 
1984
 
                                int attempts = 0;
1985
 
                                while (snapshot_to_delete == null){
1986
 
                                        attempts++;
1987
 
                                        if (attempts > 3) { break; }
1988
 
                                        stdout.printf(_("Enter snapshot number (a=Abort, p=Previous, n=Next)") + ": ");
1989
 
                                        stdout.flush();
1990
 
                                        snapshot_to_delete = read_stdin_snapshot();
1991
 
                                }
1992
 
                                log_msg("");
1993
 
 
1994
 
                                if (snapshot_to_delete == null){
1995
 
                                        log_error(_("Failed to get input from user in 3 attempts"));
1996
 
                                        log_msg(_("Aborted."));
1997
 
                                        exit_app();
1998
 
                                        exit(0);
1999
 
                                }
2000
 
                        }
2001
 
                }
2002
 
 
2003
 
                if (snapshot_to_delete == null){
2004
 
                        //print error
2005
 
                        log_error(_("Snapshot to delete not specified!"));
2006
 
                        return;
2007
 
                }
2008
 
 
2009
 
                snapshot_to_delete.remove(true);
2010
 
        }
2011
 
 
2012
 
        public bool delete_all_snapshots(){
2013
 
                return repo.remove_all();
2014
 
        }
2015
 
 
2016
 
        // gui delete
2017
 
 
2018
 
        public void delete_begin(){
2019
 
 
2020
 
                log_debug("delete_begin()");
2021
 
                
2022
 
                try {
2023
 
                        thread_delete_running = true;
2024
 
                        thread_delete_success = false;
2025
 
                        Thread.create<void> (delete_thread, true);
2026
 
 
2027
 
                        //new Thread<bool> ("", delete_thread);
2028
 
 
2029
 
                        log_debug("delete_begin(): thread created");
2030
 
                }
2031
 
                catch (Error e) {
2032
 
                        thread_delete_running = false;
2033
 
                        thread_delete_success = false;
2034
 
                        log_error (e.message);
2035
 
                }
2036
 
        }
2037
 
 
2038
 
        public void delete_thread(){
2039
 
 
2040
 
                log_debug("delete_thread()");
2041
 
 
2042
 
                foreach(var bak in delete_list){
2043
 
                        bak.mark_for_deletion();
2044
 
                }
2045
 
                
2046
 
                while (delete_list.size > 0){
2047
 
 
2048
 
                        var bak = delete_list[0];
2049
 
                        bak.mark_for_deletion(); // mark for deletion again since initial list may have changed
2050
 
                        
2051
 
                        App.delete_file_task = bak.delete_file_task;
2052
 
                        App.delete_file_task.prg_count_total = Main.first_snapshot_count;
2053
 
                        
2054
 
                        bak.remove(true); // wait till complete
2055
 
 
2056
 
                        if (App.delete_file_task.status != AppStatus.CANCELLED){
2057
 
                                
2058
 
                                var message = "%s '%s' (%s)".printf(
2059
 
                                        _("Removed"), bak.name, App.delete_file_task.stat_time_elapsed);
2060
 
                                        
2061
 
                                log_msg(message);
2062
 
                                
2063
 
                                OSDNotify.notify_send("TimeShift", message, 10000, "low");
2064
 
 
2065
 
                                delete_list.remove(bak);
2066
 
                        }
2067
 
                }
2068
 
 
2069
 
                thread_delete_running = false;
2070
 
                thread_delete_success = false;
2071
 
 
2072
 
                //return thread_delete_success;
2073
 
        }
2074
 
        
2075
 
        // restore
2076
 
 
2077
 
        public void init_mount_list(){
2078
 
 
2079
 
                log_debug("Main: init_mount_list()");
2080
 
                
2081
 
                mount_list.clear();
2082
 
 
2083
 
                Gee.ArrayList<FsTabEntry> fstab_list = null;
2084
 
                Gee.ArrayList<CryptTabEntry> crypttab_list = null;
2085
 
                
2086
 
                if (mirror_system){
2087
 
                        string fstab_path = "/etc/fstab";
2088
 
                        fstab_list = FsTabEntry.read_file(fstab_path);
2089
 
                        string cryttab_path = "/etc/crypttab";
2090
 
                        crypttab_list = CryptTabEntry.read_file(cryttab_path);
2091
 
                }
2092
 
                else{
2093
 
                        fstab_list = snapshot_to_restore.fstab_list;
2094
 
                        crypttab_list = snapshot_to_restore.cryttab_list;
2095
 
                }
2096
 
 
2097
 
                bool root_found = false;
2098
 
                bool boot_found = false;
2099
 
                bool home_found = false;
2100
 
                restore_target = null;
2101
 
                
2102
 
                foreach(var mnt in fstab_list){
2103
 
 
2104
 
                        // skip mounting for non-system devices
2105
 
                        
2106
 
                        if (!mnt.is_for_system_directory()){
2107
 
                                continue;
2108
 
                        }
2109
 
 
2110
 
                        // find device by name or uuid
2111
 
                        
2112
 
                        Device mnt_dev = null;
2113
 
                        if (mnt.device_uuid.length > 0){
2114
 
                                mnt_dev = Device.get_device_by_uuid(mnt.device_uuid);
2115
 
                        }
2116
 
                        else{
2117
 
                                mnt_dev = Device.get_device_by_name(mnt.device);
2118
 
                        }
2119
 
 
2120
 
                        // replace mapped name with parent device
2121
 
 
2122
 
                        if (mnt_dev == null){
2123
 
                                
2124
 
                                /*
2125
 
                                Note: This is required since the mapped name may be different on running system.
2126
 
                                Since we don't have the same mapped name, we cannot resolve the device without
2127
 
                                identifying the parent partition
2128
 
                                */
2129
 
 
2130
 
                                if (mnt.device.has_prefix("/dev/mapper/")){
2131
 
                                        string mapped_name = mnt.device.replace("/dev/mapper/","");
2132
 
                                        foreach(var entry in crypttab_list){
2133
 
                                                if (entry.mapped_name == mapped_name){
2134
 
                                                        mnt.device = entry.device;
2135
 
                                                        break;
2136
 
                                                }
2137
 
                                        }
2138
 
                                }
2139
 
 
2140
 
                                // try again - find device by name or uuid
2141
 
                        
2142
 
                                if (mnt.device_uuid.length > 0){
2143
 
                                        mnt_dev = Device.get_device_by_uuid(mnt.device_uuid);
2144
 
                                }
2145
 
                                else{
2146
 
                                        mnt_dev = Device.get_device_by_name(mnt.device);
2147
 
                                }
2148
 
                        }
2149
 
 
2150
 
                        if (mnt_dev != null){
2151
 
                                
2152
 
                                log_debug("added: dev: %s, path: %s, options: %s".printf(
2153
 
                                        mnt_dev.device, mnt.mount_point, mnt.options));
2154
 
                                        
2155
 
                                mount_list.add(new MountEntry(mnt_dev, mnt.mount_point, mnt.options));
2156
 
                                
2157
 
                                if (mnt.mount_point == "/"){
2158
 
                                        restore_target = mnt_dev;
2159
 
                                }
2160
 
                        }
2161
 
                        else{
2162
 
                                log_debug("missing: dev: %s, path: %s, options: %s".printf(
2163
 
                                        mnt.device, mnt.mount_point, mnt.options));
2164
 
 
2165
 
                                mount_list.add(new MountEntry(null, mnt.mount_point, mnt.options));
2166
 
                        }
2167
 
 
2168
 
                        if (mnt.mount_point == "/"){
2169
 
                                root_found = true;
2170
 
                        }
2171
 
                        if (mnt.mount_point == "/boot"){
2172
 
                                boot_found = true;
2173
 
                        }
2174
 
                        if (mnt.mount_point == "/home"){
2175
 
                                home_found = true;
2176
 
                        }
2177
 
                }
2178
 
 
2179
 
                if (!root_found){
2180
 
                        mount_list.add(new MountEntry(null, "/", "")); // add root entry
2181
 
                }
2182
 
 
2183
 
                if (!boot_found){
2184
 
                        mount_list.add(new MountEntry(null, "/boot", "")); // add boot entry
2185
 
                }
2186
 
 
2187
 
                if (!home_found){
2188
 
                        mount_list.add(new MountEntry(null, "/home", "")); // add home entry
2189
 
                }
2190
 
 
2191
 
                /*
2192
 
                While cloning the system, /boot is the only mount point that
2193
 
                we will leave unchanged (to avoid encrypted systems from breaking).
2194
 
                All other mounts like /home will be defaulted to target device
2195
 
                (to prevent the "cloned" system from using the original device)
2196
 
                */
2197
 
                
2198
 
                if (App.mirror_system){
2199
 
                        restore_target = null;
2200
 
                        foreach (var entry in mount_list){
2201
 
                                // user should select another device
2202
 
                                entry.device = null; 
2203
 
                        }
2204
 
                }
2205
 
 
2206
 
                foreach(var mnt in mount_list){
2207
 
                        if (mnt.device != null){
2208
 
                                log_debug("Entry: %s -> %s".printf(mnt.device.device, mnt.mount_point));
2209
 
                        }
2210
 
                        else{
2211
 
                                log_debug("Entry: null -> %s".printf(mnt.mount_point));
2212
 
                        }
2213
 
                }
2214
 
 
2215
 
                // sort - parent mountpoints will be placed above children
2216
 
                mount_list.sort((a,b) => {
2217
 
                        return strcmp(a.mount_point, b.mount_point);
2218
 
                });
2219
 
 
2220
 
                log_debug("Main: init_mount_list(): exit");
2221
 
        }
2222
 
 
2223
 
        public bool restore_snapshot(Gtk.Window? parent_win){
2224
 
                bool found = false;
2225
 
 
2226
 
                // set snapshot device --------------------------------
2227
 
 
2228
 
                if (!mirror_system){
2229
 
                        
2230
 
                        if (repo.device != null){
2231
 
                                //print snapshot_device name
2232
 
                                log_msg(string.nfill(78, '*'));
2233
 
                                log_msg(_("Backup Device") + ": %s".printf(repo.device.device));
2234
 
                                log_msg(string.nfill(78, '*'));
2235
 
                        }
2236
 
                        else{
2237
 
                                //print error
2238
 
                                log_error(_("Backup device not specified!"));
2239
 
                                return false;
2240
 
                        }
2241
 
                }
2242
 
 
2243
 
                // set snapshot ----------------------------------------
2244
 
 
2245
 
                if (!mirror_system){
2246
 
 
2247
 
                        if (app_mode != ""){ //command-line mode
2248
 
 
2249
 
                                if (cmd_snapshot.length > 0){
2250
 
 
2251
 
                                        //check command line arguments
2252
 
                                        found = false;
2253
 
                                        foreach(var bak in repo.snapshots) {
2254
 
                                                if (bak.name == cmd_snapshot){
2255
 
                                                        snapshot_to_restore = bak;
2256
 
                                                        found = true;
2257
 
                                                        break;
2258
 
                                                }
2259
 
                                        }
2260
 
 
2261
 
                                        //check if found
2262
 
                                        if (!found){
2263
 
                                                log_error(_("Could not find snapshot") + ": '%s'".printf(cmd_snapshot));
2264
 
                                                return false;
2265
 
                                        }
2266
 
                                }
2267
 
 
2268
 
                                //prompt user for snapshot
2269
 
                                if (snapshot_to_restore == null){
2270
 
 
2271
 
                                        if (!repo.has_snapshots()){
2272
 
                                                log_error(_("No snapshots found on device") + ": '%s'".printf(repo.device.device));
2273
 
                                                return false;
2274
 
                                        }
2275
 
 
2276
 
                                        log_msg("");
2277
 
                                        log_msg(_("Select snapshot to restore") + ":\n");
2278
 
                                        list_snapshots(true);
2279
 
                                        log_msg("");
2280
 
 
2281
 
                                        int attempts = 0;
2282
 
                                        while (snapshot_to_restore == null){
2283
 
                                                attempts++;
2284
 
                                                if (attempts > 3) { break; }
2285
 
                                                stdout.printf(_("Enter snapshot number (a=Abort, p=Previous, n=Next)") + ": ");
2286
 
                                                stdout.flush();
2287
 
                                                snapshot_to_restore = read_stdin_snapshot();
2288
 
                                        }
2289
 
                                        log_msg("");
2290
 
 
2291
 
                                        if (snapshot_to_restore == null){
2292
 
                                                log_error(_("Failed to get input from user in 3 attempts"));
2293
 
                                                log_msg(_("Aborted."));
2294
 
                                                exit_app();
2295
 
                                                exit(0);
2296
 
                                        }
2297
 
                                }
2298
 
                        }
2299
 
 
2300
 
                        if ((snapshot_to_restore != null) && (snapshot_to_restore.marked_for_deletion)){
2301
 
                                log_error(_("Invalid Snapshot"));
2302
 
                                log_error(_("Selected snapshot is marked for deletion"));
2303
 
                                return false;
2304
 
                        }
2305
 
                        
2306
 
                        if (snapshot_to_restore != null){
2307
 
                                //print snapshot name
2308
 
                                log_msg(string.nfill(78, '*'));
2309
 
                                log_msg(_("Snapshot") + ": %s ~ %s".printf(
2310
 
                                        snapshot_to_restore.name, snapshot_to_restore.description));
2311
 
                                log_msg(string.nfill(78, '*'));
2312
 
                        }
2313
 
                        else{
2314
 
                                //print error
2315
 
                                log_error(_("Snapshot to restore not specified!"));
2316
 
                                return false;
2317
 
                        }
2318
 
                }
2319
 
 
2320
 
                // init mounts ---------------
2321
 
 
2322
 
                if (app_mode != ""){
2323
 
                        
2324
 
                        init_mount_list();
2325
 
 
2326
 
                        // remove mount points which will remain on root fs
2327
 
                        for(int i = App.mount_list.size-1; i >= 0; i--){
2328
 
                                
2329
 
                                var entry = App.mount_list[i];
2330
 
                                
2331
 
                                if (entry.device == null){
2332
 
                                        App.mount_list.remove(entry);
2333
 
                                }
2334
 
 
2335
 
                                if (entry.mount_point == "/"){
2336
 
                                        App.restore_target = entry.device;
2337
 
                                }
2338
 
                        }
2339
 
                }
2340
 
 
2341
 
                if (app_mode != ""){ //command line mode
2342
 
 
2343
 
                        // set target device from cmd argument
2344
 
                        if (cmd_target_device.length > 0){
2345
 
 
2346
 
                                //check command line arguments
2347
 
                                found = false;
2348
 
                                foreach(Device pi in partitions) {
2349
 
                                        if (!pi.has_linux_filesystem()) { continue; }
2350
 
                                        if ((pi.device == cmd_target_device)||((pi.uuid == cmd_target_device))){
2351
 
                                                restore_target = pi;
2352
 
                                                found = true;
2353
 
                                                break;
2354
 
                                        }
2355
 
                                        else {
2356
 
                                                foreach(string symlink in pi.symlinks){
2357
 
                                                        if (symlink == cmd_target_device){
2358
 
                                                                restore_target = pi;
2359
 
                                                                found = true;
2360
 
                                                                break;
2361
 
                                                        }
2362
 
                                                }
2363
 
                                                if (found){ break; }
2364
 
                                        }
2365
 
                                }
2366
 
 
2367
 
                                //check if found
2368
 
                                if (!found){
2369
 
                                        log_error(_("Could not find device") + ": '%s'".printf(cmd_target_device));
2370
 
                                        exit_app();
2371
 
                                        exit(1);
2372
 
                                        return false;
2373
 
                                }
2374
 
                        }
2375
 
                }
2376
 
                
2377
 
                // select devices in mount_list --------------------
2378
 
 
2379
 
                log_debug("Selecting devices for mount points");
2380
 
                
2381
 
                if (app_mode != ""){ //command line mode
2382
 
 
2383
 
                        for(int i = 0; i < mount_list.size; i++){
2384
 
                                MountEntry mnt = mount_list[i];
2385
 
                                Device dev = null;
2386
 
                                string default_device = "";
2387
 
 
2388
 
                                log_debug("selecting: %s".printf(mnt.mount_point));
2389
 
 
2390
 
                                // no need to ask user to map remaining devices if restoring same system
2391
 
                                if ((restore_target != null) && (sys_root != null)
2392
 
                                        && (restore_target.uuid == sys_root.uuid)){
2393
 
                                                
2394
 
                                        break;
2395
 
                                }
2396
 
 
2397
 
                                if (mirror_system){
2398
 
                                        default_device = (restore_target != null) ? restore_target.device : "";
2399
 
                                }
2400
 
                                else{
2401
 
                                        if (mnt.device != null){
2402
 
                                                default_device = mnt.device.device;
2403
 
                                        }
2404
 
                                        else{
2405
 
                                                default_device = (restore_target != null) ? restore_target.device : "";
2406
 
                                        }
2407
 
                                }
2408
 
 
2409
 
                                //prompt user for device
2410
 
                                if (dev == null){
2411
 
                                        log_msg("");
2412
 
                                        log_msg(_("Select '%s' device (default = %s)").printf(
2413
 
                                                mnt.mount_point, default_device) + ":\n");
2414
 
                                        var device_list = list_all_devices();
2415
 
                                        log_msg("");
2416
 
 
2417
 
                                        int attempts = 0;
2418
 
                                        while (dev == null){
2419
 
                                                attempts++;
2420
 
                                                if (attempts > 3) { break; }
2421
 
                                                
2422
 
                                                stdout.printf("" +
2423
 
                                                        _("[a = Abort, d = Default (%s), r = Root device]").printf(default_device) + "\n\n");
2424
 
                                                        
2425
 
                                                stdout.printf(
2426
 
                                                        _("Enter device name or number")
2427
 
                                                                + ": ");
2428
 
                                                                
2429
 
                                                stdout.flush();
2430
 
                                                dev = read_stdin_device_mounts(device_list, mnt);
2431
 
                                        }
2432
 
                                        log_msg("");
2433
 
 
2434
 
                                        if (dev == null){
2435
 
                                                log_error(_("Failed to get input from user in 3 attempts"));
2436
 
                                                log_msg(_("Aborted."));
2437
 
                                                exit_app();
2438
 
                                                exit(0);
2439
 
                                        }
2440
 
                                }
2441
 
 
2442
 
                                if (dev != null){
2443
 
 
2444
 
                                        log_debug("selected: %s".printf(dev.uuid));
2445
 
                                        
2446
 
                                        mnt.device = dev;
2447
 
 
2448
 
                                        if (mnt.mount_point == "/"){
2449
 
                                                restore_target = dev;
2450
 
                                        }
2451
 
 
2452
 
                                        log_msg(string.nfill(78, '*'));
2453
 
                                        
2454
 
                                        if ((mnt.mount_point != "/")
2455
 
                                                && (restore_target != null)
2456
 
                                                && (dev.device == restore_target.device)){
2457
 
                                                        
2458
 
                                                log_msg(_("'%s' will be on root device").printf(mnt.mount_point), true);
2459
 
                                        }
2460
 
                                        else{
2461
 
                                                log_msg(_("'%s' will be on '%s'").printf(
2462
 
                                                        mnt.mount_point, mnt.device.short_name_with_alias), true);
2463
 
                                                        
2464
 
                                                //log_debug("UUID=%s".printf(restore_target.uuid));
2465
 
                                        }
2466
 
                                        log_msg(string.nfill(78, '*'));
2467
 
                                }
2468
 
                        
2469
 
                        }
2470
 
                }
2471
 
                
2472
 
                // mount selected devices ---------------------------------------
2473
 
 
2474
 
                log_debug("Mounting selected devices");
2475
 
                
2476
 
                if (restore_target != null){
2477
 
                        if (app_mode != ""){ //commandline mode
2478
 
                                if ((sys_root == null) || (restore_target.uuid != sys_root.uuid)){
2479
 
                                        //mount target device and other devices
2480
 
                                        bool status = mount_target_device(null);
2481
 
                                        if (status == false){
2482
 
                                                return false;
2483
 
                                        }
2484
 
                                }
2485
 
                        }
2486
 
                        else{
2487
 
                                //mounting is already done
2488
 
                        }
2489
 
                }
2490
 
                else{
2491
 
                        //print error
2492
 
                        log_error(_("Target device not specified!"));
2493
 
                        return false;
2494
 
                }
2495
 
 
2496
 
                // set grub device -----------------------------------------------
2497
 
 
2498
 
                log_debug("Setting grub device");
2499
 
                
2500
 
                if (app_mode != ""){ //command line mode
2501
 
 
2502
 
                        if (cmd_grub_device.length > 0){
2503
 
 
2504
 
                                log_debug("Grub device is specified as command argument");
2505
 
                                
2506
 
                                //check command line arguments
2507
 
                                found = false;
2508
 
                                var device_list = list_grub_devices(false);
2509
 
                                
2510
 
                                foreach(Device dev in device_list) {
2511
 
                                        
2512
 
                                        if ((dev.device == cmd_grub_device)
2513
 
                                                ||((dev.uuid.length > 0) && (dev.uuid == cmd_grub_device))){
2514
 
 
2515
 
                                                grub_device = dev.device;
2516
 
                                                found = true;
2517
 
                                                break;
2518
 
                                        }
2519
 
                                        else {
2520
 
                                                if (dev.type == "part"){
2521
 
                                                        foreach(string symlink in dev.symlinks){
2522
 
                                                                if (symlink == cmd_grub_device){
2523
 
                                                                        grub_device = dev.device;
2524
 
                                                                        found = true;
2525
 
                                                                        break;
2526
 
                                                                }
2527
 
                                                        }
2528
 
                                                        if (found){ break; }
2529
 
                                                }
2530
 
                                        }
2531
 
                                }
2532
 
 
2533
 
                                //check if found
2534
 
                                if (!found){
2535
 
                                        log_error(_("Could not find device") + ": '%s'".printf(cmd_grub_device));
2536
 
                                        exit_app();
2537
 
                                        exit(1);
2538
 
                                        return false;
2539
 
                                }
2540
 
                        }
2541
 
                        
2542
 
                        if (mirror_system){
2543
 
                                reinstall_grub2 = true;
2544
 
                        }
2545
 
                        else {
2546
 
                                if ((cmd_skip_grub == false) && (reinstall_grub2 == false)){
2547
 
                                        log_msg("");
2548
 
 
2549
 
                                        int attempts = 0;
2550
 
                                        while ((cmd_skip_grub == false) && (reinstall_grub2 == false)){
2551
 
                                                attempts++;
2552
 
                                                if (attempts > 3) { break; }
2553
 
                                                stdout.printf(_("Re-install GRUB2 bootloader? (y/n)") + ": ");
2554
 
                                                stdout.flush();
2555
 
                                                read_stdin_grub_install();
2556
 
                                        }
2557
 
 
2558
 
                                        if ((cmd_skip_grub == false) && (reinstall_grub2 == false)){
2559
 
                                                log_error(_("Failed to get input from user in 3 attempts"));
2560
 
                                                log_msg(_("Aborted."));
2561
 
                                                exit_app();
2562
 
                                                exit(0);
2563
 
                                        }
2564
 
                                }
2565
 
                        }
2566
 
 
2567
 
                        if ((reinstall_grub2) && (grub_device.length == 0)){
2568
 
                                
2569
 
                                log_msg("");
2570
 
                                log_msg(_("Select GRUB device") + ":\n");
2571
 
                                var device_list = list_grub_devices();
2572
 
                                log_msg("");
2573
 
 
2574
 
                                int attempts = 0;
2575
 
                                while (grub_device.length == 0){
2576
 
                                        
2577
 
                                        attempts++;
2578
 
                                        if (attempts > 3) { break; }
2579
 
                                        
2580
 
                                        stdout.printf(_("Enter device name or number (a=Abort)") + ": ");
2581
 
                                        stdout.flush();
2582
 
 
2583
 
                                        // TODO: provide option for default boot device
2584
 
 
2585
 
                                        var list = new Gee.ArrayList<Device>();
2586
 
                                        foreach(var pi in partitions){
2587
 
                                                if (pi.has_linux_filesystem()){
2588
 
                                                        list.add(pi);
2589
 
                                                }
2590
 
                                        }
2591
 
                                        
2592
 
                                        Device dev = read_stdin_device(device_list);
2593
 
                                        if (dev != null) { grub_device = dev.device; }
2594
 
                                }
2595
 
                                
2596
 
                                log_msg("");
2597
 
 
2598
 
                                if (grub_device.length == 0){
2599
 
                                        
2600
 
                                        log_error(_("Failed to get input from user in 3 attempts"));
2601
 
                                        log_msg(_("Aborted."));
2602
 
                                        exit_app();
2603
 
                                        exit(0);
2604
 
                                }
2605
 
                        }
2606
 
 
2607
 
                        if ((reinstall_grub2) && (grub_device.length > 0)){
2608
 
                                
2609
 
                                log_msg(string.nfill(78, '*'));
2610
 
                                log_msg(_("GRUB Device") + ": %s".printf(grub_device));
2611
 
                                log_msg(string.nfill(78, '*'));
2612
 
                        }
2613
 
                        else{
2614
 
                                log_msg(string.nfill(78, '*'));
2615
 
                                log_msg(_("GRUB will NOT be reinstalled"));
2616
 
                                log_msg(string.nfill(78, '*'));
2617
 
                        }
2618
 
                }
2619
 
 
2620
 
                if ((app_mode != "")&&(cmd_confirm == false)){
2621
 
 
2622
 
                        string msg_devices = "";
2623
 
                        string msg_reboot = "";
2624
 
                        string msg_disclaimer = "";
2625
 
 
2626
 
                        App.disclaimer_pre_restore(
2627
 
                                false, out msg_devices, out msg_reboot,
2628
 
                                out msg_disclaimer);
2629
 
 
2630
 
                        int attempts = 0;
2631
 
                        while (cmd_confirm == false){
2632
 
                                attempts++;
2633
 
                                if (attempts > 3) { break; }
2634
 
                                stdout.printf(_("Continue with restore? (y/n): "));
2635
 
                                stdout.flush();
2636
 
                                read_stdin_restore_confirm();
2637
 
                        }
2638
 
 
2639
 
                        if (cmd_confirm == false){
2640
 
                                log_error(_("Failed to get input from user in 3 attempts"));
2641
 
                                log_msg(_("Aborted."));
2642
 
                                exit_app();
2643
 
                                exit(0);
2644
 
                        }
2645
 
                }
2646
 
 
2647
 
                try {
2648
 
                        thread_restore_running = true;
2649
 
                        thr_success = false;
2650
 
                        Thread.create<void> (restore_snapshot_thread, true);
2651
 
                }
2652
 
                catch (ThreadError e) {
2653
 
                        thread_restore_running = false;
2654
 
                        thr_success = false;
2655
 
                        log_error (e.message);
2656
 
                }
2657
 
 
2658
 
                while (thread_restore_running){
2659
 
                        gtk_do_events ();
2660
 
                        Thread.usleep((ulong) GLib.TimeSpan.MILLISECOND * 100);
2661
 
                }
2662
 
 
2663
 
                snapshot_to_restore = null;
2664
 
 
2665
 
                return thr_success;
2666
 
        }
2667
 
 
2668
 
        public void disclaimer_pre_restore(bool formatted,
2669
 
                out string msg_devices, out string msg_reboot,
2670
 
                out string msg_disclaimer){
2671
 
                        
2672
 
                string msg = "";
2673
 
 
2674
 
                log_debug("Main: disclaimer_pre_restore()");
2675
 
 
2676
 
                // msg_devices -----------------------------------------
2677
 
                
2678
 
                if (!formatted){
2679
 
                        msg += "\n%s\n%s\n%s\n".printf(
2680
 
                                string.nfill(70,'='),
2681
 
                                _("Warning").up(),
2682
 
                                string.nfill(70,'=')
2683
 
                        );
2684
 
                }
2685
 
                
2686
 
                msg += _("Data will be modified on following devices:") + "\n\n";
2687
 
 
2688
 
                int max_mount = _("Mount").length;
2689
 
                int max_dev = _("Device").length;
2690
 
                int max_vol = _("Subvol").length;
2691
 
 
2692
 
                foreach(var entry in mount_list){
2693
 
                        if (entry.device == null){ continue; }
2694
 
 
2695
 
                        string dev_name = entry.device.short_name_with_alias;
2696
 
                        
2697
 
                        if (dev_name.length > max_dev){
2698
 
                                max_dev = dev_name.length;
2699
 
                        }
2700
 
                        if (entry.mount_point.length > max_mount){
2701
 
                                max_mount = entry.mount_point.length;
2702
 
                        }
2703
 
                        if (entry.subvolume_name().length > max_vol){
2704
 
                                max_vol = entry.subvolume_name().length;
2705
 
                        }
2706
 
                }
2707
 
 
2708
 
                bool show_subvolume = false;
2709
 
                foreach(var entry in App.mount_list){
2710
 
                        if (entry.device == null){ continue; }
2711
 
                        
2712
 
                        if ((entry.device != null)
2713
 
                                && (entry.device.fstype == "btrfs")
2714
 
                                && (entry.subvolume_name().length > 0)){
2715
 
                                        
2716
 
                                // subvolumes are used - show subvolume column
2717
 
                                show_subvolume = true;
2718
 
                                break;
2719
 
                        }
2720
 
                }
2721
 
                
2722
 
                var txt = ("%%-%ds  %%-%ds".printf(max_dev, max_mount))
2723
 
                        .printf(_("Device"),_("Mount"));
2724
 
                if (show_subvolume){
2725
 
                        txt += "  %s".printf(_("Subvol"));
2726
 
                }
2727
 
                txt += "\n";
2728
 
 
2729
 
                txt += string.nfill(max_dev, '-') + "  " + string.nfill(max_mount, '-');
2730
 
                if (show_subvolume){
2731
 
                        txt += "  " + string.nfill(max_vol, '-');
2732
 
                }
2733
 
                txt += "\n";
2734
 
                
2735
 
                foreach(var entry in App.mount_list){
2736
 
                        if (entry.device == null){ continue; }
2737
 
                        
2738
 
                        txt += ("%%-%ds  %%-%ds".printf(max_dev, max_mount)).printf(
2739
 
                                entry.device.device_name_with_parent, entry.mount_point);
2740
 
 
2741
 
                        if (show_subvolume){
2742
 
                                txt += "  %s".printf(entry.subvolume_name());
2743
 
                        }
2744
 
 
2745
 
                        txt += "\n";
2746
 
                }
2747
 
 
2748
 
                if (formatted){
2749
 
                        msg += "<span size=\"medium\"><tt>%s</tt></span>".printf(txt);
2750
 
                }
2751
 
                else{
2752
 
                        msg += "%s\n".printf(txt);
2753
 
                }
2754
 
 
2755
 
                msg_devices = msg;
2756
 
 
2757
 
                //msg += _("Files will be overwritten on the target device!") + "\n";
2758
 
                //msg += _("If restore fails and you are unable to boot the system, then boot from the Ubuntu Live CD, install Timeshift, and try to restore again.") + "\n";
2759
 
 
2760
 
                // msg_reboot -----------------------
2761
 
                
2762
 
                msg = "";
2763
 
                if ((sys_root != null) && (restore_target != null)
2764
 
                        && (restore_target.device == sys_root.device)){
2765
 
                                
2766
 
                        msg += _("Please save your work and close all applications.") + "\n";
2767
 
                        msg += _("System will reboot after files are restored.");
2768
 
                }
2769
 
 
2770
 
                msg_reboot = msg;
2771
 
 
2772
 
                // msg_disclaimer --------------------------------------
2773
 
 
2774
 
                msg = "";
2775
 
                if (!formatted){
2776
 
                        msg += "\n%s\n%s\n%s\n".printf(
2777
 
                                string.nfill(70,'='),
2778
 
                                _("Disclaimer").up(),
2779
 
                                string.nfill(70,'=')
2780
 
                        );
2781
 
                }
2782
 
                
2783
 
                msg += _("This software comes without absolutely NO warranty and the author takes no responsibility for any damage arising from the use of this program.");
2784
 
                msg += " " + _("If these terms are not acceptable to you, please do not proceed beyond this point!");
2785
 
 
2786
 
                if (!formatted){
2787
 
                        msg += "\n";
2788
 
                }
2789
 
                
2790
 
                msg_disclaimer = msg;
2791
 
 
2792
 
                // display messages in console mode
2793
 
                
2794
 
                if (app_mode.length > 0){
2795
 
                        log_msg(msg_devices);
2796
 
                        log_msg(msg_reboot);
2797
 
                        log_msg(msg_disclaimer);
2798
 
                }
2799
 
 
2800
 
                log_debug("Main: disclaimer_pre_restore(): exit");
2801
 
        }
2802
 
 
2803
 
        public void restore_snapshot_thread(){
2804
 
                string sh = "";
2805
 
                int ret_val = -1;
2806
 
                string temp_script;
2807
 
                string sh_grub = "";
2808
 
                string sh_reboot = "";
2809
 
                
2810
 
                try{
2811
 
 
2812
 
                        string source_path = "";
2813
 
 
2814
 
                        if (snapshot_to_restore != null){
2815
 
                                source_path = snapshot_to_restore.path;
2816
 
                        }
2817
 
                        else{
2818
 
                                source_path = "/tmp/timeshift";
2819
 
                                if (!dir_exists(source_path)){
2820
 
                                        dir_create(source_path);
2821
 
                                }
2822
 
                        }
2823
 
 
2824
 
                        log_debug("source_path=%s".printf(source_path));
2825
 
 
2826
 
                        //set target path ----------------
2827
 
 
2828
 
                        bool restore_current_system;
2829
 
                        string target_path;
2830
 
 
2831
 
                        if ((sys_root != null)
2832
 
                                && ((restore_target.device == sys_root.device)
2833
 
                                        || (restore_target.uuid == sys_root.uuid))){
2834
 
                                        
2835
 
                                restore_current_system = true;
2836
 
                                target_path = "/";
2837
 
                        }
2838
 
                        else{
2839
 
                                restore_current_system = false;
2840
 
                                target_path = mount_point_restore + "/";
2841
 
 
2842
 
                                if (mount_point_restore.strip().length == 0){
2843
 
                                        log_error(_("Target device is not mounted"));
2844
 
                                        thr_success = false;
2845
 
                                        thread_restore_running = false;
2846
 
                                        return;
2847
 
                                }
2848
 
                        }
2849
 
 
2850
 
                        log_debug("target_path=%s".printf(target_path));
2851
 
 
2852
 
                        //save exclude list for restore --------------
2853
 
 
2854
 
                        save_exclude_list_for_restore(source_path);
2855
 
 
2856
 
                        //create script -------------
2857
 
 
2858
 
                        sh = "";
2859
 
                        sh += "echo ''\n";
2860
 
                        if (restore_current_system){
2861
 
                                log_debug("restoring current system");
2862
 
                                
2863
 
                                sh += "echo '" + _("Please do not interrupt the restore process!") + "'\n";
2864
 
                                sh += "echo '" + _("System will reboot after files are restored") + "'\n";
2865
 
                        }
2866
 
                        sh += "echo ''\n";
2867
 
                        sh += "sleep 3s\n";
2868
 
 
2869
 
                        //log file
2870
 
                        var log_path = source_path + "/rsync-log-restore";
2871
 
                        var f = File.new_for_path(log_path);
2872
 
                        if (f.query_exists()){
2873
 
                                f.delete();
2874
 
                        }
2875
 
 
2876
 
                        //run rsync ----------
2877
 
 
2878
 
                        sh += "rsync -avir --force --delete-after";
2879
 
                        sh += " --log-file=\"%s\"".printf(log_path);
2880
 
                        sh += " --exclude-from=\"%s\"".printf(source_path + "/exclude-restore.list");
2881
 
 
2882
 
                        if (mirror_system){
2883
 
                                sh += " \"%s\" \"%s\" \n".printf("/", target_path);
2884
 
                        }
2885
 
                        else{
2886
 
                                sh += " \"%s\" \"%s\" \n".printf(source_path + "/localhost/", target_path);
2887
 
                        }
2888
 
 
2889
 
                        //sync file system
2890
 
                        sh += "sync \n";
2891
 
 
2892
 
                        log_debug("rsync script:");
2893
 
                        log_debug(sh);
2894
 
                        
2895
 
                        //chroot and re-install grub2 --------
2896
 
 
2897
 
                        log_debug("reinstall_grub2=%s".printf(
2898
 
                                reinstall_grub2.to_string()));
2899
 
                                
2900
 
                        log_debug("grub_device=%s".printf(
2901
 
                                (grub_device == null) ? "null" : grub_device));
2902
 
 
2903
 
                        var target_distro = LinuxDistro.get_dist_info(target_path);
2904
 
                        
2905
 
                        sh_grub = "";
2906
 
                        
2907
 
                        if (reinstall_grub2 && (grub_device != null) && (grub_device.length > 0)){
2908
 
                                
2909
 
                                sh_grub += "sync \n";
2910
 
                                sh_grub += "echo '' \n";
2911
 
                                sh_grub += "echo '" + _("Re-installing GRUB2 bootloader...") + "' \n";
2912
 
 
2913
 
                                string chroot = "";
2914
 
                                if (!restore_current_system){
2915
 
                                        if (target_distro.dist_type == "arch"){
2916
 
                                                chroot += "arch-chroot \"%s\"".printf(target_path);
2917
 
                                        }
2918
 
                                        else{
2919
 
                                                chroot += "chroot \"%s\"".printf(target_path);
2920
 
                                        }
2921
 
                                }
2922
 
                                
2923
 
                                // bind system directories for chrooted system
2924
 
                                sh_grub += "for i in /dev /proc /run /sys; do mount --bind \"$i\" \"%s$i\"; done \n".printf(target_path);
2925
 
 
2926
 
                                // search for other operating systems
2927
 
                                //sh_grub += "chroot \"%s\" os-prober \n".printf(target_path);
2928
 
                                
2929
 
                                // re-install grub ---------------
2930
 
 
2931
 
                                if (target_distro.dist_type == "redhat"){
2932
 
 
2933
 
                                        // this will run only in clone mode
2934
 
                                        
2935
 
                                        sh_grub += "%s grub2-install --recheck %s \n".printf(chroot, grub_device);
2936
 
 
2937
 
                                        /* NOTE:
2938
 
                                         * grub2-install should NOT be run on Fedora EFI systems 
2939
 
                                         * https://fedoraproject.org/wiki/GRUB_2
2940
 
                                         * Instead following packages should be reinstalled:
2941
 
                                         * dnf reinstall grub2-efi grub2-efi-modules shim
2942
 
                                         *
2943
 
                                         * Bootloader installation will be skipped while restoring in GUI mode.
2944
 
                                         * Fedora seems to boot correctly even after installing new
2945
 
                                         * kernels and restoring a snapshot with an older kernel.
2946
 
                                        */
2947
 
                                }
2948
 
                                else {
2949
 
                                        sh_grub += "%s grub-install --recheck %s \n".printf(chroot, grub_device);
2950
 
                                }
2951
 
 
2952
 
                                // create new grub menu
2953
 
                                //sh_grub += "chroot \"%s\" grub-mkconfig -o /boot/grub/grub.cfg \n".printf(target_path);
2954
 
 
2955
 
                                // update initramfs --------------
2956
 
 
2957
 
                                if (target_distro.dist_type == "redhat"){
2958
 
                                        sh_grub += "%s dracut -f -v \n".printf(chroot);
2959
 
                                }
2960
 
                                else if (target_distro.dist_type == "arch"){
2961
 
                                        sh_grub += "%s mkinitcpio -p /etc/mkinitcpio.d/*.preset\n".printf(chroot);
2962
 
                                }
2963
 
                                else{
2964
 
                                        sh_grub += "%s update-initramfs -u -k all \n".printf(chroot);
2965
 
                                }
2966
 
                                        
2967
 
                                // update grub menu --------------
2968
 
 
2969
 
                                if ((target_distro.dist_type == "redhat") || (target_distro.dist_type == "arch")){
2970
 
                                        sh_grub += "%s grub-mkconfig -o /boot/grub2/grub.cfg \n".printf(chroot);
2971
 
                                }
2972
 
                                else{
2973
 
                                        sh_grub += "%s update-grub \n".printf(chroot);
2974
 
                                }
2975
 
 
2976
 
                                sh_grub += "echo '' \n";
2977
 
 
2978
 
                                // sync file systems
2979
 
                                sh_grub += "echo '" + _("Synching file systems...") + "' \n";
2980
 
                                sh_grub += "sync \n";
2981
 
                                sh_grub += "echo '' \n";
2982
 
 
2983
 
                                // unmount chrooted system
2984
 
                                sh_grub += "echo '" + _("Cleaning up...") + "' \n";
2985
 
                                sh_grub += "for i in /dev /proc /run /sys; do umount -f \"%s$i\"; done \n".printf(target_path);
2986
 
                                sh_grub += "sync \n";
2987
 
 
2988
 
                                log_debug("GRUB2 install script:");
2989
 
                                log_debug(sh_grub);
2990
 
                        
2991
 
                                //sh += sh_grub;
2992
 
                        }
2993
 
                        else{
2994
 
                                log_debug("skipping sh_grub: reinstall_grub2=%s, grub_device=%s".printf(
2995
 
                                        reinstall_grub2.to_string(), (grub_device == null) ? "null" : grub_device));
2996
 
                        }
2997
 
 
2998
 
                        //reboot if required --------
2999
 
 
3000
 
                        if (restore_current_system){
3001
 
                                sh_reboot += "echo '' \n";
3002
 
                                sh_reboot += "echo '" + _("Rebooting system...") + "' \n";
3003
 
                                sh += "reboot -f \n";
3004
 
                                //sh_reboot += "shutdown -r now \n";
3005
 
                        }
3006
 
 
3007
 
                        //check if current system is being restored and do some housekeeping ---------
3008
 
 
3009
 
                        if (restore_current_system){
3010
 
 
3011
 
                                //invalidate the .sync snapshot  -------
3012
 
 
3013
 
                                string sync_name = ".sync";
3014
 
                                string snapshot_dir = path_combine(repo.snapshot_location, "timeshift/snapshots");
3015
 
                                string sync_path = snapshot_dir + "/" + sync_name;
3016
 
                                string control_file_path = sync_path + "/info.json";
3017
 
 
3018
 
                                f = File.new_for_path(control_file_path);
3019
 
                                if(f.query_exists()){
3020
 
                                        f.delete(); //delete the control file
3021
 
                                }
3022
 
 
3023
 
                                //save a control file for updating the .sync snapshot -----
3024
 
 
3025
 
                                control_file_path = snapshot_dir + "/.sync-restore";
3026
 
 
3027
 
                                f = File.new_for_path(control_file_path);
3028
 
                                if(f.query_exists()){
3029
 
                                        f.delete(); //delete existing file
3030
 
                                }
3031
 
 
3032
 
                                file_write(control_file_path, snapshot_to_restore.path); //save snapshot name
3033
 
                        }
3034
 
 
3035
 
                        //run the script --------------------
3036
 
                
3037
 
                        if (snapshot_to_restore != null){
3038
 
                                log_msg(_("Restoring snapshot..."));
3039
 
                        }
3040
 
                        else{
3041
 
                                log_msg(_("Cloning system..."));
3042
 
                        }
3043
 
 
3044
 
                        progress_text = _("Synching files with rsync...");
3045
 
                        log_msg(progress_text);
3046
 
 
3047
 
                        if (app_mode == ""){
3048
 
 
3049
 
                                // gui mode --------------
3050
 
                                
3051
 
                                if (restore_current_system){
3052
 
                                        //current system, gui, fullscreen
3053
 
                                        temp_script = save_bash_script_temp(sh + sh_grub + sh_reboot);
3054
 
                                        // Note: sh_grub will be empty if reinstall_grub2 = false 
3055
 
 
3056
 
                                        //restore or clone
3057
 
                                        var dlg = new TerminalWindow.with_parent(null);
3058
 
                                        dlg.execute_script(temp_script, true);
3059
 
                                }
3060
 
                                else{
3061
 
                                        // other system, gui ------------------------
3062
 
 
3063
 
                                        //App.progress_text = "Sync";
3064
 
 
3065
 
                                        progress_text = _("Building file list...");
3066
 
                                        //log_msg(progress_text); // gui-only message
3067
 
                                        
3068
 
                                        task = new RsyncTask();
3069
 
                                        task.relative = false;
3070
 
                                        task.verbose = true;
3071
 
                                        task.delete_extra = true;
3072
 
                                        task.delete_excluded = false;
3073
 
                                        task.delete_after = true;
3074
 
                                        
3075
 
                                        if (mirror_system){
3076
 
                                                task.source_path = "/";
3077
 
                                        }
3078
 
                                        else{
3079
 
                                                task.source_path =
3080
 
                                                        path_combine(source_path, "localhost");
3081
 
                                        }
3082
 
 
3083
 
                                        task.dest_path = target_path;
3084
 
                                        
3085
 
                                        task.exclude_from_file =
3086
 
                                                path_combine(source_path, "exclude-restore.list");
3087
 
 
3088
 
                                        task.rsync_log_file = log_path;
3089
 
                                        task.prg_count_total = Main.first_snapshot_count;       
3090
 
                                        task.execute();
3091
 
 
3092
 
                                        while (task.status == AppStatus.RUNNING){
3093
 
                                                sleep(1000);
3094
 
 
3095
 
                                                if (App.task.status_line.length > 0){
3096
 
                                                        progress_text = _("Synching files with rsync...");
3097
 
                                                }
3098
 
                                                
3099
 
                                                gtk_do_events();
3100
 
                                        }
3101
 
 
3102
 
                                        if (!restore_current_system){
3103
 
                                                App.progress_text = "Updating /etc/fstab and /etc/crypttab on target system...";
3104
 
                                                log_msg(App.progress_text);
3105
 
                                                
3106
 
                                                fix_fstab_file(target_path);
3107
 
                                                fix_crypttab_file(target_path);
3108
 
                                        }
3109
 
 
3110
 
                                        // re-install grub ------------
3111
 
                                
3112
 
                                        if (reinstall_grub2){
3113
 
 
3114
 
                                                App.progress_text = "Re-installing GRUB2 bootloader...";
3115
 
                                                log_msg(App.progress_text);
3116
 
 
3117
 
                                                log_debug(sh_grub);
3118
 
                                                
3119
 
                                                //string std_out, std_err;
3120
 
                                                ret_val = exec_script_sync(sh_grub, null, null);
3121
 
                                                //log_to_file(std_out);
3122
 
                                                //log_to_file(std_err);
3123
 
                                                //log_msg(std_out);
3124
 
                                                //log_msg(std_err);
3125
 
                                                log_debug("GRUB2 install completed");
3126
 
                                        }
3127
 
 
3128
 
                                        ret_val = task.exit_code;
3129
 
                                }
3130
 
                        }
3131
 
                        else{
3132
 
 
3133
 
                                // console mode ----------
3134
 
                                var script = sh;
3135
 
                                if (restore_current_system){
3136
 
                                        script += sh_grub + sh_reboot;
3137
 
                                }
3138
 
 
3139
 
                                log_debug("verbose=%s".printf(cmd_verbose.to_string()));
3140
 
                                
3141
 
                                if (cmd_verbose){
3142
 
                                        //current/other system, console, verbose
3143
 
                                        ret_val = exec_script_sync(script, null, null, false, false, false, true);
3144
 
                                        log_msg("");
3145
 
                                }
3146
 
                                else{
3147
 
                                        //current/other system, console, quiet
3148
 
                                        string std_out, std_err;
3149
 
                                        ret_val = exec_script_sync(script, out std_out, out std_err);
3150
 
                                        log_to_file(std_out);
3151
 
                                        log_to_file(std_err);
3152
 
                                }
3153
 
 
3154
 
                                if (!restore_current_system){
3155
 
                                        
3156
 
                                        // fix fstab and crypttab files ------
3157
 
                                        
3158
 
                                        fix_fstab_file(target_path);
3159
 
                                        fix_crypttab_file(target_path);
3160
 
 
3161
 
                                        // re-install grub ------------
3162
 
                                
3163
 
                                        if (reinstall_grub2){
3164
 
 
3165
 
                                                App.progress_text = "Re-installing GRUB2 bootloader...";
3166
 
                                                log_msg(App.progress_text);
3167
 
                                                
3168
 
                                                if (cmd_verbose){
3169
 
                                                        //current/other system, console, verbose
3170
 
                                                        ret_val = exec_script_sync(sh_grub, null, null, false, false, false, true);
3171
 
                                                        log_msg("");
3172
 
                                                }
3173
 
                                                else{
3174
 
                                                        //current/other system, console, quiet
3175
 
                                                        string std_out, std_err;
3176
 
                                                        ret_val = exec_script_sync(sh_grub, out std_out, out std_err);
3177
 
                                                        log_to_file(std_out);
3178
 
                                                        log_to_file(std_err);
3179
 
                                                }
3180
 
                                        }
3181
 
                                }
3182
 
                        }
3183
 
 
3184
 
                        // check for errors ----------------------
3185
 
 
3186
 
                        if (ret_val != 0){
3187
 
                                log_error(_("Restore failed with exit code") + ": %d".printf(ret_val));
3188
 
                                thr_success = false;
3189
 
                                thread_restore_running = false;
3190
 
                        }
3191
 
                        else{
3192
 
                                log_msg(_("Restore completed without errors"));
3193
 
                                //thr_success = true;
3194
 
                                //thread_restore_running = false;
3195
 
                        }
3196
 
 
3197
 
                        // unmount ----------
3198
 
                        
3199
 
                        unmount_target_device(false);
3200
 
                }
3201
 
                catch(Error e){
3202
 
                        log_error (e.message);
3203
 
                        thr_success = false;
3204
 
                        thread_restore_running = false;
3205
 
                }
3206
 
 
3207
 
                thread_restore_running = false;
3208
 
        }
3209
 
 
3210
 
        public void fix_fstab_file(string target_path){
3211
 
                
3212
 
                string fstab_path = target_path + "etc/fstab";
3213
 
                var fstab_list = FsTabEntry.read_file(fstab_path);
3214
 
 
3215
 
                foreach(var mnt in mount_list){
3216
 
                        // find existing
3217
 
                        var entry = FsTabEntry.find_entry_by_mount_point(fstab_list, mnt.mount_point);
3218
 
 
3219
 
                        // add if missing
3220
 
                        if (entry == null){
3221
 
                                entry = new FsTabEntry();
3222
 
                                entry.mount_point = mnt.mount_point;
3223
 
                                fstab_list.add(entry);
3224
 
                        }
3225
 
 
3226
 
                        //update fstab entry
3227
 
                        entry.device = "UUID=%s".printf(mnt.device.uuid);
3228
 
                        entry.type = mnt.device.fstype;
3229
 
 
3230
 
                        // fix mount options for non-btrfs device
3231
 
                        if (mnt.device.fstype != "btrfs"){
3232
 
                                // remove subvol option
3233
 
                                entry.remove_option("subvol=%s".printf(entry.subvolume_name()));
3234
 
                        }
3235
 
                }
3236
 
 
3237
 
                /*
3238
 
                 * Remove fstab entries for any system directories that
3239
 
                 * the user has not explicitly mapped before restore/clone
3240
 
                 * This ensures that the cloned/restored system does not mount
3241
 
                 * any devices to system paths that the user has not explicitly specified
3242
 
                 * */
3243
 
 
3244
 
                for(int i = fstab_list.size - 1; i >= 0; i--){
3245
 
                        var entry = fstab_list[i];
3246
 
                        
3247
 
                        if (!entry.is_for_system_directory()){ continue; }
3248
 
                        
3249
 
                        var mnt = MountEntry.find_entry_by_mount_point(mount_list, entry.mount_point);
3250
 
                        if (mnt == null){
3251
 
                                fstab_list.remove(entry);
3252
 
                        }
3253
 
                }
3254
 
                
3255
 
                // write the updated file
3256
 
 
3257
 
                FsTabEntry.write_file(fstab_list, fstab_path, false);
3258
 
 
3259
 
                log_msg(_("Updated /etc/fstab on target device") + ": %s".printf(fstab_path));
3260
 
 
3261
 
                // create directories on disk for mount points in /etc/fstab
3262
 
 
3263
 
                foreach(var entry in fstab_list){
3264
 
                        if (entry.mount_point.length == 0){ continue; }
3265
 
                        if (!entry.mount_point.has_prefix("/")){ continue; }
3266
 
                        
3267
 
                        string mount_path = path_combine(
3268
 
                                target_path, entry.mount_point);
3269
 
                                
3270
 
                        if (entry.is_comment
3271
 
                                || entry.is_empty_line
3272
 
                                || (mount_path.length == 0)){
3273
 
                                
3274
 
                                continue;
3275
 
                        }
3276
 
 
3277
 
                        if (!dir_exists(mount_path)){
3278
 
                                
3279
 
                                log_msg("Created mount point on target device: %s".printf(
3280
 
                                        entry.mount_point));
3281
 
                                        
3282
 
                                dir_create(mount_path);
3283
 
                        }
3284
 
                }
3285
 
        }
3286
 
 
3287
 
        public void fix_crypttab_file(string target_path){
3288
 
                string crypttab_path = target_path + "etc/crypttab";
3289
 
                var crypttab_list = CryptTabEntry.read_file(crypttab_path);
3290
 
 
3291
 
                // add option "nofail" to existing entries
3292
 
                
3293
 
                foreach(var entry in crypttab_list){
3294
 
                        entry.append_option("nofail");
3295
 
                }
3296
 
 
3297
 
                // check and add entries for mapped devices which are encrypted
3298
 
                
3299
 
                foreach(var mnt in mount_list){
3300
 
                        if ((mnt.device != null) && (mnt.device.is_on_encrypted_partition())){
3301
 
                                
3302
 
                                // find existing
3303
 
                                var entry = CryptTabEntry.find_entry_by_uuid(
3304
 
                                        crypttab_list, mnt.device.parent.uuid);
3305
 
 
3306
 
                                // add if missing
3307
 
                                if (entry == null){
3308
 
                                        entry = new CryptTabEntry();
3309
 
                                        crypttab_list.add(entry);
3310
 
                                }
3311
 
                                
3312
 
                                // set custom values
3313
 
                                entry.device_uuid = mnt.device.parent.uuid;
3314
 
                                entry.mapped_name = "luks-%s".printf(mnt.device.parent.uuid);
3315
 
                                entry.keyfile = "none";
3316
 
                                entry.options = "luks,nofail";
3317
 
                        }
3318
 
                }
3319
 
 
3320
 
                CryptTabEntry.write_file(crypttab_list, crypttab_path, false);
3321
 
 
3322
 
                log_msg(_("Updated /etc/crypttab on target device") + ": %s".printf(crypttab_path));
3323
 
        }
3324
 
 
3325
 
        public Device? dst_root{
3326
 
                get {
3327
 
                        foreach(var mnt in mount_list){
3328
 
                                if (mnt.mount_point == "/"){
3329
 
                                        return mnt.device;
3330
 
                                }
3331
 
                        }
3332
 
                        return null;
3333
 
                }
3334
 
        }
3335
 
 
3336
 
        public Device? dst_boot{
3337
 
                get {
3338
 
                        foreach(var mnt in mount_list){
3339
 
                                if (mnt.mount_point == "/boot"){
3340
 
                                        return mnt.device;
3341
 
                                }
3342
 
                        }
3343
 
                        return null;
3344
 
                }
3345
 
        }
3346
 
 
3347
 
        public Device? dst_efi{
3348
 
                get {
3349
 
                        foreach(var mnt in mount_list){
3350
 
                                if (mnt.mount_point == "/boot/efi"){
3351
 
                                        return mnt.device;
3352
 
                                }
3353
 
                        }
3354
 
                        return null;
3355
 
                }
3356
 
        }
3357
 
 
3358
 
        public Device? dst_home{
3359
 
                get {
3360
 
                        foreach(var mnt in mount_list){
3361
 
                                if (mnt.mount_point == "/home"){
3362
 
                                        return mnt.device;
3363
 
                                }
3364
 
                        }
3365
 
                        return null;
3366
 
                }
3367
 
        }
3368
 
        
3369
 
        //app config
3370
 
 
3371
 
        public void save_app_config(){
3372
 
 
3373
 
                log_debug("load_app_config()");
3374
 
                
3375
 
                var config = new Json.Object();
3376
 
 
3377
 
                if ((repo != null) && repo.available()){
3378
 
                        // save backup device uuid
3379
 
                        config.set_string_member("backup_device_uuid",
3380
 
                                (repo.device == null) ? "" : repo.device.uuid);
3381
 
                        
3382
 
                        // save parent uuid if backup device has parent
3383
 
                        config.set_string_member("parent_device_uuid",
3384
 
                                (repo.device.has_parent()) ? repo.device.parent.uuid : "");
3385
 
                }
3386
 
                else{
3387
 
                        // retain values for next run
3388
 
                        config.set_string_member("backup_device_uuid", backup_uuid);
3389
 
                        config.set_string_member("parent_device_uuid", backup_parent_uuid); 
3390
 
                }
3391
 
 
3392
 
                config.set_string_member("use_snapshot_path_user",
3393
 
                        repo.use_snapshot_path_custom.to_string());
3394
 
                        
3395
 
                config.set_string_member("snapshot_path_user",
3396
 
                        repo.snapshot_path_user.to_string());
3397
 
 
3398
 
                config.set_string_member("schedule_monthly", schedule_monthly.to_string());
3399
 
                config.set_string_member("schedule_weekly", schedule_weekly.to_string());
3400
 
                config.set_string_member("schedule_daily", schedule_daily.to_string());
3401
 
                config.set_string_member("schedule_hourly", schedule_hourly.to_string());
3402
 
                config.set_string_member("schedule_boot", schedule_boot.to_string());
3403
 
 
3404
 
                config.set_string_member("count_monthly", count_monthly.to_string());
3405
 
                config.set_string_member("count_weekly", count_weekly.to_string());
3406
 
                config.set_string_member("count_daily", count_daily.to_string());
3407
 
                config.set_string_member("count_hourly", count_hourly.to_string());
3408
 
                config.set_string_member("count_boot", count_boot.to_string());
3409
 
 
3410
 
                config.set_string_member("snapshot_size", first_snapshot_size.to_string());
3411
 
                config.set_string_member("snapshot_count", first_snapshot_count.to_string());
3412
 
 
3413
 
                Json.Array arr = new Json.Array();
3414
 
                foreach(string path in exclude_list_user){
3415
 
                        arr.add_string_element(path);
3416
 
                }
3417
 
                config.set_array_member("exclude",arr);
3418
 
 
3419
 
                arr = new Json.Array();
3420
 
                foreach(var name in exclude_app_names){
3421
 
                        arr.add_string_element(name);
3422
 
                }
3423
 
                config.set_array_member("exclude-apps",arr);
3424
 
 
3425
 
                var json = new Json.Generator();
3426
 
                json.pretty = true;
3427
 
                json.indent = 2;
3428
 
                var node = new Json.Node(NodeType.OBJECT);
3429
 
                node.set_object(config);
3430
 
                json.set_root(node);
3431
 
 
3432
 
                try{
3433
 
                        json.to_file(this.app_conf_path);
3434
 
                } catch (Error e) {
3435
 
                log_error (e.message);
3436
 
            }
3437
 
 
3438
 
            if ((app_mode == "")||(LOG_DEBUG)){
3439
 
                        log_msg(_("App config saved") + ": '%s'".printf(this.app_conf_path));
3440
 
                }
3441
 
        }
3442
 
 
3443
 
        public void load_app_config(){
3444
 
 
3445
 
                log_debug("load_app_config()");
3446
 
                
3447
 
                var f = File.new_for_path(this.app_conf_path);
3448
 
                if (!f.query_exists()) {
3449
 
                        first_run = true;
3450
 
                        log_debug("first run mode: config file not found");
3451
 
                        initialize_repo();
3452
 
                        return;
3453
 
                }
3454
 
 
3455
 
                var parser = new Json.Parser();
3456
 
        try{
3457
 
                        parser.load_from_file(this.app_conf_path);
3458
 
                } catch (Error e) {
3459
 
                log_error (e.message);
3460
 
            }
3461
 
        var node = parser.get_root();
3462
 
        var config = node.get_object();
3463
 
 
3464
 
                // initialize repo using config file values
3465
 
 
3466
 
                backup_uuid = json_get_string(config,"backup_device_uuid", backup_uuid);
3467
 
                backup_parent_uuid = json_get_string(config,"parent_device_uuid", backup_parent_uuid);
3468
 
                
3469
 
                initialize_repo();
3470
 
 
3471
 
        this.schedule_monthly = json_get_bool(config,"schedule_monthly",schedule_monthly);
3472
 
                this.schedule_weekly = json_get_bool(config,"schedule_weekly",schedule_weekly);
3473
 
                this.schedule_daily = json_get_bool(config,"schedule_daily",schedule_daily);
3474
 
                this.schedule_hourly = json_get_bool(config,"schedule_hourly",schedule_hourly);
3475
 
                this.schedule_boot = json_get_bool(config,"schedule_boot",schedule_boot);
3476
 
 
3477
 
                this.count_monthly = json_get_int(config,"count_monthly",count_monthly);
3478
 
                this.count_weekly = json_get_int(config,"count_weekly",count_weekly);
3479
 
                this.count_daily = json_get_int(config,"count_daily",count_daily);
3480
 
                this.count_hourly = json_get_int(config,"count_hourly",count_hourly);
3481
 
                this.count_boot = json_get_int(config,"count_boot",count_boot);
3482
 
 
3483
 
                Main.first_snapshot_size = json_get_int64(config,"snapshot_size",
3484
 
                        Main.first_snapshot_size);
3485
 
                        
3486
 
                Main.first_snapshot_count = json_get_int64(config,"snapshot_count",
3487
 
                        Main.first_snapshot_count);
3488
 
                
3489
 
                exclude_list_user.clear();
3490
 
                
3491
 
                if (config.has_member ("exclude")){
3492
 
                        foreach (Json.Node jnode in config.get_array_member ("exclude").get_elements()) {
3493
 
                                
3494
 
                                string path = jnode.get_string();
3495
 
                                
3496
 
                                if (!exclude_list_user.contains(path)
3497
 
                                        && !exclude_list_default.contains(path)
3498
 
                                        && !exclude_list_home.contains(path)){
3499
 
                                                
3500
 
                                        exclude_list_user.add(path);
3501
 
                                }
3502
 
                        }
3503
 
                }
3504
 
 
3505
 
                exclude_app_names.clear();
3506
 
 
3507
 
                if (config.has_member ("exclude-apps")){
3508
 
                        var apps = config.get_array_member("exclude-apps");
3509
 
                        foreach (Json.Node jnode in apps.get_elements()) {
3510
 
                                
3511
 
                                string name = jnode.get_string();
3512
 
                                
3513
 
                                if (!exclude_app_names.contains(name)){
3514
 
                                        exclude_app_names.add(name);
3515
 
                                }
3516
 
                        }
3517
 
                }
3518
 
 
3519
 
                if ((app_mode == "")||(LOG_DEBUG)){
3520
 
                        log_msg(_("App config loaded") + ": '%s'".printf(this.app_conf_path));
3521
 
                }
3522
 
        }
3523
 
 
3524
 
        public void initialize_repo(){
3525
 
 
3526
 
                log_debug("backup_uuid=%s".printf(backup_uuid));
3527
 
                log_debug("backup_parent_uuid=%s".printf(backup_parent_uuid));
3528
 
                
3529
 
                if (backup_uuid.length > 0){
3530
 
                        log_debug("repo: creating from uuid");
3531
 
                        repo = new SnapshotRepo.from_uuid(backup_uuid, null);
3532
 
 
3533
 
                        if ((repo == null) || !repo.available()){
3534
 
                                if (backup_parent_uuid.length > 0){
3535
 
                                        log_debug("repo: creating from parent uuid");
3536
 
                                        repo = new SnapshotRepo.from_uuid(backup_parent_uuid, null);
3537
 
                                }
3538
 
                        }
3539
 
                }
3540
 
                else{
3541
 
                        if (sys_root != null){
3542
 
                                log_debug("repo: uuid is empty, creating from root device");
3543
 
                                repo = new SnapshotRepo.from_device(sys_root, null);
3544
 
                        }
3545
 
                        else{
3546
 
                                log_debug("repo: root device is null");
3547
 
                                repo = new SnapshotRepo.from_null(null);
3548
 
                        }
3549
 
                }
3550
 
 
3551
 
                // initialize repo using command line parameter
3552
 
                 
3553
 
                if (cmd_backup_device.length > 0){
3554
 
                        var cmd_dev = Device.get_device_by_name(cmd_backup_device);
3555
 
                        if (cmd_dev != null){
3556
 
                                log_debug("repo: creating from command argument: %s".printf(cmd_backup_device));
3557
 
                                repo = new SnapshotRepo.from_device(cmd_dev, null);
3558
 
                                
3559
 
                                // TODO: move this code to main window
3560
 
                        }
3561
 
                        else{
3562
 
                                log_error(_("Could not find device") + ": '%s'".printf(cmd_backup_device));
3563
 
                                exit_app();
3564
 
                                exit(1);
3565
 
                        }
3566
 
                }
3567
 
 
3568
 
                /* Note: In command-line mode, user will be prompted for backup device */
3569
 
 
3570
 
                /* The backup device specified in config file will be mounted at this point if:
3571
 
                 * 1) app is running in GUI mode, OR
3572
 
                 * 2) app is running command mode without backup device argument
3573
 
                 * */
3574
 
        }
3575
 
        
3576
 
        //core functions
3577
 
 
3578
 
        public void update_partitions(){
3579
 
 
3580
 
                log_debug("update_partitions()");
3581
 
                
3582
 
                partitions.clear();
3583
 
                
3584
 
                partitions = Device.get_filesystems();
3585
 
 
3586
 
                foreach(var pi in partitions){
3587
 
 
3588
 
                        // sys_root and sys_home will be detected by detect_system_devices()
3589
 
                        if ((repo != null) && (repo.device != null) && (pi.uuid == repo.device.uuid)){
3590
 
                                repo.device = pi;
3591
 
                        }
3592
 
                        
3593
 
                        if (pi.is_mounted){
3594
 
                                pi.dist_info = LinuxDistro.get_dist_info(pi.mount_points[0].mount_point).full_name();
3595
 
                        }
3596
 
                }
3597
 
                
3598
 
                if (partitions.size == 0){
3599
 
                        log_error("ts: " + _("Failed to get partition list."));
3600
 
                }
3601
 
 
3602
 
                log_debug("partition list updated");
3603
 
        }
3604
 
 
3605
 
        public void detect_system_devices(){
3606
 
 
3607
 
                log_debug("detect_system_devices()");
3608
 
 
3609
 
                sys_root = null;
3610
 
                sys_boot = null;
3611
 
                sys_efi = null;
3612
 
                sys_home = null;
3613
 
                
3614
 
                foreach(Device pi in partitions){
3615
 
                        foreach(var mp in pi.mount_points){
3616
 
                                if (mp.mount_point == "/"){
3617
 
                                        sys_root = pi;
3618
 
                                        if ((app_mode == "")||(LOG_DEBUG)){
3619
 
                                                log_msg(_("/ is mapped to device") + ": %s, UUID=%s".printf(pi.device,pi.uuid));
3620
 
                                        }
3621
 
                                }
3622
 
 
3623
 
                                if (mp.mount_point == "/home"){
3624
 
                                        sys_home = pi;
3625
 
                                        if ((app_mode == "")||(LOG_DEBUG)){
3626
 
                                                log_msg(_("/home is mapped to device") + ": %s, UUID=%s".printf(pi.device,pi.uuid));
3627
 
                                        }
3628
 
                                }
3629
 
 
3630
 
                                if (mp.mount_point == "/boot"){
3631
 
                                        sys_boot = pi;
3632
 
                                        if ((app_mode == "")||(LOG_DEBUG)){
3633
 
                                                log_msg(_("/boot is mapped to device") + ": %s, UUID=%s".printf(pi.device,pi.uuid));
3634
 
                                        }
3635
 
                                }
3636
 
 
3637
 
                                if (mp.mount_point == "/boot/efi"){
3638
 
                                        sys_efi = pi;
3639
 
                                        if ((app_mode == "")||(LOG_DEBUG)){
3640
 
                                                log_msg(_("/boot/efi is mapped to device") + ": %s, UUID=%s".printf(pi.device,pi.uuid));
3641
 
                                        }
3642
 
                                }
3643
 
                        }
3644
 
                }
3645
 
        }
3646
 
 
3647
 
        public bool mount_target_device(Gtk.Window? parent_win){
3648
 
                /* Note:
3649
 
                 * Target device will be mounted explicitly to /mnt/timeshift/restore
3650
 
                 * Existing mount points are not used since we need to mount other devices in sub-directories
3651
 
                 * */
3652
 
 
3653
 
                log_debug("mount_target_device()");
3654
 
                
3655
 
                if (restore_target == null){
3656
 
                        return false;
3657
 
                }
3658
 
        
3659
 
                //check and create restore mount point for restore
3660
 
                mount_point_restore = mount_point_app + "/restore";
3661
 
                dir_create(mount_point_restore);
3662
 
 
3663
 
                /*var already_mounted = false;
3664
 
                var dev_mounted = Device.get_device_by_path(mount_point_restore);
3665
 
                if ((dev_mounted != null)
3666
 
                        && (dev_mounted.uuid == restore_target.uuid)){
3667
 
 
3668
 
                        foreach(var mp in dev_mounted.mount_points){
3669
 
                                if ((mp.mount_point == mount_point_restore)
3670
 
                                        && (mp.mount_options == "subvol=@")){
3671
 
                                                
3672
 
                                         = true;
3673
 
                                        return; //already_mounted
3674
 
                                }
3675
 
                        }
3676
 
                }*/
3677
 
                
3678
 
                // unmount
3679
 
                unmount_target_device();
3680
 
 
3681
 
                // unlock encrypted device
3682
 
                if (restore_target.is_encrypted_partition()){
3683
 
                        
3684
 
                        string msg_out, msg_err;
3685
 
                        
3686
 
                        restore_target = Device.luks_unlock(
3687
 
                                restore_target, "", "", parent_win, out msg_out, out msg_err);
3688
 
 
3689
 
                        //exit if not found
3690
 
                        if (restore_target == null){
3691
 
                                log_error(_("Target device not specified!"));
3692
 
                                return false;
3693
 
                        }
3694
 
 
3695
 
                        //update mount entry
3696
 
                        foreach (MountEntry mnt in mount_list) {
3697
 
                                if (mnt.mount_point == "/"){
3698
 
                                        mnt.device = restore_target;
3699
 
                                        break;
3700
 
                                }
3701
 
                        }
3702
 
                }
3703
 
 
3704
 
                // mount root device
3705
 
                if (restore_target.fstype == "btrfs"){
3706
 
 
3707
 
                        //check subvolume layout
3708
 
 
3709
 
                        bool supported = check_btrfs_layout(dst_root, dst_home);
3710
 
                        
3711
 
                        if (!supported && snapshot_to_restore.has_subvolumes()){
3712
 
                                string msg = _("The target partition has an unsupported subvolume layout.") + "\n";
3713
 
                                msg += _("Only ubuntu-type layouts with @ and @home subvolumes are currently supported.");
3714
 
 
3715
 
                                if (app_mode == ""){
3716
 
                                        string title = _("Unsupported Subvolume Layout");
3717
 
                                        gtk_messagebox(title, msg, null, true);
3718
 
                                }
3719
 
                                else{
3720
 
                                        log_error("\n" + msg);
3721
 
                                }
3722
 
 
3723
 
                                return false;
3724
 
                        }
3725
 
                }
3726
 
 
3727
 
                // mount all devices
3728
 
                foreach (var mnt in mount_list) {
3729
 
                        
3730
 
                        // unlock encrypted device
3731
 
                        if (mnt.device.is_encrypted_partition()){
3732
 
 
3733
 
                                string msg_out, msg_err;
3734
 
                
3735
 
                                mnt.device = Device.luks_unlock(
3736
 
                                        mnt.device, "", "", parent_win, out msg_out, out msg_err);
3737
 
 
3738
 
                                //exit if not found
3739
 
                                if (mnt.device == null){
3740
 
                                        return false;
3741
 
                                }
3742
 
                        }
3743
 
 
3744
 
                        string mount_options = "";
3745
 
                        if (mnt.device.fstype == "btrfs"){
3746
 
                                if (mnt.mount_point == "/"){
3747
 
                                        mount_options = "subvol=@";
3748
 
                                }
3749
 
                                else if (mnt.mount_point == "/home"){
3750
 
                                        mount_options = "subvol=@home";
3751
 
                                }
3752
 
                        }
3753
 
 
3754
 
                        if (!Device.mount(mnt.device.uuid, mount_point_restore + mnt.mount_point, mount_options)){
3755
 
                                return false;
3756
 
                        }
3757
 
                }
3758
 
 
3759
 
                return true;
3760
 
        }
3761
 
 
3762
 
        public void unmount_target_device(bool exit_on_error = true){
3763
 
                if (mount_point_restore == null) { return; }
3764
 
 
3765
 
                log_debug("unmount_target_device()");
3766
 
                
3767
 
                //unmount the target device only if it was mounted by application
3768
 
                if (mount_point_restore.has_prefix(mount_point_app)){   //always true
3769
 
                        unmount_device(mount_point_restore,exit_on_error);
3770
 
                }
3771
 
                else{
3772
 
                        //don't unmount
3773
 
                }
3774
 
        }
3775
 
 
3776
 
        public bool unmount_device(string mount_point, bool exit_on_error = true){
3777
 
                bool is_unmounted = Device.unmount(mount_point);
3778
 
                if (!is_unmounted){
3779
 
                        if (exit_on_error){
3780
 
                                if (app_mode == ""){
3781
 
                                        string title = _("Critical Error");
3782
 
                                        string msg = _("Failed to unmount device!") + "\n" + _("Application will exit");
3783
 
                                        gtk_messagebox(title, msg, null, true);
3784
 
                                }
3785
 
                                exit_app();
3786
 
                                exit(0);
3787
 
                        }
3788
 
                }
3789
 
                return is_unmounted;
3790
 
        }
3791
 
 
3792
 
        public SnapshotLocationStatus check_backup_location(out string message, out string details){
3793
 
                repo.check_status();
3794
 
                message = repo.status_message;
3795
 
                details = repo.status_details;
3796
 
                return repo.status_code;
3797
 
        }
3798
 
 
3799
 
        public bool check_btrfs_volume(Device dev, string subvol_names){
3800
 
 
3801
 
                log_debug("check_btrfs_volume():%s".printf(subvol_names));
3802
 
                
3803
 
                string mnt_btrfs = mount_point_app + "/btrfs";
3804
 
                dir_create(mnt_btrfs);
3805
 
 
3806
 
                Device.unmount(mnt_btrfs);
3807
 
                Device.mount(dev.uuid, mnt_btrfs);
3808
 
 
3809
 
                bool supported = true;
3810
 
 
3811
 
                foreach(string subvol_name in subvol_names.split(",")){
3812
 
                        supported = supported && dir_exists(path_combine(mnt_btrfs,subvol_name));
3813
 
                }
3814
 
 
3815
 
                if (Device.unmount(mnt_btrfs)){
3816
 
                        if (dir_exists(mnt_btrfs) && (dir_count(mnt_btrfs) == 0)){
3817
 
                                dir_delete(mnt_btrfs);
3818
 
                                log_debug(_("Removed mount directory: '%s'").printf(mnt_btrfs));
3819
 
                        }
3820
 
                }
3821
 
 
3822
 
                return supported;
3823
 
        }
3824
 
 
3825
 
        public bool backup_device_online(){
3826
 
                /*if (snapshot_device != null){
3827
 
                        //mount_backup_device(null);
3828
 
                        if (Device.get_device_mount_points(snapshot_device.uuid).size > 0){
3829
 
                                return true;
3830
 
                        }
3831
 
                }*/
3832
 
 
3833
 
                string message, details;
3834
 
                var status = App.check_backup_location(out message, out details);
3835
 
 
3836
 
                switch(status){
3837
 
                case SnapshotLocationStatus.NO_SNAPSHOTS_HAS_SPACE:
3838
 
                case SnapshotLocationStatus.NO_SNAPSHOTS_NO_SPACE:
3839
 
                case SnapshotLocationStatus.HAS_SNAPSHOTS_HAS_SPACE:
3840
 
                case SnapshotLocationStatus.HAS_SNAPSHOTS_NO_SPACE:
3841
 
                        return true;
3842
 
                default:
3843
 
                        //gtk_messagebox(message,details, this, true);
3844
 
                        return false;
3845
 
                }
3846
 
                //return false;
3847
 
        }
3848
 
 
3849
 
        public int64 estimate_system_size(){
3850
 
 
3851
 
                log_debug("estimate_system_size()");
3852
 
                
3853
 
                if (Main.first_snapshot_size > 0){
3854
 
                        return Main.first_snapshot_size;
3855
 
                }
3856
 
                else if (live_system()){
3857
 
                        return 0;
3858
 
                }
3859
 
 
3860
 
                try {
3861
 
                        thread_estimate_running = true;
3862
 
                        thr_success = false;
3863
 
                        Thread.create<void> (estimate_system_size_thread, true);
3864
 
                } catch (ThreadError e) {
3865
 
                        thread_estimate_running = false;
3866
 
                        thr_success = false;
3867
 
                        log_error (e.message);
3868
 
                }
3869
 
 
3870
 
                while (thread_estimate_running){
3871
 
                        gtk_do_events ();
3872
 
                        Thread.usleep((ulong) GLib.TimeSpan.MILLISECOND * 100);
3873
 
                }
3874
 
 
3875
 
                save_app_config();
3876
 
 
3877
 
                log_debug("estimate_system_size(): ok");
3878
 
                
3879
 
                return Main.first_snapshot_size;
3880
 
        }
3881
 
 
3882
 
        public void estimate_system_size_thread(){
3883
 
                thread_estimate_running = true;
3884
 
 
3885
 
                string cmd = "";
3886
 
                string std_out;
3887
 
                string std_err;
3888
 
                int ret_val;
3889
 
                int64 required_space = 0;
3890
 
                int64 file_count = 0;
3891
 
 
3892
 
                try{
3893
 
 
3894
 
                        log_msg("Using temp dir '%s'".printf(TEMP_DIR));
3895
 
 
3896
 
                        string file_exclude_list = path_combine(TEMP_DIR, "exclude.list");
3897
 
                        var f = File.new_for_path(file_exclude_list);
3898
 
                        if (f.query_exists()){
3899
 
                                f.delete();
3900
 
                        }
3901
 
 
3902
 
                        string file_log = path_combine(TEMP_DIR, "rsync.log");
3903
 
                        f = File.new_for_path(file_log);
3904
 
                        if (f.query_exists()){
3905
 
                                f.delete();
3906
 
                        }
3907
 
 
3908
 
                        string dir_empty = path_combine(TEMP_DIR, "empty");
3909
 
                        f = File.new_for_path(dir_empty);
3910
 
                        if (!f.query_exists()){
3911
 
                                dir_create(dir_empty);
3912
 
                        }
3913
 
 
3914
 
                        save_exclude_list_for_backup(TEMP_DIR);
3915
 
                        
3916
 
                        cmd  = "LC_ALL=C ; rsync -ai --delete --numeric-ids --relative --stats --dry-run --delete-excluded --exclude-from='%s' /. '%s' &> '%s'".printf(file_exclude_list, dir_empty, file_log);
3917
 
 
3918
 
                        log_debug(cmd);
3919
 
                        ret_val = exec_script_sync(cmd, out std_out, out std_err);
3920
 
 
3921
 
                        if (file_exists(file_log)){
3922
 
                                cmd = "cat '%s' | awk '/Total file size/ {print $4}'".printf(file_log);
3923
 
                                ret_val = exec_script_sync(cmd, out std_out, out std_err);
3924
 
                                if (ret_val == 0){
3925
 
                                        required_space = long.parse(std_out.replace(",","").strip());
3926
 
 
3927
 
                                        cmd = "wc -l '%s'".printf(escape_single_quote(file_log));
3928
 
                                        ret_val = exec_script_sync(cmd, out std_out, out std_err);
3929
 
                                        if (ret_val == 0){
3930
 
                                                file_count = long.parse(std_out.split(" ")[0].strip());
3931
 
                                        }
3932
 
                                        
3933
 
                                        thr_success = true;
3934
 
                                }
3935
 
                                else{
3936
 
                                        log_error (_("Failed to estimate system size"));
3937
 
                                        log_error (std_err);
3938
 
                                        thr_success = false;
3939
 
                                }
3940
 
                        }
3941
 
                        else{
3942
 
                                log_error (_("Failed to estimate system size"));
3943
 
                                log_error (std_err);
3944
 
                                log_error (std_out);
3945
 
                                thr_success = false;
3946
 
                        }
3947
 
                }
3948
 
                catch(Error e){
3949
 
                        log_error (e.message);
3950
 
                        thr_success = false;
3951
 
                }
3952
 
 
3953
 
                if ((required_space == 0) && (sys_root != null)){
3954
 
                        required_space = sys_root.used_bytes;
3955
 
                }
3956
 
 
3957
 
                Main.first_snapshot_size = required_space;
3958
 
                Main.first_snapshot_count = file_count;
3959
 
 
3960
 
                log_debug("First snapshot size: %s".printf(format_file_size(required_space)));
3961
 
                log_debug("File count: %lld".printf(first_snapshot_count));
3962
 
 
3963
 
                thread_estimate_running = false;
3964
 
        }
3965
 
 
3966
 
        //cron jobs
3967
 
 
3968
 
        public void cron_job_update(){
3969
 
 
3970
 
                if (live_system()) { return; }
3971
 
 
3972
 
                // check and remove crontab entries created by previous versions of timeshift
3973
 
 
3974
 
                string entry = "*/30 * * * * timeshift --backup";
3975
 
                CronTab.remove_job(entry);
3976
 
 
3977
 
                foreach(string interval in new string[] {"@monthly","@weekly","@daily"}){
3978
 
                        entry = "%s timeshift --backup".printf(interval);
3979
 
                        CronTab.remove_job(entry);
3980
 
                }
3981
 
 
3982
 
                //entry = "^@(daily|weekly|monthly|hourly) timeshift --backup$";
3983
 
                //CronTab.remove_job(entry, true);
3984
 
 
3985
 
                //entry = "^@reboot sleep [0-9]*m && timeshift --backup$";
3986
 
                //CronTab.remove_job(entry, true);
3987
 
 
3988
 
                // update crontab entries
3989
 
 
3990
 
                string entry_boot = "@reboot sleep %dm && timeshift --backup".printf(startup_delay_interval_mins);
3991
 
                //entry_boot += " #timeshift-16.10-hourly";
3992
 
                
3993
 
                string entry_hourly = "@hourly timeshift --backup";
3994
 
                //entry_hourly += " #timeshift-16.10-boot";
3995
 
                
3996
 
                if (scheduled){
3997
 
                        CronTab.add_job(entry_boot);
3998
 
                        CronTab.add_job(entry_hourly);
3999
 
                }
4000
 
                else{
4001
 
                        CronTab.remove_job(entry_boot);
4002
 
                        CronTab.remove_job(entry_hourly);
4003
 
                }
4004
 
 
4005
 
                /*string cmd = "timeshift --backup";
4006
 
                
4007
 
                if (scheduled){
4008
 
                        CronTab.add_script_hourly("timeshift-backup", cmd);
4009
 
                }
4010
 
                else{
4011
 
                        CronTab.remove_script_hourly("timeshift-backup");
4012
 
                }*/
4013
 
        }
4014
 
 
4015
 
        //cleanup
4016
 
 
4017
 
        public void clean_logs(){
4018
 
 
4019
 
                log_debug("clean_logs()");
4020
 
                
4021
 
                Gee.ArrayList<string> list = new Gee.ArrayList<string>();
4022
 
 
4023
 
                try{
4024
 
                        var dir = File.new_for_path (log_dir);
4025
 
                        var enumerator = dir.enumerate_children ("*", 0);
4026
 
 
4027
 
                        var info = enumerator.next_file ();
4028
 
                        string path;
4029
 
 
4030
 
                        while (info != null) {
4031
 
                                if (info.get_file_type() == FileType.REGULAR) {
4032
 
                                        path = log_dir + "/" + info.get_name();
4033
 
                                        if (path != log_file) {
4034
 
                                                list.add(path);
4035
 
                                        }
4036
 
                                }
4037
 
                                info = enumerator.next_file ();
4038
 
                        }
4039
 
 
4040
 
                        CompareDataFunc<string> compare_func = (a, b) => {
4041
 
                                return strcmp(a,b);
4042
 
                        };
4043
 
 
4044
 
                        list.sort((owned) compare_func);
4045
 
 
4046
 
                        if (list.size > 500){
4047
 
                                for(int k=0; k<100; k++){
4048
 
                                        var file = File.new_for_path (list[k]);
4049
 
                                        if (file.query_exists()){
4050
 
                                                file.delete();
4051
 
                                        }
4052
 
                                }
4053
 
                                log_msg(_("Older log files removed"));
4054
 
                        }
4055
 
                }
4056
 
                catch(Error e){
4057
 
                        log_error (e.message);
4058
 
                }
4059
 
        }
4060
 
 
4061
 
        public void exit_app (){
4062
 
 
4063
 
                log_debug("exit_app()");
4064
 
                
4065
 
                if (app_mode == ""){
4066
 
                        //update app config only in GUI mode
4067
 
                        save_app_config();
4068
 
                }
4069
 
 
4070
 
                cron_job_update();
4071
 
 
4072
 
                unmount_target_device(false);
4073
 
 
4074
 
                clean_logs();
4075
 
 
4076
 
                app_lock.remove();
4077
 
 
4078
 
                //Gtk.main_quit ();
4079
 
        }
4080
 
}
4081
 
 
4082
 
 
4083
 
 
4084
 
 
4085
 
 
4086