]> git.ipfire.org Git - thirdparty/snapper.git/commitdiff
- added option to cleanup to make requested free space available 569/head
authorArvin Schnell <aschnell@suse.de>
Tue, 22 Sep 2020 07:35:31 +0000 (09:35 +0200)
committerArvin Schnell <aschnell@suse.de>
Tue, 22 Sep 2020 07:38:06 +0000 (09:38 +0200)
VERSION
client/cleanup.cc
client/cleanup.h
client/cmd-cleanup.cc
client/cmd.h
client/snapper.cc
doc/snapper.xml.in
package/snapper.changes

diff --git a/VERSION b/VERSION
index c2f73c6ecf7c1cc6f599de5ed2a93b43522ac09a..832bad274048acaac69e0e57dbcfdcc111572591 100644 (file)
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.8.13
+0.8.14
index 2119e04713e9ba4c7cd8434cdc6f35b796d69c96..6803c20f8ef3c505334d7a872e9a1164a9b4d21b 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * Copyright (c) [2011-2014] Novell, Inc.
- * Copyright (c) [2016,2018] SUSE LLC
+ * Copyright (c) [2016-2020] SUSE LLC
  *
  * All Rights Reserved.
  *
@@ -89,6 +89,7 @@ public:
     virtual ~Cleaner() {}
 
     void cleanup();
+    void cleanup(std::function<bool()> condition);
 
 protected:
 
@@ -423,6 +424,19 @@ Cleaner::cleanup()
 }
 
 
+void
+Cleaner::cleanup(std::function<bool()> condition)
+{
+    ProxySnapshots& snapshots = snapper->getSnapshots();
+
+#ifdef VERBOSE_LOGGING
+    cout << "cleanup with user condition" << '\n';
+#endif
+
+    cleanup(snapshots, condition);
+}
+
+
 struct NumberParameters : public Parameters
 {
     NumberParameters(const ProxySnapper* snapper);
@@ -534,6 +548,15 @@ do_cleanup_number(ProxySnapper* snapper, bool verbose)
 }
 
 
+void
+do_cleanup_number(ProxySnapper* snapper, bool verbose, std::function<bool()> condition)
+{
+    NumberParameters parameters(snapper);
+    NumberCleaner cleaner(snapper, verbose, parameters);
+    cleaner.cleanup(condition);
+}
+
+
 struct TimelineParameters : public Parameters
 {
     TimelineParameters(const ProxySnapper* snapper);
@@ -737,6 +760,15 @@ do_cleanup_timeline(ProxySnapper* snapper, bool verbose)
 }
 
 
+void
+do_cleanup_timeline(ProxySnapper* snapper, bool verbose, std::function<bool()> condition)
+{
+    TimelineParameters parameters(snapper);
+    TimelineCleaner cleaner(snapper, verbose, parameters);
+    cleaner.cleanup(condition);
+}
+
+
 struct EmptyPrePostParameters : public Parameters
 {
     EmptyPrePostParameters(const ProxySnapper* snapper);
@@ -796,3 +828,12 @@ do_cleanup_empty_pre_post(ProxySnapper* snapper, bool verbose)
     EmptyPrePostCleaner cleaner(snapper, verbose, parameters);
     cleaner.cleanup();
 }
+
+
+void
+do_cleanup_empty_pre_post(ProxySnapper* snapper, bool verbose, std::function<bool()> condition)
+{
+    EmptyPrePostParameters parameters(snapper);
+    EmptyPrePostCleaner cleaner(snapper, verbose, parameters);
+    cleaner.cleanup(condition);
+}
index 000b66989ecdc84ba6443bd4c46532d936646bf0..6d250079a9de6f034c5f1b6f6e3999f586271618 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * Copyright (c) [2011-2012] Novell, Inc.
- * Copyright (c) 2016 SUSE LLC
+ * Copyright (c) [2016-2020] SUSE LLC
  *
  * All Rights Reserved.
  *
  */
 
 
+#include <functional>
+
 #include "proxy.h"
 
 
