summaryrefslogtreecommitdiff
path: root/src/filter.c
blob: 7bc2a684c0b874cc187f7a06aaa03c9cc4725a17 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
#include "filter.h"
#include "log.h"

#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>

#define FILTER_TIMEOUT_MS 250

/* ── filter_start ────────────────────────────────────────────────────────── */

void filter_start(Unit *u) {
    assert(u != NULL);
    assert(u->filter_in_fd == -1);

    if (!u->log_filter[0]) return;

    int to_filter[2], from_filter[2];
    if (pipe(to_filter) < 0) {
        log_warn("filter: pipe() failed for %s: %s", u->name, strerror(errno));
        return;
    }
    if (pipe(from_filter) < 0) {
        log_warn("filter: pipe() failed for %s: %s", u->name, strerror(errno));
        close(to_filter[0]); close(to_filter[1]);
        return;
    }

    pid_t pid = fork();
    if (pid < 0) {
        log_warn("filter: fork() failed for %s: %s", u->name, strerror(errno));
        close(to_filter[0]);  close(to_filter[1]);
        close(from_filter[0]); close(from_filter[1]);
        return;
    }

    if (pid == 0) {
        /* Child: wire pipes and exec the filter */
        if (dup2(to_filter[0],   STDIN_FILENO)  < 0) _exit(1);
        if (dup2(from_filter[1], STDOUT_FILENO) < 0) _exit(1);
        close(to_filter[0]);  close(to_filter[1]);
        close(from_filter[0]); close(from_filter[1]);
        execl(u->log_filter, u->log_filter, (char *)NULL);
        _exit(1);
    }

    /* Parent: keep write end of to_filter, read end of from_filter */
    close(to_filter[0]);
    close(from_filter[1]);
    if (fcntl(from_filter[0], F_SETFL, O_NONBLOCK) < 0)
        log_warn("filter: fcntl O_NONBLOCK failed for %s", u->name);

    u->filter_pid    = pid;
    u->filter_in_fd  = to_filter[1];
    u->filter_out_fd = from_filter[0];
    log_info("filter: started '%s' (pid %d) for unit %s",
             u->log_filter, (int)pid, u->name);
}

/* ── filter_apply ────────────────────────────────────────────────────────── */

void filter_apply(Unit *u, char *buf, int bufsize, int *len) {
    assert(u != NULL);
    assert(buf != NULL);
    assert(len != NULL);

    if (u->filter_in_fd < 0) return;

    ssize_t w = write(u->filter_in_fd, buf, (size_t)*len);
    if (w < 0) {
        log_warn("filter: write failed for %s: %s — stopping filter",
                 u->name, strerror(errno));
        filter_stop(u);
        return;
    }

    struct pollfd pfd = { u->filter_out_fd, POLLIN, 0 };
    int r = poll(&pfd, 1, FILTER_TIMEOUT_MS);
    if (r < 0) {
        log_warn("filter: poll failed for %s: %s", u->name, strerror(errno));
        return; /* pass through unchanged */
    }
    if (r == 0) return; /* timeout — pass through unchanged */

    ssize_t n = read(u->filter_out_fd, buf, bufsize - 1);
    if (n < 0 && errno != EAGAIN)
        log_warn("filter: read failed for %s: %s", u->name, strerror(errno));
    if (n > 0) {
        buf[n] = '\0';
        *len = (int)n;
    } else {
        *len = 0; /* filter suppressed all output */
    }
}

/* ── filter_stop / filter_stop_all ───────────────────────────────────────── */

void filter_stop(Unit *u) {
    assert(u != NULL);

    if (u->filter_pid <= 0) return;

    close(u->filter_in_fd);
    close(u->filter_out_fd);
    kill(u->filter_pid, SIGTERM);

    u->filter_in_fd  = -1;
    u->filter_out_fd = -1;
    u->filter_pid    = 0;
    log_info("filter: stopped filter for unit %s", u->name);
}

void filter_stop_all(void) {
    assert(g.unit_count >= 0);

    for (int i = 0; i < g.unit_count; i++)
        filter_stop(&g.units[i]);
}