Skip to content

Commit fc4fa5e

Browse files
authored
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
1 parent 196ac8c commit fc4fa5e

5 files changed

Lines changed: 190 additions & 13 deletions

File tree

plugins/header_rewrite/header_rewrite.cc

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,6 @@ RulesConfig::parse_config(const std::string &fname, TSHttpHookID default_hook, c
150150
{
151151
std::unique_ptr<RuleSet> rule(nullptr);
152152
std::string filename;
153-
std::ifstream f;
154153
int lineno = 0;
155154
std::stack<ConditionGroup *> group_stack;
156155
ConditionGroup *group = nullptr;
@@ -167,17 +166,18 @@ RulesConfig::parse_config(const std::string &fname, TSHttpHookID default_hook, c
167166
filename = fname;
168167
}
169168

170-
f.open(filename.c_str(), std::ios::in);
171-
if (!f.is_open()) {
169+
auto reader = openConfig(filename);
170+
if (!reader || !reader->stream) {
172171
TSError("[%s] unable to open %s", PLUGIN_NAME, filename.c_str());
173172
return false;
174173
}
175174

176175
Dbg(dbg_ctl, "Parsing started on file: %s", filename.c_str());
177-
while (!f.eof()) {
176+
177+
while (!reader->stream->eof()) {
178178
std::string line;
179179

180-
getline(f, line);
180+
getline(*reader->stream, line);
181181
++lineno;
182182
Dbg(dbg_ctl, "Reading line: %d: %s", lineno, line.c_str());
183183

@@ -289,6 +289,15 @@ RulesConfig::parse_config(const std::string &fname, TSHttpHookID default_hook, c
289289
}
290290
}
291291

292+
if (reader->pipebuf) {
293+
reader->pipebuf->close();
294+
if (reader->pipebuf->exit_status() != 0) {
295+
TSError("[%s] hrw4u preprocessor exited with non-zero status (%d): %s", PLUGIN_NAME, reader->pipebuf->exit_status(),
296+
fname.c_str());
297+
return false;
298+
}
299+
}
300+
292301
if (!group_stack.empty()) {
293302
TSError("[%s] missing final %%{GROUP:END} condition in file: %s, lineno: %d", PLUGIN_NAME, fname.c_str(), lineno);
294303
return false;

plugins/header_rewrite/lulu.cc

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
limitations under the License.
1717
*/
1818

19-
#include <string>
2019
#include <netinet/in.h>
2120

2221
#include "ts/ts.h"

plugins/header_rewrite/lulu.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,12 @@
3232
#define TS_REMAP_PSEUDO_HOOK TS_HTTP_LAST_HOOK // Ugly, but use the "last hook" for remap instances.
3333
const int OVECCOUNT = 30; // We support $1 - $9 only, and this needs to be 3x that
3434

35+
template <typename T> constexpr bool ALWAYS_FALSE_V = false;
36+
3537
std::string getIP(sockaddr const *s_sockaddr);
3638
char *getIP(sockaddr const *s_sockaddr, char res[INET6_ADDRSTRLEN]);
3739
uint16_t getPort(sockaddr const *s_sockaddr);
3840

39-
template <typename T> constexpr bool ALWAYS_FALSE_V = false;
40-
4141
namespace header_rewrite_ns
4242
{
4343
extern const char PLUGIN_NAME[];

plugins/header_rewrite/parser.cc

Lines changed: 98 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,13 @@
2020
//
2121
//
2222
#include <utility>
23-
#include <iostream>
2423
#include <string>
24+
#include <fstream>
2525
#include <sstream>
26+
#include <filesystem>
2627

2728
#include "ts/ts.h"
29+
#include "tscore/Layout.h"
2830

2931
#include "parser.h"
3032

@@ -50,7 +52,7 @@ Parser::parse_line(const std::string &original_line)
5052
state = PARSER_DEFAULT;
5153
} else if (!std::isspace(line[i])) {
5254
// we got a standalone =, > or <
53-
_tokens.push_back(std::string(1, line[i]));
55+
_tokens.emplace_back(1, line[i]);
5456
}
5557
} else if ((state != PARSER_IN_QUOTE) && (line[i] == '/')) {
5658
// Deal with regexes, nothing gets escaped / quoted in here
@@ -115,7 +117,7 @@ Parser::parse_line(const std::string &original_line)
115117

116118
if ((line[i] == '=') || (line[i] == '+')) {
117119
// These are always a separate token
118-
_tokens.push_back(std::string(1, line[i]));
120+
_tokens.emplace_back(1, line[i]);
119121
continue;
120122
}
121123

@@ -278,9 +280,8 @@ Parser::cond_is_hook(TSHttpHookID &hook) const
278280
return false;
279281
}
280282

281-
HRWSimpleTokenizer::HRWSimpleTokenizer(const std::string &original_line)
283+
HRWSimpleTokenizer::HRWSimpleTokenizer(const std::string &line)
282284
{
283-
std::string line = original_line;
284285
ParserState state = PARSER_DEFAULT;
285286
bool extracting_token = false;
286287
off_t cur_token_start = 0;
@@ -325,3 +326,95 @@ HRWSimpleTokenizer::HRWSimpleTokenizer(const std::string &original_line)
325326
_tokens.push_back(line.substr(cur_token_start));
326327
}
327328
}
329+
330+
// This is the universal configuration reader, which can read both
331+
// a raw file, as well as executing an external compiler (hrw4u) to parse
332+
// the configuration file.
333+
namespace
334+
{
335+
void
336+
_log_stderr(int fd)
337+
{
338+
char buffer[512];
339+
std::string partial;
340+
341+
while (ssize_t n = read(fd, buffer, sizeof(buffer))) {
342+
if (n <= 0) {
343+
break;
344+
}
345+
partial.append(buffer, n);
346+
size_t pos = 0;
347+
while ((pos = partial.find('\n')) != std::string::npos) {
348+
std::string line = partial.substr(0, pos);
349+
TSError("[header_rewrite: hrw4u] %s", line.c_str());
350+
partial.erase(0, pos + 1);
351+
}
352+
}
353+
354+
if (!partial.empty()) {
355+
TSError("[hrw4u] stderr: %s", partial.c_str());
356+
}
357+
358+
close(fd);
359+
}
360+
} // namespace
361+
362+
std::optional<ConfReader>
363+
openConfig(const std::string &filename)
364+
{
365+
namespace fs = std::filesystem;
366+
const std::string suffix = ".hrw4u";
367+
std::string hrw4u = Layout::get()->bindir + "/traffic_hrw4u";
368+
369+
static const bool has_compiler = [hrw4u]() {
370+
fs::path path(hrw4u);
371+
std::error_code ec;
372+
auto status = fs::status(path, ec);
373+
auto perms = status.permissions();
374+
return fs::exists(path, ec) && fs::is_regular_file(path, ec) && (perms & fs::perms::owner_exec) != fs::perms::none;
375+
}();
376+
377+
if (filename.ends_with(suffix) && has_compiler) {
378+
int pipe_fds[2];
379+
int stderr_pipe[2];
380+
381+
if (pipe(pipe_fds) != 0 || pipe(stderr_pipe) != 0) {
382+
TSError("[header_rewrite] failed to create pipe for hrw4u compiler: %s", strerror(errno));
383+
return std::nullopt;
384+
}
385+
386+
pid_t pid = fork();
387+
if (pid < 0) {
388+
TSError("[header_rewrite] failed to fork for hrw4u compiler: %s", strerror(errno));
389+
return std::nullopt;
390+
} else if (pid == 0) {
391+
dup2(pipe_fds[1], STDOUT_FILENO);
392+
dup2(stderr_pipe[1], STDERR_FILENO);
393+
close(pipe_fds[0]);
394+
close(stderr_pipe[0]);
395+
396+
const char *argv[] = {hrw4u.c_str(), filename.c_str(), nullptr};
397+
execvp(argv[0], const_cast<char **>(argv));
398+
_exit(127); // child exec failed
399+
}
400+
401+
// Parent
402+
close(pipe_fds[1]);
403+
close(stderr_pipe[1]);
404+
405+
_log_stderr(stderr_pipe[0]);
406+
407+
auto pipebuf = std::make_shared<HRW4UPipe>(fdopen(pipe_fds[0], "r"));
408+
pipebuf->set_pid(pid);
409+
auto stream = std::make_unique<std::istream>(pipebuf.get());
410+
411+
return ConfReader{.stream = std::move(stream), .pipebuf = std::move(pipebuf)};
412+
} else {
413+
auto file = std::make_unique<std::ifstream>(filename);
414+
if (!file->is_open()) {
415+
return std::nullopt;
416+
}
417+
418+
return ConfReader{.stream = std::move(file), .pipebuf = nullptr};
419+
}
420+
}

plugins/header_rewrite/parser.h

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,86 @@
2828
#include <charconv>
2929
#include <optional>
3030
#include <limits>
31+
#include <memory>
3132

3233
#include "ts/ts.h"
3334
#include "lulu.h"
3435

36+
///////////////////////////////////////////////////////////////////////////////
37+
// Simple wrapper, for dealing with raw configurations, and the compiled
38+
// configurations.
39+
class HRW4UPipe : public std::streambuf
40+
{
41+
public:
42+
explicit HRW4UPipe(FILE *pipe) : _pipe(pipe) { setg(_buffer, _buffer, _buffer); }
43+
44+
~HRW4UPipe() override { close(); }
45+
46+
void
47+
set_pid(pid_t pid)
48+
{
49+
_pid = pid;
50+
}
51+
52+
int
53+
exit_status() const
54+
{
55+
return _exit_code;
56+
}
57+
58+
void
59+
close()
60+
{
61+
if (_pipe) {
62+
fclose(_pipe);
63+
_pipe = nullptr;
64+
}
65+
66+
if (_pid > 0) {
67+
int status = -1;
68+
waitpid(_pid, &status, 0);
69+
if (WIFEXITED(status)) {
70+
_exit_code = WEXITSTATUS(status);
71+
} else if (WIFSIGNALED(status)) {
72+
_exit_code = 128 + WTERMSIG(status);
73+
} else {
74+
_exit_code = -1;
75+
}
76+
_pid = -1;
77+
}
78+
}
79+
80+
protected:
81+
int
82+
underflow() override
83+
{
84+
if (!_pipe) {
85+
return traits_type::eof();
86+
}
87+
88+
size_t n = fread(_buffer, 1, sizeof(_buffer), _pipe);
89+
if (n == 0) {
90+
return traits_type::eof();
91+
}
92+
93+
setg(_buffer, _buffer, _buffer + n);
94+
return traits_type::to_int_type(*gptr());
95+
}
96+
97+
private:
98+
char _buffer[65536];
99+
FILE *_pipe = nullptr;
100+
pid_t _pid = -1;
101+
int _exit_code = -1;
102+
};
103+
104+
struct ConfReader {
105+
std::unique_ptr<std::istream> stream;
106+
std::shared_ptr<HRW4UPipe> pipebuf;
107+
};
108+
109+
std::optional<ConfReader> openConfig(const std::string &filename);
110+
35111
///////////////////////////////////////////////////////////////////////////////
36112
//
37113
class Parser

0 commit comments

Comments
 (0)