/* GNU Mailutils -- a suite of utilities for electronic mail
Copyright (C) 2010, 2011 Free Software Foundation, Inc.
This library is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
This library 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with GNU Mailutils. If not, see . */
#ifdef HAVE_CONFIG_H
# include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
char *_mu_severity_str[] = {
N_("debug"),
N_("info"),
N_("notice"),
N_("warning"),
N_("error"),
N_("crit"),
N_("alert"),
N_("emerg"),
};
int _mu_severity_num = MU_ARRAY_SIZE (_mu_severity_str);
int
mu_severity_from_string (const char *str, unsigned *pn)
{
int i;
for (i = 0; i < _mu_severity_num; i++)
{
if (mu_c_strcasecmp (_mu_severity_str[i], str) == 0)
{
*pn = i;
return 0;
}
}
return MU_ERR_NOENT;
}
int
mu_severity_to_string (unsigned n, const char **pstr)
{
if (n >= _mu_severity_num)
return MU_ERR_NOENT;
*pstr = _mu_severity_str[n];
return 0;
}
static int
_locus_set_file (struct mu_locus *loc, const char *file, size_t len)
{
free (loc->mu_file);
if (file)
{
loc->mu_file = malloc (len + 1);
if (!loc->mu_file)
return ENOMEM;
memcpy (loc->mu_file, file, len);
loc->mu_file[len] = 0;
}
else
loc->mu_file = NULL;
return 0;
}
#define _locus_set_line(loc, line) ((loc)->mu_line = line)
#define _locus_set_col(loc, col) ((loc)->mu_col = col)
static int
_log_write (struct _mu_stream *str, const char *buf, size_t size,
size_t *pnwrite)
{
struct _mu_log_stream *sp = (struct _mu_log_stream *)str;
unsigned severity = sp->severity;
int logmode = sp->logmode;
struct mu_locus loc = sp->locus;
const char *fname = NULL;
unsigned flen = 0;
int save_locus = 0;
int rc;
int escape_error = 0;
#define NEXT do { buf++; size--; if (size == 0) return EINVAL; } while (0)
#define READNUM(n) do { \
unsigned __x = 0; \
if (*buf != '<') \
return EINVAL; \
NEXT; \
while (*buf != '>') \
{ \
if (!mu_isdigit (*buf)) \
return EINVAL; \
__x = __x * 10 + (*buf - '0'); \
NEXT; \
} \
NEXT; \
n = __x; \
} while (0)
/* Tell them we've consumed everything */
*pnwrite = size;
/* Process escapes */
while (!escape_error && *buf == '\033')
{
int code;
unsigned n;
NEXT;
code = *buf;
NEXT;
switch (code)
{
case 'S':
/* Save locus */
save_locus = 1;
break;
case 's':
/* Severity code in decimal (single digit) */
if (*buf == '<')
READNUM (severity);
else if (mu_isdigit (*buf))
{
severity = *buf - '0';
NEXT;
}
else
return EINVAL;
break;
case 'O':
/* Flags on. Followed by a bitmask of MU_LOGMODE_*, in decimal */
READNUM (n);
logmode |= n;
break;
case 'X':
/* Flags off. Followed by a bitmask of MU_LOGMODE_*, in decimal */
READNUM (n);
logmode &= ~n;
break;
case 'l':
/* Input line (decimal) */
READNUM (n);
_locus_set_line (&loc, n);
break;
case 'c':
/* Column in input line (decimal) */
READNUM (n);
_locus_set_col (&loc, n);
break;
case 'f':
/* File name. Format: S */
READNUM (flen);
fname = buf;
buf += flen;
size -= flen;
break;
default:
buf -= 2;
size += 2;
escape_error = 1;
}
}
if (severity >= _mu_severity_num)
severity = MU_LOG_EMERG;
if (fname)
{
loc.mu_file = NULL;
_locus_set_file (&loc, fname, flen);
}
if (save_locus)
{
_locus_set_file (&sp->locus, loc.mu_file, strlen (loc.mu_file));
_locus_set_line (&sp->locus, loc.mu_line);
_locus_set_col (&sp->locus, loc.mu_col);
}
if (severity < sp->threshold)
{
if (fname)
free (loc.mu_file);
return 0;
}
mu_stream_ioctl (sp->transport, MU_IOCTL_LOGSTREAM,
MU_IOCTL_LOGSTREAM_SET_SEVERITY, &severity);
if (logmode & MU_LOGMODE_LOCUS)
{
if (loc.mu_file)
{
mu_stream_write (sp->transport, loc.mu_file,
strlen (loc.mu_file), NULL);
mu_stream_write (sp->transport, ":", 1, NULL);
if (loc.mu_line)
mu_stream_printf (sp->transport, "%u", loc.mu_line);
mu_stream_write (sp->transport, ":", 1, NULL);
if (loc.mu_col)
mu_stream_printf (sp->transport, "%u:", loc.mu_col);
mu_stream_write (sp->transport, " ", 1, NULL);
}
}
if (fname)
free (loc.mu_file);
if ((logmode & MU_LOGMODE_SEVERITY) &&
!(sp->sevmask & MU_DEBUG_LEVEL_MASK(severity)))
{
char *s = gettext (_mu_severity_str[severity]);
rc = mu_stream_write (sp->transport, s, strlen (s), NULL);
if (rc)
return rc;
mu_stream_write (sp->transport, ": ", 2, NULL);
}
return mu_stream_write (sp->transport, buf, size, NULL);
}
static int
_log_flush (struct _mu_stream *str)
{
struct _mu_log_stream *sp = (struct _mu_log_stream *)str;
return mu_stream_flush (sp->transport);
}
static void
_log_done (struct _mu_stream *str)
{
struct _mu_log_stream *sp = (struct _mu_log_stream *)str;
mu_stream_destroy (&sp->transport);
}
static int
_log_close (struct _mu_stream *str)
{
struct _mu_log_stream *sp = (struct _mu_log_stream *)str;
return mu_stream_close (sp->transport);
}
static int
_log_setbuf_hook (mu_stream_t str, enum mu_buffer_type type, size_t size)
{
if (type != mu_buffer_line)
return EACCES;
return 0;
}
static int
_log_ctl (struct _mu_stream *str, int code, int opcode, void *arg)
{
struct _mu_log_stream *sp = (struct _mu_log_stream *)str;
switch (code)
{
case MU_IOCTL_TRANSPORT:
if (!arg)
return EINVAL;
else
{
mu_transport_t *ptrans = arg;
switch (opcode)
{
case MU_IOCTL_OP_GET:
ptrans[0] = (mu_transport_t) sp->transport;
ptrans[1] = NULL;
break;
case MU_IOCTL_OP_SET:
ptrans = arg;
if (ptrans[0])
sp->transport = (mu_stream_t) ptrans[0];
break;
default:
return EINVAL;
}
}
break;
case MU_IOCTL_LOGSTREAM:
switch (opcode)
{
case MU_IOCTL_LOGSTREAM_GET_SEVERITY:
if (!arg)
return EINVAL;
*(unsigned*)arg = sp->severity;
break;
case MU_IOCTL_LOGSTREAM_SET_SEVERITY:
if (!arg)
return EINVAL;
if (*(unsigned*)arg >= _mu_severity_num)
return EINVAL;
sp->severity = *(unsigned*)arg;
break;
case MU_IOCTL_LOGSTREAM_GET_MODE:
if (!arg)
return EINVAL;
*(int*)arg = sp->logmode;
break;
case MU_IOCTL_LOGSTREAM_SET_MODE:
if (!arg)
return EINVAL;
sp->logmode = *(int*)arg;
break;
case MU_IOCTL_LOGSTREAM_GET_LOCUS:
if (!arg)
return EINVAL;
else
{
struct mu_locus *ploc = arg;
if (sp->locus.mu_file)
{
ploc->mu_file = strdup (sp->locus.mu_file);
if (!ploc->mu_file)
return ENOMEM;
}
else
ploc->mu_file = NULL;
ploc->mu_line = sp->locus.mu_line;
ploc->mu_col = sp->locus.mu_col;
}
break;
case MU_IOCTL_LOGSTREAM_SET_LOCUS:
{
struct mu_locus *ploc = arg;
if (!arg)
{
free (sp->locus.mu_file);
sp->locus.mu_file = NULL;
sp->locus.mu_line = 0;
sp->locus.mu_col = 0;
}
else
{
if (ploc->mu_file)
_locus_set_file (&sp->locus, ploc->mu_file,
strlen (ploc->mu_file));
if (ploc->mu_line)
_locus_set_line (&sp->locus, ploc->mu_line);
if (ploc->mu_col)
_locus_set_col (&sp->locus, ploc->mu_col);
}
break;
}
case MU_IOCTL_LOGSTREAM_SET_LOCUS_LINE:
if (!arg)
return EINVAL;
sp->locus.mu_line = *(unsigned*)arg;
break;
case MU_IOCTL_LOGSTREAM_SET_LOCUS_COL:
if (!arg)
return EINVAL;
sp->locus.mu_col = *(unsigned*)arg;
break;
case MU_IOCTL_LOGSTREAM_ADVANCE_LOCUS_LINE:
if (!arg)
sp->locus.mu_line++;
else
sp->locus.mu_line += *(int*)arg;
break;
case MU_IOCTL_LOGSTREAM_ADVANCE_LOCUS_COL:
if (!arg)
sp->locus.mu_col++;
else
sp->locus.mu_col += *(int*)arg;
break;
case MU_IOCTL_LOGSTREAM_SUPPRESS_SEVERITY:
if (!arg)
sp->threshold = MU_LOG_DEBUG;
else if (*(unsigned*)arg >= _mu_severity_num)
return MU_ERR_NOENT;
sp->threshold = *(unsigned*)arg;
break;
case MU_IOCTL_LOGSTREAM_SUPPRESS_SEVERITY_NAME:
if (!arg)
sp->threshold = MU_LOG_DEBUG;
else
return mu_severity_from_string ((const char *) arg, &sp->threshold);
case MU_IOCTL_LOGSTREAM_GET_SEVERITY_MASK:
if (!arg)
return EINVAL;
*(int*)arg = sp->sevmask;
break;
case MU_IOCTL_LOGSTREAM_SET_SEVERITY_MASK:
if (!arg)
return EINVAL;
sp->sevmask = *(int*)arg;
break;
case MU_IOCTL_LOGSTREAM_CLONE:
if (!arg)
return EINVAL;
else
{
mu_stream_t str;
struct _mu_log_stream *newp;
int rc = mu_log_stream_create (&str, sp->transport);
if (rc)
return rc;
newp = (struct _mu_log_stream *) str;
newp->severity = sp->severity;
newp->threshold = sp->threshold;
newp->logmode = sp->logmode;
newp->sevmask = sp->sevmask;
if (sp->locus.mu_file)
newp->locus.mu_file = strdup (sp->locus.mu_file);
newp->locus.mu_line = sp->locus.mu_line;
newp->locus.mu_col = sp->locus.mu_col;
*(mu_stream_t*) arg = str;
}
break;
default:
return EINVAL;
}
break;
case MU_IOCTL_FILTER:
case MU_IOCTL_SYSLOGSTREAM:
return mu_stream_ioctl (sp->transport, code, opcode, arg);
default:
return ENOSYS;
}
return 0;
}
int
mu_log_stream_create (mu_stream_t *pstr, mu_stream_t transport)
{
struct _mu_log_stream *sp;
mu_stream_t stream;
int rc;
sp = (struct _mu_log_stream *)
_mu_stream_create (sizeof (*sp), MU_STREAM_WRITE);
if (!sp)
return ENOMEM;
sp->base.write = _log_write;
sp->base.flush = _log_flush;
sp->base.close = _log_close;
sp->base.done = _log_done;
sp->base.setbuf_hook = _log_setbuf_hook;
sp->base.ctl = _log_ctl;
sp->transport = transport;
mu_stream_ref (transport);
sp->severity = MU_LOG_ERROR;
sp->logmode = 0;
stream = (mu_stream_t) sp;
mu_stream_set_buffer (stream, mu_buffer_line, 0);
rc = mu_stream_open (stream);
if (rc)
mu_stream_destroy (&stream);
else
*pstr = stream;
return rc;
}