mirror of
https://github.com/samba-team/samba.git
synced 2024-12-23 17:34:34 +03:00
1596a3e84b
This allows ctdb_set_child_logging() to work. BUG: https://bugzilla.samba.org/show_bug.cgi?id=15090 Signed-off-by: Martin Schwenke <martin@meltin.net> Reviewed-by: Volker Lendecke <vl@samba.org>
746 lines
16 KiB
C
746 lines
16 KiB
C
/*
|
|
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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#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; i<ARRAY_SIZE(log_string_map); i++) {
|
|
if (strncasecmp(log_string_map[i].log_string,
|
|
log_string, strlen(log_string)) == 0) {
|
|
*log_level = log_string_map[i].log_level;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
const char *debug_level_to_string(int log_level)
|
|
{
|
|
size_t i;
|
|
|
|
for (i=0; i < ARRAY_SIZE(log_string_map); i++) {
|
|
if (log_string_map[i].log_level == log_level) {
|
|
return log_string_map[i].log_string;
|
|
}
|
|
}
|
|
return "UNKNOWN";
|
|
}
|
|
|
|
int debug_level_from_string(const char *log_string)
|
|
{
|
|
bool found;
|
|
int log_level;
|
|
|
|
found = debug_level_parse(log_string, &log_level);
|
|
if (found) {
|
|
return log_level;
|
|
}
|
|
|
|
/* Default debug level */
|
|
return DEBUG_ERR;
|
|
}
|
|
|
|
/*
|
|
* file logging backend
|
|
*/
|
|
|
|
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 debug_settings settings = {
|
|
.debug_syslog_format = true,
|
|
.debug_hires_timestamp = true,
|
|
.debug_no_stderr_redirect = true,
|
|
};
|
|
const char *t = NULL;
|
|
|
|
if (option == NULL || strcmp(option, "-") == 0) {
|
|
/*
|
|
* Logging to stderr is the default and has already
|
|
* been done in logging init
|
|
*/
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Support logging of fake hostname in local daemons. This
|
|
* hostname is basename(getenv(CTDB_BASE)).
|
|
*/
|
|
t = getenv("CTDB_TEST_MODE");
|
|
if (t != NULL) {
|
|
t = getenv("CTDB_BASE");
|
|
if (t != NULL) {
|
|
const char *p = strrchr(t, '/');
|
|
if (p != NULL) {
|
|
p++;
|
|
if (p[0] == '\0') {
|
|
p = "unknown";
|
|
}
|
|
} else {
|
|
p = t;
|
|
}
|
|
|
|
debug_set_hostname(p);
|
|
}
|
|
}
|
|
|
|
debug_set_settings(&settings, "file", 0, false);
|
|
debug_set_logfile(option);
|
|
setup_logging(app_name, DEBUG_FILE);
|
|
|
|
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; i<ARRAY_SIZE(log_backend); i++) {
|
|
if (strcmp(log_backend[i].name, name) == 0) {
|
|
b = &log_backend[i];
|
|
}
|
|
}
|
|
|
|
if (b == NULL) {
|
|
talloc_free(t);
|
|
return ENOENT;
|
|
}
|
|
|
|
*backend = b;
|
|
if (option != NULL) {
|
|
*backend_option = talloc_strdup(mem_ctx, option);
|
|
if (*backend_option == NULL) {
|
|
talloc_free(t);
|
|
return ENOMEM;
|
|
}
|
|
} else {
|
|
*backend_option = NULL;
|
|
}
|
|
|
|
talloc_free(t);
|
|
return 0;
|
|
}
|
|
|
|
bool logging_validate(const char *logging)
|
|
{
|
|
TALLOC_CTX *tmp_ctx;
|
|
struct log_backend *backend;
|
|
char *option;
|
|
int ret;
|
|
bool status;
|
|
|
|
tmp_ctx = talloc_new(NULL);
|
|
if (tmp_ctx == NULL) {
|
|
return false;
|
|
}
|
|
|
|
ret = log_backend_parse(tmp_ctx, logging, &backend, &option);
|
|
if (ret != 0) {
|
|
talloc_free(tmp_ctx);
|
|
return false;
|
|
}
|
|
|
|
status = backend->validate(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_DEFAULT_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;
|
|
}
|
|
|
|
bool logging_reopen_logs(void)
|
|
{
|
|
bool status;
|
|
|
|
status = reopen_logs_internal();
|
|
|
|
return status;
|
|
}
|
|
|
|
struct logging_reopen_logs_data {
|
|
void (*hook)(void *private_data);
|
|
void *private_data;
|
|
};
|
|
|
|
static void logging_sig_hup_handler(struct tevent_context *ev,
|
|
struct tevent_signal *se,
|
|
int signum,
|
|
int count,
|
|
void *dont_care,
|
|
void *private_data)
|
|
{
|
|
bool status;
|
|
|
|
if (private_data != NULL) {
|
|
struct logging_reopen_logs_data *data = talloc_get_type_abort(
|
|
private_data, struct logging_reopen_logs_data);
|
|
|
|
if (data->hook != NULL) {
|
|
data->hook(data->private_data);
|
|
}
|
|
}
|
|
|
|
status = logging_reopen_logs();
|
|
if (!status) {
|
|
D_WARNING("Failed to reopen logs\n");
|
|
return;
|
|
}
|
|
|
|
D_NOTICE("Reopened logs\n");
|
|
|
|
}
|
|
|
|
bool logging_setup_sighup_handler(struct tevent_context *ev,
|
|
TALLOC_CTX *talloc_ctx,
|
|
void (*hook)(void *private_data),
|
|
void *private_data)
|
|
{
|
|
struct logging_reopen_logs_data *data = NULL;
|
|
struct tevent_signal *se;
|
|
|
|
if (hook != NULL) {
|
|
data = talloc(talloc_ctx, struct logging_reopen_logs_data);
|
|
if (data == NULL) {
|
|
return false;
|
|
}
|
|
|
|
data->hook = hook;
|
|
data->private_data = private_data;
|
|
}
|
|
|
|
|
|
se = tevent_add_signal(ev,
|
|
talloc_ctx,
|
|
SIGHUP,
|
|
0,
|
|
logging_sig_hup_handler,
|
|
data);
|
|
if (se == NULL) {
|
|
talloc_free(data);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|