/* * libslack - https://libslack.org * * Copyright (C) 1999-2004, 2010, 2020-2023 raf * * 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 2 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 . * * 20230824 raf */ /* =head1 NAME I - message module =head1 SYNOPSIS #include #include typedef struct Msg Msg; typedef void msg_out_t(void *data, const void *mesg, size_t mesglen); typedef int msg_filter_t(void **mesgp, const void *mesg, size_t mesglen); typedef void msg_release_t(void *data); Msg *msg_create(int type, msg_out_t *out, void *data, msg_release_t *destroy); Msg *msg_create_with_locker(Locker *locker, int type, msg_out_t *out, void *data, msg_release_t *destroy); int msg_rdlock(Msg *mesg); int msg_wrlock(Msg *mesg); int msg_unlock(Msg *mesg); void msg_release(Msg *mesg); void *msg_destroy(Msg **mesg); void msg_out(Msg *dst, const char *format, ...); void msg_out_unlocked(Msg *dst, const char *format, ...); void vmsg_out(Msg *dst, const char *format, va_list args); void vmsg_out_unlocked(Msg *dst, const char *format, va_list args); Msg *msg_create_fd(int fd); Msg *msg_create_fd_with_locker(Locker *locker, int fd); Msg *msg_create_stderr(void); Msg *msg_create_stderr_with_locker(Locker *locker); Msg *msg_create_stdout(void); Msg *msg_create_stdout_with_locker(Locker *locker); Msg *msg_create_file(const char *path); Msg *msg_create_file_with_locker(Locker *locker, const char *path); Msg *msg_create_syslog(const char *ident, int option, int facility, int priority); Msg *msg_create_syslog_with_locker(Locker *locker, const char *ident, int option, int facility, int priority); Msg *msg_syslog_set_facility(Msg *mesg, int facility); Msg *msg_syslog_set_facility_unlocked(Msg *mesg, int facility); Msg *msg_syslog_set_priority(Msg *mesg, int priority); Msg *msg_syslog_set_priority_unlocked(Msg *mesg, int priority); Msg *msg_create_plex(Msg *msg1, Msg *msg2); Msg *msg_create_plex_with_locker(Locker *locker, Msg *msg1, Msg *msg2); int msg_add_plex(Msg *mesg, Msg *item); int msg_add_plex_unlocked(Msg *mesg, Msg *item); Msg *msg_create_filter(msg_filter_t *filter, Msg *mesg); Msg *msg_create_filter_with_locker(Locker *locker, msg_filter_t *filter, Msg *mesg); const char *msg_set_timestamp_format(const char *format); int msg_set_timestamp_format_locker(Locker *locker); int syslog_lookup_facility(const char *facility); int syslog_lookup_priority(const char *priority); const char *syslog_facility_str(int spec); const char *syslog_priority_str(int spec); int syslog_parse(const char *spec, int *facility, int *priority); =head1 DESCRIPTION This module provides general messaging functions. Message channels can be created that send messages to a file descriptor, a file, I or a client defined message handler or that multiplexes messages to any combination of the above. Messages sent to files are timestamped using (by default) the I format: C<"%Y%m%d %H:%M:%S">. It also provides functions for parsing I targets, converting between I facility names and codes, and converting between I priority names and codes. =over 4 =cut */ #ifndef _BSD_SOURCE #define _BSD_SOURCE /* For snprintf() on OpenBSD-4.7 */ #endif #ifndef _DEFAULT_SOURCE #define _DEFAULT_SOURCE /* New name for _BSD_SOURCE */ #endif #include "config.h" #include "std.h" #include #include #include #include #include "msg.h" #include "mem.h" #include "err.h" #include "str.h" #ifndef HAVE_SNPRINTF #include "snprintf.h" #endif typedef int MsgFDData; typedef struct MsgFileData MsgFileData; typedef struct MsgSyslogData MsgSyslogData; typedef struct MsgFilterData MsgFilterData; typedef struct MsgPlexData MsgPlexData; #define MSG_FD 1 #define MSG_FILE 2 #define MSG_SYSLOG 3 #define MSG_PLEX 4 #define MSG_FILTER 5 struct Msg { int type; /* subtype */ msg_out_t *out; /* message handling function */ void *data; /* subtype specific data */ msg_release_t *destroy; /* destructor function for data */ Locker *locker; /* locking strategy for this structure */ }; struct MsgFileData { int fd; /* file descriptor (-1 if open failed) */ }; struct MsgSyslogData { int facility; /* syslog(3) priority */ int priority; /* syslog(3) priority */ }; struct MsgPlexData { size_t size; /* elements allocated */ size_t length; /* length of Msg list */ Msg **list; /* list of Msg objects */ }; struct MsgFilterData { msg_filter_t *filter; /* filter function */ Msg *mesg; /* destination Msg */ }; typedef struct syslog_map_t syslog_map_t; struct syslog_map_t { char *name; int val; }; /* ** The following masks might be wrong on some systems. */ #ifndef LOG_PRIMASK #define LOG_PRIMASK 0x0007 #endif #ifndef LOG_FACMASK #define LOG_FACMASK 0x03f8 #endif static const syslog_map_t syslog_facility_map[] = { { "kern", LOG_KERN }, { "user", LOG_USER }, { "mail", LOG_MAIL }, { "daemon", LOG_DAEMON }, { "auth", LOG_AUTH }, { "syslog", LOG_SYSLOG }, { "lpr", LOG_LPR }, { "news", LOG_NEWS }, { "uucp", LOG_UUCP }, { "cron", LOG_CRON }, { "local0", LOG_LOCAL0 }, { "local1", LOG_LOCAL1 }, { "local2", LOG_LOCAL2 }, { "local3", LOG_LOCAL3 }, { "local4", LOG_LOCAL4 }, { "local5", LOG_LOCAL5 }, { "local6", LOG_LOCAL6 }, { "local7", LOG_LOCAL7 }, { NULL, -1 } }; static const syslog_map_t syslog_priority_map[] = { { "emerg", LOG_EMERG }, { "alert", LOG_ALERT }, { "crit", LOG_CRIT }, { "err", LOG_ERR }, { "warning", LOG_WARNING }, #ifdef LOG_NOTICE { "notice", LOG_NOTICE }, #endif { "info", LOG_INFO }, { "debug", LOG_DEBUG }, { NULL, -1 } }; #ifndef TEST static const char *timestamp_format = "%Y%m%d %H:%M:%S "; static Locker *timestamp_format_locker = NULL; /* =item C Creates a I object initialised with C, C, C and C. Client-defined message handlers must specify a C greater than C<5>. It is the caller's responsibility to deallocate the new I with I or I. It is strongly recommended to use I, because it also sets the pointer variable to C. On success, returns the new I object. On error, returns C. =cut */ Msg *msg_create(int type, msg_out_t *out, void *data, msg_release_t *destroy) { return msg_create_with_locker(NULL, type, out, data, destroy); } /* =item C Equivalent to I except that multiple threads accessing the new I will be synchronised by C. =cut */ Msg *msg_create_with_locker(Locker *locker, int type, msg_out_t *out, void *data, msg_release_t *destroy) { Msg *mesg; if (!(mesg = mem_new(Msg))) return NULL; mesg->type = type; mesg->out = out; mesg->data = data; mesg->destroy = destroy; mesg->locker = locker; return mesg; } /* =item C Claims a read lock on C (if C was created with a I). This is needed when multiple read-only I module functions need to be called atomically. It is the caller's responsibility to call I after the atomic operation. The only functions that may be called on C between calls to I and I are any read-only I module functions whose name ends with C<_unlocked>. On success, returns C<0>. On error, returns an error code. =cut */ #define msg_rdlock(mesg) ((mesg) ? locker_rdlock((mesg)->locker) : EINVAL) #define msg_wrlock(mesg) ((mesg) ? locker_wrlock((mesg)->locker) : EINVAL) #define msg_unlock(mesg) ((mesg) ? locker_unlock((mesg)->locker) : EINVAL) int (msg_rdlock)(Msg *mesg) { return msg_rdlock(mesg); } /* =item C Claims a write lock on C. Claims a write lock on C (if C was created with a I). This is needed when multiple read/write I module functions need to be called atomically. It is the caller's responsibility to call I after the atomic operation. The only functions that may be called on C between calls to I and I are any I module functions whose name ends with C<_unlocked>. On success, returns C<0>. On error, returns an error code. =cut */ int (msg_wrlock)(Msg *mesg) { return msg_wrlock(mesg); } /* =item C Unlocks a read or write lock on C obtained with I or I (if C was created with a I). On success, returns C<0>. On error, returns an error code. =cut */ int (msg_unlock)(Msg *mesg) { return msg_unlock(mesg); } /* =item C Releases (deallocates) C and its internal data. =cut */ void msg_release(Msg *mesg) { if (!mesg) return; if (mesg->destroy) mesg->destroy(mesg->data); mem_release(mesg); } /* =item C Destroys (deallocates and sets to C) C<*mesg>. Returns C. =cut */ void *msg_destroy(Msg **mesg) { if (mesg && *mesg) { msg_release(*mesg); *mesg = NULL; } return NULL; } /* =item C Sends a message to C. C is a I-like format string. Any remaining arguments are processed as in I. B msg_out(dst, buf); // EVIL msg_out(dst, "%s", buf); // GOOD =cut */ void msg_out(Msg *dst, const char *format, ...) { va_list args; va_start(args, format); vmsg_out(dst, format, args); va_end(args); } /* =item C Equivalent to I except that C is not read-locked. =cut */ void msg_out_unlocked(Msg *dst, const char *format, ...) { va_list args; va_start(args, format); vmsg_out_unlocked(dst, format, args); va_end(args); } /* =item C Sends a message to C. C is a I-like format string. C is processed as in I. =cut */ void vmsg_out(Msg *dst, const char *format, va_list args) { int err; if (!dst) return; if ((err = msg_rdlock(dst))) { set_errno(err); return; } vmsg_out_unlocked(dst, format, args); if ((err = msg_unlock(dst))) set_errno(err); } /* =item C Equivalent to I except that C is not read-locked. =cut */ void vmsg_out_unlocked(Msg *dst, const char *format, va_list args) { if (!dst) return; if (dst->out) { char mesg[MSG_SIZE]; vsnprintf(mesg, MSG_SIZE, format, args); dst->out(dst->data, mesg, strlen(mesg)); } } /* C Creates and initialises the internal data needed by a I object that sends messages to file descriptor C. On success, returns the data. On error, returns C. */ static MsgFDData *msg_fddata_create(int fd) { MsgFDData *data; if (!(data = mem_new(MsgFDData))) return NULL; *data = fd; return data; } /* C Releases (deallocates) the internal data needed by a I object that sends messages to a file descriptor. The file descriptor is not closed. */ static void msg_fddata_release(MsgFDData *data) { mem_release(data); } /* C Sends a message to a file descriptor. C is a pointer to the file descriptor. C is the message. C is its length. */ static void msg_out_fd(void *data, const void *mesg, size_t mesglen) { if (data && mesg) if (write(*(MsgFDData *)data, mesg, mesglen) == -1) /* Avoid gcc warning */; } /* =item C Creates a I object that sends messages to file descriptor C. It is the caller's responsibility to deallocate the new I with I or I. On success, returns the new I object. On error, returns C. =cut */ Msg *msg_create_fd(int fd) { return msg_create_fd_with_locker(NULL, fd); } /* =item C Equivalent to I except that multiple threads accessing the new I will be synchronised by C. =cut */ Msg *msg_create_fd_with_locker(Locker *locker, int fd) { MsgFDData *data; Msg *mesg; if (!(data = msg_fddata_create(fd))) return NULL; if (!(mesg = msg_create_with_locker(locker, MSG_FD, msg_out_fd, data, (msg_release_t *)msg_fddata_release))) { msg_fddata_release(data); return NULL; } return mesg; } /* =item C Creates a I object that sends messages to standard error. It is the caller's responsibility to deallocate the new I with I or I. It is strongly recommended to use I, because it also sets the pointer variable to C. On success, returns the new I object. On error, returns C. =cut */ Msg *msg_create_stderr(void) { return msg_create_fd_with_locker(NULL, STDERR_FILENO); } /* =item C Equivalent to I except that multiple threads accessing the new I will be synchronised by C. =cut */ Msg *msg_create_stderr_with_locker(Locker *locker) { return msg_create_fd_with_locker(locker, STDERR_FILENO); } /* =item C Creates a I object that sends messages to standard output. It is the caller's responsibility to deallocate the new I with I or I. It is strongly recommended to use I, because it also sets the pointer variable to C. On success, returns the new I object. On error, returns C. =cut */ Msg *msg_create_stdout(void) { return msg_create_fd_with_locker(NULL, STDOUT_FILENO); } /* =item C Equivalent to I except that multiple threads accessing the new I will be synchronised by C. =cut */ Msg *msg_create_stdout_with_locker(Locker *locker) { return msg_create_fd_with_locker(locker, STDOUT_FILENO); } /* C Initialises the internal data needed by a I object that sends messages to the file specified by C. This data consists of a copy of C and an open file descriptor to the file. The file descriptor is opened with the C, C, and C flags. On success, returns C<0>. On error, returns C<-1> with C set appropriately. */ static int msg_filedata_init(MsgFileData *data, const char *path) { mode_t mode; if (!data || !path) return set_errno(EINVAL); mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; if ((data->fd = open(path, O_WRONLY | O_CREAT | O_APPEND, mode)) == -1) return -1; return 0; } /* C Creates the internal data needed by a I object that sends messages to the file specified by C. On success, returns the data. On error, returns C with C set appropriately. */ static MsgFileData *msg_filedata_create(const char *path) { MsgFileData *data; if (!(data = mem_new(MsgFileData))) return NULL; if (msg_filedata_init(data, path) == -1) { mem_release(data); return NULL; } return data; } /* C Releases (deallocates) the internal data needed by a I object that sends messages to a file. The file descriptor is closed first. */ static void msg_filedata_release(MsgFileData *data) { if (!data) return; if (data->fd != -1) close(data->fd); mem_release(data); } /* C Sends a message to a file. C contains the file descriptor. C is the message. C is its length. On error, sets C appropriately. */ static void msg_out_file(void *data, const void *mesg, size_t mesglen) { MsgFileData *dst = data; char buf[MSG_SIZE]; size_t buflen; int err; time_t t = time(NULL); if ((err = locker_rdlock(timestamp_format_locker))) { set_errno(err); return; } strftime(buf, MSG_SIZE, timestamp_format, localtime(&t)); if ((err = locker_unlock(timestamp_format_locker))) { set_errno(err); return; } buflen = strlen(buf); if (buflen + mesglen >= MSG_SIZE) mesglen -= MSG_SIZE - buflen; memmove(buf + buflen, mesg, mesglen); if (mesg && dst && dst->fd != -1) if (write(dst->fd, buf, buflen + mesglen) == -1) /* Avoid gcc warning */; } /* =item C Creates a I object that sends messages to the file specified by C. It is the caller's responsibility to deallocate the new I with I or I. It is strongly recommended to use I, because it also sets the pointer variable to C. On success, returns the new I object. On error, returns C with C set appropriately. =cut */ Msg *msg_create_file(const char *path) { return msg_create_file_with_locker(NULL, path); } /* =item C Equivalent to I except that multiple threads accessing the new I will be synchronised by C. =cut */ Msg *msg_create_file_with_locker(Locker *locker, const char *path) { MsgFileData *data; Msg *mesg; if (!(data = msg_filedata_create(path))) return NULL; if (!(mesg = msg_create_with_locker(locker, MSG_FILE, msg_out_file, data, (msg_release_t *)msg_filedata_release))) { msg_filedata_release(data); return NULL; } return mesg; } /* C Initialises the internal data needed by a I object that sends messages to I. I is called with C and C