bin: consolidate clients
authorMichael Wallner <mike@php.net>
Tue, 17 Nov 2020 19:35:06 +0000 (20:35 +0100)
committerMichael Wallner <mike@php.net>
Tue, 17 Nov 2020 19:35:06 +0000 (20:35 +0100)
src/bin/common/checks.hpp
src/bin/common/options.cpp
src/bin/common/options.hpp
src/bin/memcp.cc
test/tests/bin/memcp.cpp

index 470b4b4a9e1da73701e46abdc7d2da4adc7c2d87..dc0ed92cb976d243d3318adbae9ae78945a23ae7 100644 (file)
@@ -18,6 +18,8 @@
 #include "options.hpp"
 #include "libmemcached/common.h"
 
+#include <cerrno>
+#include <cstring>
 #include <fstream>
 
 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;
+}
index 4583e7b73e9a2537b5daf2e1ed906b0cc0561b9d..229f4f0f943a1b55997e7dce174cc9092d7c6071 100644 (file)
@@ -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;
 }
index 28ade9733c8d1b233561b22969157b5202420754..fbcd10f6eb7b210022b617ec8539c28c17d0df3e 100644 (file)
@@ -34,7 +34,7 @@ public:
     std::string help;
     std::function<bool(client_options &, extended_option &)> parse;
     std::function<bool(const client_options &, const extended_option &, memcached_st *)> 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<extended_option>::iterator;
+  using const_iterator = std::vector<extended_option>::const_iterator;
+  using predicate = std::function<bool(const extended_option &ext)>;
+
   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 == '-');
+    });
+  }
 };
index e2dcccdc6a9bb3a60d9b8f6368aac1b33bd7e8b6..114a2f8562328250e8822c12ab398a5a39d95cba 100644 (file)
@@ -22,6 +22,7 @@
 #include "common/options.hpp"
 #include "common/checks.hpp"
 
+#include <cerrno>
 #include <climits>
 #include <cstdlib>
 #include <libgen.h>
@@ -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<memcp_file> &files, const client_options &opt, const char *file) {
+static inline std::string stream2string(const std::istream &istream) {
+  return dynamic_cast<std::ostringstream &>(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<memcp_file> &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<memcp_file> &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<memcp_file> 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<char *>(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;
     }
   }
 
index 9a3071287f348f3d3b1014fa5f2944facbd4b10a..c3fa788466461652b47a1d3a4438db62031a23d3 100644 (file)
@@ -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") {