[devel] [PATCH 3/6] syslogd: Implement customization of log file records

Alexey Gladkov legion на altlinux.ru
Вт Окт 27 14:33:48 MSK 2020


Signed-off-by: Alexey Gladkov <gladkov.alexey на gmail.com>
---
 syslog.conf |  17 +++
 syslogd.c   | 303 ++++++++++++++++++++++++++++++++++++++++++++--------
 2 files changed, 277 insertions(+), 43 deletions(-)

diff --git a/syslog.conf b/syslog.conf
index 801f35f..83fa433 100644
--- a/syslog.conf
+++ b/syslog.conf
@@ -1,4 +1,21 @@
 # /etc/syslog.conf - Configuration file for syslogd(8)
+#
+# Specifies log format. The log format can contain placeholders for log message
+# elements:
+#   %t - time of last occurrence.
+#   %h - host from which this message.
+#   %m - message text.
+#   %% - '%' character.
+#   \\ - '\' character.
+#   \b - backspace character.
+#   \t - horizontal tab character.
+#   \f - form feed character.
+#   \n - new line character.
+#   \r - carriage return character.
+#
+# Default:
+#log_format: %t %h %m
+
 #
 # For info about the format of this file, see "man syslog.conf".
 #
diff --git a/syslogd.c b/syslogd.c
index d4aa9e4..214c4e9 100644
--- a/syslogd.c
+++ b/syslogd.c
@@ -289,16 +289,38 @@ struct sourceinfo {
 	unsigned int flags;
 } sinfo;
 
