This is an automated email from the ASF dual-hosted git repository.

zwoop pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git


The following commit(s) were added to refs/heads/master by this push:
     new fc4fa5ed11 Allow header_rewrite to run a compiler for HRW4u (#12230)
fc4fa5ed11 is described below

commit fc4fa5ed1138439948ea74de655e54d6b38c5089
Author: Leif Hedstrom <[email protected]>
AuthorDate: Fri Jun 6 14:55:44 2025 -0500

    Allow header_rewrite to run a compiler for HRW4u (#12230)
    
    * Allow HRW plugin to run a compiler for HRW4u
    
    * Address the review concerns, and clang-tidy
---
 plugins/header_rewrite/header_rewrite.cc |  19 ++++--
 plugins/header_rewrite/lulu.cc           |   1 -
 plugins/header_rewrite/lulu.h            |   4 +-
 plugins/header_rewrite/parser.cc         | 103 +++++++++++++++++++++++++++++--
 plugins/header_rewrite/parser.h          |  76 +++++++++++++++++++++++
 5 files changed, 190 insertions(+), 13 deletions(-)

diff --git a/plugins/header_rewrite/header_rewrite.cc 
b/plugins/header_rewrite/header_rewrite.cc
index b4f2da2505..449e6ea1f0 100644
--- a/plugins/header_rewrite/header_rewrite.cc
+++ b/plugins/header_rewrite/header_rewrite.cc
@@ -150,7 +150,6 @@ RulesConfig::parse_config(const std::string &fname, 
TSHttpHookID default_hook, c
 {
   std::unique_ptr<RuleSet>     rule(nullptr);
   std::string                  filename;
-  std::ifstream                f;
   int                          lineno = 0;
   std::stack<ConditionGroup *> group_stack;
   ConditionGroup              *group = nullptr;
@@ -167,17 +166,18 @@ RulesConfig::parse_config(const std::string &fname, 
TSHttpHookID default_hook, c
     filename = fname;
   }
 
-  f.open(filename.c_str(), std::ios::in);
-  if (!f.is_open()) {
+  auto reader = openConfig(filename);
+  if (!reader || !reader->stream) {
     TSError("[%s] unable to open %s", PLUGIN_NAME, filename.c_str());
     return false;
   }
 
   Dbg(dbg_ctl, "Parsing started on file: %s", filename.c_str());
-  while (!f.eof()) {
+
+  while (!reader->stream->eof()) {
     std::string line;
 
-    getline(f, line);
+    getline(*reader->stream, line);
     ++lineno;
     Dbg(dbg_ctl, "Reading line: %d: %s", lineno, line.c_str());
 
@@ -289,6 +289,15 @@ RulesConfig::parse_config(const std::string &fname, 
TSHttpHookID default_hook, c
     }
   }
 
+  if (reader->pipebuf) {
+    reader->pipebuf->close();
+    if (reader->pipebuf->exit_status() != 0) {
+      TSError("[%s] hrw4u preprocessor exited with non-zero status (%d): %s", 
PLUGIN_NAME, reader->pipebuf->exit_status(),
+              fname.c_str());
+      return false;
+    }
+  }
+
   if (!group_stack.empty()) {
     TSError("[%s] missing final %%{GROUP:END} condition in file: %s, lineno: 
%d", PLUGIN_NAME, fname.c_str(), lineno);
     return false;
diff --git a/plugins/header_rewrite/lulu.cc b/plugins/header_rewrite/lulu.cc
index b4eb9f6341..d883b12223 100644
--- a/plugins/header_rewrite/lulu.cc
+++ b/plugins/header_rewrite/lulu.cc
@@ -16,7 +16,6 @@
   limitations under the License.
 */
 
-#include <string>
 #include <netinet/in.h>
 
 #include "ts/ts.h"
diff --git a/plugins/header_rewrite/lulu.h b/plugins/header_rewrite/lulu.h
index 9058d11552..6db69270eb 100644
--- a/plugins/header_rewrite/lulu.h
+++ b/plugins/header_rewrite/lulu.h
@@ -32,12 +32,12 @@
 #define TS_REMAP_PSEUDO_HOOK TS_HTTP_LAST_HOOK // Ugly, but use the "last 
hook" for remap instances.
 const int OVECCOUNT = 30;                      // We support $1 - $9 only, and 
this needs to be 3x that
 
+template <typename T> constexpr bool ALWAYS_FALSE_V = false;
+
 std::string getIP(sockaddr const *s_sockaddr);
 char       *getIP(sockaddr const *s_sockaddr, char res[INET6_ADDRSTRLEN]);
 uint16_t    getPort(sockaddr const *s_sockaddr);
 
-template <typename T> constexpr bool ALWAYS_FALSE_V = false;
-
 namespace header_rewrite_ns
 {
 extern const char PLUGIN_NAME[];
diff --git a/plugins/header_rewrite/parser.cc b/plugins/header_rewrite/parser.cc
index fb73b5aea7..0cd16aa4ae 100644
--- a/plugins/header_rewrite/parser.cc
+++ b/plugins/header_rewrite/parser.cc
@@ -20,11 +20,13 @@
 //
 //
 #include <utility>
-#include <iostream>
 #include <string>
+#include <fstream>
 #include <sstream>
+#include <filesystem>
 
 #include "ts/ts.h"
+#include "tscore/Layout.h"
 
 #include "parser.h"
 
@@ -50,7 +52,7 @@ Parser::parse_line(const std::string &original_line)
         state            = PARSER_DEFAULT;
       } else if (!std::isspace(line[i])) {
         // we got a standalone =, > or <
-        _tokens.push_back(std::string(1, line[i]));
+        _tokens.emplace_back(1, line[i]);
       }
     } else if ((state != PARSER_IN_QUOTE) && (line[i] == '/')) {
       // Deal with regexes, nothing gets escaped / quoted in here
@@ -115,7 +117,7 @@ Parser::parse_line(const std::string &original_line)
 
       if ((line[i] == '=') || (line[i] == '+')) {
         // These are always a separate token
-        _tokens.push_back(std::string(1, line[i]));
+        _tokens.emplace_back(1, line[i]);
         continue;
       }
 
@@ -278,9 +280,8 @@ Parser::cond_is_hook(TSHttpHookID &hook) const
   return false;
 }
 
-HRWSimpleTokenizer::HRWSimpleTokenizer(const std::string &original_line)
+HRWSimpleTokenizer::HRWSimpleTokenizer(const std::string &line)
 {
-  std::string line             = original_line;
   ParserState state            = PARSER_DEFAULT;
   bool        extracting_token = false;
   off_t       cur_token_start  = 0;
@@ -325,3 +326,95 @@ HRWSimpleTokenizer::HRWSimpleTokenizer(const std::string 
&original_line)
     _tokens.push_back(line.substr(cur_token_start));
   }
 }
+
+// This is the universal configuration reader, which can read both
+// a raw file, as well as executing an external compiler (hrw4u) to parse
+// the configuration file.
+namespace
+{
+void
+_log_stderr(int fd)
+{
+  char        buffer[512];
+  std::string partial;
+
+  while (ssize_t n = read(fd, buffer, sizeof(buffer))) {
+    if (n <= 0) {
+      break;
+    }
+    partial.append(buffer, n);
+    size_t pos = 0;
+    while ((pos = partial.find('\n')) != std::string::npos) {
+      std::string line = partial.substr(0, pos);
+      TSError("[header_rewrite: hrw4u] %s", line.c_str());
+      partial.erase(0, pos + 1);
+    }
+  }
+
+  if (!partial.empty()) {
+    TSError("[hrw4u] stderr: %s", partial.c_str());
+  }
+
+  close(fd);
+}
+} // namespace
+
+std::optional<ConfReader>
+openConfig(const std::string &filename)
+{
+  namespace fs             = std::filesystem;
+  const std::string suffix = ".hrw4u";
+  std::string       hrw4u  = Layout::get()->bindir + "/traffic_hrw4u";
+
+  static const bool has_compiler = [hrw4u]() {
+    fs::path        path(hrw4u);
+    std::error_code ec;
+    auto            status = fs::status(path, ec);
+    auto            perms  = status.permissions();
+    return fs::exists(path, ec) && fs::is_regular_file(path, ec) && (perms & 
fs::perms::owner_exec) != fs::perms::none;
+  }();
+
+  if (filename.ends_with(suffix) && has_compiler) {
+    int pipe_fds[2];
+    int stderr_pipe[2];
+
+    if (pipe(pipe_fds) != 0 || pipe(stderr_pipe) != 0) {
+      TSError("[header_rewrite] failed to create pipe for hrw4u compiler: %s", 
strerror(errno));
+      return std::nullopt;
+    }
+
+    pid_t pid = fork();
+    if (pid < 0) {
+      TSError("[header_rewrite] failed to fork for hrw4u compiler: %s", 
strerror(errno));
+      return std::nullopt;
+    } else if (pid == 0) {
+      dup2(pipe_fds[1], STDOUT_FILENO);
+      dup2(stderr_pipe[1], STDERR_FILENO);
+      close(pipe_fds[0]);
+      close(stderr_pipe[0]);
+
+      const char *argv[] = {hrw4u.c_str(), filename.c_str(), nullptr};
+      execvp(argv[0], const_cast<char **>(argv));
+      _exit(127); // child exec failed
+    }
+
+    // Parent
+    close(pipe_fds[1]);
+    close(stderr_pipe[1]);
+
+    _log_stderr(stderr_pipe[0]);
+
+    auto pipebuf = std::make_shared<HRW4UPipe>(fdopen(pipe_fds[0], "r"));
+    pipebuf->set_pid(pid);
+    auto stream = std::make_unique<std::istream>(pipebuf.get());
+
+    return ConfReader{.stream = std::move(stream), .pipebuf = 
std::move(pipebuf)};
+  } else {
+    auto file = std::make_unique<std::ifstream>(filename);
+    if (!file->is_open()) {
+      return std::nullopt;
+    }
+
+    return ConfReader{.stream = std::move(file), .pipebuf = nullptr};
+  }
+}
diff --git a/plugins/header_rewrite/parser.h b/plugins/header_rewrite/parser.h
index cc0f3d985a..2e46648490 100644
--- a/plugins/header_rewrite/parser.h
+++ b/plugins/header_rewrite/parser.h
@@ -28,10 +28,86 @@
 #include <charconv>
 #include <optional>
 #include <limits>
+#include <memory>
 
 #include "ts/ts.h"
 #include "lulu.h"
 
+///////////////////////////////////////////////////////////////////////////////
+// Simple wrapper, for dealing with raw configurations, and the compiled
+// configurations.
+class HRW4UPipe : public std::streambuf
+{
+public:
+  explicit HRW4UPipe(FILE *pipe) : _pipe(pipe) { setg(_buffer, _buffer, 
_buffer); }
+
+  ~HRW4UPipe() override { close(); }
+
+  void
+  set_pid(pid_t pid)
+  {
+    _pid = pid;
+  }
+
+  int
+  exit_status() const
+  {
+    return _exit_code;
+  }
+
+  void
+  close()
+  {
+    if (_pipe) {
+      fclose(_pipe);
+      _pipe = nullptr;
+    }
+
+    if (_pid > 0) {
+      int status = -1;
+      waitpid(_pid, &status, 0);
+      if (WIFEXITED(status)) {
+        _exit_code = WEXITSTATUS(status);
+      } else if (WIFSIGNALED(status)) {
+        _exit_code = 128 + WTERMSIG(status);
+      } else {
+        _exit_code = -1;
+      }
+      _pid = -1;
+    }
+  }
+
+protected:
+  int
+  underflow() override
+  {
+    if (!_pipe) {
+      return traits_type::eof();
+    }
+
+    size_t n = fread(_buffer, 1, sizeof(_buffer), _pipe);
+    if (n == 0) {
+      return traits_type::eof();
+    }
+
+    setg(_buffer, _buffer, _buffer + n);
+    return traits_type::to_int_type(*gptr());
+  }
+
+private:
+  char  _buffer[65536];
+  FILE *_pipe      = nullptr;
+  pid_t _pid       = -1;
+  int   _exit_code = -1;
+};
+
+struct ConfReader {
+  std::unique_ptr<std::istream> stream;
+  std::shared_ptr<HRW4UPipe>    pipebuf;
+};
+
+std::optional<ConfReader> openConfig(const std::string &filename);
+
 ///////////////////////////////////////////////////////////////////////////////
 //
 class Parser

Reply via email to