/*
Logging utilities
Copyright (C) Andrew Tridgell 2008
Copyright (C) Martin Schwenke 2014
Copyright (C) Amitay Isaacs 2015
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, see .
*/
#include "replace.h"
#include "system/network.h"
#include "system/locale.h"
#include "system/time.h"
#include "system/filesys.h"
#include "system/syslog.h"
#include "system/dir.h"
#include "lib/util/time_basic.h"
#include "lib/util/sys_rw.h"
#include "lib/util/debug.h"
#include "lib/util/blocking.h"
#include "lib/util/samba_util.h" /* get_myname() */
#include "common/logging.h"
struct {
int log_level;
const char *log_string;
} log_string_map[] = {
{ DEBUG_ERR, "ERROR" },
{ DEBUG_WARNING, "WARNING" },
{ 2, "WARNING" },
{ DEBUG_NOTICE, "NOTICE" },
{ 4, "NOTICE" },
{ DEBUG_INFO, "INFO" },
{ 6, "INFO" },
{ 7, "INFO" },
{ 8, "INFO" },
{ 9, "INFO" },
{ DEBUG_DEBUG, "DEBUG" },
};
bool debug_level_parse(const char *log_string, int *log_level)
{
size_t i;
if (log_string == NULL) {
return false;
}
if (isdigit(log_string[0])) {
int level = atoi(log_string);
if (level >= 0 && (size_t)level < ARRAY_SIZE(log_string_map)) {
*log_level = level;
return true;
}
return false;
}
for (i=0; ifd == STDERR_FILENO) {
ret = snprintf(state->buffer, sizeof(state->buffer),
"%s[%u]: %s\n",
state->app_name, (unsigned)getpid(), msg);
} else {
GetTimeOfDay(&tv);
timeval_str_buf(&tv, false, true, &tvbuf);
ret = snprintf(state->buffer, sizeof(state->buffer),
"%s %s[%u]: %s\n", tvbuf.buf,
state->app_name, (unsigned)getpid(), msg);
}
if (ret < 0) {
return;
}
state->buffer[sizeof(state->buffer)-1] = '\0';
sys_write_v(state->fd, state->buffer, strlen(state->buffer));
}
static int file_log_state_destructor(struct file_log_state *state)
{
if (state->fd != -1 && state->fd != STDERR_FILENO) {
close(state->fd);
state->fd = -1;
}
return 0;
}
static bool file_log_validate(const char *option)
{
char *t, *dir;
struct stat st;
int ret;
if (option == NULL || strcmp(option, "-") == 0) {
return true;
}
t = strdup(option);
if (t == NULL) {
return false;
}
dir = dirname(t);
ret = stat(dir, &st);
free(t);
if (ret != 0) {
return false;
}
if (! S_ISDIR(st.st_mode)) {
return false;
}
return true;
}
static int file_log_setup(TALLOC_CTX *mem_ctx, const char *option,
const char *app_name)
{
struct file_log_state *state;
state = talloc_zero(mem_ctx, struct file_log_state);
if (state == NULL) {
return ENOMEM;
}
state->app_name = app_name;
if (option == NULL || strcmp(option, "-") == 0) {
int ret;
state->fd = STDERR_FILENO;
ret = dup2(STDERR_FILENO, STDOUT_FILENO);
if (ret == -1) {
int save_errno = errno;
talloc_free(state);
return save_errno;
}
} else {
state->fd = open(option, O_WRONLY|O_APPEND|O_CREAT, 0644);
if (state->fd == -1) {
int save_errno = errno;
talloc_free(state);
return save_errno;
}
if (! set_close_on_exec(state->fd)) {
int save_errno = errno;
talloc_free(state);
return save_errno;
}
}
talloc_set_destructor(state, file_log_state_destructor);
debug_set_callback(state, file_log);
return 0;
}
/*
* syslog logging backend
*/
/* Copied from lib/util/debug.c */
static int debug_level_to_priority(int level)
{
/*
* map debug levels to syslog() priorities
*/
static const int priority_map[] = {
LOG_ERR, /* 0 */
LOG_WARNING, /* 1 */
LOG_NOTICE, /* 2 */
LOG_NOTICE, /* 3 */
LOG_NOTICE, /* 4 */
LOG_NOTICE, /* 5 */
LOG_INFO, /* 6 */
LOG_INFO, /* 7 */
LOG_INFO, /* 8 */
LOG_INFO, /* 9 */
};
int priority;
if ((size_t)level >= ARRAY_SIZE(priority_map) || level < 0) {
priority = LOG_DEBUG;
} else {
priority = priority_map[level];
}
return priority;
}
struct syslog_log_state {
int fd;
const char *app_name;
const char *hostname;
int (*format)(int dbglevel, struct syslog_log_state *state,
const char *str, char *buf, int bsize);
/* RFC3164 says: The total length of the packet MUST be 1024
bytes or less. */
char buffer[1024];
unsigned int dropped_count;
};
/* Format messages as per RFC3164
*
* It appears that some syslog daemon implementations do not allow a
* hostname when messages are sent via a Unix domain socket, so omit
* it. Similarly, syslogd on FreeBSD does not understand the hostname
* part of the header, even when logging via UDP. Note that most
* implementations will log messages against "localhost" when logging
* via UDP. A timestamp could be sent but rsyslogd on Linux limits
* the timestamp logged to the precision that was received on
* /dev/log. It seems sane to send degenerate RFC3164 messages
* without a header at all, so that the daemon will generate high
* resolution timestamps if configured.
*/
static int format_rfc3164(int dbglevel, struct syslog_log_state *state,
const char *str, char *buf, int bsize)
{
int pri;
int len;
pri = LOG_DAEMON | debug_level_to_priority(dbglevel);
len = snprintf(buf, bsize, "<%d>%s[%u]: %s",
pri, state->app_name, getpid(), str);
buf[bsize-1] = '\0';
len = MIN(len, bsize - 1);
return len;
}
/* Format messages as per RFC5424
*
* <165>1 2003-08-24T05:14:15.000003-07:00 192.0.2.1
* myproc 8710 - - %% It's time to make the do-nuts.
*/
static int format_rfc5424(int dbglevel, struct syslog_log_state *state,
const char *str, char *buf, int bsize)
{
int pri;
struct timeval tv;
struct timeval_buf tvbuf;
int len, s;
/* Header */
pri = LOG_DAEMON | debug_level_to_priority(dbglevel);
GetTimeOfDay(&tv);
len = snprintf(buf, bsize,
"<%d>1 %s %s %s %u - - ",
pri, timeval_str_buf(&tv, true, true, &tvbuf),
state->hostname, state->app_name, getpid());
/* A truncated header is not useful... */
if (len >= bsize) {
return -1;
}
/* Message */
s = snprintf(&buf[len], bsize - len, "%s", str);
buf[bsize-1] = '\0';
len = MIN(len + s, bsize - 1);
return len;
}
static void syslog_log(void *private_data, int level, const char *msg)
{
syslog(debug_level_to_priority(level), "%s", msg);
}
static int syslog_log_sock_maybe(struct syslog_log_state *state,
int level, const char *msg)
{
int n;
ssize_t ret;
n = state->format(level, state, msg, state->buffer,
sizeof(state->buffer));
if (n == -1) {
return E2BIG;
}
do {
ret = write(state->fd, state->buffer, n);
} while (ret == -1 && errno == EINTR);
if (ret == -1) {
return errno;
}
return 0;
}
static void syslog_log_sock(void *private_data, int level, const char *msg)
{
struct syslog_log_state *state = talloc_get_type_abort(
private_data, struct syslog_log_state);
int ret;
if (state->dropped_count > 0) {
char t[64] = { 0 };
snprintf(t, sizeof(t),
"[Dropped %u log messages]\n",
state->dropped_count);
t[sizeof(t)-1] = '\0';
ret = syslog_log_sock_maybe(state, level, t);
if (ret == EAGAIN || ret == EWOULDBLOCK) {
state->dropped_count++;
/*
* If above failed then actually drop the
* message that would be logged below, since
* it would have been dropped anyway and it is
* also likely to fail. Falling through and
* attempting to log the message also means
* that the dropped message count will be
* logged out of order.
*/
return;
}
if (ret != 0) {
/* Silent failure on any other error */
return;
}
state->dropped_count = 0;
}
ret = syslog_log_sock_maybe(state, level, msg);
if (ret == EAGAIN || ret == EWOULDBLOCK) {
state->dropped_count++;
}
}
static int syslog_log_setup_syslog(TALLOC_CTX *mem_ctx, const char *app_name)
{
openlog(app_name, LOG_PID, LOG_DAEMON);
debug_set_callback(NULL, syslog_log);
return 0;
}
static int syslog_log_state_destructor(struct syslog_log_state *state)
{
if (state->fd != -1) {
close(state->fd);
state->fd = -1;
}
return 0;
}
static int syslog_log_setup_common(TALLOC_CTX *mem_ctx, const char *app_name,
struct syslog_log_state **result)
{
struct syslog_log_state *state;
state = talloc_zero(mem_ctx, struct syslog_log_state);
if (state == NULL) {
return ENOMEM;
}
state->fd = -1;
state->app_name = app_name;
talloc_set_destructor(state, syslog_log_state_destructor);
*result = state;
return 0;
}
#ifdef _PATH_LOG
static int syslog_log_setup_nonblocking(TALLOC_CTX *mem_ctx,
const char *app_name)
{
struct syslog_log_state *state = NULL;
struct sockaddr_un dest;
int ret;
ret = syslog_log_setup_common(mem_ctx, app_name, &state);
if (ret != 0) {
return ret;
}
state->fd = socket(AF_UNIX, SOCK_DGRAM, 0);
if (state->fd == -1) {
int save_errno = errno;
talloc_free(state);
return save_errno;
}
dest.sun_family = AF_UNIX;
strncpy(dest.sun_path, _PATH_LOG, sizeof(dest.sun_path)-1);
ret = connect(state->fd,
(struct sockaddr *)&dest, sizeof(dest));
if (ret == -1) {
int save_errno = errno;
talloc_free(state);
return save_errno;
}
ret = set_blocking(state->fd, false);
if (ret != 0) {
int save_errno = errno;
talloc_free(state);
return save_errno;
}
if (! set_close_on_exec(state->fd)) {
int save_errno = errno;
talloc_free(state);
return save_errno;
}
state->hostname = NULL; /* Make this explicit */
state->format = format_rfc3164;
debug_set_callback(state, syslog_log_sock);
return 0;
}
#endif /* _PATH_LOG */
static int syslog_log_setup_udp(TALLOC_CTX *mem_ctx, const char *app_name,
bool rfc5424)
{
struct syslog_log_state *state = NULL;
struct sockaddr_in dest;
int ret;
ret = syslog_log_setup_common(mem_ctx, app_name, &state);
if (ret != 0) {
return ret;
}
state->fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (state->fd == -1) {
int save_errno = errno;
talloc_free(state);
return save_errno;
}
dest.sin_family = AF_INET;
dest.sin_port = htons(514);
dest.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
ret = connect(state->fd,
(struct sockaddr *)&dest, sizeof(dest));
if (ret == -1) {
int save_errno = errno;
talloc_free(state);
return save_errno;
}
if (! set_close_on_exec(state->fd)) {
int save_errno = errno;
talloc_free(state);
return save_errno;
}
state->hostname = get_myname(state);
if (state->hostname == NULL) {
/* Use a fallback instead of failing initialisation */
state->hostname = "localhost";
}
if (rfc5424) {
state->format = format_rfc5424;
} else {
state->format = format_rfc3164;
}
debug_set_callback(state, syslog_log_sock);
return 0;
}
static bool syslog_log_validate(const char *option)
{
if (option == NULL) {
return true;
#ifdef _PATH_LOG
} else if (strcmp(option, "nonblocking") == 0) {
return true;
#endif
} else if (strcmp(option, "udp") == 0) {
return true;
} else if (strcmp(option, "udp-rfc5424") == 0) {
return true;
}
return false;
}
static int syslog_log_setup(TALLOC_CTX *mem_ctx, const char *option,
const char *app_name)
{
if (option == NULL) {
return syslog_log_setup_syslog(mem_ctx, app_name);
#ifdef _PATH_LOG
} else if (strcmp(option, "nonblocking") == 0) {
return syslog_log_setup_nonblocking(mem_ctx, app_name);
#endif
} else if (strcmp(option, "udp") == 0) {
return syslog_log_setup_udp(mem_ctx, app_name, false);
} else if (strcmp(option, "udp-rfc5424") == 0) {
return syslog_log_setup_udp(mem_ctx, app_name, true);
}
return EINVAL;
}
struct log_backend {
const char *name;
bool (*validate)(const char *option);
int (*setup)(TALLOC_CTX *mem_ctx,
const char *option,
const char *app_name);
};
static struct log_backend log_backend[] = {
{
.name = "file",
.validate = file_log_validate,
.setup = file_log_setup,
},
{
.name = "syslog",
.validate = syslog_log_validate,
.setup = syslog_log_setup,
},
};
static int log_backend_parse(TALLOC_CTX *mem_ctx,
const char *logging,
struct log_backend **backend,
char **backend_option)
{
struct log_backend *b = NULL;
char *t, *name, *option;
size_t i;
t = talloc_strdup(mem_ctx, logging);
if (t == NULL) {
return ENOMEM;
}
name = strtok(t, ":");
if (name == NULL) {
talloc_free(t);
return EINVAL;
}
option = strtok(NULL, ":");
for (i=0; ivalidate(option);
talloc_free(tmp_ctx);
return status;
}
/* Initialise logging */
int logging_init(TALLOC_CTX *mem_ctx, const char *logging,
const char *debug_level, const char *app_name)
{
struct log_backend *backend = NULL;
char *option = NULL;
int level;
int ret;
setup_logging(app_name, DEBUG_STDERR);
if (debug_level == NULL) {
debug_level = getenv("CTDB_DEBUGLEVEL");
}
if (! debug_level_parse(debug_level, &level)) {
return EINVAL;
}
debuglevel_set(level);
if (logging == NULL) {
logging = getenv("CTDB_LOGGING");
}
if (logging == NULL || logging[0] == '\0') {
return EINVAL;
}
ret = log_backend_parse(mem_ctx, logging, &backend, &option);
if (ret != 0) {
if (ret == ENOENT) {
fprintf(stderr, "Invalid logging option \'%s\'\n",
logging);
}
talloc_free(option);
return ret;
}
ret = backend->setup(mem_ctx, option, app_name);
talloc_free(option);
return ret;
}