-enum record_fields_type {
-	RECORD_FIELD_TIME = 0,
-	RECORD_FIELD_SEP1,
-	RECORD_FIELD_HOST,
-	RECORD_FIELD_SEP2,
-	RECORD_FIELD_MSG,
-	RECORD_FIELD_EOL,
-	RECORD_FIELD_COUNTS,
+enum log_format_type {
+	LOG_FORMAT_NONE = 0,
+	LOG_FORMAT_BOL,
+	LOG_FORMAT_TIME,
+	LOG_FORMAT_HOST,
+	LOG_FORMAT_MSG,
+	LOG_FORMAT_EOL,
+	LOG_FORMAT_COUNTS,
 };
 
+#define LOG_FORMAT_REPEAT_MAX  5
+#define LOG_FORMAT_FIELDS_MAX  LOG_FORMAT_COUNTS * LOG_FORMAT_REPEAT_MAX
+#define LOG_FORMAT_IOVEC_MAX   LOG_FORMAT_FIELDS_MAX * 2 + 1
+
+struct log_format_field {
+	enum log_format_type f_type;
+	struct iovec *f_iov;
+};
+
+struct log_format {
+	char *line;
+
+	struct iovec *iov;
+	size_t iovec_nr;
+
+	struct log_format_field *fields;
+	size_t fields_nr;
+};
+
+static struct log_format log_fmt = { 0 };
+static long int iovec_max = 0;
+
 static int	Debug;			/* debug flag */
 static int	Compress = 1;		/* compress repeated messages flag */
 static char	LocalHostName[MAXHOSTNAMELEN+1];	/* our hostname */
@@ -307,6 +329,7 @@ static char	*emptystring = "";
 static int	InetInuse = 0;		/* non-zero if INET sockets are being used */
 static int	*finet = NULL;		/* Internet datagram sockets */
 static int	Initialized = 0;	/* set when we have initialized ourselves */
+static int	LogFormatInitialized = 0; /* set when we have initialized log_format */
 static int	MarkInterval = 20 * 60;	/* interval between marks in seconds */
 #ifdef INET6
 static int	family = PF_UNSPEC;	/* protocol family (IPv4, IPv6 or both) */
@@ -340,9 +363,15 @@ void untty(void);
 void printchopped(const struct sourceinfo* const, char *msg, size_t len, int fd);
 void printline(const struct sourceinfo* const, char *msg);
 void logmsg(int pri, char *msg, const struct sourceinfo* const, int flags);
+char *get_record_field(struct log_format *log_fmt, enum log_format_type name)
+	SYSKLOGD_NONNULL((1));
+void clear_record_fields(struct log_format *log_fmt)
+	SYSKLOGD_NONNULL((1));
+void set_record_field(struct log_format *log_fmt, enum log_format_type name, char *value, ssize_t len)
+	SYSKLOGD_NONNULL((1));
 void fprintlog(register struct filed *f, char *from, int flags, char *msg);
 void endtty(int);
-void wallmsg(register struct filed *f, struct iovec iov[RECORD_FIELD_COUNTS]);
+void wallmsg(register struct filed *f, struct log_format *log_fmt);
 void reapchild(int);
 const char *cvtaddr(struct sockaddr_storage *f, int len);
 const char *cvthname(struct sockaddr_storage *f, int len);
@@ -358,6 +387,9 @@ int decode(char *name, struct code *codetab);
 static void verbosef(char *, ...)
 	SYSKLOGD_FORMAT((__printf__, 1, 2)) SYSKLOGD_NONNULL((1));
 static void allocate_log(void);
+int set_log_format_field(struct log_format *log_fmt, size_t i, enum log_format_type t, char *s, size_t n)
+	SYSKLOGD_NONNULL((1));
+int parse_log_format(struct log_format *log_fmt, char *s);
 void sighup_handler(int);
 
 #ifdef SYSLOG_UNIXAF
@@ -648,6 +680,9 @@ int main(int argc, char **argv)
 	if (funix_dir && *funix_dir)
 		add_funix_dir(funix_dir);
 
+	if (parse_log_format(&log_fmt, "%t %h %m") < 0)
+		exit(1);
+
 	if ( !(Debug || NoFork) )
 	{
 		verbosef("Checking pidfile.\n");
@@ -1454,16 +1489,38 @@ finish:
 	sigprocmask(SIG_UNBLOCK, &mask, NULL);
 }
 
-static inline void set_record_field(struct iovec iov[RECORD_FIELD_COUNTS],
-		enum record_fields_type name, char *value, size_t len)
+char *get_record_field(struct log_format *log_fmt, enum log_format_type name)
 {
-	iov[name].iov_base = value;
-	iov[name].iov_len = len == -1 ? strlen(value) : len;
+	for (int i = 0; i < LOG_FORMAT_FIELDS_MAX && log_fmt->fields[i].f_iov; i++) {
+		if (log_fmt->fields[i].f_type == name)
+			return log_fmt->fields[i].f_iov->iov_base;
+	}
+	return NULL;
+}
+
+void set_record_field(struct log_format *log_fmt,
+		enum log_format_type name, char *value, ssize_t len)
+{
+	size_t iov_len = len == -1 ? strlen(value) : len;
+
+	for (int i = 0; i < LOG_FORMAT_FIELDS_MAX && log_fmt->fields[i].f_iov; i++) {
+		if (log_fmt->fields[i].f_type == name) {
+			log_fmt->fields[i].f_iov->iov_base = value;
+			log_fmt->fields[i].f_iov->iov_len = iov_len;
+		}
+	}
+}
+
+void clear_record_fields(struct log_format *log_fmt)
+{
+	for (int i = 0; i < LOG_FORMAT_FIELDS_MAX && log_fmt->fields[i].f_iov; i++) {
+		log_fmt->fields[i].f_iov->iov_base = NULL;
+		log_fmt->fields[i].f_iov->iov_len = 0;
+	}
 }
 
 void fprintlog(struct filed *f, char *from, int flags, char *msg)
 {
-	struct iovec iov[RECORD_FIELD_COUNTS];
 	char repbuf[80];
 #ifdef SYSLOG_INET
 	register int l;
@@ -1475,19 +1532,18 @@ void fprintlog(struct filed *f, char *from, int flags, char *msg)
 
 	verbosef("Called fprintlog, ");
 
-	set_record_field(iov, RECORD_FIELD_TIME, f->f_lasttime, 15);
-	set_record_field(iov, RECORD_FIELD_SEP1, " ", 1);
-	set_record_field(iov, RECORD_FIELD_HOST, f->f_prevhost, -1);
-	set_record_field(iov, RECORD_FIELD_SEP2, " ", 1);
+	clear_record_fields(&log_fmt);
 
+	set_record_field(&log_fmt, LOG_FORMAT_TIME, f->f_lasttime, 15);
+	set_record_field(&log_fmt, LOG_FORMAT_HOST, f->f_prevhost, -1);
 	if (msg) {
-		set_record_field(iov, RECORD_FIELD_MSG, msg, -1);
+		set_record_field(&log_fmt, LOG_FORMAT_MSG, msg, -1);
 	} else if (f->f_prevcount > 1) {
 		(void) snprintf(repbuf, sizeof(repbuf), "last message repeated %d times",
 		    f->f_prevcount);
-		set_record_field(iov, RECORD_FIELD_MSG, repbuf, -1);
+		set_record_field(&log_fmt, LOG_FORMAT_MSG, repbuf, -1);
 	} else {
-		set_record_field(iov, RECORD_FIELD_MSG, f->f_prevline, f->f_prevlen);
+		set_record_field(&log_fmt, LOG_FORMAT_MSG, f->f_prevline, f->f_prevlen);
 	}
 
 	verbosef("logging to %s", TypeNames[f->f_type]);
@@ -1567,7 +1623,7 @@ void fprintlog(struct filed *f, char *from, int flags, char *msg)
 			int i;
 			f->f_time = now;
 			(void) snprintf(line, sizeof(line), "<%d>%s", f->f_prevpri, \
-				(char *) iov[RECORD_FIELD_MSG].iov_base);
+				(char *) log_fmt.iov[LOG_FORMAT_MSG].iov_base);
 			l = strlen(line);
 			if (l > MAXLINE)
 				l = MAXLINE;
@@ -1615,9 +1671,9 @@ void fprintlog(struct filed *f, char *from, int flags, char *msg)
 		f->f_time = now;
 		verbosef(" %s\n", f->f_un.f_fname);
 		if (f->f_type == F_TTY || f->f_type == F_CONSOLE) {
-			set_record_field(iov, RECORD_FIELD_EOL, "\r\n", 2);
+			set_record_field(&log_fmt, LOG_FORMAT_EOL, "\r\n", 2);
 		} else {
-			set_record_field(iov, RECORD_FIELD_EOL, "\n", 1);
+			set_record_field(&log_fmt, LOG_FORMAT_EOL, "\n", 1);
 		}
 	again:
 		/* f->f_file == -1 is an indicator that we couldn't
@@ -1625,7 +1681,7 @@ void fprintlog(struct filed *f, char *from, int flags, char *msg)
 		if (f->f_file == -1)
 			break;
 
-		if (writev(f->f_file, iov, RECORD_FIELD_COUNTS) < 0) {
+		if (writev(f->f_file, log_fmt.iov, LOG_FORMAT_IOVEC_MAX) < 0) {
 			int e = errno;
 
 			/* If a named pipe is full, just ignore it for now */
@@ -1672,8 +1728,8 @@ void fprintlog(struct filed *f, char *from, int flags, char *msg)
 	case F_WALL:
 		f->f_time = now;
 		verbosef("\n");
-		set_record_field(iov, RECORD_FIELD_EOL, "\r\n", 2);
-		wallmsg(f, iov);
+		set_record_field(&log_fmt, LOG_FORMAT_EOL, "\r\n", 2);
+		wallmsg(f, &log_fmt);
 		break;
 	} /* switch */
 	if (f->f_type != F_FORW_UNKN)
