diff options
Diffstat (limited to 'usr/src/cmd/zoneadmd/log.c')
-rw-r--r-- | usr/src/cmd/zoneadmd/log.c | 1027 |
1 files changed, 1027 insertions, 0 deletions
diff --git a/usr/src/cmd/zoneadmd/log.c b/usr/src/cmd/zoneadmd/log.c new file mode 100644 index 0000000000..a4ecc3e1e8 --- /dev/null +++ b/usr/src/cmd/zoneadmd/log.c @@ -0,0 +1,1027 @@ +/* + * This file and its contents are supplied under the terms of the + * Common Development and Distribution License ("CDDL"), version 1.0. + * You may only use this file in accordance with the terms of version + * 1.0 of the CDDL. + * + * A full copy of the text of the CDDL should have accompanied this + * source. A copy of the CDDL is also available via the Internet at + * http://www.illumos.org/license/CDDL. + */ + +/* + * Copyright 2020 Joyent, Inc. + */ + +/* + * zoneadmd logging + * + * zoneadmd logs to log files under <zonepath>/logs. Each log entry is a json + * structure of the form: + * + * { + * "log": "some message\n", + * "stream": "stderr", + * "time": "2018-03-28T13:25:02.670423000Z" + * } + * + * Unlike the example above, the entries in the log file are not pretty-printed. + * Messages are processed so that they have the proper json escapes for + * problematic characters. Excessively long messages may be truncated. + * + * To use these interfaces: + * + * int logid; + * + * logstream_init(zlogp); + * + * logid = logstream_open("stdio.log", "stdout", flags); + * ... + * logstream_write(logid, buf, len); + * ... + * logstream_close(logid); + * + * logstream_init() needs to be called only once. + * + * logstream_open() opens a log file (if not already open) and associates the + * specified stream with it. + * + * The following flag is supported: + * + * LS_LINE_BUFFERED Buffer writes until a newline is encountered or the + * buffer fills. This should only be used with streams + * that are written to by a single thread. The timestamp + * on log messages are the time that the log entry was + * written to the log file. This means the timestamp is + * the time when the console user hits enter, not the time + * that the prompt was printed. + * + * Line buffering is particularly useful for bhyve console logging because + * bhyve's UART emulation causes read() calls in zcons.c to return far fewer + * than 10 characters at a time. Without line buffering, a small number of + * logged characters are accompanied by about 64 characters of timestamp and + * other overhead. Line buffering saves quite a lot of space and makes the log + * much easier to read. + * + * + * Log rotation + * + * Two attributes, zlog-max-size and zlog-keep-rotated are used for automatic + * log rotation. zlog-max-size is the approximate maximum size of a log before + * it is automatically rotated. Rotated logs are renamed as + * <log>.<iso-8601-stamp>. If zlog-keep-rotated is specified and is an integer + * greater than zero, only that number of rotated logs will be retained. + * + * If zlog-max-size is not specified, log rotation will not happen + * automatically. An external log rotation program may rename the log file(s), + * then send SIGHUP to zoneadmd. + * + * Log rotation can be forced with SIGUSR1. In this case, the log will be + * rotated as though it hit the maximum size and will be subject to retention + * rules described above. + * + * + * Locking strategy + * + * Callers need not worry about locking. In the interest of simplicity, a + * single global lock is used to protect the state of the log files and the + * associated streams. Locking is necessary because reboots and log rotations + * can cause various state changes. Without locking, races could cause log + * entries to be directed to the wrong file descriptors. + * + * The simplistic global lock complicates error reporting within logging + * routines. zerror() must not be called while holding logging_lock. Rather, + * logstream_err() should be used to log via syslog. + */ + +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <glob.h> +#include <libcustr.h> +#include <netdb.h> +#include <signal.h> +#include <stdarg.h> +#include <stdio.h> +#include <strings.h> +#include <synch.h> +#include <syslog.h> +#include <time.h> +#include <thread.h> +#include <unistd.h> + +#include <sys/debug.h> +#include <sys/stat.h> +#include <sys/sysmacros.h> +#include <sys/time.h> +#include <sys/types.h> +#include <sys/varargs.h> + +#include "zoneadmd.h" + +/* + * Currently we only expect stdout, stderr, zoneadmd, and console. Increase + * MAX_ZLOG_STREAMS if more streams are added. If the count increases + * significantly, logfile_t and logstream_t elements should be dynamically + * allocated and the algorithms associated with opening and closing them should + * become more efficient. + */ +#define MAX_LOG_STREAMS 4 + +#define ZLOG_MAXSZ "zlog-max-size" /* zonecfg attr */ +#define ZLOG_MAXSZ_MIN (1024 * 1024) /* min size for autorotate */ +#define ZLOG_KEEP "zlog-keep-rotated" /* zonecfg attr */ +#define ZLOG_KEEP_MAX 1000 /* number of log files */ + +/* + * While we could get the numeric value of BUNYAN_LOG_INFO from bunyan.h, + * the log version is internal to the library, so we just define the values + * we need here. + */ +#define BUNYAN_VERSION 0 +#define BUNYAN_LOG_LEVEL 30 /* info */ + +typedef struct logfile { + char lf_path[MAXPATHLEN]; /* log file name (absolute path) */ + char lf_name[MAXNAMELEN]; /* tail of log file name */ + char lf_buf[BUFSIZ]; /* Buffer for event messages */ + custr_t *lf_cus; /* custr_t wrapper for lf_buf */ + int lf_fd; /* file descriptor */ + size_t lf_size; /* Current size */ + boolean_t lf_write_err; /* Avoid spamming console via logsys */ + boolean_t lf_closing; /* Avoid rotation recursion */ +} logfile_t; + +/* Large enough to hold BUFSIZ bytes with some escaping */ +#define LS_BUFSZ (BUFSIZ * 2) + +/* Large enough to hold LS_BUF contents + bunyan mandatory properties */ +#define LS_OBUFSZ (LS_BUFSZ + MAXNAMELEN + 128) + +typedef struct logstream { + char ls_stream[MAXNAMELEN]; /* stdout, stderr, etc. */ + char ls_buf[LS_BUFSZ]; /* Not-yet written data, json */ + char ls_obuf[LS_OBUFSZ]; /* Buffer to form output json */ + custr_t *ls_cusbuf; /* custr_t wrapper to ls_buf */ + custr_t *ls_cusobuf; /* custr_t wrapper to ls_ofbuf */ + logstream_flags_t ls_flags; + logfile_t *ls_logfile; /* N streams per log file */ +} logstream_t; + +typedef struct jsonpair { + const char *jp_key; + const char *jp_val; +} jsonpair_t; + +boolean_t logging_poisoned = B_FALSE; + +/* + * MAX_LOG_STREAMS is a small number so we allocate in the simplest way. + */ +static logstream_t streams[MAX_LOG_STREAMS]; +static logfile_t logfiles[MAX_LOG_STREAMS]; + +static char host[MAXHOSTNAMELEN]; +static char pidstr[10]; + +static boolean_t logging_initialized = B_FALSE; +static uint64_t logging_rot_size; /* See ZLOG_MAXSZ */ +static uint64_t logging_rot_keep; /* See ZLOG_KEEP */ +static int logging_pending_sig = 0; /* Signal recvd while logging */ +static mutex_t logging_lock = ERRORCHECKMUTEX; /* The global logging lock */ + +static void logstream_flush_all(logfile_t *); +static void logstream_sighandler(int); +static void rotate_log(logfile_t *); +static size_t make_json(jsonpair_t *, size_t, custr_t *); +static void logfile_write(logfile_t *, custr_t *); + +/* + * If errors are encountered while logging_lock is held, we can't use zerror(). + */ +static void +logstream_err(boolean_t use_strerror, const char *fmt, ...) +{ + va_list alist; + char buf[MAXPATHLEN * 2]; + char *bp; + int saved_errno = errno; + + (void) snprintf(buf, sizeof (buf), "[zone %s] ", zone_name); + + bp = &buf[strlen(buf)]; + + va_start(alist, fmt); + (void) vsnprintf(bp, sizeof (buf) - (bp - buf), fmt, alist); + va_end(alist); + + if (use_strerror) { + bp = &buf[strlen(buf)]; + (void) snprintf(bp, sizeof (buf) - (bp - buf), ": %s", + strerror(saved_errno)); + } + syslog(LOG_ERR, "%s", buf); + + errno = saved_errno; +} + +static void +logstream_lock(void) +{ + VERIFY(logging_initialized); + VERIFY(!logging_poisoned); + + mutex_enter(&logging_lock); +} + +static void +logstream_unlock(void) +{ + int sig = logging_pending_sig; + + logging_pending_sig = 0; + mutex_exit(&logging_lock); + + /* + * If a signal arrived while this thread was holding the lock, call the + * handler. + */ + if (sig != 0) { + logstream_sighandler(sig); + } +} + +static void +logfile_write_event(logfile_t *lfp, const char *stream, const char *event) +{ + size_t len; + jsonpair_t pairs[] = { + { "stream", stream }, + { "msg", event } + }; + + len = make_json(pairs, ARRAY_SIZE(pairs), lfp->lf_cus); + if (len >= sizeof (lfp->lf_buf)) { + logstream_err(B_FALSE, "%s: buffer too small. Need %zu bytes, " + "have %zu bytes", __func__, len + 1, sizeof (lfp->lf_buf)); + return; + } + + logfile_write(lfp, lfp->lf_cus); +} + +static void +close_log(logfile_t *lfp, const char *why, boolean_t ign_err) +{ + int err; + + VERIFY(MUTEX_HELD(&logging_lock)); + + /* + * Something may have gone wrong during log rotation, leading to a + * zombie log. + */ + if (lfp->lf_fd == -1) { + return; + } + + lfp->lf_closing = B_TRUE; + + logstream_flush_all(lfp); + + logfile_write_event(lfp, "logfile", why); + + err = close(lfp->lf_fd); + if (!ign_err) + VERIFY0(err); + + lfp->lf_size = 0; + lfp->lf_fd = -1; +} + +static void +open_log(logfile_t *lfp, const char *why) +{ + struct stat64 sb; + + VERIFY(MUTEX_HELD(&logging_lock)); + VERIFY3S(lfp->lf_fd, ==, -1); + + lfp->lf_fd = open(lfp->lf_path, + O_WRONLY | O_APPEND | O_CREAT | O_CLOEXEC, 0600); + if (lfp->lf_fd == -1) { + logstream_err(B_TRUE, "Cannot open log file %s", + lfp->lf_path); + lfp->lf_write_err = B_TRUE; + return; + } + + VERIFY0(fstat64(lfp->lf_fd, &sb)); + lfp->lf_size = sb.st_size; + lfp->lf_write_err = B_FALSE; + lfp->lf_closing = B_FALSE; + + logfile_write_event(lfp, "logfile", why); +} + +static void +logstream_sighandler(int sig) +{ + int i; + + /* + * Protect against recursive mutex enters when a signal comes during + * logging. This will cause this function to be called again just after + * this thread drops the lock. + */ + if (MUTEX_HELD(&logging_lock)) { + logging_pending_sig = sig; + return; + } + + logstream_lock(); + if (logging_poisoned) { + logstream_unlock(); + return; + } + + for (i = 0; i < ARRAY_SIZE(logfiles); i++) { + /* Inactive logfile slot */ + if (logfiles[i].lf_name[0] == '\0') { + continue; + } + + switch (sig) { + case SIGHUP: + close_log(&logfiles[i], "close-rotate", B_FALSE); + open_log(&logfiles[i], "open-rotate"); + break; + case SIGUSR1: + rotate_log(&logfiles[i]); + break; + default: + logstream_err(B_FALSE, "unhandled signal %d", sig); + } + } + + logstream_unlock(); +} + +static void +get_attr_uint64(zlog_t *zlogp, zone_dochandle_t handle, const char *name, + uint64_t max, uint64_t *valp) +{ + struct zone_attrtab tab = { 0 }; + char *p; + uint64_t val; + + ASSERT(!MUTEX_HELD(&logging_lock)); + + (void) strlcpy(tab.zone_attr_name, name, sizeof (tab.zone_attr_name)); + if (zonecfg_lookup_attr(handle, &tab) != Z_OK) { + return; + } + + errno = 0; + val = strtol(tab.zone_attr_value, &p, 10); + if (errno != 0 && *p == '\0') { + zerror(zlogp, errno != 0, "Bad value '%s' for 'attr name=%s'", + tab.zone_attr_value, tab.zone_attr_name); + return; + } + if (val > max) { + zerror(zlogp, B_FALSE, "Value of attr '%s' is too large. " + "Reducing to %llu", name, max); + val = max; + } + + *valp = val; +} + +static void +logstream_atfork_prepare(void) +{ + logstream_lock(); +} + +static void +logstream_atfork_parent(void) +{ + logstream_unlock(); +} + +/* + * logstream_*() should never be called in a child process, so we make sure this + * code is never called there. + * + * zerror() in a child process is still safe: it knows to check for poisoning, + * and in such a case will redirect its output to stderr on the presumption it + * is a pipe to the parent. + */ +static void +logstream_atfork_child(void) +{ + logging_poisoned = B_TRUE; + logging_pending_sig = 0; + (void) snprintf(pidstr, sizeof (pidstr), "%d", getpid()); + logstream_unlock(); +} + +void +logstream_init(zlog_t *zlogp) +{ + zone_dochandle_t handle; + int i; + + VERIFY(!logging_initialized); + + VERIFY0(gethostname(host, sizeof (host))); + (void) snprintf(pidstr, sizeof (pidstr), "%d", getpid()); + + for (i = 0; i < ARRAY_SIZE(logfiles); i++) { + logfile_t *lfp = &logfiles[i]; + + lfp->lf_fd = -1; + if (custr_alloc_buf(&lfp->lf_cus, lfp->lf_buf, + sizeof (lfp->lf_buf)) != 0) { + (void) fprintf(stderr, "failed to allocate custr_t for " + "log file\n"); + abort(); + } + } + + for (i = 0; i < ARRAY_SIZE(streams); i++) { + logstream_t *lsp = &streams[i]; + + if (custr_alloc_buf(&lsp->ls_cusbuf, lsp->ls_buf, + sizeof (lsp->ls_buf)) != 0 || + custr_alloc_buf(&lsp->ls_cusobuf, lsp->ls_obuf, + sizeof (lsp->ls_obuf)) != 0) { + (void) fprintf(stderr, "failed to allocate custr_t for " + "log stream\n"); + abort(); + } + } + + VERIFY0(pthread_atfork(logstream_atfork_prepare, + logstream_atfork_parent, logstream_atfork_child)); + + logging_initialized = B_TRUE; + + /* Now it is safe to use zlogp */ + + if ((handle = zonecfg_init_handle()) == NULL || + zonecfg_get_handle(zone_name, handle) != Z_OK) { + zerror(zlogp, B_FALSE, "failed to open zone configuration " + "while initializing logging"); + } else { + get_attr_uint64(zlogp, handle, ZLOG_MAXSZ, UINT64_MAX, + &logging_rot_size); + if (logging_rot_size != 0 && + logging_rot_size < ZLOG_MAXSZ_MIN) { + zerror(zlogp, B_FALSE, "%s value %llu is too small. " + "Setting to %d", ZLOG_MAXSZ, logging_rot_size, + ZLOG_MAXSZ_MIN); + logging_rot_size = ZLOG_MAXSZ_MIN; + } + get_attr_uint64(zlogp, handle, ZLOG_KEEP, ZLOG_KEEP_MAX, + &logging_rot_keep); + } + + zonecfg_fini_handle(handle); + + /* + * This thread should receive SIGHUP so that it can close the log + * file and reopen it during log rotation. SIGUSR1 can be used to force + * a log rotation. + */ + (void) sigset(SIGHUP, logstream_sighandler); + (void) sigset(SIGUSR1, logstream_sighandler); +} + +/* + * Rotate a single log file. The global lock must be held while this is called. + */ +static void +rotate_log(logfile_t *lfp) +{ + time_t t; + struct tm gtm; + char path[MAXPATHLEN]; + int64_t i; + size_t len; + glob_t glb = { 0 }; + int err; + + VERIFY(MUTEX_HELD(&logging_lock)); + + if (lfp->lf_closing) { + return; + } + + if ((t = time(NULL)) == (time_t)-1 || gmtime_r(&t, >m) == NULL) { + logstream_err(B_TRUE, "failed to format time"); + return; + } + + (void) snprintf(path, sizeof (path), "%s.%04d%02d%02dT%02d%02d%02dZ", + lfp->lf_path, gtm.tm_year + 1900, gtm.tm_mon + 1, gtm.tm_mday, + gtm.tm_hour, gtm.tm_min, gtm.tm_sec); + + if (rename(lfp->lf_path, path) != 0) { + logstream_err(B_TRUE, "failed to rotate log file " + "'%s' to '%s'", lfp->lf_path, path); + } + + close_log(lfp, "close-rotate", B_FALSE); + open_log(lfp, "open-rotate"); + + if (logging_rot_keep == 0) { + return; + } + + /* + * Remove old logs. + */ + len = snprintf(path, sizeof (path), + /* <lf_path>.YYYYmmdd */ + "%s.[12][0-9][0-9][0-9][01][0-9][0-3][0-9]" + /* THHMMSSZ */ + "T[012][0-9][0-5][0-9][0-6][0-9]Z", lfp->lf_path); + if (len >= sizeof (path)) { + logstream_err(B_FALSE, "log rotation glob too long"); + return; + } + + if ((err = glob(path, GLOB_LIMIT, NULL, &glb)) != 0) { + if (err != GLOB_NOMATCH) { + logstream_err(B_TRUE, "glob terminated with error %d", + err); + } + globfree(&glb); + return; + } + + if (glb.gl_pathc <= logging_rot_keep) { + globfree(&glb); + return; + } + + for (i = glb.gl_pathc - logging_rot_keep - 1; i >= 0; i--) { + if (unlink(glb.gl_pathv[i]) != 0) { + logstream_err(B_TRUE, "log rotation could not remove " + "%s", glb.gl_pathv[i]); + } + } + globfree(&glb); +} + +/* + * Modify the input string with json escapes. Since the destination can thus + * be larger than the source, multiple calls may be required to fully convert + * sbuf to json. + * + * sbuf, slen Source buffer and the number of bytes in it to process + * dest Destination custr_t containing escaped JSON. + * scntp On return, *scntp stores number of scnt bytes consumed + * flushp If non-NULL, line-buffered mode is enabled. Processing + * will stop at the first newline or when dest is full and + * *flushp will be set to B_TRUE. + * + * This function makes no attempt to handle wide characters properly because + * the messages that come in may be using any character encoding. Since + * characters other than 7-bit ASCII are not directly readable in the log + * anyway, it is better to log the raw data and leave it to specialized log + * readers to interpret non-ASCII data. + */ +static void +escape_json(const char *sbuf, size_t slen, custr_t *dest, size_t *scntp, + boolean_t *flushp) +{ + char c; + const char *save_sbuf = sbuf; + const char *sbuf_end = sbuf + slen - 1; + char append_buf[7]; /* "\\u0000\0" */ + const char *append; + int len; + + if (slen == 0) { + *scntp = 0; + return; + } + + if (flushp != NULL) { + *flushp = B_FALSE; + } + + while (sbuf <= sbuf_end) { + c = sbuf[0]; + + switch (c) { + case '\\': + append = "\\\\"; + break; + + case '"': + append = "\\\""; + break; + + case '\b': + append = "\\b"; + break; + + case '\f': + append = "\\f"; + break; + + case '\n': + append = "\\n"; + if (flushp != NULL) { + *flushp = B_TRUE; + } + break; + + case '\r': + append = "\\r"; + break; + + case '\t': + append = "\\t"; + break; + + default: + if (c >= 0x20 && c < 0x7f) { + append_buf[0] = c; + append_buf[1] = '\0'; + } else { + len = snprintf(append_buf, sizeof (append_buf), + "\\u%04x", (int)(0xff & c)); + VERIFY3S(len, <, sizeof (append_buf)); + } + append = append_buf; + break; + } + + if (custr_append(dest, append) != 0) { + VERIFY3S(errno, ==, EOVERFLOW); + if (flushp != NULL) { + *flushp = B_TRUE; + } + break; + } + + sbuf++; + + if (flushp != NULL && *flushp) { + break; + } + } + + *scntp = sbuf - save_sbuf; + + VERIFY3U(*scntp, <=, slen); +} + +/* + * Like write(2), but to a logfile_t and with retries on short writes. + */ +static void +logfile_write(logfile_t *lfp, custr_t *cus) +{ + const char *buf = custr_cstr(cus); + size_t buflen = custr_len(cus); + ssize_t wlen; + size_t wanted = buflen; + + while (buflen > 0) { + wlen = write(lfp->lf_fd, buf, buflen); + if (wlen == -1) { + if (lfp->lf_write_err) { + lfp->lf_write_err = B_TRUE; + logstream_err(B_TRUE, "log file fd %d '%s': " + "failed to write %llu of %llu bytes", + lfp->lf_fd, lfp->lf_path, buflen, wanted); + } + return; + } + buf += wlen; + buflen -= wlen; + lfp->lf_size += wlen; + + lfp->lf_write_err = B_FALSE; + } + + if (logging_rot_size != 0 && lfp->lf_size > logging_rot_size) { + rotate_log(lfp); + } +} + +static void +add_bunyan_preamble(custr_t *cus) +{ + struct tm gtm; + struct timeval tv; + /* Large enough for YYYY-MM-DDTHH:MM:SS.000000000Z + NUL */ + char timestr[32] = { 0 }; + size_t len; + + if (gettimeofday(&tv, NULL) != 0 || + gmtime_r(&tv.tv_sec, >m) == NULL) { + logstream_err(B_TRUE, "failed to get time of day"); + abort(); + } + + len = strftime(timestr, sizeof (timestr) - 1, "%FT%T", >m); + VERIFY3U(len, >, 0); + VERIFY3U(len, <, sizeof (timestr) - 1); + + VERIFY0(custr_append_printf(cus, "\"time\": \"%s.%09ldZ\", ", + timestr, tv.tv_usec * 1000)); + VERIFY0(custr_append_printf(cus, "\"v\": %d, ", BUNYAN_VERSION)); + VERIFY0(custr_append_printf(cus, "\"hostname\": \"%s\", ", host)); + VERIFY0(custr_append(cus, "\"name\": \"zoneadmd\",")); + VERIFY0(custr_append_printf(cus, "\"pid\": %s, ", pidstr)); + VERIFY0(custr_append_printf(cus, "\"level\": %d", BUNYAN_LOG_LEVEL)); +} + +/* + * Convert the json pairs into a json object. The properties required for + * bunyan-formatted json objects are added to every object. + * Returns the number of bytes that would have been written to + * buf if bufsz had buf been sufficiently large (excluding the terminating null + * byte). Like snprintf(). + */ +static size_t +make_json(jsonpair_t *pairs, size_t npairs, custr_t *cus) +{ + int i; + const char *key, *val; + const char *start = ", "; + + VERIFY3S(npairs, >, 0); + + custr_reset(cus); + + VERIFY0(custr_append(cus, "{ ")); + + add_bunyan_preamble(cus); + + for (i = 0; i < npairs; i++) { + size_t len; + + key = pairs[i].jp_key; + val = pairs[i].jp_val; + + /* The total number of bytes we're adding to cus */ + len = 3 + strlen(key) + 3 + strlen(val) + 1; + if (custr_append_printf(cus, "%s\"%s\":\"%s\"", + start, key, val) != 0) { + VERIFY3S(errno, ==, EOVERFLOW); + return (custr_len(cus) + len); + } + } + + if (custr_append(cus, " }\n") != 0) { + return (custr_len(cus) + 3); + } + + return (custr_len(cus)); +} + +static void +logstream_write_json(logstream_t *lsp) +{ + size_t len; + jsonpair_t pairs[] = { + { "msg", lsp->ls_buf }, + { "stream", lsp->ls_stream }, + }; + + if (custr_len(lsp->ls_cusbuf) == 0) { + return; + } + + len = make_json(pairs, ARRAY_SIZE(pairs), lsp->ls_cusobuf); + + custr_reset(lsp->ls_cusbuf); + if (len >= sizeof (lsp->ls_obuf)) { + logstream_err(B_FALSE, "%s: buffer too small. Need %llu bytes, " + "have %llu bytes", __func__, len + 1, + sizeof (lsp->ls_obuf)); + return; + } + + logfile_write(lsp->ls_logfile, lsp->ls_cusobuf); +} + +/* + * We output to the log file as json. + * ex. for string 'msg\n' on the zone's stdout: + * {"log":"msg\n","stream":"stdout","time":"2014-10-24T20:12:11.101973117Z"} + * + * We use ns in the last field of the timestamp for compatibility. + * + * We keep track of the size of the log file and rotate it when we exceed + * the log size limit (if one is set). + */ +void +logstream_write(int ls, char *buf, int len) +{ + logstream_t *lsp; + size_t scnt; + boolean_t newline; + boolean_t buffered; + + if (ls == -1 || len == 0) { + return; + } + VERIFY3S(ls, >=, 0); + VERIFY3S(ls, <, ARRAY_SIZE(streams)); + + logstream_lock(); + + lsp = &streams[ls]; + if (lsp->ls_stream[0] == '\0' || lsp->ls_logfile == NULL) { + logstream_unlock(); + return; + } + + buffered = !!(lsp->ls_flags & LS_LINE_BUFFERED); + + do { + escape_json(buf, len, lsp->ls_cusbuf, &scnt, + buffered ? &newline : NULL); + + buf += scnt; + len -= scnt; + + if (!buffered || newline) { + logstream_write_json(lsp); + } + } while (len > 0 && (!buffered || newline)); + + logstream_unlock(); +} + +static void +logstream_flush(int ls) +{ + logstream_t *lsp; + + VERIFY(MUTEX_HELD(&logging_lock)); + + lsp = &streams[ls]; + if (lsp->ls_stream[0] == '\0' || lsp->ls_logfile == NULL) { + return; + } + logstream_write_json(lsp); +} + +static void +logstream_flush_all(logfile_t *lfp) +{ + int i; + + VERIFY(MUTEX_HELD(&logging_lock)); + + for (i = 0; i < ARRAY_SIZE(streams); i++) { + if (streams[i].ls_logfile == lfp) { + logstream_flush(i); + } + } +} + +int +logstream_open(const char *logname, const char *stream, logstream_flags_t flags) +{ + int ls = -1; + int i; + logstream_t *lsp; + logfile_t *lfp = NULL; + + VERIFY3U(strlen(logname), <, sizeof (lfp->lf_name)); + VERIFY3U(strlen(stream), <, sizeof (lsp->ls_stream)); + + logstream_lock(); + + /* + * Find an empty logstream_t and verify that the stream is not already + * open. + */ + for (i = 0; i < ARRAY_SIZE(streams); i++) { + if (ls == -1 && streams[i].ls_stream[0] == '\0') { + VERIFY3P(streams[i].ls_logfile, ==, NULL); + ls = i; + continue; + } + if (strcmp(stream, streams[i].ls_stream) == 0) { + logstream_unlock(); + logstream_err(B_FALSE, "log stream %s already open", + stream); + return (-1); + } + } + VERIFY3S(ls, !=, -1); + + /* Find an existing or available logfile_t */ + for (i = 0; i < ARRAY_SIZE(logfiles); i++) { + if (lfp == NULL && logfiles[i].lf_name[0] == '\0') { + lfp = &logfiles[i]; + } + if (strcmp(logname, logfiles[i].lf_name) == 0) { + lfp = &logfiles[i]; + break; + } + } + if (lfp->lf_name[0] == '\0') { + (void) strlcpy(lfp->lf_name, logname, sizeof (lfp->lf_name)); + (void) snprintf(lfp->lf_path, sizeof (lfp->lf_path), "%s/logs", + zonepath); + (void) mkdir(lfp->lf_path, 0700); + + (void) snprintf(lfp->lf_path, sizeof (lfp->lf_path), + "%s/logs/%s", zonepath, logname); + + open_log(lfp, "open"); + if (lfp->lf_fd == -1) { + logstream_unlock(); + return (-1); + } + } + + lsp = &streams[ls]; + (void) strlcpy(lsp->ls_stream, stream, sizeof (lsp->ls_stream)); + + lsp->ls_flags = flags; + lsp->ls_logfile = lfp; + + logstream_unlock(); + + return (ls); +} + +static void +logstream_reset(logstream_t *lsp) +{ + custr_t *buf = lsp->ls_cusbuf; + custr_t *obuf = lsp->ls_cusobuf; + + (void) memset(lsp, 0, sizeof (*lsp)); + lsp->ls_cusbuf = buf; + lsp->ls_cusobuf = obuf; + + custr_reset(buf); + custr_reset(obuf); +} + +static void +logfile_reset(logfile_t *lfp) +{ + custr_t *buf = lfp->lf_cus; + + (void) memset(lfp, 0, sizeof (*lfp)); + lfp->lf_cus = buf; + lfp->lf_fd = -1; + + custr_reset(buf); +} + +void +logstream_close(int ls, boolean_t abrupt) +{ + logstream_t *lsp; + logfile_t *lfp; + int i; + + if (ls == -1) { + return; + } + VERIFY3S(ls, >=, 0); + VERIFY3S(ls, <, ARRAY_SIZE(streams)); + + logstream_lock(); + logstream_flush(ls); + + lsp = &streams[ls]; + lfp = lsp->ls_logfile; + + VERIFY(lsp->ls_stream[0] != '\0'); + VERIFY3P(lfp, !=, NULL); + + logstream_reset(lsp); + + for (i = 0; i < ARRAY_SIZE(streams); i++) { + if (streams[i].ls_logfile == lfp) { + logstream_unlock(); + return; + } + } + + /* No more streams using this log file so return to initial state */ + + close_log(lfp, "close", abrupt); + + logfile_reset(lfp); + + logstream_unlock(); +} |