From fc26b35e46788a21a8ca37077340ca351e9f8fc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franc=CC=A7ois-Xavier=20Payet?= Date: Thu, 11 Dec 2025 13:33:56 +0100 Subject: [PATCH] Add LUP/EPLUP policies --- .../config/functions_categories_policies.md | 6 +- src/policies.cpp | 8 + src/policies.hpp | 8 + src/policy_eplup.cpp | 236 ++++++++++++++++++ src/policy_eplup.hpp | 70 ++++++ src/policy_lup.cpp | 234 +++++++++++++++++ src/policy_lup.hpp | 70 ++++++ 7 files changed, 631 insertions(+), 1 deletion(-) create mode 100644 src/policy_eplup.cpp create mode 100644 src/policy_eplup.hpp create mode 100644 src/policy_lup.cpp create mode 100644 src/policy_lup.hpp diff --git a/mkdocs/docs/config/functions_categories_policies.md b/mkdocs/docs/config/functions_categories_policies.md index 0a99c64e..16dc8875 100644 --- a/mkdocs/docs/config/functions_categories_policies.md +++ b/mkdocs/docs/config/functions_categories_policies.md @@ -90,7 +90,7 @@ suddenly become read-only when it encounters an error. Policies, as described below, are of two basic classifications. `path preserving` and `non-path preserving`. -All policies which start with `ep` (`epff`, `eplfs`, `eplus`, `epmfs`, +All policies which start with `ep` (`epff`, `eplfs`, `eplus`, `eplup`, `epmfs`, `eprand`) are `path preserving`. `ep` stands for `existing path`. A path preserving policy will only consider branches where the relative @@ -116,6 +116,8 @@ but it makes things a bit more uniform. | mfs (most free space) | Pick the branch with the most available free space. | | ff (first found) | Given the order of the branches, as defined at mount time or configured at runtime, act on the first one found. | | lfs (least free space) | Pick the branch with the least available free space. | +| lup (least used percent) | Pick the branch with the least used +percentage of space | | lus (least used space) | Pick the branch with the least used space. | | all | Search: For **mkdir**, **mknod**, and **symlink** it will apply to all branches. **create** works like **ff**. | | msppfrd (most shared path, percentage free random distribution) | Like **eppfrd** but if it fails to find a branch it will try again with the parent directory. Continues this pattern till finding one. | @@ -127,6 +129,8 @@ but it makes things a bit more uniform. | eprand (existing path, random) | Calls **epall** and then randomizes. Returns 1. | | epff (existing path, first found) | Given the order of the branches, as defined at mount time or configured at runtime, act on the first one found where the relative path exists. | | eplfs (existing path, least free space) | Of all the branches on which the relative path exists choose the branch with the least free space. | +| eplup (existing path, least used percent) | Of all the branches on which the relative path exists choose the branch with the least used +percentage of space | | eplus (existing path, least used space) | Of all the branches on which the relative path exists choose the branch with the least used space. | | epall (existing path, all) | For **mkdir**, **mknod**, and **symlink** it will apply to all found. **create** works like **epff** (but more expensive because it doesn't stop after finding a valid branch). | | newest | Pick the file / directory with the largest mtime. | diff --git a/src/policies.cpp b/src/policies.cpp index 4886a666..4ddf2999 100644 --- a/src/policies.cpp +++ b/src/policies.cpp @@ -28,6 +28,7 @@ FUNC(epff) \ FUNC(eplfs) \ FUNC(eplus) \ + FUNC(eplup) \ FUNC(epmfs) \ FUNC(eppfrd) \ FUNC(eprand) \ @@ -35,6 +36,7 @@ FUNC(ff) \ FUNC(lfs) \ FUNC(lus) \ + FUNC(lup) \ FUNC(mfs) \ FUNC(msplfs) \ FUNC(msplus) \ @@ -73,6 +75,7 @@ Policy::EPAll::Action Policies::Action::epall; Policy::EPFF::Action Policies::Action::epff; Policy::EPLFS::Action Policies::Action::eplfs; Policy::EPLUS::Action Policies::Action::eplus; +Policy::EPLUP::Action Policies::Action::eplup; Policy::EPMFS::Action Policies::Action::epmfs; Policy::EPPFRD::Action Policies::Action::eppfrd; Policy::EPRand::Action Policies::Action::eprand; @@ -80,6 +83,7 @@ Policy::ERoFS::Action Policies::Action::erofs; Policy::FF::Action Policies::Action::ff; Policy::LFS::Action Policies::Action::lfs; Policy::LUS::Action Policies::Action::lus; +Policy::LUP::Action Policies::Action::lup; Policy::MFS::Action Policies::Action::mfs; Policy::MSPLFS::Action Policies::Action::msplfs; Policy::MSPLUS::Action Policies::Action::msplus; @@ -94,6 +98,7 @@ Policy::EPAll::Create Policies::Create::epall; Policy::EPFF::Create Policies::Create::epff; Policy::EPLFS::Create Policies::Create::eplfs; Policy::EPLUS::Create Policies::Create::eplus; +Policy::EPLUP::Create Policies::Create::eplup; Policy::EPMFS::Create Policies::Create::epmfs; Policy::EPPFRD::Create Policies::Create::eppfrd; Policy::EPRand::Create Policies::Create::eprand; @@ -101,6 +106,7 @@ Policy::ERoFS::Create Policies::Create::erofs; Policy::FF::Create Policies::Create::ff; Policy::LFS::Create Policies::Create::lfs; Policy::LUS::Create Policies::Create::lus; +Policy::LUP::Create Policies::Create::lup; Policy::MFS::Create Policies::Create::mfs; Policy::MSPLFS::Create Policies::Create::msplfs; Policy::MSPLUS::Create Policies::Create::msplus; @@ -115,6 +121,7 @@ Policy::EPAll::Search Policies::Search::epall; Policy::EPFF::Search Policies::Search::epff; Policy::EPLFS::Search Policies::Search::eplfs; Policy::EPLUS::Search Policies::Search::eplus; +Policy::EPLUP::Search Policies::Search::eplup; Policy::EPMFS::Search Policies::Search::epmfs; Policy::EPPFRD::Search Policies::Search::eppfrd; Policy::EPRand::Search Policies::Search::eprand; @@ -122,6 +129,7 @@ Policy::ERoFS::Search Policies::Search::erofs; Policy::FF::Search Policies::Search::ff; Policy::LFS::Search Policies::Search::lfs; Policy::LUS::Search Policies::Search::lus; +Policy::LUP::Search Policies::Search::lup; Policy::MFS::Search Policies::Search::mfs; Policy::MSPLFS::Search Policies::Search::msplfs; Policy::MSPLUS::Search Policies::Search::msplus; diff --git a/src/policies.hpp b/src/policies.hpp index 3ff833c5..c8220f4b 100644 --- a/src/policies.hpp +++ b/src/policies.hpp @@ -23,6 +23,7 @@ #include "policy_epff.hpp" #include "policy_eplfs.hpp" #include "policy_eplus.hpp" +#include "policy_eplup.hpp" #include "policy_epmfs.hpp" #include "policy_eppfrd.hpp" #include "policy_eprand.hpp" @@ -30,6 +31,7 @@ #include "policy_ff.hpp" #include "policy_lfs.hpp" #include "policy_lus.hpp" +#include "policy_lup.hpp" #include "policy_mfs.hpp" #include "policy_msplfs.hpp" #include "policy_msplus.hpp" @@ -49,6 +51,7 @@ struct Policies static Policy::EPAll::Action epall; static Policy::EPFF::Action epff; static Policy::EPLFS::Action eplfs; + static Policy::EPLUP::Action eplup; static Policy::EPLUS::Action eplus; static Policy::EPMFS::Action epmfs; static Policy::EPPFRD::Action eppfrd; @@ -56,6 +59,7 @@ struct Policies static Policy::ERoFS::Action erofs; static Policy::FF::Action ff; static Policy::LFS::Action lfs; + static Policy::LUP::Action lup; static Policy::LUS::Action lus; static Policy::MFS::Action mfs; static Policy::MSPLFS::Action msplfs; @@ -75,6 +79,7 @@ struct Policies static Policy::EPAll::Create epall; static Policy::EPFF::Create epff; static Policy::EPLFS::Create eplfs; + static Policy::EPLUP::Create eplup; static Policy::EPLUS::Create eplus; static Policy::EPMFS::Create epmfs; static Policy::EPPFRD::Create eppfrd; @@ -82,6 +87,7 @@ struct Policies static Policy::ERoFS::Create erofs; static Policy::FF::Create ff; static Policy::LFS::Create lfs; + static Policy::LUP::Create lup; static Policy::LUS::Create lus; static Policy::MFS::Create mfs; static Policy::MSPLFS::Create msplfs; @@ -101,6 +107,7 @@ struct Policies static Policy::EPAll::Search epall; static Policy::EPFF::Search epff; static Policy::EPLFS::Search eplfs; + static Policy::EPLUP::Search eplup; static Policy::EPLUS::Search eplus; static Policy::EPMFS::Search epmfs; static Policy::EPPFRD::Search eppfrd; @@ -108,6 +115,7 @@ struct Policies static Policy::ERoFS::Search erofs; static Policy::FF::Search ff; static Policy::LFS::Search lfs; + static Policy::LUP::Search lup; static Policy::LUS::Search lus; static Policy::MFS::Search mfs; static Policy::MSPLFS::Search msplfs; diff --git a/src/policy_eplup.cpp b/src/policy_eplup.cpp new file mode 100644 index 00000000..94f621f4 --- /dev/null +++ b/src/policy_eplup.cpp @@ -0,0 +1,236 @@ +/* + Copyright (c) 2025, François-Xavier Payet + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "policy_eplup.hpp" + +#include "errno.hpp" +#include "fs_exists.hpp" +#include "fs_info.hpp" +#include "fs_path.hpp" +#include "fs_statvfs_cache.hpp" +#include "policy.hpp" +#include "policy_error.hpp" +#include "rwlock.hpp" + +#include +#include + +using std::string; + +static int +_create(const Branches::Ptr &branches_, + const fs::path &fusepath_, + std::vector &paths_) +{ + int rv; + int error; + fs::info_t info; + Branch *obranch; + + obranch = nullptr; + error = ENOENT; + + uint64_t best_used = 0; + uint64_t best_total = 1; /* avoid div-by-zero */ + + for (auto &branch : *branches_) + { + if (branch.ro_or_nc()) + error_and_continue(error, EROFS); + if (!fs::exists(branch.path, fusepath_)) + error_and_continue(error, ENOENT); + rv = fs::info(branch.path, &info); + if (rv < 0) + error_and_continue(error, ENOENT); + if (info.readonly) + error_and_continue(error, EROFS); + if (info.spaceavail < branch.minfreespace()) + error_and_continue(error, ENOSPC); + + uint64_t used = info.spaceused; + uint64_t total = info.spaceused + info.spaceavail; + if (total == 0) + { + used = 0; + total = 1; + } + + if (obranch == nullptr) + { + best_used = used; + best_total = total; + obranch = &branch; + continue; + } + + unsigned __int128 lhs = (unsigned __int128)used * (unsigned __int128)best_total; + unsigned __int128 rhs = (unsigned __int128)best_used * (unsigned __int128)total; + if (lhs >= rhs) + continue; + + best_used = used; + best_total = total; + obranch = &branch; + } + + if (obranch == nullptr) + return -error; + + paths_.emplace_back(obranch); + + return 0; +} + +static int +_action(const Branches::Ptr &branches_, + const fs::path &fusepath_, + std::vector &paths_) +{ + int rv; + int error; + fs::info_t info; + Branch *obranch; + + obranch = nullptr; + error = ENOENT; + + uint64_t best_used = 0; + uint64_t best_total = 1; + + for (auto &branch : *branches_) + { + if (branch.ro()) + error_and_continue(error, EROFS); + if (!fs::exists(branch.path, fusepath_)) + error_and_continue(error, ENOENT); + rv = fs::info(branch.path, &info); + if (rv < 0) + error_and_continue(error, ENOENT); + if (info.readonly) + error_and_continue(error, EROFS); + + uint64_t used = info.spaceused; + uint64_t total = info.spaceused + info.spaceavail; + if (total == 0) + { + used = 0; + total = 1; + } + + if (obranch == nullptr) + { + best_used = used; + best_total = total; + obranch = &branch; + continue; + } + + unsigned __int128 lhs = (unsigned __int128)used * (unsigned __int128)best_total; + unsigned __int128 rhs = (unsigned __int128)best_used * (unsigned __int128)total; + if (lhs >= rhs) + continue; + + best_used = used; + best_total = total; + obranch = &branch; + } + + if (obranch == nullptr) + return -error; + + paths_.emplace_back(obranch); + + return 0; +} + +static int +_search(const Branches::Ptr &branches_, + const fs::path &fusepath_, + std::vector &paths_) +{ + int rv; + uint64_t used; + uint64_t avail; + uint64_t best_used = 0; + uint64_t best_total = 1; + Branch *obranch; + + obranch = nullptr; + + for (auto &branch : *branches_) + { + if (!fs::exists(branch.path, fusepath_)) + continue; + rv = fs::statvfs_cache_spaceused(branch.path, &used); + if (rv < 0) + continue; + rv = fs::statvfs_cache_spaceavail(branch.path, &avail); + if (rv < 0) + continue; + + uint64_t total = used + avail; + if (total == 0) + { + used = 0; + total = 1; + } + + if (obranch == nullptr) + { + best_used = used; + best_total = total; + obranch = &branch; + continue; + } + + unsigned __int128 lhs = (unsigned __int128)used * (unsigned __int128)best_total; + unsigned __int128 rhs = (unsigned __int128)best_used * (unsigned __int128)total; + if (lhs >= rhs) + continue; + + best_used = used; + best_total = total; + obranch = &branch; + } + + if (obranch == nullptr) + return -ENOENT; + + paths_.emplace_back(obranch); + + return 0; +} + +int Policy::EPLUP::Action::operator()(const Branches::Ptr &branches_, + const fs::path &fusepath_, + std::vector &paths_) const +{ + return ::_action(branches_, fusepath_, paths_); +} + +int Policy::EPLUP::Create::operator()(const Branches::Ptr &branches_, + const fs::path &fusepath_, + std::vector &paths_) const +{ + return ::_create(branches_, fusepath_, paths_); +} + +int Policy::EPLUP::Search::operator()(const Branches::Ptr &branches_, + const fs::path &fusepath_, + std::vector &paths_) const +{ + return ::_search(branches_, fusepath_, paths_); +} diff --git a/src/policy_eplup.hpp b/src/policy_eplup.hpp new file mode 100644 index 00000000..c9a13a33 --- /dev/null +++ b/src/policy_eplup.hpp @@ -0,0 +1,70 @@ +/* + ISC License + + Copyright (c) 2025, François-Xavier Payet + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#pragma once + +#include "policy.hpp" + +namespace Policy +{ + namespace EPLUP + { + class Action final : public Policy::ActionImpl + { + public: + Action() + : Policy::ActionImpl("eplup") + { + } + + public: + int operator()(const Branches::Ptr &, + const fs::path &, + std::vector &) const final; + }; + + class Create final : public Policy::CreateImpl + { + public: + Create() + : Policy::CreateImpl("eplup") + { + } + + public: + int operator()(const Branches::Ptr &, + const fs::path &, + std::vector &) const final; + bool path_preserving(void) const final { return true; } + }; + + class Search final : public Policy::SearchImpl + { + public: + Search() + : Policy::SearchImpl("eplup") + { + } + + public: + int operator()(const Branches::Ptr &, + const fs::path &, + std::vector &) const final; + }; + } +} diff --git a/src/policy_lup.cpp b/src/policy_lup.cpp new file mode 100644 index 00000000..e99bb619 --- /dev/null +++ b/src/policy_lup.cpp @@ -0,0 +1,234 @@ +/* + Copyright (c) 2025, François-Xavier Payet + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "policy_lup.hpp" + +#include "errno.hpp" +#include "fs_exists.hpp" +#include "fs_info.hpp" +#include "fs_path.hpp" +#include "policies.hpp" +#include "fs_statvfs_cache.hpp" +#include "policy.hpp" +#include "policy_error.hpp" +#include "rwlock.hpp" + +#include +#include + +using std::string; + +static int +_action(const Branches::Ptr &branches_, + const fs::path &fusepath_, + std::vector &paths_) +{ + int rv; + int error; + fs::info_t info; + Branch *obranch; + + obranch = nullptr; + error = ENOENT; + + uint64_t best_used = 0; + uint64_t best_total = 1; + + for (auto &branch : *branches_) + { + if (branch.ro()) + error_and_continue(error, EROFS); + if (!fs::exists(branch.path, fusepath_)) + error_and_continue(error, ENOENT); + rv = fs::info(branch.path, &info); + if (rv < 0) + error_and_continue(error, ENOENT); + if (info.readonly) + error_and_continue(error, EROFS); + + uint64_t used = info.spaceused; + uint64_t total = info.spaceused + info.spaceavail; + if (total == 0) + { + used = 0; + total = 1; + } + + if (obranch == nullptr) + { + best_used = used; + best_total = total; + obranch = &branch; + continue; + } + + unsigned __int128 lhs = (unsigned __int128)used * (unsigned __int128)best_total; + unsigned __int128 rhs = (unsigned __int128)best_used * (unsigned __int128)total; + if (lhs >= rhs) + continue; + + best_used = used; + best_total = total; + obranch = &branch; + } + + if (obranch == nullptr) + return -error; + + paths_.push_back(obranch); + + return 0; +} + +static int +_search(const Branches::Ptr &branches_, + const fs::path &fusepath_, + std::vector &paths_) +{ + int rv; + uint64_t used; + uint64_t avail; + uint64_t best_used = 0; + uint64_t best_total = 1; + Branch *obranch; + + obranch = nullptr; + + for (auto &branch : *branches_) + { + if (!fs::exists(branch.path, fusepath_)) + continue; + rv = fs::statvfs_cache_spaceused(branch.path, &used); + if (rv < 0) + continue; + rv = fs::statvfs_cache_spaceavail(branch.path, &avail); + if (rv < 0) + continue; + + uint64_t total = used + avail; + if (total == 0) + { + used = 0; + total = 1; + } + + if (obranch == nullptr) + { + best_used = used; + best_total = total; + obranch = &branch; + continue; + } + + unsigned __int128 lhs = (unsigned __int128)used * (unsigned __int128)best_total; + unsigned __int128 rhs = (unsigned __int128)best_used * (unsigned __int128)total; + if (lhs >= rhs) + continue; + + best_used = used; + best_total = total; + obranch = &branch; + } + + if (obranch == nullptr) + return -ENOENT; + + paths_.push_back(obranch); + + return 0; +} + +static int +_create(const Branches::Ptr &branches_, + std::vector &paths_) +{ + int rv; + int error; + fs::info_t info; + Branch *obranch; + + obranch = nullptr; + error = ENOENT; + + uint64_t best_used = 0; + uint64_t best_total = 1; + + for (auto &branch : *branches_) + { + if (branch.ro_or_nc()) + error_and_continue(error, EROFS); + rv = fs::info(branch.path, &info); + if (rv < 0) + error_and_continue(error, ENOENT); + if (info.readonly) + error_and_continue(error, EROFS); + if (info.spaceavail < branch.minfreespace()) + error_and_continue(error, ENOSPC); + + uint64_t used = info.spaceused; + uint64_t total = info.spaceused + info.spaceavail; + if (total == 0) + { + used = 0; + total = 1; + } + + if (obranch == nullptr) + { + best_used = used; + best_total = total; + obranch = &branch; + continue; + } + + unsigned __int128 lhs = (unsigned __int128)used * (unsigned __int128)best_total; + unsigned __int128 rhs = (unsigned __int128)best_used * (unsigned __int128)total; + if (lhs >= rhs) + continue; + + best_used = used; + best_total = total; + obranch = &branch; + } + + if (obranch == nullptr) + return -error; + + paths_.push_back(obranch); + + return 0; +} + +int Policy::LUP::Action::operator()(const Branches::Ptr &branches_, + const fs::path &fusepath_, + std::vector &paths_) const +{ + return ::_action(branches_, fusepath_, paths_); +} + +int Policy::LUP::Create::operator()(const Branches::Ptr &branches_, + const fs::path &fusepath_, + std::vector &paths_) const +{ + return ::_create(branches_, paths_); +} + +int Policy::LUP::Search::operator()(const Branches::Ptr &branches_, + const fs::path &fusepath_, + std::vector &paths_) const +{ + return ::_search(branches_, fusepath_, paths_); +} diff --git a/src/policy_lup.hpp b/src/policy_lup.hpp new file mode 100644 index 00000000..83d30ffb --- /dev/null +++ b/src/policy_lup.hpp @@ -0,0 +1,70 @@ +/* + ISC License + + Copyright (c) 2025, François-Xavier Payet + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#pragma once + +#include "policy.hpp" + +namespace Policy +{ + namespace LUP + { + class Action final : public Policy::ActionImpl + { + public: + Action() + : Policy::ActionImpl("lup") + { + } + + public: + int operator()(const Branches::Ptr &, + const fs::path &, + std::vector &) const final; + }; + + class Create final : public Policy::CreateImpl + { + public: + Create() + : Policy::CreateImpl("lup") + { + } + + public: + int operator()(const Branches::Ptr &, + const fs::path &, + std::vector &) const final; + bool path_preserving() const final { return false; } + }; + + class Search final : public Policy::SearchImpl + { + public: + Search() + : Policy::SearchImpl("lup") + { + } + + public: + int operator()(const Branches::Ptr &, + const fs::path &, + std::vector &) const final; + }; + } +}