@@ -1695,25 +1751,22 @@ void endtty(int sig)
  *	world, or a list of approved users.
  */
 
-void wallmsg(struct filed *f, struct iovec iov[RECORD_FIELD_COUNTS])
+void wallmsg(struct filed *f, struct log_format *log_fmt)
 {
 	char p[sizeof (_PATH_DEV) + UNAMESZ];
 	register int i;
-	int ttyf, len;
+	int ttyf;
 	static int reenter = 0;
 	struct utmp ut;
 	struct utmp *uptr;
 	char greetings[200];
 
-	(void) &len;
-
 	if (reenter++)
 		return;
 
 	/* open the user login file */
 	setutent();
 
-
 	/*
 	 * Might as well fork instead of using nonblocking I/O
 	 * and doing notty().
@@ -1722,10 +1775,13 @@ void wallmsg(struct filed *f, struct iovec iov[RECORD_FIELD_COUNTS])
 		(void) signal(SIGTERM, SIG_DFL);
 		(void) alarm(0);
 
-		(void) snprintf(greetings, sizeof(greetings),
-		    "\r\n\7Message from syslogd@%s at %.24s ...\r\n",
-			(char *) iov[RECORD_FIELD_HOST].iov_base, ctime(&now));
-		len = strlen(greetings);
+		if (f->f_type == F_WALL) {
+			snprintf(greetings, sizeof(greetings),
+			    "\r\n\7Message from syslogd@%s at %.24s ...\r\n",
+				get_record_field(log_fmt, LOG_FORMAT_HOST), ctime(&now));
+
+			set_record_field(log_fmt, LOG_FORMAT_BOL, greetings, -1);
+		}
 
 		/* scan the user login file */
 		while ((uptr = getutent())) {
@@ -1757,11 +1813,6 @@ void wallmsg(struct filed *f, struct iovec iov[RECORD_FIELD_COUNTS])
 			strcpy(p, _PATH_DEV);
 			strncat(p, ut.ut_line, UNAMESZ);
 
-			if (f->f_type == F_WALL) {
-				iov[0].iov_base = greetings;
-				iov[0].iov_len = len;
-				iov[1].iov_len = 0;
-			}
 			if (setjmp(ttybuf) == 0) {
 				(void) signal(SIGALRM, endtty);
 				(void) alarm(15);
@@ -1771,7 +1822,7 @@ void wallmsg(struct filed *f, struct iovec iov[RECORD_FIELD_COUNTS])
 					struct stat statb;
 
 					if (!fstat(ttyf, &statb) && (statb.st_mode & S_IWRITE)) {
-						if (writev(ttyf, iov, RECORD_FIELD_COUNTS) < 0)
+						if (writev(ttyf, log_fmt->iov, LOG_FORMAT_IOVEC_MAX) < 0)
 							errno = 0; /* ignore */
 					}
 					close(ttyf);
@@ -1924,7 +1975,6 @@ void debug_switch(int sig)
 	signal(SIGUSR1, debug_switch);
 }
 
-
 /*
  * Print syslogd errors some place.
  */
@@ -1949,6 +1999,11 @@ void logerror(const char *fmt, ...)
 			errno = 0; // ignore
 	}
 
+	if (!LogFormatInitialized) {
+		fputs(buf, stderr);
+		return;
+	}
+
 	memset(&source, '\0', sizeof(source));
 
 	source.flags = SINFO_ISINTERNAL;
@@ -2163,6 +2218,13 @@ void init(void)
 
 		*++p = '\0';
 
+
+		if (!strncmp("log_format:", cline, 11)) {
+			for (p = cline + 11; isspace(*p); ++p);
+			parse_log_format(&log_fmt, p);
+			continue;
+		}
+
 		allocate_log();
 		f = &Files[lognum++];
 
@@ -2590,6 +2652,161 @@ static void allocate_log(void)
 	return;
 }
 
