From 1a09654e79be0c6b1364e4bb783ce3dd72df23d0 Mon Sep 17 00:00:00 2001 From: Joachim Nilsson Date: Sun, 13 Oct 2019 14:44:51 +0200 Subject: [PATCH] Add logger tool from Finit project to complement sysklogd This patch introduces a relicensed logit from the Finit[1] project. It has been rebranded as logger to complement the features implemented in the sysklogd project. Note, logger conflicts with the tool of the same name from util-linux. [1]: https://github.com/troglobit/finit Signed-off-by: Joachim Nilsson --- man/Makefile.am | 1 + man/logger.1 | 87 ++++++++++++++ src/.gitignore | 1 + src/Makefile.am | 4 + src/logger.c | 295 ++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 388 insertions(+) create mode 100644 man/logger.1 create mode 100644 src/.gitignore create mode 100644 src/logger.c diff --git a/man/Makefile.am b/man/Makefile.am index 69b4683..4cb66b7 100644 --- a/man/Makefile.am +++ b/man/Makefile.am @@ -1,2 +1,3 @@ +dist_man1_MANS = logger.1 dist_man5_MANS = syslog.conf.5 dist_man8_MANS = syslogd.8 klogd.8 sysklogd.8 diff --git a/man/logger.1 b/man/logger.1 new file mode 100644 index 0000000..929f11e --- /dev/null +++ b/man/logger.1 @@ -0,0 +1,87 @@ +.\" -*- nroff -*- +.\" Copyright (c) 2018, 2019 Joachim Nilsson +.\" All rights reserved. +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.Dd Oct 13, 2019 +.Dt logger 1 +.Os "sysklogd (2.0)" +.Sh NAME +.Nm logger +.Nd Send messages to system log, or a log file +.Sh SYNOPSIS +.Nm +.Op Fl hsv +.Op Fl f Ar FILE +.Op Fl n Ar SIZE +.Op Fl p Ar PRIO +.Op Fl r Ar NUM +.Op Fl t Ar TAG +.Qq Ar message +.Sh DESCRIPTIOMN +.Nm +can be used to send messages to the system log from a UNIX shell, or +script. +.Sh OPTIONS +This program follows the usual UNIX command line syntax: +.Bl -tag -width Ds +.It Fl f Ar FILE +File to write log messages to, instead of syslog. +.It Fl h +Show program help. +.It Fl n Ar SIZE +Number of bytes before rotating when logging to a file, default: 200 kB. +.It Fl p Ar PRIO +Priority, numeric or facility.level pair. +.It Fl r Ar NUM +Number of rotated files to keep when logging to a file, default: 5. +.It Fl s +Log to stderr as well as the system log. +.It Fl t Ar TAG +Log using the specified tag, default: username. +.It Fl v +Show program version. +.El +.Sh EXAMPLES +.Bd -unfilled -offset left +logger -t dropbear -p auth.notice "Successful login for user 'admin' from 1.2.3.4" +logger -t udhcpc -f /tmp/script.log "New lease 1.2.3.200 obtained for interface eth0" +.Ed +.Sh SEE ALSO +.Xr syslog 3 +.Xr syslogd 8 +.Sh AUTHORS +.Nm +was originally written by Joachim Nilsson to be a part of the +.Xr finit 1 +system monitor (PID 1), where it is called +.Nm logit . +It is included here to complement +.Xr syslogd 8 +and be extended upon in the sysklogd project. +.Sh STANDARDS +The +.Nm +command is expected to be IEEE Std 1003.2 ("POSIX.2") compatible. +.Sh AVAILABILITY diff --git a/src/.gitignore b/src/.gitignore new file mode 100644 index 0000000..7c82fcf --- /dev/null +++ b/src/.gitignore @@ -0,0 +1 @@ +logger diff --git a/src/Makefile.am b/src/Makefile.am index e9153ba..c2e4407 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -16,6 +16,7 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +bin_PROGRAMS = logger sbin_PROGRAMS = syslogd klogd AM_CFLAGS = -fomit-frame-pointer -fno-strength-reduce -Wno-unused-result @@ -28,3 +29,6 @@ klogd_SOURCES = klogd.c klogd.h syslog.c pidfile.c pidfile.h \ ksym.c ksyms.h ksym_mod.c module.h klogd_CPPFLAGS = -DSYSV -DFSSTND -DALLOW_KERNEL_LOGGING \ -D_BSD_SOURCE -D_SVID_SOURCE -D_DEFAULT_SOURCE + +logger_SOURCES = logger.c +logger_CPPFLAGS = -D_XOPEN_SOURCE=600 -D_BSD_SOURCE -D_GNU_SOURCE -D_DEFAULT_SOURCE diff --git a/src/logger.c b/src/logger.c new file mode 100644 index 0000000..4f7dace --- /dev/null +++ b/src/logger.c @@ -0,0 +1,295 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 2018, 2019 Joachim Nilsson + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#define SYSLOG_NAMES +#include +#include +#include + +static const char version_info[] = PACKAGE_NAME " v" PACKAGE_VERSION; + + +static int create(char *path, mode_t mode, uid_t uid, gid_t gid) +{ + return mknod(path, S_IFREG | mode, 0) || chown(path, uid, gid); +} + +/* + * This function triggers a log rotates of @file when size >= @sz bytes + * At most @num old versions are kept and by default it starts gzipping + * .2 and older log files. If gzip is not available in $PATH then @num + * files are kept uncompressed. + */ +static int logrotate(char *file, int num, off_t sz) +{ + int cnt; + struct stat st; + + if (stat(file, &st)) + return 1; + + if (sz > 0 && S_ISREG(st.st_mode) && st.st_size > sz) { + if (num > 0) { + size_t len = strlen(file) + 10 + 1; + char ofile[len]; + char nfile[len]; + + /* First age zipped log files */ + for (cnt = num; cnt > 2; cnt--) { + snprintf(ofile, len, "%s.%d.gz", file, cnt - 1); + snprintf(nfile, len, "%s.%d.gz", file, cnt); + + /* May fail because ofile doesn't exist yet, ignore. */ + (void)rename(ofile, nfile); + } + + for (cnt = num; cnt > 0; cnt--) { + snprintf(ofile, len, "%s.%d", file, cnt - 1); + snprintf(nfile, len, "%s.%d", file, cnt); + + /* May fail because ofile doesn't exist yet, ignore. */ + (void)rename(ofile, nfile); + + if (cnt == 2 && !access(nfile, F_OK)) { + size_t len = 5 + strlen(nfile) + 1; + char cmd[len]; + + snprintf(cmd, len, "gzip %s", nfile); + system(cmd); + + remove(nfile); + } + } + + if (rename(file, nfile)) + (void)truncate(file, 0); + else + create(file, st.st_mode, st.st_uid, st.st_gid); + } else { + if (truncate(file, 0)) + syslog(LOG_ERR | LOG_PERROR, "Failed truncating %s during logrotate: %s", file, strerror(errno)); + } + } + + return 0; +} + +static int checksz(FILE *fp, off_t sz) +{ + struct stat st; + + if (sz <= 0) + return 0; + + if (!fstat(fileno(fp), &st) && st.st_size > sz) { + fclose(fp); + return 1; + } + + return 0; +} + +static int flogit(char *logfile, int num, off_t sz, char *buf, size_t len) +{ + FILE *fp; + +reopen: + fp = fopen(logfile, "a"); + if (!fp) { + syslog(LOG_ERR | LOG_PERROR, "Failed opening %s: %s", logfile, strerror(errno)); + return 1; + } + + if (buf[0]) { + fprintf(fp, "%s\n", buf); + fsync(fileno(fp)); + if (checksz(fp, sz)) + return logrotate(logfile, num, sz); + } else { + while ((fgets(buf, len, stdin))) { + fputs(buf, fp); + fsync(fileno(fp)); + + if (checksz(fp, sz)) { + logrotate(logfile, num, sz); + buf[0] = 0; + goto reopen; + } + } + } + + return fclose(fp); +} + +static int logit(int level, char *buf, size_t len) +{ + if (buf[0]) { + syslog(level, "%s", buf); + return 0; + } + + while ((fgets(buf, len, stdin))) + syslog(level, "%s", buf); + + return 0; +} + +static int parse_prio(char *arg, int *f, int *l) +{ + char *ptr; + + ptr = strchr(arg, '.'); + if (ptr) { + *ptr++ = 0; + + for (int i = 0; facilitynames[i].c_name; i++) { + if (!strcmp(facilitynames[i].c_name, arg)) { + *f = facilitynames[i].c_val; + break; + } + } + + arg = ptr; + } + + for (int i = 0; prioritynames[i].c_name; i++) { + if (!strcmp(prioritynames[i].c_name, arg)) { + *l = prioritynames[i].c_val; + break; + } + } + + return 0; +} + +static int usage(int code) +{ + fprintf(stderr, "Usage: logger [OPTIONS] [MESSAGE]\n" + "\n" + "Write MESSAGE (or stdin) to syslog, or file (with logrotate)\n" + "\n" + " -h This help text\n" + " -p PRIO Priority (numeric or facility.level pair)\n" + " -t TAG Log using the specified tag (defaults to user name)\n" + " -s Log to stderr as well as the system log\n" + "\n" + " -f FILE File to write log messages to, instead of syslog\n" + " -n SIZE Number of bytes before rotating, default: 200 kB\n" + " -r NUM Number of rotated files to keep, default: 5\n" + " -v Show program version\n" + "\n" + "This version of logger is distributed as part of sysklogd.\n" + "Bug report address: %s\n", PACKAGE_BUGREPORT); + + return code; +} + +int main(int argc, char *argv[]) +{ + int c, rc, num = 5; + int facility = LOG_USER; + int level = LOG_INFO; + int log_opts = LOG_NOWAIT; + off_t size = 200 * 1024; + char *ident = NULL, *logfile = NULL; + char buf[512] = ""; + + while ((c = getopt(argc, argv, "f:hn:p:r:st:v")) != EOF) { + switch (c) { + case 'f': + logfile = optarg; + break; + + case 'h': + return usage(0); + + case 'n': + size = atoi(optarg); + break; + + case 'p': + if (parse_prio(optarg, &facility, &level)) + return usage(1); + break; + + case 'r': + num = atoi(optarg); + break; + + case 's': + log_opts |= LOG_PERROR; + break; + + case 't': + ident = optarg; + break; + + case 'v': /* version */ + fprintf(stderr, "%s\n", version_info); + return 0; + + default: + return usage(1); + } + } + + if (!ident) + ident = getenv("LOGNAME") ?: getenv("USER"); + + if (optind < argc) { + size_t pos = 0, len = sizeof(buf); + + while (optind < argc) { + size_t bytes; + + bytes = snprintf(&buf[pos], len, "%s ", argv[optind++]); + pos += bytes; + len -= bytes; + } + } + + openlog(ident, log_opts, facility); + + if (logfile) + rc = flogit(logfile, num, size, buf, sizeof(buf)); + else + rc = logit(level, buf, sizeof(buf)); + + closelog(); + + return rc; +}