5

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?

jesse_b
  • 37,005
ghborrmann
  • 53
  • 5
  • 1
    Are you sure that your script is ran ? Try adding a touch /etc/test. If file isn't created, it's probably because your script isn't executed. Then, check ownership and permissions (chown root:root <yourscript> chmod 755 <youscript>) – binarym Jan 03 '20 at 16:35
  • 2
    The script does run, as evidenced by the intended delay in shutdown when a card is left inserted. What doesn't work is output to console. – ghborrmann Jan 03 '20 at 18:48

2 Answers2

7

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 a StandardOutput=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-supplied debug-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 and StandardInput 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 the run program of a similar nosh service, the run 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):

% cat /etc/service-bundles/services/emergency-login@console/service/run
#!/bin/nosh
#Emergency super-user login on console
setsid
vc-get-tty console
open-controlling-tty
vc-reset-tty --hard-reset
line-banner "Emergency mode log-in."
emergency-login
%

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 the TERM environment variable so that your programs can look up the right escape and control sequences to emit in the terminfo database. Again, witness debug-shell.service. (In the aforegiven run program, for comparison, the vc-get-tty tool sets TERM.)

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 service run 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 be User=daemon:

% cat /etc/service-bundles/services/monitor-fsck-progress/service/run
#!/bin/nosh
#local socket used for monitor-fsck-progress
local-stream-socket-listen --systemd-compatibility --backlog 2 --mode 0644 /run/fsck.progress
setsid
setlogin -- daemon
vc-get-tty console
fdmove -c 4 2
open-controlling-tty
fdmove 2 4
setuidgid -- daemon
./service
%

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

JdeBP
  • 68,745
  • Thanks for your detailed explanation. It cleared up a lot of my confusion about systemd and its services and scripts. – ghborrmann Jan 03 '20 at 22:08
  • Adding TTYPath= and StandardOutput= to my service file worked (after fixing a selinux denial). However, I expected that the echo output would appear interspersed among the other shutdown messages. Instead, no other messages appeared until the script finished; at that time, messages (presumably queued during the interval) appeared. Should I be doing something else to prevent interruption of console output from other operations? – ghborrmann Jan 03 '20 at 22:19
  • That's really another question. Read https://unix.stackexchange.com/q/310737/5132 and then ask it. (-: – JdeBP Jan 03 '20 at 22:23
0

You're very close. Before using echo early in your script, use reset above it. Example:

# echo ^G
# reset
# echo ^G MESSAGE TO PUT ON THE TERMINAL

Sending a ^G to echo rings the terminal bell, i.e., beeps.

K7AAY
  • 3,816
  • 4
  • 23
  • 39
  • reset does nothing, probably because there is no terminal to write to at shutdown. – ghborrmann Jan 03 '20 at 18:39
  • Can you alter flashshutdown.sh to do echo-reset-echo before the shutdown actually starts? – K7AAY Jan 03 '20 at 18:56
  • The only way I know of to accomplish echo-reset-echo before the shutdown starts is to use ExecStart, but this would execute at startup, wouldn't it?. What I'm looking for is a way to produce output during the shutdown. – ghborrmann Jan 03 '20 at 19:27