+int set_log_format_field(struct log_format *log_fmt, size_t i,
+		enum log_format_type t, char *s, size_t n)
+{
+	if (i >= iovec_max) {
+		logerror("Too many parts in the log_format string");
+		return -1;
+	}
+
+	if (t != LOG_FORMAT_NONE) {
+		if (log_fmt->fields_nr >= LOG_FORMAT_FIELDS_MAX) {
+			logerror("Too many placeholders in the log_format string");
+			return -1;
+		}
+
+		log_fmt->fields[log_fmt->fields_nr].f_type = t;
+		log_fmt->fields[log_fmt->fields_nr].f_iov = log_fmt->iov + i;
+		log_fmt->fields_nr++;
+	}
+
+	log_fmt->iov[i].iov_base = s;
+	log_fmt->iov[i].iov_len = n;
+	log_fmt->iovec_nr++;
+
+	return 0;
+}
+
+int parse_log_format(struct log_format *log_fmt, char *str)
+{
+	char *ptr, *start;
+	int i, special, field_nr;
+	struct log_format new_fmt = { 0 };
+
+	iovec_max = sysconf(_SC_IOV_MAX);
+
+	new_fmt.line = calloc(1, LINE_MAX);
+	if (!new_fmt.line) {
+		logerror("Cannot allocate log_format string");
+		goto error;
+	}
+
+	new_fmt.iov = calloc(LOG_FORMAT_IOVEC_MAX, sizeof(struct iovec));
+	if (!new_fmt.iov) {
+		logerror("Cannot allocate records array for log_format string");
+		goto error;
+	}
+
+	new_fmt.fields = calloc(LOG_FORMAT_FIELDS_MAX, sizeof(struct log_format_field));
+	if (!new_fmt.fields) {
+		logerror("Cannot allocate rules array for log_format string");
+		goto error;
+	}
+
+	ptr = str;
+	i = special = 0;
+
+	while (*ptr != '\0' && i < LINE_MAX) {
+		char c = *ptr++;
+
+		switch (c) {
+			case 'b': if (special) c = '\b'; break;
+			case 'f': if (special) c = '\f'; break;
+			case 'n': if (special) c = '\n'; break;
+			case 'r': if (special) c = '\r'; break;
+			case 't': if (special) c = '\t'; break;
+			case '\\':
+				if (!special) {
+					special = 1;
+					continue;
+				}
+				break;
+		}
+		new_fmt.line[i++] = c;
+		special = 0;
+	}
+
+	field_nr = 0;
+	special = 0;
+	i = 0;
+
+	if (set_log_format_field(&new_fmt, field_nr++, LOG_FORMAT_BOL, NULL, 0) < 0)
+		goto error;
+
+	start = ptr = new_fmt.line;
+
+	while (*ptr != '\0') {
+		enum log_format_type f_type;
+
+		if (special) {
+			switch (*ptr) {
+				case 't': f_type = LOG_FORMAT_TIME; break;
+				case 'h': f_type = LOG_FORMAT_HOST; break;
+				case 'm': f_type = LOG_FORMAT_MSG;  break;
+				case '%':
+					special = 0;
+					goto create_special;
+				default:
+					logerror("unexpected special: '%%%c'", *ptr);
+					goto error;
+			}
+			special = 0;
+			goto create_field;
+
+		} else if (*ptr == '%')
+			special = 1;
+next:
+		ptr++;
+		continue;
+create_field:
+		if ((ptr - start - 1) > 0 &&
+		    set_log_format_field(&new_fmt, field_nr++,
+				LOG_FORMAT_NONE, start, (ptr - start - 1)) < 0)
+			goto error;
+
+		if (set_log_format_field(&new_fmt, field_nr++, f_type, NULL, 0) < 0)
+			goto error;
+
+		start = ptr + 1;
+		goto next;
+create_special:
+		if (set_log_format_field(&new_fmt, field_nr++,
+				LOG_FORMAT_NONE, start, (ptr - start - 1)) < 0)
+			goto error;
+
+		start = ptr;
+		goto next;
+	}
+
+	if (special) {
+		logerror("unexpected '%%' at the end of line");
+		goto error;
+	}
+
+	if (start != ptr &&
+	    set_log_format_field(&new_fmt, field_nr++,
+			LOG_FORMAT_NONE, start, (ptr - start)) < 0)
+		goto error;
+
+	if (set_log_format_field(&new_fmt, field_nr++,
+			LOG_FORMAT_EOL, NULL, 0) < 0)
+		goto error;
+
+	log_fmt->line   = new_fmt.line;
+	log_fmt->iov    = new_fmt.iov;
+	log_fmt->fields = new_fmt.fields;
+
+	LogFormatInitialized = 1;
+
+	return 0;
+error:
+	free(new_fmt.line);
+	free(new_fmt.iov);
+	free(new_fmt.fields);
+
+	return -1;
+}
 
 /*
  * The following function is resposible for handling a SIGHUP signal.  Since
-- 
2.25.4



Подробная информация о списке рассылки Devel