+/*
+ * The following three functions do the cleanup based on the conditionals defined in the
+ * config, that are hard limit, quota and free space.
+ */
+
 void
 do_cleanup_number(ProxySnapper* snapper, bool verbose);
 
@@ -32,3 +39,18 @@ do_cleanup_timeline(ProxySnapper* snapper, bool verbose);
 
 void
 do_cleanup_empty_pre_post(ProxySnapper* snapper, bool verbose);
+
+
+/*
+ * The following three functions do the cleanup only based on the provided
+ * conditional. The lower range and min-age defined in the config are respected.
+ */
+
+void
+do_cleanup_number(ProxySnapper* snapper, bool verbose, std::function<bool()> condition);
+
+void
+do_cleanup_timeline(ProxySnapper* snapper, bool verbose, std::function<bool()> condition);
+
+void
+do_cleanup_empty_pre_post(ProxySnapper* snapper, bool verbose, std::function<bool()> condition);
index a969a6f49439292e4a1492f1efbdcfad70a2d8d2..20f09167999cb0c6ef883c0a813198c49fc02cfb 100644 (file)
 
 
 #include <iostream>
+#include <boost/algorithm/string.hpp>
 
 #include <snapper/AppUtil.h>
+#include <snapper/BtrfsUtils.h>
+#include <snapper/FileUtils.h>
 
+#include "utils/HumanString.h"
 #include "utils/text.h"
 #include "GlobalOptions.h"
 #include "proxy.h"
 #include "cleanup.h"
+#include "misc.h"
+#include "cmd.h"
 
 
 namespace snapper
@@ -42,39 +48,327 @@ namespace snapper
     {
        cout << _("  Cleanup snapshots:") << '\n'
             << _("\tsnapper cleanup <cleanup-algorithm>") << '\n'
+            << '\n'
+            << _("    Options for 'cleanup' command:") << '\n'
+            << _("\t--path <path>\t\t\tCleanup all configs affecting path.") << '\n'
+            << _("\t--free-space <space>\t\tTry to make space available.") << '\n'
             << endl;
     }
 
 
