/* GNU Mailutils -- a suite of utilities for electronic mail Copyright (C) 2010-2021 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 <http://www.gnu.org/licenses/>. */ #ifdef HAVE_CONFIG_H # include <config.h> #endif #include <stdlib.h> #include <string.h> #include <errno.h> #include <mailutils/types.h> #include <mailutils/errno.h> #include <mailutils/cctype.h> #include <mailutils/cstr.h> #include <mailutils/log.h> #include <mailutils/nls.h> #include <mailutils/stream.h> #include <mailutils/debug.h> #include <mailutils/locus.h> #include <mailutils/sys/logstream.h> 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 void lr_set_line (struct mu_locus_range *loc, unsigned val, int end) { if (end) loc->end.mu_line = val; else loc->beg.mu_line = val; } static void lr_set_col (struct mu_locus_range *loc, unsigned val, int end) { if (end) loc->end.mu_col = val; else loc->beg.mu_col = val; } static int lr_set_file (struct mu_locus_range *loc, char const *fname, unsigned len, int end) { char const *refname; struct mu_locus_point *pt = end ? &loc->end : &loc->beg; int rc; if (fname == NULL) { refname = NULL; rc = 0; } else if (len == 0) rc = mu_ident_ref (fname, &refname); else { char *name; name = malloc (len + 1); if (!name) return errno; memcpy (name, fname, len); name[len] = 0; rc = mu_ident_ref (name, &refname); free (name); } if (rc) return rc; mu_ident_deref (pt->mu_file); pt->mu_file = refname; return 0; } /* Field modification map (binary): FfLlCc The bits f, l, and c (file, line, and column) are toggled cyclically. The value 0 means locus beg, 1 meand locus end. The bits F, L, and C are set once and indicate that the corresponding bit was toggled at least once. */ #define FMM_COL 0 #define FMM_LINE 1 #define FMM_FILE 2 #define FMM_SHIFT(n) ((n)<<1) #define FMM_MASK(n) (0x3 << FMM_SHIFT (n)) #define FMM_VAL(m,n) (((m) >> FMM_SHIFT (n)) & 0x1) #define FMM_SET(m,n,v) ((m) = ((m) & ~FMM_MASK (n)) | (((v) << FMM_SHIFT (n))|0x2)) #define FMM_CYCLE(m, n) \ FMM_SET ((m), (n), ((FMM_VAL ((m), (n)) + 1) % 2)) 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_range loc; int fmm = 0; unsigned flen = 0; int save_locus = 0; int rc; int escape_error = 0; #define NEXT do { if (size == 0) return EINVAL; buf++; size--; } 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) loc = sp->locrange; mu_ident_ref (loc.beg.mu_file, &loc.beg.mu_file); mu_ident_ref (loc.end.mu_file, &loc.end.mu_file); /* 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); lr_set_line (&loc, n, FMM_VAL (fmm, FMM_LINE)); FMM_CYCLE (fmm, FMM_LINE); logmode |= MU_LOGMODE_LOCUS; break; case 'c': /* Column in input line (decimal) */ READNUM (n); lr_set_col (&loc, n, FMM_VAL (fmm, FMM_COL)); FMM_CYCLE (fmm, FMM_COL); logmode |= MU_LOGMODE_LOCUS; break; case 'f': /* File name. Format: <N>S */ READNUM (flen); lr_set_file (&loc, buf, flen, FMM_VAL (fmm, FMM_FILE)); FMM_CYCLE (fmm, FMM_FILE); buf += flen; size -= flen; logmode |= MU_LOGMODE_LOCUS; break; default: buf -= 2; size += 2; escape_error = 1; } } if (severity >= _mu_severity_num) severity = MU_LOG_EMERG; if (save_locus) mu_locus_range_copy (&sp->locrange, &loc); if (severity < sp->threshold) rc = 0; else { mu_stream_ioctl (sp->transport, MU_IOCTL_LOGSTREAM, MU_IOCTL_LOGSTREAM_SET_SEVERITY, &severity); if ((logmode & MU_LOGMODE_LOCUS) && loc.beg.mu_file) { mu_stream_print_locus_range (sp->transport, &loc); mu_stream_write (sp->transport, ": ", 2, NULL); } if ((logmode & MU_LOGMODE_PREFIX) && sp->prefix) { mu_stream_write (sp->transport, sp->prefix, strlen (sp->prefix), NULL); mu_stream_write (sp->transport, ": ", 2, NULL); } 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); } rc = mu_stream_write (sp->transport, buf, size, NULL); } mu_ident_deref (loc.beg.mu_file); mu_ident_deref (loc.end.mu_file); return rc; } 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_locus_range_deinit (&sp->locrange); mu_ident_deref (sp->prefix); 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; int status; 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_SUBSTREAM: if (sp->transport && ((status = mu_stream_ioctl (sp->transport, code, opcode, arg)) == 0 || status != ENOSYS)) return status; /* fall through */ case MU_IOCTL_TOPSTREAM: if (!arg) return EINVAL; else { mu_stream_t *pstr = arg; switch (opcode) { case MU_IOCTL_OP_GET: pstr[0] = sp->transport; mu_stream_ref (pstr[0]); pstr[1] = NULL; break; case MU_IOCTL_OP_SET: mu_stream_unref (sp->transport); sp->transport = pstr[0]; mu_stream_ref (sp->transport); 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_SET_LOCUS_RANGE: { struct mu_locus_range *lr = arg; if (!arg) { mu_ident_deref (sp->locrange.beg.mu_file); mu_ident_deref (sp->locrange.end.mu_file); memset (&sp->locrange, 0, sizeof sp->locrange); } else { char const *begname, *endname; status = mu_ident_ref (lr->beg.mu_file, &begname); if (status) return status; status = mu_ident_ref (lr->end.mu_file, &endname); if (status) { mu_ident_deref (begname); return status; } mu_ident_deref (sp->locrange.beg.mu_file); sp->locrange.beg.mu_file = begname; sp->locrange.beg.mu_line = lr->beg.mu_line; sp->locrange.beg.mu_col = lr->beg.mu_col; mu_ident_deref (sp->locrange.end.mu_file); sp->locrange.end.mu_file = endname; sp->locrange.end.mu_line = lr->end.mu_line; sp->locrange.end.mu_col = lr->end.mu_col; } } break; case MU_IOCTL_LOGSTREAM_GET_LOCUS_RANGE: if (!arg) return EINVAL; else { struct mu_locus_range *lr = arg; char const *begname, *endname; status = mu_ident_ref (sp->locrange.beg.mu_file, &begname); if (status) return status; status = mu_ident_ref (sp->locrange.end.mu_file, &endname); if (status) { mu_ident_deref (begname); return status; } lr->beg.mu_file = begname; lr->beg.mu_line = sp->locrange.beg.mu_line; lr->beg.mu_col = sp->locrange.beg.mu_col; lr->end.mu_file = endname; lr->end.mu_line = sp->locrange.end.mu_line; lr->end.mu_col = sp->locrange.end.mu_col; } break; case MU_IOCTL_LOGSTREAM_SET_LOCUS_LINE: if (!arg) return EINVAL; sp->locrange.beg.mu_line = *(unsigned*)arg; break; case MU_IOCTL_LOGSTREAM_SET_LOCUS_COL: if (!arg) return EINVAL; sp->locrange.beg.mu_col = *(unsigned*)arg; break; case MU_IOCTL_LOGSTREAM_ADVANCE_LOCUS_LINE: if (!arg) sp->locrange.beg.mu_line++; else sp->locrange.beg.mu_line += *(int*)arg; break; case MU_IOCTL_LOGSTREAM_ADVANCE_LOCUS_COL: if (!arg) sp->locrange.beg.mu_col++; else sp->locrange.beg.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; newp->locrange = sp->locrange; mu_ident_ref (sp->locrange.beg.mu_file, &sp->locrange.beg.mu_file); mu_ident_ref (sp->locrange.end.mu_file, &sp->locrange.end.mu_file); mu_ident_ref (sp->prefix, &newp->prefix); *(mu_stream_t*) arg = str; } break; case MU_IOCTL_LOGSTREAM_GET_PREFIX: if (!arg) return EINVAL; else { char **ret_ptr = arg; if (!sp->prefix) sp->prefix = NULL; else { char *copy = strdup (sp->prefix); if (!copy) return errno; *ret_ptr = copy; } } break; case MU_IOCTL_LOGSTREAM_SET_PREFIX: { mu_ident_deref (sp->prefix); if (arg) mu_ident_ref (arg, &sp->prefix); else sp->prefix = NULL; } break; default: return EINVAL; } break; default: return mu_stream_ioctl (sp->transport, code, opcode, arg); } return 0; } void _mu_log_stream_setup (struct _mu_log_stream *sp, mu_stream_t transport) { mu_stream_t stream; 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); } int mu_log_stream_create (mu_stream_t *pstr, mu_stream_t transport) { struct _mu_log_stream *sp; sp = (struct _mu_log_stream *) _mu_stream_create (sizeof (*sp), MU_STREAM_WRITE); if (!sp) return ENOMEM; _mu_log_stream_setup (sp, transport); *pstr = (mu_stream_t) sp; return 0; }