Use the following shell function:
relay () (
#!/bin/sh
sink="${1:-/dev/ttyS0}"
exec 4<&0 2>/dev/null
while :; do
cat 3<"$sink" >/proc/self/fd/3
<&4 cat >/dev/null & sleep 2; kill -s PIPE "$!" || exit
done
)
like this:
my_daemon | relay
What looks like a shebang (#!/bin/sh) means nothing inside the function. It's just an indication what shell the code is for. If you want to build a script, not a function, save the body of the function in an executable file and then the shebang will matter.
There are few tricks:
Solutions that use cat >/dev/ttyS0 or similar redirection are potentially flawed. I assume your words "the serial device gets recreated" mean there is a time window when /dev/ttyS0 does not exist. I also assume the user running the code (possibly root) may be able to create files in /dev/. If >/dev/ttyS0 happens when there is no such file, a regular file will be created. You can test like ! [ -e /dev/ttyS0 ], but the file may disappear after the test and before >/dev/ttyS0.
For this reason I use cat 3<"$sink" >/proc/self/fd/3, where $sink is /dev/ttyS0 by default. The first redirection tries to open the file for reading; the second redirection tries to redirect stdout to the same file. The trick is the second redirection never creates a new file.
An alternative would be to arrange things, so the user has access to /dev/ttyS0, but cannot create files in /dev/. In this case the trick would not be needed and solutions using >/dev/ttyS0 should be safe. It may be a valid approach if you cannot use /proc/self/fd/3 for whatever reason. The first cat would be just:
cat >"$sink"
The purpose of the first cat is to send data to the sink. The redirections may fail or the sink may disappear eventually. Here comes the second cat. The purpose of the second cat is to discard data when there is no sink. If /dev/ttyS0 should be recreated immediately, we wouldn't need the second cat. But I'm not sure if the pathname is guaranteed to reappear very fast in your case. I guess when there is no /dev/ttyS0, you want my_daemon to continue rather than to block. The second cat lets it continue.
A simple cat >/dev/null is not a good idea, it could run indefinitely even after /dev/ttyS0 gets recreated. The trick is to run it asynchronously and kill it after few seconds. Then the code loops and the first cat tries to open the sink again.
<&4 is needed because of the trick. When job control is disabled (and it is by default in a subshell our function is, or in a script), commands terminated with & get their stdin redirected to /dev/null or an equivalent file. Thanks to prior exec 4<&0 and this <&4, the second cat can read from the stdin of the function anyway.
kill will (most likely) fail if the second cat is no more after sleep 2. This will happen if my_daemon exits. kill … || exit is a trick to detect EOF condition. E.g. date | relay should terminate, although after some delay because of sleep. Without the trick the code would loop indefinitely.
In case of EOF it may happen the PID of the second cat gets reused and kill targets the wrong process. AFAIK in Linux PIDs are allocated sequentially; it's unlikely the sequence wraps around and gets to the same PID in about two seconds. The trick assumes that if the second cat exits early because of EOF, kill will see no such process and fail, thus exit will happen.
The default $sink is /dev/ttyS0, still you can use another pathname by specifying it as the first command line argument (e.g. … | relay foo). If you want to test on a regular file, remember that removing a file when it's open does not destroy it. In my tests I used set -x to see what happens and then Ctrl+d to easily terminate (the first) cat on demand.
My testbed: Linux kernel 5.15.0, sh (ash) from Busybox 1.30.1.
straceon "my_daemon", what error/result is actually causing the exit? - could anything be generating a SIGHUP, so mightnohuphelp?nohuporsetpgrpdo not help. From how I understand it, it's that when you enterexitthen the process gets restarted per what is written ininittabfile (mind you it's a Busybox-based system) which then causes this behaviour.syslogso it starts dropping log messages when systemd decides for some asinine reason that it gets "too many" is probably the greatest sin of systemd - other than its fundamental flaw of being a brain-dead, horribly-implemented, unreliable solution in search of a problem in the first place.