+    namespace
+    {
+
+       enum class CleanupAlgorithm { ALL, NUMBER, TIMELINE, EMPTY_PRE_POST };
+
+       class FreeSpaceCondition
+       {
+       public:
+
+           FreeSpaceCondition(const string& path, unsigned long long free_space)
+               : sdir(path), free_space(free_space)
+           {
+           }
+
+           bool
+           is_satisfied() const
+           {
+               FreeSpaceData free_space_data;
+               std::tie(free_space_data.size, free_space_data.free) = sdir.statvfs();
+
+               bool satisfied = free_space_data.free >= free_space;
+
+#ifdef VERBOSE_LOGGING
+               cout << byte_to_humanstring(free_space_data.size, 2) << ", "
+                    << byte_to_humanstring(free_space_data.free, 2) << ", "
+                    << byte_to_humanstring(free_space, 2) << ", " << satisfied << '\n';
+#endif
+
+               return satisfied;
+           }
+
+           bool
+           is_satisfied(const ProxySnapper* snapper) const
+           {
+               snapper->syncFilesystem();
+
+               return is_satisfied();
+           }
+
+       private:
+
+           SDir sdir;
+
+           unsigned long long free_space;
+
+       };
+
+
+       void
+       run_cleanup(ProxySnapper* snapper, CleanupAlgorithm cleanup_algorithm, bool verbose)
+       {
+           switch (cleanup_algorithm)
+           {
+               case CleanupAlgorithm::NUMBER:
+                   do_cleanup_number(snapper, verbose);
+                   break;
+
+               case CleanupAlgorithm::TIMELINE:
+                   do_cleanup_timeline(snapper, verbose);
+                   break;
+
+               case CleanupAlgorithm::EMPTY_PRE_POST:
+                   do_cleanup_empty_pre_post(snapper, verbose);
+                   break;
+
+               case CleanupAlgorithm::ALL:
+                   do_cleanup_number(snapper, verbose);
+                   do_cleanup_timeline(snapper, verbose);
+                   do_cleanup_empty_pre_post(snapper, verbose);
+                   break;
+           }
+       }
+
+
+       void
+       run_cleanup(ProxySnapper* snapper, CleanupAlgorithm cleanup_algorithm, bool verbose,
+                   const FreeSpaceCondition& free_space_condition)
+       {
+           std::function<bool()> condition = [snapper, &free_space_condition]() {
+               return free_space_condition.is_satisfied(snapper);
+           };
+
+           switch (cleanup_algorithm)
+           {
+               case CleanupAlgorithm::NUMBER:
+                   do_cleanup_number(snapper, verbose, condition);
+                   break;
+
+               case CleanupAlgorithm::TIMELINE:
+                   do_cleanup_timeline(snapper, verbose, condition);
+                   break;
+
+               case CleanupAlgorithm::EMPTY_PRE_POST:
+                   do_cleanup_empty_pre_post(snapper, verbose, condition);
+                   break;
+
+               case CleanupAlgorithm::ALL:
+                   do_cleanup_number(snapper, verbose, condition);
+                   do_cleanup_timeline(snapper, verbose, condition);
+                   do_cleanup_empty_pre_post(snapper, verbose, condition);
+                   break;
+           }
+       }
+
+
+       void
+       run_cleanup(const vector<ProxySnapper*>& snappers, CleanupAlgorithm cleanup_algorithm, bool verbose)
+       {
+           for (ProxySnapper* snapper : snappers)
+           {
+#ifdef VERBOSE_LOGGING
+               cout << "config " << snapper->configName() << '\n';
+#endif
+
+               run_cleanup(snapper, cleanup_algorithm, verbose);
+           }
+       }
+
+
+       void
+       run_cleanup(const vector<ProxySnapper*>& snappers, CleanupAlgorithm cleanup_algorithm, bool verbose,
+                   const FreeSpaceCondition& free_space_condition)
+       {
+           for (ProxySnapper* snapper : snappers)
+           {
+#ifdef VERBOSE_LOGGING
+               cout << "config " << snapper->configName() << '\n';
+#endif
+
+               if (free_space_condition.is_satisfied())
+                   break;
+
+               try
+               {
+                   run_cleanup(snapper, cleanup_algorithm, verbose, free_space_condition);
+               }
+               catch (...)
+               {
+#ifdef VERBOSE_LOGGING
+                   cout << "failed for " << snapper->configName() << '\n';
+#endif
+               }
+           }
+       }
+
+    }
+
+
     void
