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