]> git.ipfire.org Git - thirdparty/snapper.git/commitdiff
- move commands to separate files
authorArvin Schnell <aschnell@suse.de>
Fri, 28 Aug 2020 10:45:03 +0000 (12:45 +0200)
committerArvin Schnell <aschnell@suse.de>
Fri, 28 Aug 2020 10:45:03 +0000 (12:45 +0200)
24 files changed:
client/Makefile.am
client/MyFiles.cc [new file with mode: 0644]
client/MyFiles.h [new file with mode: 0644]
client/cmd-cleanup.cc [new file with mode: 0644]
client/cmd-create-config.cc [new file with mode: 0644]
client/cmd-create.cc [new file with mode: 0644]
client/cmd-debug.cc [new file with mode: 0644]
client/cmd-delete-config.cc [new file with mode: 0644]
client/cmd-delete.cc [new file with mode: 0644]
client/cmd-diff.cc [new file with mode: 0644]
client/cmd-get-config.cc [new file with mode: 0644]
client/cmd-list-configs.cc [new file with mode: 0644]
client/cmd-list.cc [new file with mode: 0644]
client/cmd-modify.cc [new file with mode: 0644]
client/cmd-mount.cc [new file with mode: 0644]
client/cmd-rollback.cc [new file with mode: 0644]
client/cmd-set-config.cc [new file with mode: 0644]
client/cmd-setup-quota.cc [new file with mode: 0644]
client/cmd-status.cc [new file with mode: 0644]
client/cmd-umount.cc [new file with mode: 0644]
client/cmd-undochange.cc [new file with mode: 0644]
client/cmd-xadiff.cc [new file with mode: 0644]
client/cmd.h [new file with mode: 0644]
client/snapper.cc

index ae4a8e65b6c4da405e16894595fa1ace3d335fc6..8843a837cfad2113c54bfa4f5b108da6db1f2798 100644 (file)
@@ -20,11 +20,32 @@ bin_PROGRAMS = snapper
 
 snapper_SOURCES =                                      \
        snapper.cc                                      \
+       cmd.h                                           \
+       cmd-list-configs.cc                             \
+       cmd-create-config.cc                            \
+       cmd-delete-config.cc                            \
+       cmd-get-config.cc                               \
+       cmd-set-config.cc                               \
+       cmd-list.cc                                     \
+       cmd-create.cc                                   \
+       cmd-modify.cc                                   \
+       cmd-delete.cc                                   \
+       cmd-mount.cc                                    \
+       cmd-umount.cc                                   \
+       cmd-status.cc                                   \
+       cmd-diff.cc                                     \
+       cmd-xadiff.cc                                   \
+       cmd-undochange.cc                               \
+       cmd-rollback.cc                                 \
+       cmd-setup-quota.cc                              \
+       cmd-cleanup.cc                                  \
+       cmd-debug.cc                                    \
        cleanup.cc              cleanup.h               \
        proxy.cc                proxy.h                 \
        proxy-dbus.cc           proxy-dbus.h            \
        proxy-lib.cc            proxy-lib.h             \
        misc.cc                 misc.h                  \
+       MyFiles.cc              MyFiles.h               \
        Options.cc              Options.h               \
        GlobalOptions.cc        GlobalOptions.h         \
        Command.cc              Command.h
