Commit 10edd8b3 authored by Michael Terry's avatar Michael Terry

sanity-check backup after each run

parent 62fe0739
......@@ -48,10 +48,14 @@ libcommon_la_VALASOURCES = \
Network.vala \
Operation.vala \
OperationBackup.vala \
OperationFiles.vala \
OperationRestore.vala \
OperationStatus.vala \
OperationFiles.vala \
OperationVerify.vala \
PythonChecker.vala \
RecursiveDelete.vala \
RecursiveMove.vala \
RecursiveOp.vala \
SimpleSettings.vala \
ToolPlugin.vala
......
......@@ -95,12 +95,13 @@ public abstract class Operation : Object
backend = Backend.get_default();
}
public async virtual void start()
public async virtual void start(bool try_claim_bus = true)
{
action_desc_changed(_("Preparing…"));
try {
claim_bus();
if (try_claim_bus)
claim_bus();
}
catch (Error e) {
raise_error(e.message, null);
......@@ -182,7 +183,7 @@ public abstract class Operation : Object
job.done.connect((d, o, c, detail) => {operation_finished.begin(d, o, c, detail);});
job.raise_error.connect((d, s, detail) => {raise_error(s, detail);});
job.action_desc_changed.connect((d, s) => {action_desc_changed(s);});
job.action_file_changed.connect((d, f, b) => {action_file_changed(f, b);});
job.action_file_changed.connect((d, f, b) => {send_action_file_changed(f, b);});
job.progress.connect((d, p) => {progress(p);});
job.question.connect((d, t, m) => {question(t, m);});
job.is_full.connect((first) => {is_full(first);});
......@@ -195,6 +196,11 @@ public abstract class Operation : Object
});
}
protected virtual void send_action_file_changed(File file, bool actual)
{
action_file_changed(file, actual);
}
public void set_passphrase(string? passphrase)
{
needs_password = false;
......@@ -222,7 +228,33 @@ public abstract class Operation : Object
*/
return null;
}
protected async void chain_op(Operation subop, string desc)
{
/**
* Sometimes an operation wants to chain to a separate operation.
* Here is the glue to make that happen.
*/
subop.ref();
subop.done.connect((s, c, d) => {done(s, c, d); subop.unref();});
subop.raise_error.connect((e, d) => {raise_error(e, d);});
subop.progress.connect((p) => {progress(p);});
subop.passphrase_required.connect(() => {
passphrase_required();
subop.needs_password = needs_password;
subop.passphrase = passphrase;
});
subop.question.connect((t, m) => {question(t, m);});
subop.set_state(get_state());
job = subop.job;
action_desc_changed(desc);
progress(0);
yield subop.start(false);
}
uint bus_id = 0;
void claim_bus() throws BackupError
{
......@@ -239,7 +271,8 @@ public abstract class Operation : Object
void unclaim_bus()
{
Bus.unown_name(bus_id);
if (bus_id > 0)
Bus.unown_name(bus_id);
}
}
......
......@@ -23,6 +23,8 @@ namespace DejaDup {
public class OperationBackup : Operation
{
File metadir;
public OperationBackup() {
Object(mode: ToolJob.Mode.BACKUP);
}
......@@ -32,10 +34,21 @@ public class OperationBackup : Operation
/* If successfully completed, update time of last backup and run base operation_finished */
if (success)
DejaDup.update_last_run_timestamp(DejaDup.TimestampType.BACKUP);
yield base.operation_finished(job, success, cancelled, detail);
if (metadir != null)
new RecursiveDelete(metadir).start();
yield chain_op(new OperationVerify(), _("Verifying backup…"));
}
protected override void send_action_file_changed(File file, bool actual)
{
// Intercept action_file_changed signals and ignore them if they are
// metadata file, the user doesn't need to see them.
if (!file.has_prefix(metadir))
base.send_action_file_changed(file, actual);
}
protected override List<string>? make_argv()
{
var settings = get_settings();
......@@ -54,9 +67,20 @@ public class OperationBackup : Operation
job.excludes.prepend(s);
foreach (File s in include_list)
job.includes.prepend(s);
// Insert deja-dup meta info directory
string cachedir = Environment.get_user_cache_dir();
try {
metadir = File.new_for_path(Path.build_filename(cachedir, Config.PACKAGE, "metadata"));
fill_metadir();
job.includes.prepend(metadir);
}
catch (Error e) {
warning("%s\n", e.message);
}
job.local = File.new_for_path("/");
return null;
}
......@@ -99,6 +123,24 @@ public class OperationBackup : Operation
return rv;
}
void fill_metadir() throws Error
{
if (metadir == null)
return;
// Delete old dir, if any, and replace it
new RecursiveDelete(metadir).start();
metadir.make_directory_with_parents(null);
// Put a file in there that is one part always constant, and one part
// always different, for basic sanity checking. This way, it will be
// included in every backup, but we can still check its contents for
// corruption. We'll stuff seconds-since-epoch in it.
var now = new DateTime.now_utc();
var msg = "This folder can be safely deleted.\n%s".printf(now.format("%s"));
FileUtils.set_contents(Path.build_filename(metadir.get_path(), "README"), msg);
}
}
} // end namespace
......
......@@ -46,20 +46,15 @@ public class OperationRestore : Operation
mode: ToolJob.Mode.RESTORE);
}
public async override void start()
public async override void start(bool try_claim_bus = true)
{
action_desc_changed(_("Restoring files…"));
yield base.start();
}
protected override void connect_to_job()
{
base.connect_to_job();
job.restore_files = restore_files;
yield base.start(try_claim_bus);
}
protected override List<string>? make_argv()
{
job.restore_files = restore_files;
job.time = time;
job.local = File.new_for_path(dest);
return null;
......
/* -*- Mode: Vala; indent-tabs-mode: nil; tab-width: 2 -*- */
/*
This file is part of Déjà Dup.
For copyright information, see AUTHORS.
Déjà Dup is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Déjà Dup is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Déjà Dup. If not, see <http://www.gnu.org/licenses/>.
*/
using GLib;
namespace DejaDup {
/* This is meant to be used right after a successful OperationBackup to
verify the results. */
public class OperationVerify : Operation
{
File metadir;
File destdir;
public OperationVerify() {
Object(mode: ToolJob.Mode.RESTORE);
}
public async override void start(bool try_claim_bus = true)
{
action_desc_changed(_("Verifying backup…"));
yield base.start(try_claim_bus);
}
protected override void connect_to_job()
{
string cachedir = Environment.get_user_cache_dir();
metadir = File.new_for_path(Path.build_filename(cachedir, Config.PACKAGE, "metadata"));
job.restore_files.append(metadir);
destdir = File.new_for_path("/");
job.local = destdir;
base.connect_to_job();
}
internal async override void operation_finished(ToolJob job, bool success, bool cancelled, string? detail)
{
// Verify results
if (success) {
var verified = true;
string contents;
try {
FileUtils.get_contents(Path.build_filename(metadir.get_path(), "README"), out contents);
}
catch (Error e) {
verified = false;
}
if (verified) {
var lines = contents.split("\n");
verified = (lines[0] == "This folder can be safely deleted.");
}
if (!verified) {
raise_error(_("Your backup appears to be corrupted. You should delete the backup and try again."), null);
success = false;
}
}
new RecursiveDelete(metadir).start();
yield base.operation_finished(job, success, cancelled, detail);
}
}
} // end namespace
......@@ -19,7 +19,9 @@
using GLib;
internal class RecursiveDelete : RecursiveOp
namespace DejaDup {
public class RecursiveDelete : RecursiveOp
{
public RecursiveDelete(File source)
{
......@@ -57,3 +59,4 @@ internal class RecursiveDelete : RecursiveOp
}
}
} // namespace
......@@ -19,6 +19,8 @@
using GLib;
namespace DejaDup {
/**
* Recursively moves one directory into another, merging files. And by merge,
* I mean it overwrites. It skips any files it can't move and reports an
......@@ -27,7 +29,7 @@ using GLib;
* This is not optimized for remote files. It's mostly async, but it does the
* occasional sync operation.
*/
internal class RecursiveMove : RecursiveOp
public class RecursiveMove : RecursiveOp
{
public RecursiveMove(File source, File dest)
{
......@@ -162,3 +164,4 @@ internal class RecursiveMove : RecursiveOp
}
}
} // namespace
......@@ -19,7 +19,9 @@
using GLib;
internal abstract class RecursiveOp : Object
namespace DejaDup {
public abstract class RecursiveOp : Object
{
public signal void done();
public signal void raise_error(File src, File dst, string errstr);
......@@ -129,3 +131,4 @@ internal abstract class RecursiveOp : Object
}
}
} // namespace
......@@ -22,7 +22,11 @@ common/OperationBackup.vala
common/OperationFiles.vala
common/OperationRestore.vala
common/OperationStatus.vala
common/OperationVerify.vala
common/Operation.vala
common/RecursiveDelete.vala
common/RecursiveMove.vala
common/RecursiveOp.vala
common/SimpleSettings.vala
deja-dup/AssistantBackup.vala
deja-dup/AssistantOperation.vala
......@@ -39,9 +43,6 @@ preferences/preferences-main.vala
tools/duplicity/DuplicityInstance.vala
tools/duplicity/DuplicityJob.vala
tools/duplicity/DuplicityPlugin.vala
tools/duplicity/RecursiveDelete.vala
tools/duplicity/RecursiveMove.vala
tools/duplicity/RecursiveOp.vala
widgets/ConfigBool.vala
widgets/ConfigChoice.vala
widgets/ConfigDelete.vala
......
......@@ -11,7 +11,11 @@ common/OperationBackup.c
common/OperationFiles.c
common/OperationRestore.c
common/OperationStatus.c
common/OperationVerify.c
common/Operation.c
common/RecursiveDelete.c
common/RecursiveMove.c
common/RecursiveOp.c
common/SimpleSettings.c
deja-dup/AssistantBackup.c
deja-dup/AssistantOperation.c
......@@ -28,9 +32,6 @@ preferences/preferences-main.c
tools/duplicity/DuplicityInstance.c
tools/duplicity/DuplicityJob.c
tools/duplicity/DuplicityPlugin.c
tools/duplicity/RecursiveDelete.c
tools/duplicity/RecursiveMove.c
tools/duplicity/RecursiveOp.c
widgets/ConfigBool.c
widgets/ConfigChoice.c
widgets/ConfigDelete.c
......
......@@ -96,6 +96,7 @@ public enum Mode {
STATUS,
DRY,
BACKUP,
VERIFY,
CLEANUP,
RESTORE,
RESTORE_STATUS,
......@@ -113,6 +114,8 @@ string default_args(BackupRunner br, Mode mode = Mode.NONE, bool encrypted = fal
return "cleanup '--force' 'file://%s' '--gio' '--no-encryption' '--verbosity=9' '--gpg-options=--no-use-agent' '--archive-dir=%s/deja-dup' '--log-fd=?'".printf(backupdir, cachedir);
else if (mode == Mode.RESTORE)
return "'restore' '--gio' '--force' 'file://%s' '%s' '--no-encryption' '--verbosity=9' '--gpg-options=--no-use-agent' '--archive-dir=%s/deja-dup' '--log-fd=?'".printf(backupdir, restoredir, cachedir);
else if (mode == Mode.VERIFY)
return "'restore' '--file-to-restore=%s/deja-dup/metadata' '--gio' '--force' 'file://%s' '%s/deja-dup/metadata' '--no-encryption' '--verbosity=9' '--gpg-options=--no-use-agent' '--archive-dir=%s/deja-dup' '--log-fd=?'".printf(cachedir.substring(1), backupdir, cachedir, cachedir);
else if (mode == Mode.LIST)
return "'list-current-files' '--gio' 'file://%s' '--no-encryption' '--verbosity=9' '--gpg-options=--no-use-agent' '--archive-dir=%s/deja-dup' '--log-fd=?'".printf(backupdir, cachedir);
......@@ -137,7 +140,7 @@ string default_args(BackupRunner br, Mode mode = Mode.NONE, bool encrypted = fal
args += "collection-status ";
if (mode == Mode.STATUS || mode == Mode.NONE || mode == Mode.DRY || mode == Mode.BACKUP) {
args += "'--exclude=%s' ".printf(backupdir);
args += "'--exclude=%s' '--include=%s/deja-dup/metadata' ".printf(backupdir, cachedir);
string[] excludes1 = {"~/Downloads", "~/.local/share/Trash", "~/.xsession-errors", "~/.thumbnails", "~/.Private", "~/.gvfs", "~/.adobe/Flash_Player/AssetCache"};
......@@ -279,7 +282,8 @@ void add_to_mockscript(string contents)
string replace_keywords(string in)
{
var home = Environment.get_home_dir();
return in.replace("@HOME@", home);
var cachedir = Environment.get_variable("XDG_CACHE_HOME");
return in.replace("@HOME@", home).replace("@XDG_CACHE_HOME@", cachedir);
}
string run_script(string in)
......@@ -369,6 +373,8 @@ void process_duplicity_run_block(KeyFile keyfile, string run, BackupRunner br) t
mode = Mode.LIST;
else if (type == "backup")
mode = Mode.BACKUP;
else if (type == "verify")
mode = Mode.VERIFY;
else if (type == "restore")
mode = Mode.RESTORE;
else if (type == "cleanup")
......@@ -376,6 +382,8 @@ void process_duplicity_run_block(KeyFile keyfile, string run, BackupRunner br) t
else
assert_not_reached();
var cachedir = Environment.get_variable("XDG_CACHE_HOME");
var dupscript = "ARGS: " + default_args(br, mode, encrypted, extra_args);
if (cancel) {
......@@ -394,6 +402,8 @@ void process_duplicity_run_block(KeyFile keyfile, string run, BackupRunner br) t
if (script != null)
dupscript += "\n" + "SCRIPT: " + script;
else if (mode == Mode.VERIFY)
dupscript += "\n" + "SCRIPT: mkdir -p %s/deja-dup/metadata; echo 'This folder can be safely deleted.\\n0' > %s/deja-dup/metadata/README".printf(cachedir, cachedir);
if (outputscript != null && outputscript != "")
dupscript += "\n\n" + outputscript + "\n";
......
......@@ -2,7 +2,7 @@
Type=backup
[Duplicity]
Runs=status;dry 1;dry 2;backup;
Runs=status;dry 1;dry 2;backup;status-restore;list;verify;
[Duplicity dry 1]
#ERROR 3 new old
......
......@@ -6,7 +6,7 @@
Type=backup
[Duplicity]
Runs=status;dry;backup 1;cleanup;backup 2;
Runs=status;dry;backup 1;cleanup;backup 2;status-restore;list;verify;
[Duplicity backup 1]
#WARNING 2
......
......@@ -5,7 +5,7 @@ Type=backup
Detail=Could not back up the following files. Please make sure you are able to open them.\n\n@HOME@/1\n@HOME@/2
[Duplicity]
Runs=status;dry;backup;
Runs=status;dry;backup;status-restore;list;verify;
[Duplicity backup]
#WARNING 10 '/blarg'
......
......@@ -6,7 +6,7 @@ Type=backup
IsFull=true
[Duplicity]
Runs=status;dry;backup;
Runs=status;dry;backup;status-restore;list;verify;
[Duplicity status]
#echo "INFO 3"
......
......@@ -7,7 +7,7 @@ Type=backup
IsFull=false
[Duplicity]
Runs=status;dry;backup;
Runs=status;dry;backup;status-restore;list;verify;
[Duplicity status]
#echo "INFO 3"
......
[Operation]
Type=backup
Success=false
Error=Your backup appears to be corrupted. You should delete the backup and try again.
[Duplicity]
Runs=status;dry;backup;status-restore;list;verify;
[Duplicity verify]
Script=mkdir -p @XDG_CACHE_HOME@/deja-dup/metadata; echo 'Nope' > @XDG_CACHE_HOME@/deja-dup/metadata/README
......@@ -40,7 +40,10 @@ internal class DuplicityInstance : Object
if (!settings.get_boolean(DejaDup.ROOT_PROMPT_KEY))
as_root = false;
}
if (as_root && Environment.get_variable("DEJA_DUP_TESTING") != null)
as_root = false;
// Copy current environment, add custom variables
var myenv = Environment.list_variables();
int myenv_len = 0;
......
......@@ -826,7 +826,7 @@ internal class DuplicityJob : DejaDup.ToolJob
return;
var cachedir = Path.build_filename(dir, Config.PACKAGE);
var del = new RecursiveDelete(File.new_for_path(cachedir));
var del = new DejaDup.RecursiveDelete(File.new_for_path(cachedir));
del.start();
}
......
......@@ -37,10 +37,7 @@ libduplicity_la_LIBADD = \
libduplicity_la_VALASOURCES = \
DuplicityInstance.vala \
DuplicityJob.vala \
DuplicityPlugin.vala \
RecursiveDelete.vala \
RecursiveMove.vala \
RecursiveOp.vala
DuplicityPlugin.vala
libduplicity_la_SOURCES = \
$(libduplicity_la_VALASOURCES)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment