-
Notifications
You must be signed in to change notification settings - Fork 157
/
msg.c
248 lines (231 loc) · 6.7 KB
/
msg.c
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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
// SPDX-License-Identifier: MIT
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h> // need strlen()
#include <syslog.h>
#include <unistd.h>
#include "globals.h"
#include "msg.h"
static int use_syslog = 0;
// color_log writes to `f`, prefixing the `color` code if `f` is a tty.
static void color_log(FILE* f, const char* color, const char* fmt, va_list vl)
{
// Find out (and cache) if we should use color
static int stdout_is_tty = -1;
static int stderr_is_tty = -1;
static int no_color = -1;
bool is_tty = false;
if (no_color == -1) {
// https://no-color.org/
if (getenv("NO_COLOR") != NULL) {
no_color = 1;
} else {
no_color = 0;
}
}
if (no_color == 0) {
if (fileno(f) == fileno(stdout)) {
if (stdout_is_tty == -1) {
stdout_is_tty = isatty(fileno(stdout));
}
is_tty = stdout_is_tty;
} else if (fileno(f) == fileno(stderr)) {
if (stderr_is_tty == -1) {
stderr_is_tty = isatty(fileno(stderr));
}
is_tty = stderr_is_tty;
}
}
// fds other than stdout and stderr never get color
const char* reset = "\033[0m";
if (!is_tty) {
color = "";
reset = "";
}
fputs(color, f);
vfprintf(f, fmt, vl);
fputs(reset, f);
// The `reset` control was not flushed out by the
// newline as it was sent after. Manually flush
// it now to prevent artifacts when stderr and stdout
// mix.
if (fmt[strlen(fmt) - 1] == '\n') {
fflush(f);
}
}
static void logger(int priority, FILE* f, const char* color, const char* fmt, va_list vl)
{
if (use_syslog) {
vsyslog(priority, fmt, vl);
} else {
color_log(f, color, fmt, vl);
}
}
void earlyoom_syslog_init(void)
{
if (!use_syslog) {
use_syslog = 1;
openlog("earlyoom", LOG_PID | LOG_NDELAY, LOG_DAEMON);
atexit(closelog);
}
}
// Print message, prefixed with "fatal: ", to stderr and exit with "code".
// Example: fatal(6, "could not compile regexp '%s'\n", regex_str);
int fatal(int code, char* fmt, ...)
{
const char* red = "\033[31m";
char fmt2[MSG_LEN] = { 0 };
snprintf(fmt2, sizeof(fmt2), "fatal: %s", fmt);
va_list vl;
va_start(vl, fmt);
logger(LOG_ERR, stderr, red, fmt2, vl);
va_end(vl);
exit(code);
}
// Print a yellow warning message to stderr. No "warning" prefix is added.
int warn(const char* fmt, ...)
{
const char* yellow = "\033[33m";
va_list vl;
va_start(vl, fmt);
logger(LOG_WARNING, stderr, yellow, fmt, vl);
va_end(vl);
return 0;
}
// Print a gray debug message to stdout. No prefix is added.
int debug(const char* fmt, ...)
{
if (!enable_debug) {
return 0;
}
const char* gray = "\033[2m";
va_list vl;
va_start(vl, fmt);
logger(LOG_DEBUG, stdout, gray, fmt, vl);
va_end(vl);
return 0;
}
// Print info message to stdout. No prefix is added.
int info(const char* fmt, ...)
{
va_list vl;
va_start(vl, fmt);
logger(LOG_INFO, stdout, "", fmt, vl);
va_end(vl);
return 0;
}
// Parse a floating point value, check conversion errors and allowed range.
// Guaranteed value range: 0 <= val <= upper_limit.
// An error is indicated by storing an error message in tuple->err and returning 0.
static double parse_part(term_kill_tuple_t* tuple, const char* part, long long upper_limit)
{
errno = 0;
char* endptr = 0;
double val = strtod(part, &endptr);
if (*endptr != '\0') {
snprintf(tuple->err, sizeof(tuple->err),
"trailing garbage '%s'", endptr);
return 0;
}
if (errno) {
snprintf(tuple->err, sizeof(tuple->err),
"conversion error: %s", strerror(errno));
return 0;
}
if (val > (double)upper_limit) {
snprintf(tuple->err, sizeof(tuple->err),
"value %lf exceeds limit %lld", val, upper_limit);
return 0;
}
if (val < 0) {
snprintf(tuple->err, sizeof(tuple->err),
"value %lf below zero", val);
return 0;
}
return val;
}
// Parse the "term[,kill]" tuple in optarg, examples: "123", "123,456".
// Guaranteed value range: 0 <= term <= kill <= upper_limit.
term_kill_tuple_t parse_term_kill_tuple(const char* optarg, long long upper_limit)
{
term_kill_tuple_t tuple = { 0 };
// writable copy of optarg
char buf[MSG_LEN] = { 0 };
if (strlen(optarg) > (sizeof(buf) - 1)) {
snprintf(tuple.err, sizeof(tuple.err),
"argument too long (%zu bytes)", strlen(optarg));
return tuple;
}
strncpy(buf, optarg, sizeof(buf) - 1);
// Split string on "," into two parts
char* part1 = buf;
char* part2 = NULL;
char* comma = strchr(buf, ',');
if (comma) {
// Zero-out the comma, truncates part1
*comma = '\0';
// part2 gets zero or more bytes after the comma
part2 = comma + 1;
}
// Parse part1
tuple.term = parse_part(&tuple, part1, upper_limit);
if (strlen(tuple.err)) {
return tuple;
}
if (part2) {
// Parse part2
tuple.kill = parse_part(&tuple, part2, upper_limit);
if (strlen(tuple.err)) {
return tuple;
}
} else {
// User passed only the SIGTERM value: the SIGKILL value is calculated as
// SIGTERM/2.
tuple.kill = tuple.term / 2;
}
// Setting term < kill makes no sense
if (tuple.term < tuple.kill) {
warn("warning: SIGTERM value %.2lf is below SIGKILL value %.2lf, setting SIGTERM = SIGKILL = %.2lf\n",
tuple.term, tuple.kill, tuple.kill);
tuple.term = tuple.kill;
}
// Sanity checks
if (tuple.kill == 0 && tuple.term == 0) {
snprintf(tuple.err, sizeof(tuple.err),
"both SIGTERM and SIGKILL values are zero");
return tuple;
}
return tuple;
}
// Credit to https://gist.github.com/w-vi/67fe49106c62421992a2
// Only works for string of length 3 and up. This is good enough
// for our use case, which is fixing the 16-byte value we get
// from /proc/[pid]/comm.
//
// Tested in unit_test.go: Test_fix_truncated_utf8()
void fix_truncated_utf8(char* str)
{
size_t len = strlen(str);
if (len < 3) {
return;
}
// We only need to look at the last three bytes
char* b = str + len - 3;
// Last byte is ascii
if ((b[2] & 0x80) == 0) {
return;
}
// Last byte is multi-byte sequence start
if (b[2] & 0x40) {
b[2] = 0;
}
// Truncated 3-byte sequence
else if ((b[1] & 0xe0) == 0xe0) {
b[1] = 0;
// Truncated 4-byte sequence
} else if ((b[0] & 0xf0) == 0xf0) {
b[0] = 0;
}
}