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;
}
}
|