system/syslogd: Initial implementation

Initial implementation of syslogd including version information, simple
usage help and UDP transmission. The current implementation transmits
RFC 5424 formatted syslog entries over UDP for consumption by a syslog
collector. Only some options from the manpage are implemented since
NuttX doesn't currently have the ability for some of the more complex
features (-l, etc.).

Signed-off-by: Matteo Golin <matteo.golin@gmail.com>
This commit is contained in:
Matteo Golin 2025-06-07 15:01:58 -04:00 committed by Xiang Xiao
parent 7375f6574e
commit 5e50e2c1f9
6 changed files with 527 additions and 0 deletions

1
.gitignore vendored
View file

@ -48,3 +48,4 @@ build
compile_commands.json
.aider*
.clang-format
.cache

View file

@ -0,0 +1,36 @@
# ##############################################################################
# apps/system/syslogd/CMakeLists.txt
#
# SPDX-License-Identifier: Apache-2.0
#
# Licensed to the Apache Software Foundation (ASF) under one or more contributor
# license agreements. See the NOTICE file distributed with this work for
# additional information regarding copyright ownership. The ASF licenses this
# file to you under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy of
# the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under
# the License.
#
# ##############################################################################
if(CONFIG_SYSTEM_SYSLOGD)
nuttx_add_application(
MODULE
${CONFIG_SYSTEM_SYSLOGD}
NAME
${CONFIG_SYSTEM_SYSLOGD_PROGNAME}
STACKSIZE
${CONFIG_SYSTEM_SYSLOGD_STACKSIZE}
PRIORITY
${CONFIG_SYSTEM_SYSLOGD_PRIORITY}
SRCS
syslogd_main.c)
endif()

53
system/syslogd/Kconfig Normal file
View file

@ -0,0 +1,53 @@
#
# For a description of the syntax of this configuration file,
# see the file kconfig-language.txt in the NuttX tools repository.
#
config SYSTEM_SYSLOGD
bool "syslogd utility"
default n
depends on NET_UDP && SYSLOG_RFC5424
---help---
Enable support for the 'syslogd' utility. This utility will read syslog
messages from the syslog device and transmit them over UDP in RFC 5424
compatible format. Ensure the syslog device being used is capable of being
read from.
if SYSTEM_SYSLOGD
config SYSTEM_SYSLOGD_PROGNAME
string "syslogd progname"
default "syslogd"
---help---
This is the name of the program that will be used when the syslogd
program is installed.
config SYSTEM_SYSLOGD_PRIORITY
int "syslogd task priority"
default 100
config SYSTEM_SYSLOGD_STACKSIZE
int "syslogd stack size"
default DEFAULT_TASK_STACKSIZE
config SYSTEM_SYSLOGD_ENTRYSIZE
int "Max entry size"
default 480
---help---
The maximum size (in bytes) of the UDP message buffer for sending syslog
entries. Set this value to the expected maximum length of a syslog entry. RFC
5424 specifies a minimum maximum of 480.
config SYSTEM_SYSLOGD_PORT
int "syslogd port"
default 514
---help---
The default port for syslogd to send UDP traffic to.
config SYSTEM_SYSLOGD_ADDR
string "Log server address"
default "127.0.0.1"
---help---
The network address for syslogd to send UDP traffic to.
endif

25
system/syslogd/Make.defs Normal file
View file

@ -0,0 +1,25 @@
############################################################################
# apps/system/syslogd/Make.defs
#
# SPDX-License-Identifier: Apache-2.0
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for asyslogditional information regarding copyright ownership. The
# ASF licenses this file to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance with the
# License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
############################################################################
ifneq ($(CONFIG_SYSTEM_SYSLOGD),)
CONFIGURED_APPS += $(APPDIR)/system/syslogd
endif

36
system/syslogd/Makefile Normal file
View file

@ -0,0 +1,36 @@
############################################################################
# apps/system/syslogd/Makefile
#
# SPDX-License-Identifier: Apache-2.0
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership. The
# ASF licenses this file to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance with the
# License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
############################################################################
include $(APPDIR)/Make.defs
# Standalone syslogd command
PROGNAME = $(CONFIG_SYSTEM_SYSLOGD_PROGNAME)
PRIORITY = $(CONFIG_SYSTEM_SYSLOGD_PRIORITY)
STACKSIZE = $(CONFIG_SYSTEM_SYSLOGD_STACKSIZE)
MODULE = $(CONFIG_SYSTEM_SYSLOGD)
# Files
MAINSRC = syslogd_main.c
include $(APPDIR)/Application.mk

