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 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281
|
/* Copyright © Charliecloud contributors. */
#define _GNU_SOURCE
#include "config.h"
#include <string.h>
#include <unistd.h>
#include "all.h"
/** Constants **/
/* Text colors. Note leading escape characters (U+001B), which don’t always
show up depending on your viewer.
In principle, we should be using a library for this, e.g. terminfo(5).
However, moderately thorough web searching suggests that pretty much any
modern terminal will support 256-color ANSI codes, and this is way
simpler [1]. Probably should coordinate these colors with the Python code
somehow.
[1]: https://stackoverflow.com/a/3219471 */
static const char COLOUR_CYAN_DARK[] = "[0;38;5;6m";
static const char COLOUR_CYAN_LIGHT[] = "[0;38;5;14m";
//static const char COLOUR_GRAY[] = "[0;90m";
static const char COLOUR_RED[] = "[0;31m";
static const char COLOUR_RED_BOLD[] = "[1;31m";
static const char COLOUR_RESET[] = "[0m";
static const char COLOUR_YELLOW[] = "[0;33m";
static const char *_LL_COLOURS[] = { COLOUR_RED_BOLD, // fatal
COLOUR_RED_BOLD, // stderr
COLOUR_RED, // warning
COLOUR_YELLOW, // info
COLOUR_CYAN_LIGHT, // verbose
COLOUR_CYAN_DARK, // debug
COLOUR_CYAN_DARK }; // trace
/* This lets us index by verbosity, which can be negative. */
static const char **LL_COLOURS = _LL_COLOURS + 3;
/* Number of supplemental GIDs we can deal with. */
static const size_t SUPP_GIDS_MAX = 128;
/** External variables **/
/* If true, exit abnormally on fatal error. Set in ch-run.c during argument
parsing, so will always be default value before that. */
bool abort_fatal = false;
/* If true, use coloured logging. Set in ch-run.c. */
bool log_color_p = false;
/* Level of chatter on stderr. */
enum log_level verbose;
/* List of warnings to be re-printed on exit. This is a buffer of shared memory
allocated by mmap(2), structured as a sequence of null-terminated character
strings. Warnings that do not fit in this buffer will be lost, though we
allocate enough memory that this is unlikely. See “string_append()” for
more details. */
char *warnings;
/* Current byte offset from start of “warnings” buffer. This gives the address
where the next appended string will start. This means that the null
terminator of the previous string is warnings_offset - 1. */
size_t warnings_offset = 0;
/** Function prototypes (private) **/
void msgv(enum log_level level, const char *file, int line, int errno_,
const char *fmt, va_list ap)
__attribute__ ((format (printf, 5, 0)));
/** Functions **/
/* If verbose enough, print uids and gids on stderr prefixed with where.
FIXME: Should change to DEBUG(), but that will give the file/line within
this function, which we don’t want. */
void log_ids(const char *func, int line)
{
uid_t ruid, euid, suid;
gid_t rgid, egid, sgid;
gid_t supp_gids[SUPP_GIDS_MAX];
int supp_gid_ct;
if (verbose >= LL_TRACE + 1) { // don’t bother b/c haven’t needed in ages
Z_e (getresuid(&ruid, &euid, &suid));
Z_e (getresgid(&rgid, &egid, &sgid));
if (log_color_p)
T_e (EOF != fputs(LL_COLOURS[LL_TRACE], stderr));
fprintf(stderr, "%s %d: uids=%d,%d,%d, gids=%d,%d,%d + ", func, line,
ruid, euid, suid, rgid, egid, sgid);
supp_gid_ct = getgroups(SUPP_GIDS_MAX, supp_gids);
if (supp_gid_ct == -1) {
T_e (errno == EINVAL);
Tf_ (0, "more than %zu groups", SUPP_GIDS_MAX);
}
for (int i = 0; i < supp_gid_ct; i++) {
if (i > 0)
fprintf(stderr, ",");
fprintf(stderr, "%d", supp_gids[i]);
}
fprintf(stderr, "\n");
if (log_color_p)
T_e (EOF != fputs(COLOUR_RESET, stderr));
Z_e (fflush(stderr));
}
}
/* Set up logging. Note ch-run(1) specifies a bunch of color synonyms; this
translation happens during argument parsing.*/
void logging_init(enum log_color_when when, enum log_test test)
{
// set up colors
switch (when) {
case LL_COLOR_AUTO:
if (isatty(fileno(stderr)))
log_color_p = true;
else {
T_e (errno == ENOTTY);
log_color_p = false;
}
break;
case LL_COLOR_YES:
log_color_p = true;
break;
case LL_COLOR_NO:
log_color_p = false;
break;
case LL_COLOR_NULL:
T__ (0); // unreachable
break;
}
// test logging
if (test >= LL_TEST_YES) {
TRACE("trace");
DEBUG("debug");
VERBOSE("verbose");
INFO("info");
WARNING("warning");
if (test >= LL_TEST_FATAL)
FATAL(-1, "the program failed inexplicably (\"log-fail\" specified)");
exit(0);
}
}
/* Print a formatted message on stderr if the level warrants it. */
void msg(enum log_level level, const char *file, int line, int errno_,
const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
msgv(level, file, line, errno_, fmt, ap);
va_end(ap);
}
void msg_error(const char *file, int line, int errno_,
const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
/* We print errors at LL_FATAL because, according to our documentation,
errors are never suppressed. Perhaps we need to rename this log level
(see issue #1914). */
msgv(LL_FATAL, file, line, errno_, fmt, ap);
va_end(ap);
}
/* Note that msg_fatal doesn’t call msg_error like we do in the Python code
because the variable number of arguments make it easier to simply define
separate functions. */
noreturn msg_fatal(const char *file, int line, int errno_,
const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
msgv(LL_FATAL, file, line, errno_, fmt, ap);
va_end(ap);
if (abort_fatal)
abort();
else
exit(EXIT_ERR_MISC);
}
/* va_list form of msg(). */
void msgv(enum log_level level, const char *file, int line, int errno_,
const char *fmt, va_list ap)
{
// note: all components contain appropriate leading/trailing space
char *text_formatted; // caller’s message, formatted
char *level_prefix; // level prefix
char *errno_code; // errno code/number
char *errno_desc; // errno description
char *text_full; // complete text but w/o color codes
const char * colour; // ANSI codes for color
const char * colour_reset; // ANSI codes to reset color
if (level > verbose) // not verbose enough; do nothing
return;
// Format caller message.
if (fmt == NULL)
text_formatted = "assertion failure: please report this bug";
else
text_formatted = vasprintf_ch(fmt, ap);
// Prefix some of the levels.
switch (level) {
case LL_FATAL:
level_prefix = "error: "; // "fatal" too morbid for users
break;
case LL_WARNING:
level_prefix = "warning: ";
break;
default:
level_prefix = "";
break;
}
// errno.
if (errno_ < 0) {
errno_code = "";
errno_desc = "";
} else {
errno_code = cat(" ", errno_nerd_str(errno_));
errno_desc = asprintf_ch(": %s", strerror(errno_));
}
// Color.
if (log_color_p) {
colour = LL_COLOURS[level];
colour_reset = COLOUR_RESET;
} else {
colour = "";
colour_reset = "";
};
// Format and print.
text_full = asprintf_ch("%s[%d]: %s%s%s (%s:%d%s)",
program_invocation_short_name, getpid(),
level_prefix, text_formatted, errno_desc,
file, line, errno_code);
fprintf(stderr, "%s%s%s\n", colour, text_full, colour_reset);
if (fflush(stderr))
abort(); // can’t print an error b/c already trying to do that
if (level == LL_WARNING)
warnings_offset += string_append(warnings, text_full,
WARNINGS_SIZE, warnings_offset);
}
/* Reprint messages stored in “warnings” memory buffer. */
void warnings_reprint(void)
{
size_t offset = 0;
int warn_ct = buf_strings_count(warnings, WARNINGS_SIZE);
if (warn_ct > 0) {
if (log_color_p)
T_e (EOF != fputs(LL_COLOURS[LL_WARNING], stderr));
T_e (1 <= fprintf(stderr, "%s[%d]: reprinting first %d warning(s)\n",
program_invocation_short_name, getpid(), warn_ct));
while ( warnings[offset] != 0
|| (offset < (WARNINGS_SIZE - 1) && warnings[offset+1] != 0)) {
T_e (EOF != fputs(warnings + offset, stderr));
T_e (EOF != fputc('\n', stderr));
offset += strlen(warnings + offset) + 1;
}
if (log_color_p)
T_e (EOF != fputs(COLOUR_RESET, stderr));
if (fflush(stderr))
abort(); // can't print an error b/c already trying to do that
}
}
|