diff --git a/client/MyFiles.cc b/client/MyFiles.cc
new file mode 100644 (file)
index 0000000..2a19a8a
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) [2011-2015] Novell, Inc.
+ * Copyright (c) [2016-2020] SUSE LLC
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License as published
+ * by the Free Software Foundation.
+ *
+ * This program 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 this program; if not, contact Novell, Inc.
+ *
+ * To contact Novell about this file by physical or electronic mail, you may
+ * find current contact information at www.novell.com.
+ */
+
+
+#include <iostream>
+
+#include "proxy.h"
+#include "utils/text.h"
+#include "GlobalOptions.h"
+
+#include <snapper/AppUtil.h>
+#include <snapper/AsciiFile.h>
+
+#include "MyFiles.h"
+
+
+namespace snapper
+{
+
+    void
+    MyFiles::bulk_process(FILE* file, GetOpts& get_opts, std::function<void(File& file)> callback)
+    {
+       if (file)
+       {
+           AsciiFileReader asciifile(file);
+
+           string line;
+           while (asciifile.getline(line))
+           {
+               if (line.empty())
+                   continue;
+
+               string name = line;
+
+               // strip optional status
+               if (name[0] != '/')
+               {
+                   string::size_type pos = name.find(" ");
+                   if (pos == string::npos)
+                       continue;
+
+                   name.erase(0, pos + 1);
+               }
+
+               Files::iterator it = findAbsolutePath(name);
+               if (it == end())
+               {
+                   cerr << sformat(_("File '%s' not found."), name.c_str()) << endl;
+                   exit(EXIT_FAILURE);
+               }
+
+               callback(*it);
+           }
+       }
+       else
+       {
+           if (get_opts.num_args() == 0)
+           {
+               for (Files::iterator it = begin(); it != end(); ++it)
+                   callback(*it);
+           }
+           else
+           {
+               while (get_opts.num_args() > 0)
+               {
+                   string name = get_opts.pop_arg();
+
+                   Files::iterator it = findAbsolutePath(name);
+                   if (it == end())
+                   {
+                       cerr << sformat(_("File '%s' not found."), name.c_str()) << endl;
+                       exit(EXIT_FAILURE);
+                   }
+
+                   callback(*it);
+               }
+           }
+       }
+    }
+
+}
diff --git a/client/MyFiles.h b/client/MyFiles.h
new file mode 100644 (file)
index 0000000..b3dfcd6
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) [2011-2015] Novell, Inc.
+ * Copyright (c) [2016-2020] SUSE LLC
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License as published
+ * by the Free Software Foundation.
+ *
+ * This program 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 this program; if not, contact Novell, Inc.
+ *
+ * To contact Novell about this file by physical or electronic mail, you may
+ * find current contact information at www.novell.com.
+ */
+
+
+#include <functional>
+
+#include <snapper/File.h>
+
+#include "utils/GetOpts.h"
+
+
+namespace snapper
+{
+
+    struct MyFiles : public Files
+    {
+
+       MyFiles(const Files& files) : Files(files) {}
+
+       void bulk_process(FILE* file, GetOpts& get_opts, std::function<void(File& file)> callback);
+
+    };
+
+}
diff --git a/client/cmd-cleanup.cc b/client/cmd-cleanup.cc
new file mode 100644 (file)
index 0000000..62bf065
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) [2011-2015] Novell, Inc.
+ * Copyright (c) [2016-2020] SUSE LLC
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License as published
+ * by the Free Software Foundation.
+ *
+ * This program 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 this program; if not, contact Novell, Inc.
+ *
+ * To contact Novell about this file by physical or electronic mail, you may
+ * find current contact information at www.novell.com.
+ */
+
+
+#include <iostream>
+
+#include <snapper/AppUtil.h>
+
+#include "utils/text.h"
+#include "GlobalOptions.h"
+#include "proxy.h"
+#include "cleanup.h"
+
+
+namespace snapper
+{
+
+    using namespace std;
+
+
+    void
+    help_cleanup()
+    {
+       cout << _("  Cleanup snapshots:") << '\n'
+            << _("\tsnapper cleanup <cleanup-algorithm>") << '\n'
+            << endl;
+    }
+
+
+    void
+    command_cleanup(GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper* snapper)
+    {
+       ParsedOpts opts = get_opts.parse("cleanup", GetOpts::no_options);
+       if (get_opts.num_args() != 1)
+       {
+           cerr << _("Command 'cleanup' needs one arguments.") << endl;
+           exit(EXIT_FAILURE);
+       }
+
+       string cleanup = get_opts.pop_arg();
+
+       if (cleanup == "number")
+       {
+           do_cleanup_number(snapper, global_options.verbose());
+       }
+       else if (cleanup == "timeline")
+       {
+           do_cleanup_timeline(snapper, global_options.verbose());
+       }
+       else if (cleanup == "empty-pre-post")
+       {
+           do_cleanup_empty_pre_post(snapper, global_options.verbose());
+       }
+       else
+       {
+           cerr << sformat(_("Unknown cleanup algorithm '%s'."), cleanup.c_str()) << endl;
+           exit(EXIT_FAILURE);
+       }
+    }
+
+}
diff --git a/client/cmd-create-config.cc b/client/cmd-create-config.cc
new file mode 100644 (file)
index 0000000..b7e426b
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) [2011-2015] Novell, Inc.
+ * Copyright (c) [2016-2020] SUSE LLC
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License as published
+ * by the Free Software Foundation.
+ *
+ * This program 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 this program; if not, contact Novell, Inc.
+ *
+ * To contact Novell about this file by physical or electronic mail, you may
+ * find current contact information at www.novell.com.
+ */
+
+
+#include <iostream>
+
+#include <snapper/AppUtil.h>
+
+#include "utils/text.h"
+#include "GlobalOptions.h"
+#include "proxy.h"
+
+
+namespace snapper
+{
+
+    using namespace std;
+
+
+    void
+    help_create_config()
+    {
+       cout << _("  Create config:") << '\n'
+            << _("\tsnapper create-config <subvolume>") << '\n'
+            << '\n'
+            << _("    Options for 'create-config' command:") << '\n'
+            << _("\t--fstype, -f <fstype>\t\tManually set filesystem type.") << '\n'
+            << _("\t--template, -t <name>\t\tName of config template to use.") << '\n'
+            << endl;
+    }
+
+
+    void
+    command_create_config(GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper*)
+    {
+       const vector<Option> options = {
+           Option("fstype",    required_argument,      'f'),
+           Option("template",  required_argument,      't')
+       };
+
+       ParsedOpts opts = get_opts.parse("create-config", options);
+       if (get_opts.num_args() != 1)
+       {
+           cerr << _("Command 'create-config' needs one argument.") << endl;
+           exit(EXIT_FAILURE);
+       }
+
+       string subvolume = realpath(get_opts.pop_arg());
+       if (subvolume.empty())
+       {
+           cerr << _("Invalid subvolume.") << endl;
+           exit(EXIT_FAILURE);
+       }
+
+       string fstype = "";
+       string template_name = "default";
+
+       ParsedOpts::const_iterator opt;
+
+       if ((opt = opts.find("fstype")) != opts.end())
+           fstype = opt->second;
+
+       if ((opt = opts.find("template")) != opts.end())
+           template_name = opt->second;
+
+       if (fstype.empty() && !Snapper::detectFstype(subvolume, fstype))
+       {
+           cerr << _("Detecting filesystem type failed.") << endl;
+           exit(EXIT_FAILURE);
+       }
+
+       snappers->createConfig(global_options.config(), subvolume, fstype, template_name);
+    }
+
+}
diff --git a/client/cmd-create.cc b/client/cmd-create.cc
new file mode 100644 (file)
index 0000000..2dd8b6e
--- /dev/null
@@ -0,0 +1,197 @@
+/*
+ * Copyright (c) [2011-2015] Novell, Inc.
+ * Copyright (c) [2016-2020] SUSE LLC
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License as published
+ * by the Free Software Foundation.
+ *
+ * This program 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 this program; if not, contact Novell, Inc.
+ *
+ * To contact Novell about this file by physical or electronic mail, you may
+ * find current contact information at www.novell.com.
+ */
+
+
+#include <iostream>
+
+#include "utils/text.h"
+#include "GlobalOptions.h"
+#include "proxy.h"
+#include "misc.h"
+
+
+namespace snapper
+{
+
+    using namespace std;
+
+
+    void
+    help_create()
+    {
+       cout << _("  Create snapshot:") << '\n'
+            << _("\tsnapper create") << '\n'
+            << '\n'
+            << _("    Options for 'create' command:") << '\n'
+            << _("\t--type, -t <type>\t\tType for snapshot.") << '\n'
+            << _("\t--pre-number <number>\t\tNumber of corresponding pre snapshot.") << '\n'
+            << _("\t--print-number, -p\t\tPrint number of created snapshot.") << '\n'
+            << _("\t--description, -d <description>\tDescription for snapshot.") << '\n'
+            << _("\t--cleanup-algorithm, -c <algo>\tCleanup algorithm for snapshot.") << '\n'
+            << _("\t--userdata, -u <userdata>\tUserdata for snapshot.") << '\n'
+            << _("\t--command <command>\t\tRun command and create pre and post snapshots.") << endl
+            << _("\t--read-only\t\t\tCreate read-only snapshot.") << '\n'
+            << _("\t--read-write\t\t\tCreate read-write snapshot.") << '\n'
+            << _("\t--from\t\t\t\tCreate a snapshot from the specified snapshot.") << '\n'
+            << endl;
+    }
+
+
+    void
+    command_create(GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper* snapper)
+    {
+       const vector<Option> options = {
+           Option("type",                      required_argument,      't'),
+           Option("pre-number",                required_argument),
+           Option("print-number",              no_argument,            'p'),
+           Option("description",               required_argument,      'd'),
+           Option("cleanup-algorithm",         required_argument,      'c'),
+           Option("userdata",                  required_argument,      'u'),
+           Option("command",                   required_argument),
+           Option("read-only",                 no_argument),
+           Option("read-write",                no_argument),
+           Option("from",                      required_argument)
+       };
+
+       ParsedOpts opts = get_opts.parse("create", options);
+       if (get_opts.has_args())
+       {
+           cerr << _("Command 'create' does not take arguments.") << endl;
+           exit(EXIT_FAILURE);
+       }
+
+       enum class CreateType { SINGLE, PRE, POST, PRE_POST };
+
+       const ProxySnapshots& snapshots = snapper->getSnapshots();
+
+       CreateType type = CreateType::SINGLE;
+       ProxySnapshots::const_iterator snapshot1 = snapshots.end();
+       ProxySnapshots::const_iterator snapshot2 = snapshots.end();
+       bool print_number = false;
+       SCD scd;
+       string command;
+       ProxySnapshots::const_iterator parent = snapshots.getCurrent();
+
+       ParsedOpts::const_iterator opt;
+
+       if ((opt = opts.find("type")) != opts.end())
+       {
+           if (opt->second == "single")
+               type = CreateType::SINGLE;
+           else if (opt->second == "pre")
+               type = CreateType::PRE;
+           else if (opt->second == "post")
+               type = CreateType::POST;
+           else if (opt->second == "pre-post")
+               type = CreateType::PRE_POST;
+           else
+           {
+               cerr << _("Unknown type of snapshot.") << endl;
+               exit(EXIT_FAILURE);
+           }
+       }
+
+       if ((opt = opts.find("pre-number")) != opts.end())
+           snapshot1 = snapshots.findNum(opt->second);
+
+       if ((opt = opts.find("print-number")) != opts.end())
+           print_number = true;
+
+       if ((opt = opts.find("description")) != opts.end())
+           scd.description = opt->second;
+
+       if ((opt = opts.find("cleanup-algorithm")) != opts.end())
+           scd.cleanup = opt->second;
+
+       if ((opt = opts.find("userdata")) != opts.end())
+           scd.userdata = read_userdata(opt->second);
+
+       if ((opt = opts.find("command")) != opts.end())
+       {
+           command = opt->second;
+           type = CreateType::PRE_POST;
+       }
+
+       if ((opt = opts.find("read-only")) != opts.end())
+           scd.read_only = true;
+
+       if ((opt = opts.find("read-write")) != opts.end())
+           scd.read_only = false;
+
+       if ((opt = opts.find("from")) != opts.end())
+           parent = snapshots.findNum(opt->second);
+
+       if (type == CreateType::POST && snapshot1 == snapshots.end())
+       {
+           cerr << _("Missing or invalid pre-number.") << endl;
+           exit(EXIT_FAILURE);
+       }
+
+       if (type == CreateType::PRE_POST && command.empty())
+       {
+           cerr << _("Missing command argument.") << endl;
+           exit(EXIT_FAILURE);
+       }
+
+       if (type != CreateType::SINGLE && !scd.read_only)
+       {
+           cerr << _("Option --read-write only supported for snapshots of type single.") << endl;
+           exit(EXIT_FAILURE);
+       }
+
+       if (type != CreateType::SINGLE && parent != snapshots.getCurrent())
+       {
+           cerr << _("Option --from only supported for snapshots of type single.") << endl;
+           exit(EXIT_FAILURE);
+       }
+
+       switch (type)
+       {
+           case CreateType::SINGLE: {
+               snapshot1 = snapper->createSingleSnapshot(parent, scd);
+               if (print_number)
+                   cout << snapshot1->getNum() << endl;
+           } break;
+
+           case CreateType::PRE: {
+               snapshot1 = snapper->createPreSnapshot(scd);
+               if (print_number)
+                   cout << snapshot1->getNum() << endl;
+           } break;
+
+           case CreateType::POST: {
+               snapshot2 = snapper->createPostSnapshot(snapshot1, scd);
+               if (print_number)
+                   cout << snapshot2->getNum() << endl;
+           } break;
+
+           case CreateType::PRE_POST: {
+               snapshot1 = snapper->createPreSnapshot(scd);
+               system(command.c_str());
+               snapshot2 = snapper->createPostSnapshot(snapshot1, scd);
+               if (print_number)
+                   cout << snapshot1->getNum() << ".." << snapshot2->getNum() << endl;
+           } break;
+       }
+    }
+
+}
diff --git a/client/cmd-debug.cc b/client/cmd-debug.cc
new file mode 100644 (file)
index 0000000..e2531af
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) [2011-2015] Novell, Inc.
+ * Copyright (c) [2016-2020] SUSE LLC
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License as published
+ * by the Free Software Foundation.
+ *
+ * This program 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 this program; if not, contact Novell, Inc.
+ *
+ * To contact Novell about this file by physical or electronic mail, you may
+ * find current contact information at www.novell.com.
+ */
+
+
+#include <iostream>
+
+#include "utils/text.h"
+#include "GlobalOptions.h"
+#include "proxy.h"
+
+
+namespace snapper
+{
+
+    using namespace std;
+
+
+    void
+    help_debug()
+    {
+    }
+
+
+    void
+    command_debug(GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper*)
+    {
+       get_opts.parse("debug", GetOpts::no_options);
+       if (get_opts.has_args())
+       {
+           cerr << _("Command 'debug' does not take arguments.") << endl;
+           exit(EXIT_FAILURE);
+       }
+
+       for (const string& line : snappers->debug())
+           cout << line << endl;
+    }
+
+}
diff --git a/client/cmd-delete-config.cc b/client/cmd-delete-config.cc
new file mode 100644 (file)
index 0000000..bf9b665
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) [2011-2015] Novell, Inc.
+ * Copyright (c) [2016-2020] SUSE LLC
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License as published
+ * by the Free Software Foundation.
+ *
+ * This program 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 this program; if not, contact Novell, Inc.
+ *
+ * To contact Novell about this file by physical or electronic mail, you may
+ * find current contact information at www.novell.com.
+ */
+
+
+#include <iostream>
+
+#include "utils/text.h"
+#include "GlobalOptions.h"
+#include "proxy.h"
+
+
+namespace snapper
+{
+
+    using namespace std;
+
+
+    void
+    help_delete_config()
+    {
+       cout << _("  Delete config:") << '\n'
+            << _("\tsnapper delete-config") << '\n'
+            << endl;
+    }
+
+
+    void
+    command_delete_config(GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper*)
+    {
+       get_opts.parse("delete-config", GetOpts::no_options);
+       if (get_opts.has_args())
+       {
+           cerr << _("Command 'delete-config' does not take arguments.") << endl;
+           exit(EXIT_FAILURE);
+       }
+
+       snappers->deleteConfig(global_options.config());
+    }
+
+}
diff --git a/client/cmd-delete.cc b/client/cmd-delete.cc
new file mode 100644 (file)
index 0000000..d120fc9
--- /dev/null
@@ -0,0 +1,145 @@
+/*
+ * Copyright (c) [2011-2015] Novell, Inc.
+ * Copyright (c) [2016-2020] SUSE LLC
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License as published
+ * by the Free Software Foundation.
+ *
+ * This program 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 this program; if not, contact Novell, Inc.
+ *
+ * To contact Novell about this file by physical or electronic mail, you may
+ * find current contact information at www.novell.com.
+ */
+
+
+#include <iostream>
+
+#include <snapper/AppUtil.h>
+
+#include "utils/text.h"
+#include "GlobalOptions.h"
+#include "proxy.h"
+
+
+namespace snapper
+{
+
+    using namespace std;
+
+
+    void
+    help_delete()
+    {
+       cout << _("  Delete snapshot:") << '\n'
+            << _("\tsnapper delete <number>") << '\n'
+            << '\n'
+            << _("    Options for 'delete' command:") << '\n'
+            << _("\t--sync, -s\t\t\tSync after deletion.") << '\n'
+            << endl;
+    }
+
+
+    void
+    filter_undeletables(ProxySnapshots& snapshots, vector<ProxySnapshots::iterator>& nums)
+    {
+       auto filter = [&snapshots, &nums](ProxySnapshots::const_iterator undeletable, const char* message)
+           {
+               if (undeletable == snapshots.end())
+                   return;
+
+               unsigned int num = undeletable->getNum();
+
+               vector<ProxySnapshots::iterator>::iterator keep = find_if(nums.begin(), nums.end(),
+                   [num](ProxySnapshots::iterator it){ return num == it->getNum(); });
+
+               if (keep != nums.end())
+               {
+                   cerr << sformat(message, num) << endl;
+                   nums.erase(keep);
+               }
+           };
+
+       ProxySnapshots::const_iterator current_snapshot = snapshots.begin();
+       filter(current_snapshot, _("Cannot delete snapshot %d since it is the current system."));
+
+       ProxySnapshots::const_iterator active_snapshot = snapshots.getActive();
+       filter(active_snapshot, _("Cannot delete snapshot %d since it is the currently mounted snapshot."));
+
+       ProxySnapshots::const_iterator default_snapshot = snapshots.getDefault();
+       filter(default_snapshot, _("Cannot delete snapshot %d since it is the next to be mounted snapshot."));
+    }
+
+
+    void
+    command_delete(GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper* snapper)
+    {
+       const vector<Option> options = {
+           Option("sync",      no_argument,    's')
+       };
+
+       ParsedOpts opts = get_opts.parse("delete", options);
+       if (!get_opts.has_args())
+       {
+           cerr << _("Command 'delete' needs at least one argument.") << endl;
+           exit(EXIT_FAILURE);
+       }
+
+       bool sync = false;
+
+       ParsedOpts::const_iterator opt;
+
+       if ((opt = opts.find("sync")) != opts.end())
+           sync = true;
+
+       ProxySnapshots& snapshots = snapper->getSnapshots();
+
+       vector<ProxySnapshots::iterator> nums;
+
+       while (get_opts.has_args())
+       {
+           string arg = get_opts.pop_arg();
+
+           if (arg.find_first_of("-") == string::npos)
+           {
+               ProxySnapshots::iterator tmp = snapshots.findNum(arg);
+               nums.push_back(tmp);
+           }
+           else
+           {
+               pair<ProxySnapshots::iterator, ProxySnapshots::iterator> range =
+                   snapshots.findNums(arg, "-");
+
+               if (range.first->getNum() > range.second->getNum())
+                   swap(range.first, range.second);
+
+               for (unsigned int i = range.first->getNum(); i <= range.second->getNum(); ++i)
+               {
+                   ProxySnapshots::iterator x = snapshots.find(i);
+                   if (x != snapshots.end())
+                   {
+                       if (find_if(nums.begin(), nums.end(), [i](ProxySnapshots::iterator it)
+                                                                 { return it->getNum() == i; }) == nums.end())
+                           nums.push_back(x);
+                   }
+               }
+           }
+       }
+
+       filter_undeletables(snapshots, nums);
+
+       snapper->deleteSnapshots(nums, global_options.verbose());
+
+       if (sync)
+           snapper->syncFilesystem();
+    }
+
+}
diff --git a/client/cmd-diff.cc b/client/cmd-diff.cc
new file mode 100644 (file)
index 0000000..7ff000a
--- /dev/null
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) [2011-2015] Novell, Inc.
+ * Copyright (c) [2016-2020] SUSE LLC
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License as published
+ * by the Free Software Foundation.
+ *
+ * This program 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 this program; if not, contact Novell, Inc.
+ *
+ * To contact Novell about this file by physical or electronic mail, you may
+ * find current contact information at www.novell.com.
+ */
+
+
+#include <iostream>
+
+#include <snapper/AppUtil.h>
+
+#include "utils/text.h"
+#include "GlobalOptions.h"
+#include "proxy.h"
+#include "misc.h"
+#include "MyFiles.h"
+
+
+namespace snapper
+{
+
+    using namespace std;
+
+
+    void
+    help_diff()
+    {
+       cout << _("  Comparing snapshots:") << '\n'
+            << _("\tsnapper diff <number1>..<number2> [files]") << '\n'
+            << '\n'
+            << _("    Options for 'diff' command:") << '\n'
+            << _("\t--input, -i <file>\t\tRead files to diff from file.") << '\n'
+            << _("\t--diff-cmd <command>\t\tCommand used for comparing files.") << '\n'
+            << _("\t--extensions, -x <options>\tExtra options passed to the diff command.") << '\n'
+            << endl;
+    }
+
+
+    void
+    command_diff(GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper* snapper)
+    {
+       const vector<Option> options = {
+           Option("input",             required_argument,      'i'),
+           Option("diff-cmd",          required_argument),
+           Option("extensions",        required_argument,      'x'),
+       };
+
+       ParsedOpts opts = get_opts.parse("diff", options);
+       if (get_opts.num_args() < 1)
+       {
+           cerr << _("Command 'diff' needs at least one argument.") << endl;
+           exit(EXIT_FAILURE);
+       }
+
+       FILE* file = NULL;
+       Differ differ;
+
+       ParsedOpts::const_iterator opt;
+
+       if ((opt = opts.find("input")) != opts.end())
+       {
+           file = fopen(opt->second.c_str(), "r");
+           if (!file)
+           {
+               cerr << sformat(_("Opening file '%s' failed."), opt->second.c_str()) << endl;
+               exit(EXIT_FAILURE);
+           }
+       }
+
+       if ((opt = opts.find("diff-cmd")) != opts.end())
+           differ.command = opt->second;
+
+       if ((opt = opts.find("extensions")) != opts.end())
+           differ.extensions = opt->second;
+
+       ProxySnapshots& snapshots = snapper->getSnapshots();
+
+       pair<ProxySnapshots::const_iterator, ProxySnapshots::const_iterator> range =
+           snapshots.findNums(get_opts.pop_arg());
+
+       ProxyComparison comparison = snapper->createComparison(*range.first, *range.second, true);
+
+       MyFiles files(comparison.getFiles());
+
+       files.bulk_process(file, get_opts, [differ](const File& file) {
+           differ.run(file.getAbsolutePath(LOC_PRE), file.getAbsolutePath(LOC_POST));
+       });
+    }
+
+}
diff --git a/client/cmd-get-config.cc b/client/cmd-get-config.cc
new file mode 100644 (file)
index 0000000..9d5b4eb
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) [2011-2015] Novell, Inc.
+ * Copyright (c) [2016-2020] SUSE LLC
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License as published
+ * by the Free Software Foundation.
+ *
+ * This program 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 this program; if not, contact Novell, Inc.
+ *
+ * To contact Novell about this file by physical or electronic mail, you may
+ * find current contact information at www.novell.com.
+ */
+
+
+#include <iostream>
+
+#include "utils/text.h"
+#include "GlobalOptions.h"
+#include "proxy.h"
+#include "Command/GetConfig.h"
+
+
+namespace snapper
+{
+
+    using namespace std;
+
+
+    void
+    help_get_config()
+    {
+       cout << cli::Command::GetConfig::help() << endl;
+    }
+
+
+    void
+    command_get_config(GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper*)
+    {
+       cli::Command::GetConfig command(global_options, get_opts, *snappers);
+
+       command.run();
+    }
+
+}
diff --git a/client/cmd-list-configs.cc b/client/cmd-list-configs.cc
new file mode 100644 (file)
index 0000000..6200af6
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) [2011-2015] Novell, Inc.
+ * Copyright (c) [2016-2020] SUSE LLC
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License as published
+ * by the Free Software Foundation.
+ *
+ * This program 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 this program; if not, contact Novell, Inc.
+ *
+ * To contact Novell about this file by physical or electronic mail, you may
+ * find current contact information at www.novell.com.
+ */
+
+
+#include <iostream>
+
+#include "utils/text.h"
+#include "GlobalOptions.h"
+#include "proxy.h"
+#include "Command/ListConfigs.h"
+
+
+namespace snapper
+{
+
+    using namespace std;
+
+
+    void
+    help_list_configs()
+    {
+       cout << cli::Command::ListConfigs::help() << endl;
+    }
+
+
+    void
+    command_list_configs(GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper*)
+    {
+       cli::Command::ListConfigs command(global_options, get_opts, *snappers);
+
+       command.run();
+    }
+
+}
diff --git a/client/cmd-list.cc b/client/cmd-list.cc
new file mode 100644 (file)
index 0000000..0433cbe
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) [2011-2015] Novell, Inc.
+ * Copyright (c) [2016-2020] SUSE LLC
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License as published
+ * by the Free Software Foundation.
+ *
+ * This program 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 this program; if not, contact Novell, Inc.
+ *
+ * To contact Novell about this file by physical or electronic mail, you may
+ * find current contact information at www.novell.com.
+ */
+
+
+#include <iostream>
+
+#include "utils/text.h"
+#include "GlobalOptions.h"
+#include "proxy.h"
+#include "Command/ListSnapshots.h"
+
+
+namespace snapper
+{
+
+    using namespace std;
+
+
+    void
+    help_list()
+    {
+       cout << cli::Command::ListSnapshots::help() << endl;
+    }
+
+
+    void
+    command_list(GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper*)
+    {
+       cli::Command::ListSnapshots command(global_options, get_opts, *snappers);
+
+       command.run();
+    }
+
+}
diff --git a/client/cmd-modify.cc b/client/cmd-modify.cc
new file mode 100644 (file)
index 0000000..d15d4c9
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) [2011-2015] Novell, Inc.
+ * Copyright (c) [2016-2020] SUSE LLC
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License as published
+ * by the Free Software Foundation.
+ *
+ * This program 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 this program; if not, contact Novell, Inc.
+ *
+ * To contact Novell about this file by physical or electronic mail, you may
+ * find current contact information at www.novell.com.
+ */
+
+
+#include <iostream>
+
+#include "utils/text.h"
+#include "GlobalOptions.h"
+#include "proxy.h"
+#include "misc.h"
+
+
+namespace snapper
+{
+
+    using namespace std;
+
+
+    void
+    help_modify()
+    {
+       cout << _("  Modify snapshot:") << '\n'
+            << _("\tsnapper modify <number>") << '\n'
+            << '\n'
+            << _("    Options for 'modify' command:") << '\n'
+            << _("\t--description, -d <description>\tDescription for snapshot.") << '\n'
+            << _("\t--cleanup-algorithm, -c <algo>\tCleanup algorithm for snapshot.") << '\n'
+            << _("\t--userdata, -u <userdata>\tUserdata for snapshot.") << '\n'
+            << endl;
+    }
+
+
+    void
+    command_modify(GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper* snapper)
+    {
+       const vector<Option> options = {
+           Option("description",               required_argument,      'd'),
+           Option("cleanup-algorithm",         required_argument,      'c'),
+           Option("userdata",                  required_argument,      'u')
+       };
+
+       ParsedOpts opts = get_opts.parse("modify", options);
+       if (!get_opts.has_args())
+       {
+           cerr << _("Command 'modify' needs at least one argument.") << endl;
+           exit(EXIT_FAILURE);
+       }
+
+       ProxySnapshots& snapshots = snapper->getSnapshots();
+
+       while (get_opts.has_args())
+       {
+           ProxySnapshots::iterator snapshot = snapshots.findNum(get_opts.pop_arg());
+
+           SMD smd = snapshot->getSmd();
+
+           ParsedOpts::const_iterator opt;
+
+           if ((opt = opts.find("description")) != opts.end())
+               smd.description = opt->second;
+
+           if ((opt = opts.find("cleanup-algorithm")) != opts.end())
+               smd.cleanup = opt->second;
+
+           if ((opt = opts.find("userdata")) != opts.end())
+               smd.userdata = read_userdata(opt->second, snapshot->getUserdata());
+
+           snapper->modifySnapshot(snapshot, smd);
+       }
+    }
+
+}
diff --git a/client/cmd-mount.cc b/client/cmd-mount.cc
new file mode 100644 (file)
index 0000000..c67ccbf
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) [2011-2015] Novell, Inc.
+ * Copyright (c) [2016-2020] SUSE LLC
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License as published
+ * by the Free Software Foundation.
+ *
+ * This program 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 this program; if not, contact Novell, Inc.
+ *
+ * To contact Novell about this file by physical or electronic mail, you may
+ * find current contact information at www.novell.com.
+ */
+
+
+#include <iostream>
+
+#include "utils/text.h"
+#include "GlobalOptions.h"
+#include "proxy.h"
+
+
+namespace snapper
+{
+
+    using namespace std;
+
+
+    void
+    help_mount()
+    {
+       cout << _("  Mount snapshot:") << '\n'
+            << _("\tsnapper mount <number>") << '\n'
+            << endl;
+    }
+
+
+    void
+    command_mount(GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper* snapper)
+    {
+       get_opts.parse("mount", GetOpts::no_options);
+       if (!get_opts.has_args())
+       {
+           cerr << _("Command 'mount' needs at least one argument.") << endl;
+           exit(EXIT_FAILURE);
+       }
+
+       const ProxySnapshots& snapshots = snapper->getSnapshots();
+
+       while (get_opts.has_args())
+       {
+           ProxySnapshots::const_iterator snapshot = snapshots.findNum(get_opts.pop_arg());
+           snapshot->mountFilesystemSnapshot(true);
+       }
+    }
+
+}
diff --git a/client/cmd-rollback.cc b/client/cmd-rollback.cc
new file mode 100644 (file)
index 0000000..c092870
--- /dev/null
@@ -0,0 +1,308 @@
+/*
+ * Copyright (c) [2011-2015] Novell, Inc.
+ * Copyright (c) [2016-2020] SUSE LLC
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License as published
+ * by the Free Software Foundation.
+ *
+ * This program 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 this program; if not, contact Novell, Inc.
+ *
+ * To contact Novell about this file by physical or electronic mail, you may
+ * find current contact information at www.novell.com.
+ */
+
+
+#include "config.h"
+
+#include <iostream>
+
+#ifdef ENABLE_ROLLBACK
+#include <snapper/AppUtil.h>
+#include <snapper/SnapperDefines.h>
+#include <snapper/Filesystem.h>
+#include <snapper/Hooks.h>
+#endif
+
+#include "utils/text.h"
+#include "GlobalOptions.h"
+#include "proxy.h"
+#include "misc.h"
+
+
+namespace snapper
+{
+
+    using namespace std;
+
+
+#ifdef ENABLE_ROLLBACK
+
+    const Filesystem*
+    get_filesystem(const ProxyConfig& config, const string& target_root)
+    {
+       const map<string, string>& raw = config.getAllValues();
+
+       map<string, string>::const_iterator pos1 = raw.find(KEY_FSTYPE);
+       map<string, string>::const_iterator pos2 = raw.find(KEY_SUBVOLUME);
+       if (pos1 == raw.end() || pos2 == raw.end())
+       {
+           cerr << _("Failed to initialize filesystem handler.") << endl;
+           exit(EXIT_FAILURE);
+       }
+
+       try
+       {
+           return Filesystem::create(pos1->second, pos2->second, target_root);
+       }
+       catch (const InvalidConfigException& e)
+       {
+           SN_CAUGHT(e);
+           cerr << _("Failed to initialize filesystem handler.") << endl;
+           exit(EXIT_FAILURE);
+       }
+    }
+
+
+    void
+    help_rollback()
+    {
+       cout << _("  Rollback:") << '\n'
+            << _("\tsnapper rollback [number]") << '\n'
+            << '\n'
+            << _("    Options for 'rollback' command:") << '\n'
+            << _("\t--print-number, -p\t\tPrint number of second created snapshot.") << '\n'
+            << _("\t--description, -d <description>\tDescription for snapshots.") << '\n'
+            << _("\t--cleanup-algorithm, -c <algo>\tCleanup algorithm for snapshots.") << '\n'
+            << _("\t--userdata, -u <userdata>\tUserdata for snapshots.") << '\n'
+            << endl;
+    }
+
+
+    void
+    command_rollback(GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper* snapper)
+    {
+       const vector<Option> options = {
+           Option("print-number",              no_argument,            'p'),
+           Option("description",               required_argument,      'd'),
+           Option("cleanup-algorithm",         required_argument,      'c'),
+           Option("userdata",                  required_argument,      'u')
+       };
+
+       ParsedOpts opts = get_opts.parse("rollback", options);
+       if (get_opts.has_args() && get_opts.num_args() != 1)
+       {
+           cerr << _("Command 'rollback' takes either one or no argument.") << endl;
+           exit(EXIT_FAILURE);
+       }
+
+       const string default_description1 = "rollback backup";
+       const string default_description2 = "writable copy";
+
+       bool print_number = false;
+
+       SCD scd1;
+       scd1.description = default_description1;
+       scd1.cleanup = "number";
+       scd1.userdata["important"] = "yes";
+
+       SCD scd2;
+       scd2.description = default_description2;
+
+       ParsedOpts::const_iterator opt;
+
+       if ((opt = opts.find("print-number")) != opts.end())
+           print_number = true;
+
+       if ((opt = opts.find("description")) != opts.end())
+           scd1.description = scd2.description = opt->second;
+
+       if ((opt = opts.find("cleanup-algorithm")) != opts.end())
+           scd1.cleanup = scd2.cleanup = opt->second;
+
+       if ((opt = opts.find("userdata")) != opts.end())
+       {
+           scd1.userdata = read_userdata(opt->second, scd1.userdata);
+           scd2.userdata = read_userdata(opt->second);
+       }
+
+       ProxyConfig config = snapper->getConfig();
+
+       const Filesystem* filesystem = get_filesystem(config, global_options.root());
+       if (filesystem->fstype() != "btrfs")
+       {
+           cerr << _("Command 'rollback' only available for btrfs.") << endl;
+           exit(EXIT_FAILURE);
+       }
+
+       const string subvolume = config.getSubvolume();
+       if (subvolume != "/")
+       {
+           cerr << sformat(_("Command 'rollback' cannot be used on a non-root subvolume %s."),
+                           subvolume.c_str()) << endl;
+           exit(EXIT_FAILURE);
+       }
+
+       ProxySnapshots& snapshots = snapper->getSnapshots();
+
+       ProxySnapshots::iterator previous_default = snapshots.getDefault();
+
+       if (global_options.ambit() == GlobalOptions::Ambit::AUTO)
+       {
+           if (previous_default == snapshots.end())
+           {
+               cerr << _("Cannot detect ambit since default subvolume is unknown.") << '\n'
+                    << _("This can happen if the system was not set up for rollback.") << '\n'
+                    << _("The ambit can be specified manually using the --ambit option.") << endl;
+               exit(EXIT_FAILURE);
+           }
+
+           if (filesystem->isSnapshotReadOnly(previous_default->getNum()))
+               global_options.set_ambit(GlobalOptions::Ambit::TRANSACTIONAL);
+           else
+               global_options.set_ambit(GlobalOptions::Ambit::CLASSIC);
+       }
+
+       if (!global_options.quiet())
+           cout << sformat(_("Ambit is %s."), toString(global_options.ambit()).c_str()) << endl;
+
+       if (previous_default != snapshots.end() && scd1.description == default_description1)
+           scd1.description += sformat(" of #%d", previous_default->getNum());
+
+       switch (global_options.ambit())
+       {
+           case GlobalOptions::Ambit::CLASSIC:
+           {
+               ProxySnapshots::const_iterator snapshot1 = snapshots.end();
+               ProxySnapshots::const_iterator snapshot2 = snapshots.end();
+
+               if (get_opts.num_args() == 0)
+               {
+                   if (!global_options.quiet())
+                       cout << _("Creating read-only snapshot of default subvolume.") << flush;
+
+                   scd1.read_only = true;
+                   snapshot1 = snapper->createSingleSnapshotOfDefault(scd1);
+
+                   if (!global_options.quiet())
+                       cout << " " << sformat(_("(Snapshot %d.)"), snapshot1->getNum()) << endl;
+
+                   if (!global_options.quiet())
+                       cout << _("Creating read-write snapshot of current subvolume.") << flush;
+
+                   ProxySnapshots::const_iterator active = snapshots.getActive();
+                   if (active != snapshots.end() && scd2.description == default_description2)
+                       scd2.description += sformat(" of #%d", active->getNum());
+
+                   scd2.read_only = false;
+                   snapshot2 = snapper->createSingleSnapshot(snapshots.getCurrent(), scd2);
+
+                   if (!global_options.quiet())
+                       cout << " " << sformat(_("(Snapshot %d.)"), snapshot2->getNum()) << endl;
+               }
+               else
+               {
+                   ProxySnapshots::const_iterator tmp = snapshots.findNum(get_opts.pop_arg());
+
+                   if (!global_options.quiet())
+                       cout << _("Creating read-only snapshot of current system.") << flush;
+
+                   snapshot1 = snapper->createSingleSnapshot(scd1);
+
+                   if (!global_options.quiet())
+                       cout << " " << sformat(_("(Snapshot %d.)"), snapshot1->getNum()) << endl;
+
+                   if (!global_options.quiet())
+                       cout << sformat(_("Creating read-write snapshot of snapshot %d."), tmp->getNum()) << flush;
+
+                   if (tmp != snapshots.end() && scd2.description == default_description2)
+                       scd2.description += sformat(" of #%d", tmp->getNum());
+
+                   scd2.read_only = false;
+                   snapshot2 = snapper->createSingleSnapshot(tmp, scd2);
+
+                   if (!global_options.quiet())
+                       cout << " " << sformat(_("(Snapshot %d.)"), snapshot2->getNum()) << endl;
+               }
+
+               if (previous_default != snapshots.end() && previous_default->getCleanup().empty())
+               {
+                   SMD smd = previous_default->getSmd();
+                   smd.cleanup = "number";
+                   snapper->modifySnapshot(previous_default, smd);
+               }
+
+               if (!global_options.quiet())
+                   cout << sformat(_("Setting default subvolume to snapshot %d."), snapshot2->getNum()) << endl;
+
+               filesystem->setDefault(snapshot2->getNum());
+
+               Hooks::rollback(filesystem->snapshotDir(snapshot1->getNum()),
+                               filesystem->snapshotDir(snapshot2->getNum()));
+
+               if (print_number)
+                   cout << snapshot2->getNum() << endl;
+           }
+           break;
+
+           case GlobalOptions::Ambit::TRANSACTIONAL:
+           {
+               // see bsc #1172273
+
+               if (previous_default == snapshots.end())
+               {
+                   cerr << _("Cannot do rollback since default subvolume is unknown.") << endl;
+                   exit(EXIT_FAILURE);
+               }
+
+               ProxySnapshots::iterator snapshot = snapshots.end();
+
+               if (get_opts.num_args() == 0)
+               {
+                   snapshot = snapshots.getActive();
+               }
+               else
+               {
+                   snapshot = snapshots.findNum(get_opts.pop_arg());
+               }
+
+               if (previous_default == snapshot)
+               {
+                   cerr << _("Active snapshot is already default snapshot.") << endl;
+                   exit(EXIT_FAILURE);
+               }
+
+               SMD smd = snapshot->getSmd();
+               smd.cleanup = "";
+               snapper->modifySnapshot(snapshot, smd);
+
+               if (!global_options.quiet())
+                   cout << sformat(_("Setting default subvolume to snapshot %d."), snapshot->getNum()) << endl;
+
+               filesystem->setDefault(snapshot->getNum());
+
+               Hooks::rollback(filesystem->snapshotDir(previous_default->getNum()),
+                               filesystem->snapshotDir(snapshot->getNum()));
+           }
+           break;
+
+           case GlobalOptions::Ambit::AUTO:
+           {
+               cerr << "internal error: ambit is auto" << endl;
+               exit(EXIT_FAILURE);
+           }
+           break;
+       }
+    }
+
+#endif
+
+}
diff --git a/client/cmd-set-config.cc b/client/cmd-set-config.cc
new file mode 100644 (file)
index 0000000..3dcc916
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) [2011-2015] Novell, Inc.
+ * Copyright (c) [2016-2020] SUSE LLC
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License as published
+ * by the Free Software Foundation.
+ *
+ * This program 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 this program; if not, contact Novell, Inc.
+ *
+ * To contact Novell about this file by physical or electronic mail, you may
+ * find current contact information at www.novell.com.
+ */
+
+
+#include <iostream>
+
+#include "utils/text.h"
+#include "GlobalOptions.h"
+#include "proxy.h"
+#include "misc.h"
+
+
+namespace snapper
+{
+
+    using namespace std;
+
+
+    void
+    help_set_config()
+    {
+       cout << _("  Set config:") << '\n'
+            << _("\tsnapper set-config <configdata>") << '\n'
+            << endl;
+    }
+
+
+    void
+    command_set_config(GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper* snapper)
+    {
+       get_opts.parse("set-config", GetOpts::no_options);
+       if (!get_opts.has_args())
+       {
+           cerr << _("Command 'set-config' needs at least one argument.") << endl;
+           exit(EXIT_FAILURE);
+       }
+
+       ProxyConfig config(read_configdata(get_opts.get_args()));
+
+       snapper->setConfig(config);
+    }
+
+}
diff --git a/client/cmd-setup-quota.cc b/client/cmd-setup-quota.cc
new file mode 100644 (file)
index 0000000..4473d91
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) [2011-2015] Novell, Inc.
+ * Copyright (c) [2016-2020] SUSE LLC
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License as published
+ * by the Free Software Foundation.
+ *
+ * This program 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 this program; if not, contact Novell, Inc.
+ *
+ * To contact Novell about this file by physical or electronic mail, you may
+ * find current contact information at www.novell.com.
+ */
+
+
+#include <iostream>
+
+#include "utils/text.h"
+#include "GlobalOptions.h"
+#include "proxy.h"
+
+
+namespace snapper
+{
+
+    using namespace std;
+
+
+    void
+    help_setup_quota()
+    {
+       cout << _("  Setup quota:") << '\n'
+            << _("\tsnapper setup-quota") << '\n'
+            << endl;
+    }
+
+
+    void
+    command_setup_quota(GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper* snapper)
+    {
+       ParsedOpts opts = get_opts.parse("setup-quota", GetOpts::no_options);
+       if (get_opts.num_args() != 0)
+       {
+           cerr << _("Command 'setup-quota' does not take arguments.") << endl;
+           exit(EXIT_FAILURE);
+       }
+
+       snapper->setupQuota();
+    }
+
+}
diff --git a/client/cmd-status.cc b/client/cmd-status.cc
new file mode 100644 (file)
index 0000000..d2b4235
--- /dev/null
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) [2011-2015] Novell, Inc.
+ * Copyright (c) [2016-2020] SUSE LLC
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License as published
+ * by the Free Software Foundation.
+ *
+ * This program 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 this program; if not, contact Novell, Inc.
+ *
+ * To contact Novell about this file by physical or electronic mail, you may
+ * find current contact information at www.novell.com.
+ */
+
+
+#include <iostream>
+
+#include <snapper/AppUtil.h>
+
+#include "utils/text.h"
+#include "GlobalOptions.h"
+#include "proxy.h"
+#include "MyFiles.h"
+
+
+namespace snapper
+{
+
+    using namespace std;
+
+
+    void
+    help_status()
+    {
+       cout << _("  Comparing snapshots:") << '\n'
+            << _("\tsnapper status <number1>..<number2>") << '\n'
+            << '\n'
+            << _("    Options for 'status' command:") << '\n'
+            << _("\t--output, -o <file>\t\tSave status to file.") << '\n'
+            << endl;
+    }
+
+
+    void
+    command_status(GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper* snapper)
+    {
+       const vector<Option> options = {
+           Option("output",    required_argument,      'o')
+       };
+
+       ParsedOpts opts = get_opts.parse("status", options);
+       if (get_opts.num_args() != 1)
+       {
+           cerr << _("Command 'status' needs one argument.") << endl;
+
+           if (get_opts.num_args() == 2)
+           {
+               cerr << _("Maybe you forgot the delimiter '..' between the snapshot numbers.") << endl
+                    << _("See 'man snapper' for further instructions.") << endl;
+           }
+
+           exit(EXIT_FAILURE);
+       }
+
+       ParsedOpts::const_iterator opt;
+
+       ProxySnapshots& snapshots = snapper->getSnapshots();
+
+       pair<ProxySnapshots::const_iterator, ProxySnapshots::const_iterator> range =
+           snapshots.findNums(get_opts.pop_arg());
+
+       ProxyComparison comparison = snapper->createComparison(*range.first, *range.second, false);
+
+       MyFiles files(comparison.getFiles());
+
+       FILE* file = stdout;
+
+       if ((opt = opts.find("output")) != opts.end())
+       {
+           file = fopen(opt->second.c_str(), "w");
+           if (!file)
+           {
+               cerr << sformat(_("Opening file '%s' failed."), opt->second.c_str()) << endl;
+               exit(EXIT_FAILURE);
+           }
+       }
+
+       for (Files::const_iterator it = files.begin(); it != files.end(); ++it)
+           fprintf(file, "%s %s\n", statusToString(it->getPreToPostStatus()).c_str(),
+                   it->getAbsolutePath(LOC_SYSTEM).c_str());
+
+       if (file != stdout)
+           fclose(file);
+    }
+
+}
diff --git a/client/cmd-umount.cc b/client/cmd-umount.cc
new file mode 100644 (file)
index 0000000..a4789d4
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) [2011-2015] Novell, Inc.
+ * Copyright (c) [2016-2020] SUSE LLC
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License as published
+ * by the Free Software Foundation.
+ *
+ * This program 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 this program; if not, contact Novell, Inc.
+ *
+ * To contact Novell about this file by physical or electronic mail, you may
+ * find current contact information at www.novell.com.
+ */
+
+
+#include <iostream>
+
+#include "utils/text.h"
+#include "GlobalOptions.h"
+#include "proxy.h"
+
+
+namespace snapper
+{
+
+    using namespace std;
+
+
+    void
+    help_umount()
+    {
+       cout << _("  Umount snapshot:") << '\n'
+            << _("\tsnapper umount <number>") << '\n'
+            << endl;
+    }
+
+
+    void
+    command_umount(GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper* snapper)
+    {
+       get_opts.parse("umount", GetOpts::no_options);
+       if (!get_opts.has_args())
+       {
+           cerr << _("Command 'umount' needs at least one argument.") << endl;
+           exit(EXIT_FAILURE);
+       }
+
+       const ProxySnapshots& snapshots = snapper->getSnapshots();
+
+       while (get_opts.has_args())
+       {
+           ProxySnapshots::const_iterator snapshot = snapshots.findNum(get_opts.pop_arg());
+           snapshot->umountFilesystemSnapshot(true);
+       }
+    }
+
+}
diff --git a/client/cmd-undochange.cc b/client/cmd-undochange.cc
new file mode 100644 (file)
index 0000000..afce806
--- /dev/null
@@ -0,0 +1,161 @@
+/*
+ * Copyright (c) [2011-2015] Novell, Inc.
+ * Copyright (c) [2016-2020] SUSE LLC
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License as published
+ * by the Free Software Foundation.
+ *
+ * This program 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 this program; if not, contact Novell, Inc.
+ *
+ * To contact Novell about this file by physical or electronic mail, you may
+ * find current contact information at www.novell.com.
+ */
+
+
+#include <iostream>
+
+#include <snapper/AppUtil.h>
+
+#include "utils/text.h"
+#include "GlobalOptions.h"
+#include "proxy.h"
+#include "MyFiles.h"
+
+
+namespace snapper
+{
+
+    using namespace std;
+
+
+    void
+    help_undochange()
+    {
+       cout << _("  Undo changes:") << '\n'
+            << _("\tsnapper undochange <number1>..<number2> [files]") << '\n'
+            << '\n'
+            << _("    Options for 'undochange' command:") << '\n'
+            << _("\t--input, -i <file>\t\tRead files for which to undo changes from file.") << '\n'
+            << endl;
+    }
+
+
+    void
+    command_undochange(GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper* snapper)
+    {
+       const vector<Option> options = {
+           Option("input",             required_argument,      'i')
+       };
+
+       ParsedOpts opts = get_opts.parse("undochange", options);
+       if (get_opts.num_args() < 1)
+       {
+           cerr << _("Command 'undochange' needs at least one argument.") << endl;
+           exit(EXIT_FAILURE);
+       }
+
+       ProxySnapshots& snapshots = snapper->getSnapshots();
+
+       pair<ProxySnapshots::const_iterator, ProxySnapshots::const_iterator> range =
+           snapshots.findNums(get_opts.pop_arg());
+
+       FILE* file = NULL;
+
+       ParsedOpts::const_iterator opt;
+
+       if ((opt = opts.find("input")) != opts.end())
+       {
+           file = fopen(opt->second.c_str(), "r");
+           if (!file)
+           {
+               cerr << sformat(_("Opening file '%s' failed."), opt->second.c_str()) << endl;
+               exit(EXIT_FAILURE);
+           }
+       }
+
+       if (range.first->isCurrent())
+       {
+           cerr << _("Invalid snapshots.") << endl;
+           exit(EXIT_FAILURE);
+       }
+
+       ProxyComparison comparison = snapper->createComparison(*range.first, *range.second, true);
+
+       MyFiles files(comparison.getFiles());
+
+       files.bulk_process(file, get_opts, [](File& file) {
+           file.setUndo(true);
+       });
+
+       UndoStatistic undo_statistic = files.getUndoStatistic();
+
+       if (undo_statistic.empty())
+       {
+           cout << _("nothing to do") << endl;
+           return;
+       }
+
+       cout << sformat(_("create:%d modify:%d delete:%d"), undo_statistic.numCreate,
+                       undo_statistic.numModify, undo_statistic.numDelete) << endl;
+
+       vector<UndoStep> undo_steps = files.getUndoSteps();
+
+       for (vector<UndoStep>::const_iterator it1 = undo_steps.begin(); it1 != undo_steps.end(); ++it1)
+       {
+           vector<File>::iterator it2 = files.find(it1->name);
+           if (it2 == files.end())
+           {
+               cerr << "internal error" << endl;
+               exit(EXIT_FAILURE);
+           }
+
+           if (it1->action != it2->getAction())
+           {
+               cerr << "internal error" << endl;
+               exit(EXIT_FAILURE);
+           }
+
+           if (global_options.verbose())
+           {
+               switch (it1->action)
+               {
+                   case CREATE:
+                       cout << sformat(_("creating %s"), it2->getAbsolutePath(LOC_SYSTEM).c_str()) << endl;
+                       break;
+                   case MODIFY:
+                       cout << sformat(_("modifying %s"), it2->getAbsolutePath(LOC_SYSTEM).c_str()) << endl;
+                       break;
+                   case DELETE:
+                       cout << sformat(_("deleting %s"), it2->getAbsolutePath(LOC_SYSTEM).c_str()) << endl;
+                       break;
+               }
+           }
+
+           if (!it2->doUndo())
+           {
+               switch (it1->action)
+               {
+                   case CREATE:
+                       cerr << sformat(_("failed to create %s"), it2->getAbsolutePath(LOC_SYSTEM).c_str()) << endl;
+                       break;
+                   case MODIFY:
+                       cerr << sformat(_("failed to modify %s"), it2->getAbsolutePath(LOC_SYSTEM).c_str()) << endl;
+                       break;
+                   case DELETE:
+                       cerr << sformat(_("failed to delete %s"), it2->getAbsolutePath(LOC_SYSTEM).c_str()) << endl;
+                       break;
+               }
+           }
+       }
+    }
+
+}
diff --git a/client/cmd-xadiff.cc b/client/cmd-xadiff.cc
new file mode 100644 (file)
index 0000000..3a95a59
--- /dev/null
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) [2011-2015] Novell, Inc.
+ * Copyright (c) [2016-2020] SUSE LLC
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License as published
+ * by the Free Software Foundation.
+ *
+ * This program 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 this program; if not, contact Novell, Inc.
+ *
+ * To contact Novell about this file by physical or electronic mail, you may
+ * find current contact information at www.novell.com.
+ */
+
+
+#include "config.h"
+
+#include <iostream>
+
+#include <snapper/XAttributes.h>
+
+#include "utils/text.h"
+#include "GlobalOptions.h"
+#include "proxy.h"
+#include "MyFiles.h"
+
+
+namespace snapper
+{
+
+    using namespace std;
+
+
+#ifdef ENABLE_XATTRS
+
+    void
+    help_xadiff()
+    {
+       cout << _("  Comparing snapshots extended attributes:") << '\n'
+            << _("\tsnapper xadiff <number1>..<number2> [files]") << '\n'
+            << endl;
+    }
+
+    void
+    print_xadiff(const string loc_pre, const string loc_post)
+    {
+       try
+       {
+           XAModification xa_mod = XAModification(XAttributes(loc_pre), XAttributes(loc_post));
+
+           if (!xa_mod.empty())
+           {
+               cout << "--- " << loc_pre << endl << "+++ " << loc_post << endl;
+               xa_mod.dumpDiffReport(cout);
+           }
+       }
+       catch (const XAttributesException& e)
+       {
+           SN_CAUGHT(e);
+       }
+    }
+
+    void
+    command_xadiff(GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper* snapper)
+    {
+       ParsedOpts opts = get_opts.parse("xadiff", GetOpts::no_options);
+       if (get_opts.num_args() < 1)
+       {
+           cerr << _("Command 'xadiff' needs at least one argument.") << endl;
+           exit(EXIT_FAILURE);
+       }
+
+       ProxySnapshots& snapshots = snapper->getSnapshots();
+
+       pair<ProxySnapshots::const_iterator, ProxySnapshots::const_iterator> range =
+           snapshots.findNums(get_opts.pop_arg());
+
+       ProxyComparison comparison = snapper->createComparison(*range.first, *range.second, true);
+
+       MyFiles files(comparison.getFiles());
+
+       if (get_opts.num_args() == 0)
+       {
+           for (Files::const_iterator it1 = files.begin(); it1 != files.end(); ++it1)
+               if (it1->getPreToPostStatus() & XATTRS)
+                   print_xadiff(it1->getAbsolutePath(LOC_PRE), it1->getAbsolutePath(LOC_POST));
+       }
+       else
+       {
+           while (get_opts.num_args() > 0)
+           {
+               string name = get_opts.pop_arg();
+
+               Files::const_iterator it1 = files.findAbsolutePath(name);
+               if (it1 == files.end())
+                   continue;
+
+               if (it1->getPreToPostStatus() & XATTRS)
+                   print_xadiff(it1->getAbsolutePath(LOC_PRE), it1->getAbsolutePath(LOC_POST));
+           }
+       }
+    }
+
+#endif
+
+}
diff --git a/client/cmd.h b/client/cmd.h
new file mode 100644 (file)
index 0000000..9406fb4
--- /dev/null
@@ -0,0 +1,173 @@
+/*
+ * Copyright (c) [2011-2015] Novell, Inc.
+ * Copyright (c) [2016-2020] SUSE LLC
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License as published
+ * by the Free Software Foundation.
+ *
+ * This program 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 this program; if not, contact Novell, Inc.
+ *
+ * To contact Novell about this file by physical or electronic mail, you may
+ * find current contact information at www.novell.com.
+ */
+
+
+#include "config.h"
+
+#include "proxy.h"
+#include "GlobalOptions.h"
+
+
+namespace snapper
+{
+
+    void
+    help_list_configs();
+
+    void
+    command_list_configs(GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper*);
+
+
+    void
+    help_create_config();
+
+    void
+    command_create_config(GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper*);
+
+
+    void
+    help_delete_config();
+
+    void
+    command_delete_config(GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper*);
+
+
+    void
+    help_get_config();
+
+    void
+    command_get_config(GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper*);
+
+
+    void
+    help_set_config();
+
+    void
+    command_set_config(GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper*);
+
+
+    void
+    help_list();
+
+    void
+    command_list(GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper*);
+
+
+    void
+    help_create();
+
+    void
+    command_create(GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper* snapper);
+
+
+    void
+    help_modify();
+
+    void
+    command_modify(GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper* snapper);
+
+
+    void
+    help_delete();
+
+    void
+    command_delete(GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper* snapper);
+
+
+    void
+    help_mount();
+
+    void
+    command_mount(GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper* snapper);
+
+
+    void
+    help_umount();
+
+    void
+    command_umount(GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper* snapper);
+
+
+    void
+    help_status();
+
+    void
+    command_status(GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper* snapper);
+
+
+    void
+    help_diff();
+
+    void
+    command_diff(GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper* snapper);
+
+
+#ifdef ENABLE_XATTRS
+
+    void
+    help_xadiff();
+
+    void
+    command_xadiff(GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper* snapper);
+
+#endif
+
+
+    void
+    help_undochange();
+
+    void
+    command_undochange(GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper* snapper);
+
+
+#ifdef ENABLE_ROLLBACK
+
+    void
+    help_rollback();
+
+    void
+    command_rollback(GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper* snapper);
+
+#endif
+
+
+    void
+    help_setup_quota();
+
+    void
+    command_setup_quota(GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper* snapper);
+
+
+    void
+    help_cleanup();
+
+    void
+    command_cleanup(GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper* snapper);
+
+
+    void
+    help_debug();
+
+    void
+    command_debug(GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper* snapper);
+
+}
index 5a3285ff286b0428c37f1deff919163139523397..2db73a9fe2f3781a3be336657b8288821506af98 100644 (file)
 
 #include <stdlib.h>
 #include <string.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <fcntl.h>
 #include <iostream>