-    command_cleanup(GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers*, ProxySnapper* snapper)
+    command_cleanup(GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper*)
     {
-       ParsedOpts opts = get_opts.parse("cleanup", GetOpts::no_options);
-       if (get_opts.num_args() != 1)
+       const vector<Option> options = {
+           Option("path",              required_argument),
+           Option("free-space",        required_argument)
+       };
+
+       ParsedOpts opts = get_opts.parse("cleanup", options);
+
+       CleanupAlgorithm cleanup_algorithm;
+       string path;
+       unsigned long long free_space = 0;
+
+       ParsedOpts::const_iterator opt;
+
+       if ((opt = opts.find("path")) != opts.end())
        {
-           cerr << _("Command 'cleanup' needs one arguments.") << endl;
-           exit(EXIT_FAILURE);
+           if (!boost::starts_with(opt->second, "/"))
+           {
+               string error = sformat(_("Invalid path '%s'."), opt->second.c_str());
+               SN_THROW(OptionsException(error));
+           }
+
+           path = opt->second;
        }
 
-       string cleanup = get_opts.pop_arg();
+       if ((opt = opts.find("free-space")) != opts.end())
+       {
+           try
+           {
+               free_space = humanstring_to_byte(opt->second);
+           }
+           catch (const Exception& e)
+           {
+               SN_CAUGHT(e);
+
+               string error = sformat(_("Failed to parse '%s'."), opt->second.c_str());
+               SN_THROW(OptionsException(error));
+           }
+
+           if (free_space == 0)
+           {
+               SN_THROW(OptionsException(_("Invalid free-space value.")));
+           }
+       }
 
-       if (cleanup == "number")
+       if (get_opts.num_args() != 1)
        {
-           do_cleanup_number(snapper, global_options.verbose());
+           SN_THROW(OptionsException(_("Command 'cleanup' needs one arguments.")));
        }
-       else if (cleanup == "timeline")
+
+       if (!toValue(get_opts.pop_arg(), cleanup_algorithm, false))
        {
-           do_cleanup_timeline(snapper, global_options.verbose());
+           string error = sformat(_("Unknown cleanup algorithm '%s'."), opt->second.c_str()) + '\n' +
+               possible_enum_values<CleanupAlgorithm>();
+           SN_THROW(OptionsException(error));
        }
-       else if (cleanup == "empty-pre-post")
+
+       if (path.empty())
        {
-           do_cleanup_empty_pre_post(snapper, global_options.verbose());
+           ProxySnapper* snapper = snappers->getSnapper(global_options.config());
+
+           if (free_space == 0)
+           {
+               run_cleanup(snapper, cleanup_algorithm, global_options.verbose());
+           }
+           else
+           {
+               boost::optional<FreeSpaceCondition> free_space_condition;
+
+               try
+               {
+                   free_space_condition = FreeSpaceCondition(snapper->getConfig().getSubvolume(),
+                                                             free_space);
+               }
+               catch (...)
+               {
+                   SN_THROW(CleanupException( _("Failed to query free space.")));
+               }
+
+               if (!free_space_condition->is_satisfied())
+               {
+                   run_cleanup(snapper, cleanup_algorithm, global_options.verbose(), *free_space_condition);
+
+                   if (!free_space_condition->is_satisfied())
+                   {
+                       SN_THROW(CleanupException(_("Could not make enough free space available.")));
+                   }
+               }
+           }
        }
        else
        {
-           cerr << sformat(_("Unknown cleanup algorithm '%s'."), cleanup.c_str()) << endl;
-           exit(EXIT_FAILURE);
+           vector<ProxySnapper*> affected_snappers;
+
+           try
+           {
+               Uuid uuid = BtrfsUtils::get_uuid(path);
+
+               for (const map<string, ProxyConfig>::value_type& it : snappers->getConfigs())
+               {
+                   try
+                   {
+                       if (BtrfsUtils::get_uuid(it.second.getSubvolume()) == uuid)
+                           affected_snappers.push_back(snappers->getSnapper(it.first));
+                   }
+                   catch (...)
+                   {
+                       // The config is likely not btrfs so just ignore it.
+                   }
+               }
+           }
+           catch (...)
+           {
+               // The provided path is likely not btrfs -> simply nothing will be
+               // affected.  Anyway, the exit code of snapper still tells whether enough
+               // free space is available.
+           }
+
+           if (global_options.verbose())
+           {
+               cout << "affected configs:";
+               for (const ProxySnapper* snapper : affected_snappers)
+                   cout << " " << snapper->configName();
+               cout << '\n';
+           }
+
+           if (free_space == 0)
+           {
+               run_cleanup(affected_snappers, cleanup_algorithm, global_options.verbose());
+           }
+           else
+           {
+               boost::optional<FreeSpaceCondition> free_space_condition;
+
+               try
+               {
+                   free_space_condition = FreeSpaceCondition(path, free_space);
+               }
+               catch (...)
+               {
+                   string error = sformat(_("Failed to query free space for path '%s'."), path.c_str());
+                   SN_THROW(CleanupException(error));
+               }
+
+               if (!free_space_condition->is_satisfied())
+               {
+                   run_cleanup(affected_snappers, cleanup_algorithm, global_options.verbose(),
+                               *free_space_condition);
+
+                   if (!free_space_condition->is_satisfied())
+                   {
+                       string error = sformat(_("Could not make enough free space available for path '%s'."),
+                                              path.c_str());
+                       SN_THROW(CleanupException(error));
+                   }
+               }
+           }
        }
     }
 
