From a81fd9f43408fddcf4cf2cc23eae557ed4a85a74 Mon Sep 17 00:00:00 2001 From: Luca Toniolo <10792599+grandixximo@users.noreply.github.com> Date: Sat, 17 Oct 2026 16:02:16 +1100 Subject: [PATCH] rtapi: end an error or warning that lacks the newline An error or a warning whose text does not end in a newline ran on into the next message, so a repeated homing error read "j2 end of move in home state 5j2 end of move in home state 5" (#4689). 58 error and warning formats in the tree lack the newline, and their translations match them. The default message handlers of uspace now add the newline where they print: in the main thread of rtapi_app, in the queue for messages from its realtime threads, and in user processes. Only errors and warnings get it. Info and debug messages, and rtapi_print(), may build a line from several calls, as motion's debug output, the PPMC driver and rtapi_app's cpuset report do. RTAI kernel modules are not changed. tests/rtapi-msg-newline prints errors and a warning without a newline from the main thread of rtapi_app and from a realtime thread, and an info line in two parts. --- src/rtapi/uspace_common.h | 10 ++++++++++ src/rtapi/uspace_rtapi_main.cc | 16 ++++++++++++++- src/rtapi/uspace_ulapi.c | 16 ++++++++------- tests/rtapi-msg-newline/.gitignore | 1 + tests/rtapi-msg-newline/README | 9 +++++++++ tests/rtapi-msg-newline/expected | 5 +++++ tests/rtapi-msg-newline/msgline.comp | 29 ++++++++++++++++++++++++++++ tests/rtapi-msg-newline/msgline.hal | 6 ++++++ tests/rtapi-msg-newline/print.py | 15 ++++++++++++++ tests/rtapi-msg-newline/skip | 4 ++++ tests/rtapi-msg-newline/test.sh | 6 ++++++ 11 files changed, 109 insertions(+), 8 deletions(-) create mode 100644 tests/rtapi-msg-newline/.gitignore create mode 100644 tests/rtapi-msg-newline/README create mode 100644 tests/rtapi-msg-newline/expected create mode 100644 tests/rtapi-msg-newline/msgline.comp create mode 100644 tests/rtapi-msg-newline/msgline.hal create mode 100755 tests/rtapi-msg-newline/print.py create mode 100755 tests/rtapi-msg-newline/skip create mode 100755 tests/rtapi-msg-newline/test.sh diff --git a/src/rtapi/uspace_common.h b/src/rtapi/uspace_common.h index 4032e7eb2fa..e6962ef5cc5 100644 --- a/src/rtapi/uspace_common.h +++ b/src/rtapi/uspace_common.h @@ -230,6 +230,16 @@ int rtapi_shmem_delete(int handle, int module_id) void default_rtapi_msg_handler(msg_level_t level, const char *fmt, va_list ap); +/* An error or a warning is a line of its own. The default handlers end + one that lacks the newline, so that the next message does not run on + behind it. The other levels may print a line in parts. */ +static inline int rtapi_msg_needs_newline(msg_level_t level, const char *msg) +{ + size_t n = strlen(msg); + return (level == RTAPI_MSG_ERR || level == RTAPI_MSG_WARN) + && n > 0 && msg[n - 1] != '\n'; +} + static rtapi_msg_handler_t rtapi_msg_handler = default_rtapi_msg_handler; rtapi_msg_handler_t rtapi_get_msg_handler(void) { diff --git a/src/rtapi/uspace_rtapi_main.cc b/src/rtapi/uspace_rtapi_main.cc index 9946f3d16d1..337bce75d90 100644 --- a/src/rtapi/uspace_rtapi_main.cc +++ b/src/rtapi/uspace_rtapi_main.cc @@ -1708,9 +1708,23 @@ void default_rtapi_msg_handler(msg_level_t level, const char *fmt, va_list ap) { message_t m; m.level = level; vsnprintf(m.msg, sizeof(m.msg), fmt, ap); + if (rtapi_msg_needs_newline(level, m.msg)) { + size_t n = strlen(m.msg); + if (n == sizeof(m.msg) - 1) + n--; /* truncated: the newline replaces the last character */ + m.msg[n] = '\n'; + m.msg[n + 1] = '\0'; + } rtapi_msg_queue.push(m); } else { - vfprintf(level == RTAPI_MSG_ALL ? stdout : stderr, fmt, ap); + FILE *f = level == RTAPI_MSG_ALL ? stdout : stderr; + char *msg; + if (vasprintf(&msg, fmt, ap) < 0) + return; + fputs(msg, f); + if (rtapi_msg_needs_newline(level, msg)) + fputc('\n', f); + free(msg); } } diff --git a/src/rtapi/uspace_ulapi.c b/src/rtapi/uspace_ulapi.c index 0bec4b70257..65509783d3b 100644 --- a/src/rtapi/uspace_ulapi.c +++ b/src/rtapi/uspace_ulapi.c @@ -75,11 +75,13 @@ void rtapi_delay(long ns) { } void default_rtapi_msg_handler(msg_level_t level, const char *fmt, va_list ap) { - if(level == RTAPI_MSG_ALL) { - vfprintf(stdout, fmt, ap); - fflush(stdout); - } else { - vfprintf(stderr, fmt, ap); - fflush(stderr); - } + FILE *f = level == RTAPI_MSG_ALL ? stdout : stderr; + char *msg; + if (vasprintf(&msg, fmt, ap) < 0) + return; + fputs(msg, f); + if (rtapi_msg_needs_newline(level, msg)) + fputc('\n', f); + fflush(f); + free(msg); } diff --git a/tests/rtapi-msg-newline/.gitignore b/tests/rtapi-msg-newline/.gitignore new file mode 100644 index 00000000000..284bd9713dd --- /dev/null +++ b/tests/rtapi-msg-newline/.gitignore @@ -0,0 +1 @@ +halrun.out diff --git a/tests/rtapi-msg-newline/README b/tests/rtapi-msg-newline/README new file mode 100644 index 00000000000..8b724f50a9e --- /dev/null +++ b/tests/rtapi-msg-newline/README @@ -0,0 +1,9 @@ +Errors and warnings whose text lacks the newline are still lines of +their own (#4689). + +msgline prints two errors without a newline from its setup, which runs +in the main thread of rtapi_app, and an error and a warning without a +newline from its function, whose messages rtapi_app queues and prints +from another thread. Each must be a line of its own in the output. An +info message printed in two parts must stay one line: the levels other +than errors and warnings may build a line from several messages. diff --git a/tests/rtapi-msg-newline/expected b/tests/rtapi-msg-newline/expected new file mode 100644 index 00000000000..36961ecb8e5 --- /dev/null +++ b/tests/rtapi-msg-newline/expected @@ -0,0 +1,5 @@ +msgline: error at load +msgline: error at load +msgline: error in the thread +msgline: warning in the thread +msgline: info in parts diff --git a/tests/rtapi-msg-newline/msgline.comp b/tests/rtapi-msg-newline/msgline.comp new file mode 100644 index 00000000000..536c8b50fca --- /dev/null +++ b/tests/rtapi-msg-newline/msgline.comp @@ -0,0 +1,29 @@ +component msgline "Test component for tests/rtapi-msg-newline: prints messages that lack a newline"; + +pin io bool print "set to print the messages from the realtime thread once; cleared by the function"; + +function _; +option extra_setup; +option period no; +license "GPL"; +author "Luca Toniolo"; +;; +EXTRA_SETUP() { + (void)__comp_inst; + (void)prefix; + (void)extra_arg; + rtapi_print_msg(RTAPI_MSG_ERR, "msgline: error at load"); + rtapi_print_msg(RTAPI_MSG_ERR, "msgline: error at load"); + return 0; +} + +FUNCTION(_) { + if (!print) + return; + print_set(0); + rtapi_print_msg(RTAPI_MSG_ERR, "msgline: error in the thread"); + rtapi_print_msg(RTAPI_MSG_WARN, "msgline: warning in the thread"); + /* the other levels may print a line in parts */ + rtapi_print_msg(RTAPI_MSG_INFO, "msgline: info "); + rtapi_print_msg(RTAPI_MSG_INFO, "in parts\n"); +} diff --git a/tests/rtapi-msg-newline/msgline.hal b/tests/rtapi-msg-newline/msgline.hal new file mode 100644 index 00000000000..b985977d858 --- /dev/null +++ b/tests/rtapi-msg-newline/msgline.hal @@ -0,0 +1,6 @@ +loadrt threads name1=t period1=1000000 +loadrt msgline +addf msgline.0 t +debug 3 +start +loadusr -w ./print.py diff --git a/tests/rtapi-msg-newline/print.py b/tests/rtapi-msg-newline/print.py new file mode 100755 index 00000000000..3cd8457a458 --- /dev/null +++ b/tests/rtapi-msg-newline/print.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python3 +# Make msgline print from the realtime thread and wait until it has. +# A wait that takes more than 10 s of wall time means the machine is +# dying; the test then aborts. + +import hal +import sys +import time + +hal.set_p("msgline.0.print", "1") +deadline = time.monotonic() + 10 +while hal.get_p("msgline.0.print"): + if time.monotonic() > deadline: + sys.exit("FAIL: the thread did not run msgline.0 in 10 s") + time.sleep(0.01) diff --git a/tests/rtapi-msg-newline/skip b/tests/rtapi-msg-newline/skip new file mode 100755 index 00000000000..14f65ebbd51 --- /dev/null +++ b/tests/rtapi-msg-newline/skip @@ -0,0 +1,4 @@ +#!/bin/sh +# Builds a realtime component with halcompile, which needs the build +# tree. Skip when testing installed packages. +[ -z "$SYSTEM_BUILD" ] diff --git a/tests/rtapi-msg-newline/test.sh b/tests/rtapi-msg-newline/test.sh new file mode 100755 index 00000000000..8c4801e3ed6 --- /dev/null +++ b/tests/rtapi-msg-newline/test.sh @@ -0,0 +1,6 @@ +#!/bin/bash +set -e +halcompile --install msgline.comp >/dev/null +timeout -k 5 60 halrun -f msgline.hal > halrun.out 2>&1 +# the messages of msgline, each on a line of its own +grep '^msgline:' halrun.out