View file

@ -0,0 +1,376 @@
/****************************************************************************
* apps/system/syslogd/syslogd_main.c
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for asyslogditional information regarding copyright ownership.
* The ASF licenses this file to you under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#ifdef CONFIG_LIBC_EXECFUNCS
#include <spawn.h>
#endif
#include <getopt.h>
#include <syslog.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* syslogd program version */
#define SYSLOGD_VERSION "0.0.0"
/* Minimum buffer size check */
#if CONFIG_SYSTEM_SYSLOGD_ENTRYSIZE < 480
#error "SYSTEM_SYSLOGD_ENTRYSIZE must be more than 480 to satisfy RFC 5424"
#endif
/* Maximum number of arguments that can be passed to syslogd */
#define MAX_ARGS 8
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: print_usage
****************************************************************************/
static void print_usage(void)
{
fprintf(stderr, "usage:\n");
fprintf(stderr, " %s [-vdn]\n", CONFIG_SYSTEM_SYSLOGD_PROGNAME);
}
/****************************************************************************
* Public Functions
****************************************************************************/
int main(int argc, FAR char **argv)
{
int fd;
int sock;
int c;
ssize_t bread;
ssize_t bsent;
ssize_t endpos;
char *end;
size_t bufpos = 0;
struct sockaddr_in server;
char buffer[CONFIG_SYSTEM_SYSLOGD_ENTRYSIZE];
bool debugmode = false;
bool skiplog = false;
#ifdef CONFIG_LIBC_EXECFUNCS
pid_t pid;
bool background = true;
char *new_argv[MAX_ARGS + 1];
#endif
/* Parse command line options */
while ((c = getopt(argc, argv, ":vdn")) != -1)
{
switch (c)
{
case 'v':
/* Print version and exit */
printf("%s " SYSLOGD_VERSION " (NuttX)\n", argv[0]);
return EXIT_SUCCESS;
case 'd':
/* Enable debug mode and stay in foreground */
debugmode = true;
printf("Enabling debug mode.\n");
#ifdef CONFIG_LIBC_EXECFUNCS
background = false;
#endif
break;
case 'n':
/* Stay in foreground */
#ifdef CONFIG_LIBC_EXECFUNCS
background = false;
#endif
break;
case '?':
print_usage();
exit(EXIT_FAILURE);
break;
}
}
/* Run this program in the background as a spawned task if the background
* option was selected.
*/
#ifdef CONFIG_LIBC_EXECFUNCS
if (background)
{
/* Set up the arguments, which is identical to the original except with
* an added `-n` flag to ensure that the new process does not 'respawn'
*/
if (argc > MAX_ARGS)
{
fprintf(stderr,
"Cannot spawn syslogd daemon: arg count %d exceeds %d",
argc, MAX_ARGS);
return EXIT_FAILURE;
}
new_argv[0] = argv[0]; /* Same program name */
new_argv[1] = "-n"; /* Prevent daemon from spawning another child */
memcpy(&new_argv[2], &argv[1], sizeof(char *) * (argc - 1));
/* Spawn the child for backgrounding now */
if (posix_spawn(&pid, argv[0], NULL, NULL, new_argv, NULL) != 0)
{
fprintf(stderr, "Failed to fork() to background process: %d\n",
errno);
return EXIT_FAILURE;
}
else
{
/* We succeeded in spawning, exit now. */
return EXIT_SUCCESS;
}
}
#endif /* CONFIG_LIBC_EXECFUNCS */
/* Set up client connection information */
server.sin_family = AF_INET;
server.sin_port = htons(CONFIG_SYSTEM_SYSLOGD_PORT);
server.sin_addr.s_addr = inet_addr(CONFIG_SYSTEM_SYSLOGD_ADDR);
if (server.sin_addr.s_addr == INADDR_NONE)
{
fprintf(stderr, "Invalid address '%s'\n", CONFIG_SYSTEM_SYSLOGD_ADDR);
return EXIT_FAILURE;
}
/* Create a UDP socket */
if (debugmode)
{
printf("Creating UDP socket %s:%u\n", CONFIG_SYSTEM_SYSLOGD_ADDR,
CONFIG_SYSTEM_SYSLOGD_PORT);
}
sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0)
{
fprintf(stderr, "Couldn't create UDP socket: %d\n", errno);
return EXIT_FAILURE;
}
/* Open syslog stream */
if (debugmode)
{
printf("Opening syslog device '%s' to read entries.\n",
CONFIG_SYSLOG_DEVPATH);
}
fd = open(CONFIG_SYSLOG_DEVPATH, O_RDWR);
if (fd < 0)
{
fprintf(stderr, "Could not open syslog stream: %d", errno);
close(sock);
return EXIT_FAILURE;
}
/* Transmit syslog messages forever */
if (debugmode)
{
printf("Beginning to continuously transmit syslog entries.\n");
}
for (; ; )
{
/* Read as much data as possible into the remaining space in our buffer
*/
bread = read(fd, &buffer[bufpos], sizeof(buffer) - bufpos);
if (bread < 0)
{
fprintf(stderr, "Failed to read from syslog: %d", errno);
close(fd);
close(sock);
return EXIT_FAILURE;
}
if (bread == 0 && bufpos == 0)
{
/* Stream is over, terminate the program. */
if (debugmode)
{
printf("Syslog stream depleted, exiting...\n");
}
break; /* Successful exit */
}
/* Get the position of the '\n' character of the syslog entry,
* signifying its end.
*/
end = memchr(buffer, '\n', bufpos + bread);
if (end == NULL)
{
endpos = -1;
}
else
{
endpos = end - buffer;
}
if (endpos < 0)
{
/* If we couldn't find a newline character in the buffer, it means
* that the syslog entry doesn't end in our local buffer. We either
* need to:
*
* 1) If `bread` is 0, acknowledge that there is no more data to be
* read and our buffer will never contain a newline character. We
* can exit successfully in this case.
*
* 2) Read more and try again if there's still room in our buffer
*
* 3) Acknowledge that the syslog entry is too long for our buffer
* size, and skip it since we can't construct a UDP packet if
* that's the case.
*/
if (bread == 0)
{
break; /* Successful exit */
}
else if (bufpos + bread < sizeof(buffer))
{
/* Try to get more bytes in our buffer in case we read while
* more bytes were coming.
*/
bufpos += bread;
continue;
}
/* If we are here, there's no room left in our buffer. We need to
* skip this entry.
*/
fprintf(stderr, "Couldn't find end of log in local buffer, "
"skipping entry until the next newline...\n");
skiplog = true;
bufpos = 0; /* Wipe all buffer contents */
continue;
}
/* Print out entry if we are in debug mode and not skipping this line.
* `endpos` + 1 to print newline too.
*/
if (debugmode && !skiplog)
{
bsent = write(0, buffer, endpos + 1);
if (bsent < 0)
{
fprintf(stderr, "Couldn't print syslog entry: %d\n", errno);
}
}
/* Send entry over UDP (without newline) if we're not skipping this
* line
*/
if (!skiplog)
{
bsent = sendto(sock, buffer, endpos, 0,
(const struct sockaddr *)&server, sizeof(server));
if (bsent < 0)
{
fprintf(stderr, "Couldn't send syslog over UDP: %d\n", errno);
}
}
/* Take whatever bytes were leftover from our bulk read, and move them
* to the front of the buffer. Mark the new starting point for the next
* read so we don't overwrite them.
*
* endpos + 1 to overwrite vestigial newline character.
*/
bufpos = (bufpos + bread) - (endpos + 1);
if (bufpos > 0)
{
/* Copy from right after the newline character up until the end of
* unread bytes
*/
memcpy(buffer, &buffer[endpos + 1], bufpos);
}
/* If we got here while skipping a log, it means the endpos for the log
* being skipped was found. Now we're done skipping the log.
*/
if (skiplog)
{
skiplog = false;
}
}
close(fd);
close(sock);
return EXIT_SUCCESS;
}