+
+    template <> struct EnumInfo<CleanupAlgorithm> { static const vector<string> names; };
+
+    const vector<string> EnumInfo<CleanupAlgorithm>::names({ "all", "number", "timeline", "empty-pre-post" });
+
 }
index 9406fb40561358872852cd1e48e5fbe3cac4e7b2..df59cd1b9df8628d325b7ff34e2a2eb7d124ade7 100644 (file)
@@ -157,6 +157,12 @@ namespace snapper
     command_setup_quota(GlobalOptions& global_options, GetOpts& get_opts, ProxySnappers* snappers, ProxySnapper* snapper);
 
 
+    struct CleanupException : public Exception
+    {
+       explicit CleanupException(const string& msg) : Exception(msg) {}
+    };
+
+
     void
     help_cleanup();
 
index 2db73a9fe2f3781a3be336657b8288821506af98..8ec382560df88fbcb9cbcd224d36c7906b462c22 100644 (file)
@@ -157,7 +157,7 @@ main(int argc, char** argv)
        Cmd("rollback", command_rollback, help_rollback, true),
 #endif
        Cmd("setup-quota", command_setup_quota, help_setup_quota, true),
-       Cmd("cleanup", command_cleanup, help_cleanup, true),
+       Cmd("cleanup", command_cleanup, help_cleanup, false),
        Cmd("debug", command_debug, help_debug, false)
     };
 
@@ -314,6 +314,12 @@ main(int argc, char** argv)
                 << _("Try 'snapper --help' for more information.") << endl;
            exit(EXIT_FAILURE);
        }
+       catch (const CleanupException& e)
+       {
+           SN_CAUGHT(e);
+           cerr << e.what() << endl;
+           exit(EXIT_FAILURE);
+       }
        catch (const Exception& e)
        {
            SN_CAUGHT(e);
index c5812207e59bb30affb21516bb8f32af8cd139c5..32acc1de5ab666cbfa417bba81ba8230b2e56e9c 100644 (file)
       </varlistentry>
 
       <varlistentry>
-       <term><option>cleanup <replaceable>cleanup-algorithm</replaceable></option></term>
+       <term><option>cleanup [options] <replaceable>cleanup-algorithm</replaceable></option></term>
        <listitem>
          <para>Run the cleanup algorithm
-         <replaceable>cleanup-algorithm</replaceable>. Currently implemented
-         cleanup algorithms are number, timeline and empty-pre-post.</para>
+         <replaceable>cleanup-algorithm</replaceable>. Currently implemented cleanup algorithms
+         are number, timeline and empty-pre-post. To run all cleanup algorithms, all can be
+         provided as cleanup-algorithm.</para>
+         <variablelist>
+           <varlistentry>
+             <term><option>--path</option> <replaceable>path</replaceable></term>
+             <listitem>
+               <para>Cleanup all configs affecting path. Only useful for btrfs.</para>
+             </listitem>
+           </varlistentry>
+           <varlistentry>
+             <term><option>--free-space</option> <replaceable>free-space</replaceable></term>
+             <listitem>
+               <para>Try to make free-space available. Only useful for btrfs.</para>
+             </listitem>
+           </varlistentry>
+         </variablelist>
        </listitem>
       </varlistentry>
 
index 2a0246bef43b34643b425664aa45efb40c897e93..2b0fba158f6d4791c2545a71af10f45f84579c1d 100644 (file)
@@ -1,3 +1,10 @@
+-------------------------------------------------------------------
+Tue Sep 22 09:17:28 CEST 2020 - aschnell@suse.com
+
+- added option to cleanup to make requested free space available
+  (jsc#SLE-15765)
+- version 0.8.14
+
 -------------------------------------------------------------------
 Fri Sep 04 19:10:26 CEST 2020 - aschnell@suse.com