-#include <boost/algorithm/string.hpp>
 
 #include <snapper/Snapper.h>
 #include <snapper/SnapperTmpl.h>
 #include <snapper/Enum.h>
-#include <snapper/AsciiFile.h>
-#include <snapper/SnapperDefines.h>
-#include <snapper/XAttributes.h>
-#ifdef ENABLE_ROLLBACK
-#include <snapper/Filesystem.h>
-#include <snapper/Hooks.h>
-#endif
 
 #include "utils/text.h"
 #include "utils/Table.h"
 #include "utils/GetOpts.h"
-#include "utils/HumanString.h"
 
-#include "cleanup.h"
 #include "errors.h"
 #include "proxy.h"
-#include "misc.h"
-
 #include "GlobalOptions.h"
-#include "Command/ListSnapshots.h"
-#include "Command/ListConfigs.h"
-#include "Command/GetConfig.h"
+#include "cmd.h"
+
 
 using namespace snapper;
 using namespace std;
@@ -87,1282 +71,6 @@ struct Cmd
 };
 
 
-struct MyFiles : public Files
-{
-
-    MyFiles(const Files& files) : Files(files) {}
-
-    void bulk_process(FILE* file, GetOpts& get_opts, std::function<void(File& file)> callback);
-
-};
-
-
-void
-MyFiles::bulk_process(FILE* file, GetOpts& get_opts, std::function<void(File& file)> callback)
-{
-    if (file)
-    {
-       AsciiFileReader asciifile(file);
-
-       string line;
-       while (asciifile.getline(line))
-       {
-           if (line.empty())
-               continue;
-
-           string name = line;
-
-           // strip optional status
-           if (name[0] != '/')
-           {
-               string::size_type pos = name.find(" ");
-               if (pos == string::npos)
-                   continue;
-
-               name.erase(0, pos + 1);
-           }
-
-           Files::iterator it = findAbsolutePath(name);
-           if (it == end())
-           {
-               cerr << sformat(_("File '%s' not found."), name.c_str()) << endl;
-               exit(EXIT_FAILURE);
-           }
-
-           callback(*it);
-       }
-    }
-    else
-    {
-       if (get_opts.num_args() == 0)
-       {
-           for (Files::iterator it = begin(); it != end(); ++it)
-               callback(*it);
-       }
-       else
-       {
-           while (get_opts.num_args() > 0)
-           {
-               string name = get_opts.pop_arg();
-
-               Files::iterator it = findAbsolutePath(name);
-               if (it == end())
-               {
-                   cerr << sformat(_("File '%s' not found."), name.c_str()) << endl;
-                   exit(EXIT_FAILURE);
-               }
-
-               callback(*it);
-           }
-       }
-    }
-}
-
-
-void
-help_list_configs()
-{
-    cout << cli::Command::ListConfigs::help() << endl;
-}
-
-
-void
-command_list_configs(GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper*)
-{
-    cli::Command::ListConfigs command(global_options, get_opts, *snappers);
-
-    command.run();
-}
-
-
-void
-help_create_config()
-{
-    cout << _("  Create config:") << '\n'
-        << _("\tsnapper create-config <subvolume>") << '\n'
-        << '\n'
-        << _("    Options for 'create-config' command:") << '\n'
-        << _("\t--fstype, -f <fstype>\t\tManually set filesystem type.") << '\n'
-        << _("\t--template, -t <name>\t\tName of config template to use.") << '\n'
-        << endl;
-}
-
-
-void
-command_create_config(GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper*)
-{
-    const vector<Option> options = {
-       Option("fstype",        required_argument,      'f'),
-       Option("template",      required_argument,      't')
-    };
-
-    ParsedOpts opts = get_opts.parse("create-config", options);
-    if (get_opts.num_args() != 1)
-    {
-       cerr << _("Command 'create-config' needs one argument.") << endl;
-       exit(EXIT_FAILURE);
-    }
-
-    string subvolume = realpath(get_opts.pop_arg());
-    if (subvolume.empty())
-    {
-       cerr << _("Invalid subvolume.") << endl;
-       exit(EXIT_FAILURE);
-    }
-
-    string fstype = "";
-    string template_name = "default";
-
-    ParsedOpts::const_iterator opt;
-
-    if ((opt = opts.find("fstype")) != opts.end())
-       fstype = opt->second;
-
-    if ((opt = opts.find("template")) != opts.end())
-       template_name = opt->second;
-
-    if (fstype.empty() && !Snapper::detectFstype(subvolume, fstype))
-    {
-       cerr << _("Detecting filesystem type failed.") << endl;
-       exit(EXIT_FAILURE);
-    }
-
-    snappers->createConfig(global_options.config(), subvolume, fstype, template_name);
-}
-
-
-void
-help_delete_config()
-{
-    cout << _("  Delete config:") << '\n'
-        << _("\tsnapper delete-config") << '\n'
-        << endl;
-}
-
-
-void
-command_delete_config(GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper*)
-{
-    get_opts.parse("delete-config", GetOpts::no_options);
-    if (get_opts.has_args())
-    {
-       cerr << _("Command 'delete-config' does not take arguments.") << endl;
-       exit(EXIT_FAILURE);
-    }
-
-    snappers->deleteConfig(global_options.config());
-}
-
-
-void
-help_get_config()
-{
-    cout << cli::Command::GetConfig::help() << endl;
-}
-
-
-void
-command_get_config(GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper*)
-{
-    cli::Command::GetConfig command(global_options, get_opts, *snappers);
-
-    command.run();
-}
-
-
-void
-help_set_config()
-{
-    cout << _("  Set config:") << '\n'
-        << _("\tsnapper set-config <configdata>") << '\n'
-        << endl;
-}
-
-
-void
-command_set_config(GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper* snapper)
-{
-    get_opts.parse("set-config", GetOpts::no_options);
-    if (!get_opts.has_args())
-    {
-       cerr << _("Command 'set-config' needs at least one argument.") << endl;
-       exit(EXIT_FAILURE);
-    }
-
-    ProxyConfig config(read_configdata(get_opts.get_args()));
-
-    snapper->setConfig(config);
-}
-
-
-void
-help_list()
-{
-    cout << cli::Command::ListSnapshots::help() << endl;
-}
-
-
-void
-command_list(GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper*)
-{
-    cli::Command::ListSnapshots command(global_options, get_opts, *snappers);
-
-    command.run();
-}
-
-
-void
-help_create()
-{
-    cout << _("  Create snapshot:") << '\n'
-        << _("\tsnapper create") << '\n'
-        << '\n'
-        << _("    Options for 'create' command:") << '\n'
-        << _("\t--type, -t <type>\t\tType for snapshot.") << '\n'
-        << _("\t--pre-number <number>\t\tNumber of corresponding pre snapshot.") << '\n'
-        << _("\t--print-number, -p\t\tPrint number of created snapshot.") << '\n'
-        << _("\t--description, -d <description>\tDescription for snapshot.") << '\n'
-        << _("\t--cleanup-algorithm, -c <algo>\tCleanup algorithm for snapshot.") << '\n'
-        << _("\t--userdata, -u <userdata>\tUserdata for snapshot.") << '\n'
-        << _("\t--command <command>\t\tRun command and create pre and post snapshots.") << endl
-        << _("\t--read-only\t\t\tCreate read-only snapshot.") << '\n'
-        << _("\t--read-write\t\t\tCreate read-write snapshot.") << '\n'
-        << _("\t--from\t\t\t\tCreate a snapshot from the specified snapshot.") << '\n'
-        << endl;
-}
-
-
-void
-command_create(GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper* snapper)
-{
-    const vector<Option> options = {
-       Option("type",                  required_argument,      't'),
-       Option("pre-number",            required_argument),
-       Option("print-number",          no_argument,            'p'),
-       Option("description",           required_argument,      'd'),
-       Option("cleanup-algorithm",     required_argument,      'c'),
-       Option("userdata",              required_argument,      'u'),
-       Option("command",               required_argument),
-       Option("read-only",             no_argument),
-       Option("read-write",            no_argument),
-       Option("from",                  required_argument)
-    };
-
-    ParsedOpts opts = get_opts.parse("create", options);
-    if (get_opts.has_args())
-    {
-       cerr << _("Command 'create' does not take arguments.") << endl;
-       exit(EXIT_FAILURE);
-    }
-
-    enum class CreateType { SINGLE, PRE, POST, PRE_POST };
-
-    const ProxySnapshots& snapshots = snapper->getSnapshots();
-
-    CreateType type = CreateType::SINGLE;
-    ProxySnapshots::const_iterator snapshot1 = snapshots.end();
-    ProxySnapshots::const_iterator snapshot2 = snapshots.end();
-    bool print_number = false;
-    SCD scd;
-    string command;
-    ProxySnapshots::const_iterator parent = snapshots.getCurrent();
-
-    ParsedOpts::const_iterator opt;
-
-    if ((opt = opts.find("type")) != opts.end())
-    {
-       if (opt->second == "single")
-           type = CreateType::SINGLE;
-       else if (opt->second == "pre")
-           type = CreateType::PRE;
-       else if (opt->second == "post")
-           type = CreateType::POST;
-       else if (opt->second == "pre-post")
-           type = CreateType::PRE_POST;
-       else
-       {
-           cerr << _("Unknown type of snapshot.") << endl;
-           exit(EXIT_FAILURE);
-       }
-    }
-
-    if ((opt = opts.find("pre-number")) != opts.end())
-       snapshot1 = snapshots.findNum(opt->second);
-
-    if ((opt = opts.find("print-number")) != opts.end())
-       print_number = true;
-
-    if ((opt = opts.find("description")) != opts.end())
-       scd.description = opt->second;
-
-    if ((opt = opts.find("cleanup-algorithm")) != opts.end())
-       scd.cleanup = opt->second;
-
-    if ((opt = opts.find("userdata")) != opts.end())
-       scd.userdata = read_userdata(opt->second);
-
-    if ((opt = opts.find("command")) != opts.end())
-    {
-       command = opt->second;
-       type = CreateType::PRE_POST;
-    }
-
-    if ((opt = opts.find("read-only")) != opts.end())
-       scd.read_only = true;
-
-    if ((opt = opts.find("read-write")) != opts.end())
-       scd.read_only = false;
-
-    if ((opt = opts.find("from")) != opts.end())
-       parent = snapshots.findNum(opt->second);
-
-    if (type == CreateType::POST && snapshot1 == snapshots.end())
-    {
-       cerr << _("Missing or invalid pre-number.") << endl;
-       exit(EXIT_FAILURE);
-    }
-
-    if (type == CreateType::PRE_POST && command.empty())
-    {
-       cerr << _("Missing command argument.") << endl;
-       exit(EXIT_FAILURE);
-    }
-
-    if (type != CreateType::SINGLE && !scd.read_only)
-    {
-       cerr << _("Option --read-write only supported for snapshots of type single.") << endl;
-       exit(EXIT_FAILURE);
-    }
-
-    if (type != CreateType::SINGLE && parent != snapshots.getCurrent())
-    {
-       cerr << _("Option --from only supported for snapshots of type single.") << endl;
-       exit(EXIT_FAILURE);
-    }
-
-    switch (type)
-    {
-       case CreateType::SINGLE: {
-           snapshot1 = snapper->createSingleSnapshot(parent, scd);
-           if (print_number)
-               cout << snapshot1->getNum() << endl;
-       } break;
-
-       case CreateType::PRE: {
-           snapshot1 = snapper->createPreSnapshot(scd);
-           if (print_number)
-               cout << snapshot1->getNum() << endl;
-       } break;
-
-       case CreateType::POST: {
-           snapshot2 = snapper->createPostSnapshot(snapshot1, scd);
-           if (print_number)
-               cout << snapshot2->getNum() << endl;
-       } break;
-
-       case CreateType::PRE_POST: {
-           snapshot1 = snapper->createPreSnapshot(scd);
-           system(command.c_str());
-           snapshot2 = snapper->createPostSnapshot(snapshot1, scd);
-           if (print_number)
-               cout << snapshot1->getNum() << ".." << snapshot2->getNum() << endl;
-       } break;
-    }
-}
-
-
-void
-help_modify()
-{
-    cout << _("  Modify snapshot:") << '\n'
-        << _("\tsnapper modify <number>") << '\n'
-        << '\n'
-        << _("    Options for 'modify' command:") << '\n'
-        << _("\t--description, -d <description>\tDescription for snapshot.") << '\n'
-        << _("\t--cleanup-algorithm, -c <algo>\tCleanup algorithm for snapshot.") << '\n'
-        << _("\t--userdata, -u <userdata>\tUserdata for snapshot.") << '\n'
-        << endl;
-}
-
-
-void
-command_modify(GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper* snapper)
-{
-    const vector<Option> options = {
-       Option("description",           required_argument,      'd'),
-       Option("cleanup-algorithm",     required_argument,      'c'),
-       Option("userdata",              required_argument,      'u')
-    };
-
-    ParsedOpts opts = get_opts.parse("modify", options);
-    if (!get_opts.has_args())
-    {
-       cerr << _("Command 'modify' needs at least one argument.") << endl;
-       exit(EXIT_FAILURE);
-    }
-
-    ProxySnapshots& snapshots = snapper->getSnapshots();
-
-    while (get_opts.has_args())
-    {
-       ProxySnapshots::iterator snapshot = snapshots.findNum(get_opts.pop_arg());
-
-       SMD smd = snapshot->getSmd();
-
-       ParsedOpts::const_iterator opt;
-
-       if ((opt = opts.find("description")) != opts.end())
-           smd.description = opt->second;
-
-       if ((opt = opts.find("cleanup-algorithm")) != opts.end())
-           smd.cleanup = opt->second;
-
-       if ((opt = opts.find("userdata")) != opts.end())
-           smd.userdata = read_userdata(opt->second, snapshot->getUserdata());
-
-       snapper->modifySnapshot(snapshot, smd);
-    }
-}
-
-
-void
-help_delete()
-{
-    cout << _("  Delete snapshot:") << '\n'
-        << _("\tsnapper delete <number>") << '\n'
-        << '\n'
-        << _("    Options for 'delete' command:") << '\n'
-        << _("\t--sync, -s\t\t\tSync after deletion.") << '\n'
-        << endl;
-}
-
-
-void
-filter_undeletables(ProxySnapshots& snapshots, vector<ProxySnapshots::iterator>& nums)
-{
-    auto filter = [&snapshots, &nums](ProxySnapshots::const_iterator undeletable, const char* message)
-    {
-       if (undeletable == snapshots.end())
-           return;
-
-       unsigned int num = undeletable->getNum();
-
-       vector<ProxySnapshots::iterator>::iterator keep = find_if(nums.begin(), nums.end(),
-           [num](ProxySnapshots::iterator it){ return num == it->getNum(); });
-
-       if (keep != nums.end())
-       {
-           cerr << sformat(message, num) << endl;
-           nums.erase(keep);
-       }
-    };
-
-    ProxySnapshots::const_iterator current_snapshot = snapshots.begin();
-    filter(current_snapshot, _("Cannot delete snapshot %d since it is the current system."));
-
-    ProxySnapshots::const_iterator active_snapshot = snapshots.getActive();
-    filter(active_snapshot, _("Cannot delete snapshot %d since it is the currently mounted snapshot."));
-
-    ProxySnapshots::const_iterator default_snapshot = snapshots.getDefault();
-    filter(default_snapshot, _("Cannot delete snapshot %d since it is the next to be mounted snapshot."));
-}
-
-
-void
-command_delete(GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper* snapper)
-{
-    const vector<Option> options = {
-       Option("sync",  no_argument,    's')
-    };
-
-    ParsedOpts opts = get_opts.parse("delete", options);
-    if (!get_opts.has_args())
-    {
-       cerr << _("Command 'delete' needs at least one argument.") << endl;
-       exit(EXIT_FAILURE);
-    }
-
-    bool sync = false;
-
-    ParsedOpts::const_iterator opt;
-
-    if ((opt = opts.find("sync")) != opts.end())
-       sync = true;
-
-    ProxySnapshots& snapshots = snapper->getSnapshots();
-
-    vector<ProxySnapshots::iterator> nums;
-
-    while (get_opts.has_args())
-    {
-       string arg = get_opts.pop_arg();
-
-       if (arg.find_first_of("-") == string::npos)
-       {
-           ProxySnapshots::iterator tmp = snapshots.findNum(arg);
-           nums.push_back(tmp);
-       }
-       else
-       {
-           pair<ProxySnapshots::iterator, ProxySnapshots::iterator> range =
-               snapshots.findNums(arg, "-");
-
-           if (range.first->getNum() > range.second->getNum())
-               swap(range.first, range.second);
-
-           for (unsigned int i = range.first->getNum(); i <= range.second->getNum(); ++i)
-           {
-               ProxySnapshots::iterator x = snapshots.find(i);
-               if (x != snapshots.end())
-               {
-                   if (find_if(nums.begin(), nums.end(), [i](ProxySnapshots::iterator it)
-                               { return it->getNum() == i; }) == nums.end())
-                       nums.push_back(x);
-               }
-           }
-       }
-    }
-
-    filter_undeletables(snapshots, nums);
-
-    snapper->deleteSnapshots(nums, global_options.verbose());
-
-    if (sync)
-       snapper->syncFilesystem();
-}
-
-
-void
-help_mount()
-{
-    cout << _("  Mount snapshot:") << '\n'
-        << _("\tsnapper mount <number>") << '\n'
-        << endl;
-}
-
-
-void
-command_mount(GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper* snapper)
-{
-    get_opts.parse("mount", GetOpts::no_options);
-    if (!get_opts.has_args())
-    {
-       cerr << _("Command 'mount' needs at least one argument.") << endl;
-       exit(EXIT_FAILURE);
-    }
-
-    const ProxySnapshots& snapshots = snapper->getSnapshots();
-
-    while (get_opts.has_args())
-    {
-       ProxySnapshots::const_iterator snapshot = snapshots.findNum(get_opts.pop_arg());
-       snapshot->mountFilesystemSnapshot(true);
-    }
-}
-
-
-void
-help_umount()
-{
-    cout << _("  Umount snapshot:") << '\n'
-        << _("\tsnapper umount <number>") << '\n'
-        << endl;
-}
-
-
-void
-command_umount(GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper* snapper)
-{
-    get_opts.parse("umount", GetOpts::no_options);
-    if (!get_opts.has_args())
-    {
-       cerr << _("Command 'umount' needs at least one argument.") << endl;
-       exit(EXIT_FAILURE);
-    }
-
-    const ProxySnapshots& snapshots = snapper->getSnapshots();
-
-    while (get_opts.has_args())
-    {
-       ProxySnapshots::const_iterator snapshot = snapshots.findNum(get_opts.pop_arg());
-       snapshot->umountFilesystemSnapshot(true);
-    }
-}
-
-
-void
-help_status()
-{
-    cout << _("  Comparing snapshots:") << '\n'
-        << _("\tsnapper status <number1>..<number2>") << '\n'
-        << '\n'
-        << _("    Options for 'status' command:") << '\n'
-        << _("\t--output, -o <file>\t\tSave status to file.") << '\n'
-        << endl;
-}
-
-
-void
-command_status(GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper* snapper)
-{
-    const vector<Option> options = {
-       Option("output",        required_argument,      'o')
-    };
-
-    ParsedOpts opts = get_opts.parse("status", options);
-    if (get_opts.num_args() != 1)
-    {
-       cerr << _("Command 'status' needs one argument.") << endl;
-
-       if (get_opts.num_args() == 2)
-       {
-           cerr << _("Maybe you forgot the delimiter '..' between the snapshot numbers.") << endl
-                << _("See 'man snapper' for further instructions.") << endl;
-       }
-
-       exit(EXIT_FAILURE);
-    }
-
-    ParsedOpts::const_iterator opt;
-
-    ProxySnapshots& snapshots = snapper->getSnapshots();
-
-    pair<ProxySnapshots::const_iterator, ProxySnapshots::const_iterator> range =
-       snapshots.findNums(get_opts.pop_arg());
-
-    ProxyComparison comparison = snapper->createComparison(*range.first, *range.second, false);
-
-    MyFiles files(comparison.getFiles());
-
-    FILE* file = stdout;
-
-    if ((opt = opts.find("output")) != opts.end())
-    {
-       file = fopen(opt->second.c_str(), "w");
-       if (!file)
-       {
-           cerr << sformat(_("Opening file '%s' failed."), opt->second.c_str()) << endl;
-           exit(EXIT_FAILURE);
-       }
-    }
-
-    for (Files::const_iterator it = files.begin(); it != files.end(); ++it)
-       fprintf(file, "%s %s\n", statusToString(it->getPreToPostStatus()).c_str(),
-               it->getAbsolutePath(LOC_SYSTEM).c_str());
-
-    if (file != stdout)
-       fclose(file);
-}
-
-
-void
-help_diff()
-{
-    cout << _("  Comparing snapshots:") << '\n'
-        << _("\tsnapper diff <number1>..<number2> [files]") << '\n'
-        << '\n'
-        << _("    Options for 'diff' command:") << '\n'
-        << _("\t--input, -i <file>\t\tRead files to diff from file.") << '\n'
-        << _("\t--diff-cmd <command>\t\tCommand used for comparing files.") << '\n'
-        << _("\t--extensions, -x <options>\tExtra options passed to the diff command.") << '\n'
-        << endl;
-}
-
-
-void
-command_diff(GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper* snapper)
-{
-    const vector<Option> options = {
-       Option("input",         required_argument,      'i'),
-       Option("diff-cmd",      required_argument),
-       Option("extensions",    required_argument,      'x'),
-    };
-
-    ParsedOpts opts = get_opts.parse("diff", options);
-    if (get_opts.num_args() < 1)
-    {
-       cerr << _("Command 'diff' needs at least one argument.") << endl;
-       exit(EXIT_FAILURE);
-    }
-
-    FILE* file = NULL;
-    Differ differ;
-
-    ParsedOpts::const_iterator opt;
-
-    if ((opt = opts.find("input")) != opts.end())
-    {
-       file = fopen(opt->second.c_str(), "r");
-       if (!file)
-       {
-           cerr << sformat(_("Opening file '%s' failed."), opt->second.c_str()) << endl;
-           exit(EXIT_FAILURE);
-       }
-    }
-
-    if ((opt = opts.find("diff-cmd")) != opts.end())
-       differ.command = opt->second;
-
-    if ((opt = opts.find("extensions")) != opts.end())
-       differ.extensions = opt->second;
-
-    ProxySnapshots& snapshots = snapper->getSnapshots();
-
-    pair<ProxySnapshots::const_iterator, ProxySnapshots::const_iterator> range =
-       snapshots.findNums(get_opts.pop_arg());
-
-    ProxyComparison comparison = snapper->createComparison(*range.first, *range.second, true);
-
-    MyFiles files(comparison.getFiles());
-
-    files.bulk_process(file, get_opts, [differ](const File& file) {
-       differ.run(file.getAbsolutePath(LOC_PRE), file.getAbsolutePath(LOC_POST));
-    });
-}
-
-
-void
-help_undo()
-{
-    cout << _("  Undo changes:") << '\n'
-        << _("\tsnapper undochange <number1>..<number2> [files]") << '\n'
-        << '\n'
-        << _("    Options for 'undochange' command:") << '\n'
-        << _("\t--input, -i <file>\t\tRead files for which to undo changes from file.") << '\n'
-        << endl;
-}
-
-
-void
-command_undo(GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper* snapper)
-{
-    const vector<Option> options = {
-       Option("input",         required_argument,      'i')
-    };
-
-    ParsedOpts opts = get_opts.parse("undochange", options);
-    if (get_opts.num_args() < 1)
-    {
-       cerr << _("Command 'undochange' needs at least one argument.") << endl;
-       exit(EXIT_FAILURE);
-    }
-
-    ProxySnapshots& snapshots = snapper->getSnapshots();
-
-    pair<ProxySnapshots::const_iterator, ProxySnapshots::const_iterator> range =
-       snapshots.findNums(get_opts.pop_arg());
-
-    FILE* file = NULL;
-
-    ParsedOpts::const_iterator opt;
-
-    if ((opt = opts.find("input")) != opts.end())
-    {
-       file = fopen(opt->second.c_str(), "r");
-       if (!file)
-       {
-           cerr << sformat(_("Opening file '%s' failed."), opt->second.c_str()) << endl;
-           exit(EXIT_FAILURE);
-       }
-    }
-
-    if (range.first->isCurrent())
-    {
-       cerr << _("Invalid snapshots.") << endl;
-       exit(EXIT_FAILURE);
-    }
-
-    ProxyComparison comparison = snapper->createComparison(*range.first, *range.second, true);
-
-    MyFiles files(comparison.getFiles());
-
-    files.bulk_process(file, get_opts, [](File& file) {
-       file.setUndo(true);
-    });
-
-    UndoStatistic undo_statistic = files.getUndoStatistic();
-
-    if (undo_statistic.empty())
-    {
-       cout << _("nothing to do") << endl;
-       return;
-    }
-
-    cout << sformat(_("create:%d modify:%d delete:%d"), undo_statistic.numCreate,
-                   undo_statistic.numModify, undo_statistic.numDelete) << endl;
-
-    vector<UndoStep> undo_steps = files.getUndoSteps();
-
-    for (vector<UndoStep>::const_iterator it1 = undo_steps.begin(); it1 != undo_steps.end(); ++it1)
-    {
-       vector<File>::iterator it2 = files.find(it1->name);
-       if (it2 == files.end())
-       {
-           cerr << "internal error" << endl;
-           exit(EXIT_FAILURE);
-       }
-
-       if (it1->action != it2->getAction())
-       {
-           cerr << "internal error" << endl;
-           exit(EXIT_FAILURE);
-       }
-
-       if (global_options.verbose())
-       {
-           switch (it1->action)
-           {
-               case CREATE:
-                   cout << sformat(_("creating %s"), it2->getAbsolutePath(LOC_SYSTEM).c_str()) << endl;
-                   break;
-               case MODIFY:
-                   cout << sformat(_("modifying %s"), it2->getAbsolutePath(LOC_SYSTEM).c_str()) << endl;
-                   break;
-               case DELETE:
-                   cout << sformat(_("deleting %s"), it2->getAbsolutePath(LOC_SYSTEM).c_str()) << endl;
-                   break;
-           }
-       }
-
-       if (!it2->doUndo())
-       {
-           switch (it1->action)
-           {
-               case CREATE:
-                   cerr << sformat(_("failed to create %s"), it2->getAbsolutePath(LOC_SYSTEM).c_str()) << endl;
-                   break;
-               case MODIFY:
-                   cerr << sformat(_("failed to modify %s"), it2->getAbsolutePath(LOC_SYSTEM).c_str()) << endl;
-                   break;
-               case DELETE:
-                   cerr << sformat(_("failed to delete %s"), it2->getAbsolutePath(LOC_SYSTEM).c_str()) << endl;
-                   break;
-           }
-       }
-    }
-}
-
-
-#ifdef ENABLE_ROLLBACK
-
-const Filesystem*
-getFilesystem(const ProxyConfig& config, const string& target_root)
-{
-    const map<string, string>& raw = config.getAllValues();
-
-    map<string, string>::const_iterator pos1 = raw.find(KEY_FSTYPE);
-    map<string, string>::const_iterator pos2 = raw.find(KEY_SUBVOLUME);
-    if (pos1 == raw.end() || pos2 == raw.end())
-    {
-       cerr << _("Failed to initialize filesystem handler.") << endl;
-       exit(EXIT_FAILURE);
-    }
-
-    try
-    {
-       return Filesystem::create(pos1->second, pos2->second, target_root);
-    }
-    catch (const InvalidConfigException& e)
-    {
-       SN_CAUGHT(e);
-       cerr << _("Failed to initialize filesystem handler.") << endl;
-       exit(EXIT_FAILURE);
-    }
-}
-
-
-void
-help_rollback()
-{
-    cout << _("  Rollback:") << '\n'
-        << _("\tsnapper rollback [number]") << '\n'
-        << '\n'
-        << _("    Options for 'rollback' command:") << '\n'
-        << _("\t--print-number, -p\t\tPrint number of second created snapshot.") << '\n'
-        << _("\t--description, -d <description>\tDescription for snapshots.") << '\n'
-        << _("\t--cleanup-algorithm, -c <algo>\tCleanup algorithm for snapshots.") << '\n'
-        << _("\t--userdata, -u <userdata>\tUserdata for snapshots.") << '\n'
-        << endl;
-}
-
-
-void
-command_rollback(GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper* snapper)
-{
-    const vector<Option> options = {
-       Option("print-number",          no_argument,            'p'),
-       Option("description",           required_argument,      'd'),
-       Option("cleanup-algorithm",     required_argument,      'c'),
-       Option("userdata",              required_argument,      'u')
-    };
-
-    ParsedOpts opts = get_opts.parse("rollback", options);
-    if (get_opts.has_args() && get_opts.num_args() != 1)
-    {
-       cerr << _("Command 'rollback' takes either one or no argument.") << endl;
-       exit(EXIT_FAILURE);
-    }
-
-    const string default_description1 = "rollback backup";
-    const string default_description2 = "writable copy";
-
-    bool print_number = false;
-
-    SCD scd1;
-    scd1.description = default_description1;
-    scd1.cleanup = "number";
-    scd1.userdata["important"] = "yes";
-
-    SCD scd2;
-    scd2.description = default_description2;
-
-    ParsedOpts::const_iterator opt;
-
-    if ((opt = opts.find("print-number")) != opts.end())
-       print_number = true;
-
-    if ((opt = opts.find("description")) != opts.end())
-       scd1.description = scd2.description = opt->second;
-
-    if ((opt = opts.find("cleanup-algorithm")) != opts.end())
-       scd1.cleanup = scd2.cleanup = opt->second;
-
-    if ((opt = opts.find("userdata")) != opts.end())
-    {
-       scd1.userdata = read_userdata(opt->second, scd1.userdata);
-       scd2.userdata = read_userdata(opt->second);
-    }
-
-    ProxyConfig config = snapper->getConfig();
-
-    const Filesystem* filesystem = getFilesystem(config, global_options.root());
-    if (filesystem->fstype() != "btrfs")
-    {
-       cerr << _("Command 'rollback' only available for btrfs.") << endl;
-       exit(EXIT_FAILURE);
-    }
-
-    const string subvolume = config.getSubvolume();
-    if (subvolume != "/")
-    {
-       cerr << sformat(_("Command 'rollback' cannot be used on a non-root subvolume %s."),
-                       subvolume.c_str()) << endl;
-       exit(EXIT_FAILURE);
-    }
-
-    ProxySnapshots& snapshots = snapper->getSnapshots();
-
-    ProxySnapshots::iterator previous_default = snapshots.getDefault();
-
-    if (global_options.ambit() == GlobalOptions::Ambit::AUTO)
-    {
-       if (previous_default == snapshots.end())
-       {
-           cerr << _("Cannot detect ambit since default subvolume is unknown.") << '\n'
-                << _("This can happen if the system was not set up for rollback.") << '\n'
-                << _("The ambit can be specified manually using the --ambit option.") << endl;
-           exit(EXIT_FAILURE);
-       }
-
-       if (filesystem->isSnapshotReadOnly(previous_default->getNum()))
-           global_options.set_ambit(GlobalOptions::Ambit::TRANSACTIONAL);
-       else
-           global_options.set_ambit(GlobalOptions::Ambit::CLASSIC);
-    }
-
-    if (!global_options.quiet())
-       cout << sformat(_("Ambit is %s."), toString(global_options.ambit()).c_str()) << endl;
-
-    if (previous_default != snapshots.end() && scd1.description == default_description1)
-        scd1.description += sformat(" of #%d", previous_default->getNum());
-
-    switch (global_options.ambit())
-    {
-       case GlobalOptions::Ambit::CLASSIC:
-       {
-           ProxySnapshots::const_iterator snapshot1 = snapshots.end();
-           ProxySnapshots::const_iterator snapshot2 = snapshots.end();
-
-           if (get_opts.num_args() == 0)
-           {
-               if (!global_options.quiet())
-                   cout << _("Creating read-only snapshot of default subvolume.") << flush;
-
-               scd1.read_only = true;
-               snapshot1 = snapper->createSingleSnapshotOfDefault(scd1);
-
-               if (!global_options.quiet())
-                   cout << " " << sformat(_("(Snapshot %d.)"), snapshot1->getNum()) << endl;
-
-               if (!global_options.quiet())
-                   cout << _("Creating read-write snapshot of current subvolume.") << flush;
-
-               ProxySnapshots::const_iterator active = snapshots.getActive();
-               if (active != snapshots.end() && scd2.description == default_description2)
-                   scd2.description += sformat(" of #%d", active->getNum());
-
-               scd2.read_only = false;
-               snapshot2 = snapper->createSingleSnapshot(snapshots.getCurrent(), scd2);
-
-               if (!global_options.quiet())
-                   cout << " " << sformat(_("(Snapshot %d.)"), snapshot2->getNum()) << endl;
-           }
-           else
-           {
-               ProxySnapshots::const_iterator tmp = snapshots.findNum(get_opts.pop_arg());
-
-               if (!global_options.quiet())
-                   cout << _("Creating read-only snapshot of current system.") << flush;
-
-               snapshot1 = snapper->createSingleSnapshot(scd1);
-
-               if (!global_options.quiet())
-                   cout << " " << sformat(_("(Snapshot %d.)"), snapshot1->getNum()) << endl;
-
-               if (!global_options.quiet())
-                   cout << sformat(_("Creating read-write snapshot of snapshot %d."), tmp->getNum()) << flush;
-
-               if (tmp != snapshots.end() && scd2.description == default_description2)
-                   scd2.description += sformat(" of #%d", tmp->getNum());
-
-               scd2.read_only = false;
-               snapshot2 = snapper->createSingleSnapshot(tmp, scd2);
-
-               if (!global_options.quiet())
-                   cout << " " << sformat(_("(Snapshot %d.)"), snapshot2->getNum()) << endl;
-           }
-
-           if (previous_default != snapshots.end() && previous_default->getCleanup().empty())
-           {
-               SMD smd = previous_default->getSmd();
-               smd.cleanup = "number";
-               snapper->modifySnapshot(previous_default, smd);
-           }
-
-           if (!global_options.quiet())
-               cout << sformat(_("Setting default subvolume to snapshot %d."), snapshot2->getNum()) << endl;
-
-           filesystem->setDefault(snapshot2->getNum());
-
-           Hooks::rollback(filesystem->snapshotDir(snapshot1->getNum()),
-                           filesystem->snapshotDir(snapshot2->getNum()));
-
-           if (print_number)
-               cout << snapshot2->getNum() << endl;
-       }
-       break;
-
-       case GlobalOptions::Ambit::TRANSACTIONAL:
-       {
-           // see bsc #1172273
-
-           if (previous_default == snapshots.end())
-           {
-               cerr << _("Cannot do rollback since default subvolume is unknown.") << endl;
-               exit(EXIT_FAILURE);
-           }
-
-           ProxySnapshots::iterator snapshot = snapshots.end();
-
-           if (get_opts.num_args() == 0)
-           {
-               snapshot = snapshots.getActive();
-           }
-           else
-           {
-               snapshot = snapshots.findNum(get_opts.pop_arg());
-           }
-
-           if (previous_default == snapshot)
-           {
-               cerr << _("Active snapshot is already default snapshot.") << endl;
-               exit(EXIT_FAILURE);
-           }
-
-           SMD smd = snapshot->getSmd();
-           smd.cleanup = "";
-           snapper->modifySnapshot(snapshot, smd);
-
-           if (!global_options.quiet())
-               cout << sformat(_("Setting default subvolume to snapshot %d."), snapshot->getNum()) << endl;
-
-           filesystem->setDefault(snapshot->getNum());
-
-           Hooks::rollback(filesystem->snapshotDir(previous_default->getNum()),
-                           filesystem->snapshotDir(snapshot->getNum()));
-       }
-       break;
-
-       case GlobalOptions::Ambit::AUTO:
-       {
-           cerr << "internal error: ambit is auto" << endl;
-           exit(EXIT_FAILURE);
-       }
-       break;
-    }
-}
-
-#endif
-
-
-void
-help_setup_quota()
-{
-    cout << _("  Setup quota:") << '\n'
-        << _("\tsnapper setup-quota") << '\n'
-        << endl;
-}
-
-
-void
-command_setup_quota(GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper* snapper)
-{
-    ParsedOpts opts = get_opts.parse("setup-quota", GetOpts::no_options);
-    if (get_opts.num_args() != 0)
-    {
-       cerr << _("Command 'setup-quota' does not take arguments.") << endl;
-       exit(EXIT_FAILURE);
-    }
-
-    snapper->setupQuota();
-}
-
-
-void
-help_cleanup()
-{
-    cout << _("  Cleanup snapshots:") << '\n'
-        << _("\tsnapper cleanup <cleanup-algorithm>") << '\n'
-        << endl;
-}
-
-
-void
-command_cleanup(GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper* snapper)
-{
-    ParsedOpts opts = get_opts.parse("cleanup", GetOpts::no_options);
-    if (get_opts.num_args() != 1)
-    {
-       cerr << _("Command 'cleanup' needs one arguments.") << endl;
-       exit(EXIT_FAILURE);
-    }
-
-    string cleanup = get_opts.pop_arg();
-
-    if (cleanup == "number")
-    {
-       do_cleanup_number(snapper, global_options.verbose());
-    }
-    else if (cleanup == "timeline")
-    {
-       do_cleanup_timeline(snapper, global_options.verbose());
-    }
-    else if (cleanup == "empty-pre-post")
-    {
-       do_cleanup_empty_pre_post(snapper, global_options.verbose());
-    }
-    else
-    {
-       cerr << sformat(_("Unknown cleanup algorithm '%s'."), cleanup.c_str()) << endl;
-       exit(EXIT_FAILURE);
-    }
-}
-
-
-void
-help_debug()
-{
-}
-
-
-void
-command_debug(GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper*)
-{
-    get_opts.parse("debug", GetOpts::no_options);
-    if (get_opts.has_args())
-    {
-       cerr << _("Command 'debug' does not take arguments.") << endl;
-       exit(EXIT_FAILURE);
-    }
-
-    for (const string& line : snappers->debug())
-       cout << line << endl;
-}
-
-
-#ifdef ENABLE_XATTRS
-
-void
-help_xa_diff()
-{
-    cout << _("  Comparing snapshots extended attributes:") << '\n'
-         << _("\tsnapper xadiff <number1>..<number2> [files]") << '\n'
-         << endl;
-}
-
-void
-print_xa_diff(const string loc_pre, const string loc_post)
-{
-    try
-    {
-        XAModification xa_mod = XAModification(XAttributes(loc_pre), XAttributes(loc_post));
-
-        if (!xa_mod.empty())
-       {
-           cout << "--- " << loc_pre << endl << "+++ " << loc_post << endl;
-           xa_mod.dumpDiffReport(cout);
-       }
-    }
-    catch (const XAttributesException& e)
-    {
-       SN_CAUGHT(e);
-    }
-}
-
-void
-command_xa_diff(GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper* snapper)
-{
-    ParsedOpts opts = get_opts.parse("xadiff", GetOpts::no_options);
-    if (get_opts.num_args() < 1)
-    {
-        cerr << _("Command 'xadiff' needs at least one argument.") << endl;
-        exit(EXIT_FAILURE);
-    }
-
-    ProxySnapshots& snapshots = snapper->getSnapshots();
-
-    pair<ProxySnapshots::const_iterator, ProxySnapshots::const_iterator> range =
-       snapshots.findNums(get_opts.pop_arg());
-
-    ProxyComparison comparison = snapper->createComparison(*range.first, *range.second, true);
-
-    MyFiles files(comparison.getFiles());
-
-    if (get_opts.num_args() == 0)
-    {
-       for (Files::const_iterator it1 = files.begin(); it1 != files.end(); ++it1)
-            if (it1->getPreToPostStatus() & XATTRS)
-                print_xa_diff(it1->getAbsolutePath(LOC_PRE), it1->getAbsolutePath(LOC_POST));
-    }
-    else
-    {
-        while (get_opts.num_args() > 0)
-        {
-            string name = get_opts.pop_arg();
-
-            Files::const_iterator it1 = files.findAbsolutePath(name);
-            if (it1 == files.end())
-                continue;
-
-            if (it1->getPreToPostStatus() & XATTRS)
-                print_xa_diff(it1->getAbsolutePath(LOC_PRE), it1->getAbsolutePath(LOC_POST));
-        }
-    }
-}
-
-#endif
-
-
 void
 log_do(LogLevel level, const string& component, const char* file, const int line, const char* func,
        const string& text)
@@ -1442,9 +150,9 @@ main(int argc, char** argv)
        Cmd("status", command_status, help_status, true),
        Cmd("diff", command_diff, help_diff, true),
 #ifdef ENABLE_XATTRS
-       Cmd("xadiff", command_xa_diff, help_xa_diff, true),
+       Cmd("xadiff", command_xadiff, help_xadiff, true),
 #endif
-       Cmd("undochange", command_undo, help_undo, true),
+       Cmd("undochange", command_undochange, help_undochange, true),
 #ifdef ENABLE_ROLLBACK
        Cmd("rollback", command_rollback, help_rollback, true),
 #endif