From f2d0ed355899d265edbcc7156dace706df2f160a Mon Sep 17 00:00:00 2001 From: Michael Wallner Date: Tue, 17 Nov 2020 20:35:06 +0100 Subject: [PATCH] bin: consolidate clients --- src/bin/common/checks.hpp | 26 +++++++- src/bin/common/options.cpp | 11 ++++ src/bin/common/options.hpp | 112 ++++++++++++++++++++----------- src/bin/memcp.cc | 132 ++++++++++++++++++++----------------- test/tests/bin/memcp.cpp | 17 ++--- 5 files changed, 189 insertions(+), 109 deletions(-) diff --git a/src/bin/common/checks.hpp b/src/bin/common/checks.hpp index 470b4b4a..dc0ed92c 100644 --- a/src/bin/common/checks.hpp +++ b/src/bin/common/checks.hpp @@ -18,6 +18,8 @@ #include "options.hpp" #include "libmemcached/common.h" +#include +#include #include bool check_buffering(const client_options &opt, memcached_st &memc) { @@ -76,12 +78,32 @@ std::ostream *check_ostream(const client_options &opt, const char *file, std::of if (opt.isset("debug")) { std::cerr << "Opening '" << file << "' for writing.\n"; } - stream.open(file); + errno = 0; + stream.open(file, std::ios::binary | std::ios::out); if (stream.is_open()) { return &stream; } else if (!opt.isset("quiet")) { - std::cerr << "Failed to open '" << file << "' for writing.\n"; + std::cerr << "Failed to open '" << file << "' for writing: " << strerror(errno) << ".\n"; } } return &std::cout; } + +std::istream *check_istream(const client_options &opt, const char *file, std::ifstream &stream) { + if (file && *file) { + if (file[0] != '-' || file[1] != 0) { + if (opt.isset("debug")) { + std::cerr << "Opening '" << file << "' for reading.\n"; + } + errno = 0; + stream.open(file, std::ios::binary | std::ios::in); + if (stream.is_open()) { + return &stream; + } else if (!opt.isset("quiet")) { + std::cerr << "Failed to open '" << file << "' for reading: " << strerror(errno) << ".\n"; + } + return nullptr; + } + } + return &std::cin; +} diff --git a/src/bin/common/options.cpp b/src/bin/common/options.cpp index 4583e7b7..229f4f0f 100644 --- a/src/bin/common/options.cpp +++ b/src/bin/common/options.cpp @@ -153,12 +153,23 @@ bool client_options::apply(memcached_st *memc) { } #endif // _WIN32 + extended_option *servers = nullptr; for (auto &opt : options) { if (opt.apply) { + // servers should be applied last, so they take up any behaviors previously set + if (opt.opt.val == 's' && opt.opt.name == std::string("servers")) { + servers = &opt; + continue; + } if (!opt.apply(*this, opt, memc)) { return false; } } } + if (servers) { + if (!servers->apply(*this, *servers, memc)) { + return false; + } + } return true; } diff --git a/src/bin/common/options.hpp b/src/bin/common/options.hpp index 28ade973..fbcd10f6 100644 --- a/src/bin/common/options.hpp +++ b/src/bin/common/options.hpp @@ -34,7 +34,7 @@ public: std::string help; std::function parse; std::function apply; - const char *arg; + char *arg; bool set; }; @@ -256,71 +256,71 @@ public: extended_option &get(const std::string &name) { // UB if not found - return *std::find_if(options.begin(), options.end(), [&name](extended_option &ext) { - return ext.opt.name && ext.opt.name == name; - }); + return *find(name); } extended_option &get(int c) { // UB if not found - return *std::find_if(options.begin(), options.end(), [c](extended_option &ext) { - return ext.opt.val == c || (c == 1 && ext.opt.val == '-'); - }); + return *find(c); } const extended_option &get(const std::string &name) const { - for (const auto &ext_opt : options) { - if (ext_opt.opt.name && ext_opt.opt.name == name) { - return ext_opt; - } - } - return null_ext_opt; + // UB if not found + return *find(name); } const extended_option &get(int c) const { - for (const auto &ext_opt : options) { - if (ext_opt.opt.val == c) { - return ext_opt; - } else if (c == 1 && ext_opt.opt.val == '-') { - // GNU argv extension - return ext_opt; - } - } - return null_ext_opt; + // UB if not found + return *find(c); + } + + bool has(const std::string &name) const { + auto found = find(name); + return found != options.cend(); + } + bool has(int c) const { + auto found = find(c); + return found != options.cend(); } bool isset(const std::string &name) const { - return get(name).set; + return has(name) && get(name).set; } bool isset(int c) const { - return get(c).set; + return has(c) && get(c).set; } void unset(const std::string &name) { - auto &opt = get(name); - opt.set = false; - opt.arg = nullptr; + set(name, false); } void unset(int c) { - auto &opt = get(c); - opt.set = false; - opt.arg = nullptr; + set(c, false); } - void set(const std::string &name, bool set_ = true, const char *optarg_ = nullptr) { - auto &opt = get(name); - opt.set = set_; - opt.arg = optarg_; + void set(const std::string &name, bool set_ = true, char *optarg_ = nullptr) { + if (has(name)) { + auto &opt = get(name); + opt.set = set_; + opt.arg = optarg_; + } } - void set(int c, bool set_ = true, const char *optarg_ = nullptr) { - auto &opt = get(c); - opt.set = set_; - opt.arg = optarg_; + void set(int c, bool set_ = true, char *optarg_ = nullptr) { + if (has(c)) { + auto &opt = get(c); + opt.set = set_; + opt.arg = optarg_; + } } const char *argof(const std::string &name) const { - return get(name).arg; + if (has(name)) { + return get(name).arg; + } + return nullptr; } const char *argof(int c) const { - return get(c).arg; + if (has(c)) { + return get(c).arg; + } + return nullptr; } const extended_option &operator[](const std::string &name) const { @@ -337,6 +337,38 @@ public: bool apply(memcached_st *memc); private: + using iterator = std::vector::iterator; + using const_iterator = std::vector::const_iterator; + using predicate = std::function; + static option null_opt; static const extended_option null_ext_opt; + + const_iterator find(const predicate &pred) const { + return std::find_if(options.cbegin(), options.cend(), pred); + } + const_iterator find(const std::string &name) const { + return find([&name](const extended_option &ext) { + return ext.opt.name && ext.opt.name == name; + }); + } + const_iterator find(int c) const { + return find([c](const extended_option &ext) { + return ext.opt.val == c || (c == 1 && ext.opt.val == '-'); + }); + } + + iterator find(const predicate &pred) { + return std::find_if(options.begin(), options.end(), pred); + } + iterator find(const std::string &name) { + return find([&name](const extended_option &ext) { + return ext.opt.name && ext.opt.name == name; + }); + } + iterator find(int c) { + return find([c](const extended_option &ext) { + return ext.opt.val == c || (c == 1 && ext.opt.val == '-'); + }); + } }; diff --git a/src/bin/memcp.cc b/src/bin/memcp.cc index e2dcccdc..114a2f85 100644 --- a/src/bin/memcp.cc +++ b/src/bin/memcp.cc @@ -22,6 +22,7 @@ #include "common/options.hpp" #include "common/checks.hpp" +#include #include #include #include @@ -39,12 +40,51 @@ struct memcp_file { ADD, REPLACE } op; - const char *path; + char *path; uint32_t flags; time_t expire; }; -static void add_file(std::vector &files, const client_options &opt, const char *file) { +static inline std::string stream2string(const std::istream &istream) { + return dynamic_cast(std::ostringstream{} << istream.rdbuf()).str(); +} + +static memcached_return_t memcp(const client_options &opt, memcached_st &memc, const char *key, + const memcp_file &file) { + std::ifstream fstream{}; + std::istream *istream = check_istream(opt, file.path, fstream); + + if (!istream){ + return MEMCACHED_ERROR; + } + + const char *mode; + memcached_return_t rc; + auto data = stream2string(*istream); + if (file.op == memcp_file::mode::REPLACE) { + mode = "replace"; + rc = memcached_replace(&memc, key, strlen(key), data.c_str(), data.length(), + file.expire, file.flags); + } else if (file.op == memcp_file::mode::ADD) { + mode = "add"; + rc = memcached_add(&memc, key, strlen(key), data.c_str(), data.length(), + file.expire, file.flags); + } else { + mode = "set"; + rc = memcached_set(&memc, key, strlen(key), data.c_str(), data.length(), + file.expire, file.flags); + } + + if (!memcached_success(rc)) { + auto error = memcached_last_error(&memc) + ? memcached_last_error_message(&memc) + : memcached_strerror(&memc, rc); + std::cerr << "Error occurred during memcached_" << mode <<"('" << key << "'): " << error << "\n"; + } + return rc; +} + +static void add_file(std::vector &files, const client_options &opt, char *file) { memcp_file::type type = memcp_file::type::basename; memcp_file::mode mode = memcp_file::mode::SET; uint32_t flags = 0; @@ -77,9 +117,30 @@ static void add_file(std::vector &files, const client_options &opt, files.emplace_back(memcp_file{type, mode, file, flags, expire}); } +static bool path2key(const client_options &opt, memcp_file &file, char **path) { + static char rpath[PATH_MAX + 1]; + if (file.key == memcp_file::type::absolute) { + *path = realpath(file.path, rpath); + if (!*path) { + if (!opt.isset("quiet")) { + perror(file.path); + } + return false; + } + } else if (file.key == memcp_file::type::relative) { + *path = file.path; + } else { + *path = basename((file.path)); + } + return true; +} + int main(int argc, char *argv[]) { std::vector files{}; - client_options opt{PROGRAM_NAME, PROGRAM_VERSION, PROGRAM_DESCRIPTION, "file [file ...]"}; + client_options opt{PROGRAM_NAME, PROGRAM_VERSION, PROGRAM_DESCRIPTION, + "file [file ...]" + "\n\t\t\t# NOTE: order of flags and positional" + "\n\t\t\t# arguments matters on GNU systems)"}; opt.add(nullptr, '-', no_argument, "GNU argv extension") .parse = [&files](client_options &opt_, client_options::extended_option &ext) { @@ -156,67 +217,20 @@ int main(int argc, char *argv[]) { } auto exit_code = EXIT_SUCCESS; - for (const auto &file : files) { - auto filename = file.path; - std::ifstream filestream{filename, std::ios::in|std::ios::binary}; - - if (!filestream) { - if (!opt.isset("quiet")) { - std::cerr << "Could not open file '" << filename << "'.\n"; - } + for (auto &file : files) { + char *path = nullptr; + if (!path2key(opt, file, &path)) { exit_code = EXIT_FAILURE; - // continue; - } else { - const char *path; - char rpath[PATH_MAX+1]; - - if (file.key == memcp_file::type::relative) { - path = filename; - } else if (file.key == memcp_file::type::absolute) { - path = realpath(filename, rpath); - if (!path) { - if (!opt.isset("quiet")) { - perror(filename); - } - exit_code = EXIT_FAILURE; - continue; - } - } else { - path = basename(const_cast(filename)); - } - - std::ostringstream data{}; - data << filestream.rdbuf(); - - memcached_return_t rc; - const char *mode; - if (file.op == memcp_file::mode::REPLACE) { - mode = "replace"; - rc = memcached_replace(&memc, path, strlen(path), data.str().c_str(), data.str().length(), - file.expire, file.flags); - } else if (file.op == memcp_file::mode::ADD) { - mode = "add"; - rc = memcached_add(&memc, path, strlen(path), data.str().c_str(), data.str().length(), - file.expire, file.flags); - } else { - mode = "set"; - rc = memcached_set(&memc, path, strlen(path), data.str().c_str(), data.str().length(), - file.expire, file.flags); - } - - if (!memcached_success(rc)) { - exit_code = EXIT_FAILURE; - - auto error = memcached_last_error(&memc) - ? memcached_last_error_message(&memc) - : memcached_strerror(&memc, rc); - std::cerr << "Error occurred during memcached_" << mode <<"('" << path << "'): " << error << "\n"; - break; - } + continue; + } + auto rc = memcp(opt, memc, path, file); + if (memcached_success(rc)) { if (opt.isset("verbose")) { std::cout << path << "\n"; } + } else { + exit_code = EXIT_FAILURE; } } diff --git a/test/tests/bin/memcp.cpp b/test/tests/bin/memcp.cpp index 9a307128..c3fa7884 100644 --- a/test/tests/bin/memcp.cpp +++ b/test/tests/bin/memcp.cpp @@ -12,19 +12,20 @@ TEST_CASE("bin/memcp") { SECTION("no servers provided") { string output; REQUIRE_FALSE(sh.run("memcp nonexistent", output)); - REQUIRE(output == "No Servers provided\n"); + REQUIRE(output == "No servers provided.\n"); } SECTION("--help") { string output; REQUIRE(sh.run("memcp --help", output)); - REQUIRE_THAT(output, Contains("memcp")); - REQUIRE_THAT(output, Contains("v1")); - REQUIRE_THAT(output, Contains("help")); - REQUIRE_THAT(output, Contains("version")); - REQUIRE_THAT(output, Contains("option")); - REQUIRE_THAT(output, Contains("--")); - REQUIRE_THAT(output, Contains("=")); + REQUIRE_THAT(output, Contains("memcp v1")); + REQUIRE_THAT(output, Contains("Usage:")); + REQUIRE_THAT(output, Contains("file [file ...]")); + REQUIRE_THAT(output, Contains("Options:")); + REQUIRE_THAT(output, Contains("-h|--help")); + REQUIRE_THAT(output, Contains("-V|--version")); + REQUIRE_THAT(output, Contains("Environment:")); + REQUIRE_THAT(output, Contains("MEMCACHED_SERVERS")); } SECTION("with server") { -- 2.30.2