I am trying to prevent the user from shutting down or rebooting without physically removing a flash card from the machine. To do that, I have written a SystemD service, removeflash.service:
[Unit]
Description=Prompt user to remove flash card
[Service]
ExecStop=/usr/lib/systemd/flashshutdown.sh
Type=oneshot
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
Requires=rsyslog.service
The flashshutdown.sh is a bash script, as follows:
#! /bin/bash
#
while [ -e /dev/flash ] ; do
echo "Please remove flash card"
logger "GHB: Please remove flash card"
sleep 5
done
I didn't expect the echo to do anything at shutdown, and it doesn't. However, I was hoping the logger command would work. Without the Requires=rsyslog.service, my service exited after the rsyslog.service was shut down; I inserted the requirement to prevent that happening, but the only difference is that the shutdown of removeflash.service is earlier in the shutdown sequence. By a bit of luck, the purpose of the service is rescued by the fact that systemd itself outputs to the console a message that it is running a job for Prompt user to remove flash card.
What is the proper way to issue a message to the console?
Best Answer
Standard output and error of services under service management — be it s6, runit, perp, daemontools, nosh service management, or systemd — is not the console. It is a pipe connected to some form of log writer.
For a systemd service you need a
TTYPath=/dev/console
and aStandardOutput=tty
in the .INI file to change this,StandardInput=tty
if you want to read (but you do not) as well as write. Witness systemd's pre-supplieddebug-shell.service
.This is a general principle that is not systemd specific. Dæmon context involves (amongst other things) not having a controlling terminal and not having open file descriptors for terminals, and under proper service management (such as all of the daemontools family) this is where one starts from, the state that a service process begins in when the supervisor/service manager forks it. So to use the console the service has to explicitly open it.
In systemd, the aforementioned
TTYPath
andStandardInput
settings cause the forked child process to open the console before it executes the service program proper. This is hidden inside systemd and you do not really get to see it. In therun
program of a similar nosh service, therun
program explicitly uses some of the nosh toolset chain-loading tools to do the same thing before executing the main program (emergency-login
in this case):Ironically, you do not need the
logger
command, or any syslog dependencies. There is no point in writing this interactive prompt to a log. But you really should run this service unprivileged, on principle. It does not need superuser privileges, for anything that it does.On another principle, don't make your script use
#!/bin/bash
unless you really are going to use Bashisms. One of the greatest speedups to system bootstrap/shutdown in the past couple of decades on Debian Linux and Ubuntu Linux was the switch of/bin/sh
from the Bourne Again shell to the Debian Almquist shell. If you are going to write a script as simple as this, keep it POSIX-conformant and use#!/bin/sh
anyway, even if you are not using Debian/Ubuntu, and on Debian/Ubuntu you'll get the Debian Almquist shell benefit as a bonus.Moreover, if you decide to have more than a glass TTY message, with a tool like
dialog
, you will need to set theTERM
environment variable so that your programs can look up the right escape and control sequences to emit in the terminfo database. Again, witnessdebug-shell.service
. (In the aforegivenrun
program, for comparison, thevc-get-tty
tool setsTERM
.)Similarly, you will want script errors to be logged. So standard error should be left pointing at the journal with
StandardError=journal
. Here's a nosh servicerun
program that illustrates the equivalent of this, and also shows dropping user privileges for a program that really does not need them, which in a systemd .INI file would beUser=daemon
:The program run by
./service
in this case presents a full-screen TUI on the console, whilst its errors are sent to the logging service. This is the stuff that one needs to do, under service managers in general, in order to run such programs as services, talking to the console.Of course, any such full-screen TUI program will conflict with systemd's "A stop job is running", also written to the console. But that is your problem. ☺
Further reading