/*
* Copyright (c) [2011-2014] Novell, Inc.
- * Copyright (c) [2016,2018] SUSE LLC
+ * Copyright (c) [2016-2020] SUSE LLC
*
* All Rights Reserved.
*
virtual ~Cleaner() {}
void cleanup();
+ void cleanup(std::function<bool()> condition);
protected:
}
+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);
}
+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);
}
+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);
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);
+}
#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
{
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" });
+
}