Search code examples
systemd

How to design systemd path unit?


Program my_prog is to be launched after boot. It creates a Unix domain socket /tmp/my_prog.sock owned by root:root.

I am trying to do the followings:

  • Change the owner of the socket file to www-data:www-data after it is created.
  • Delete the socket file after the program exits.

This is my first version of unit file my_prog.service:

[Unit]
Description=My Program
After=network.target

[Service]
ExecStart=/usr/local/bin/my_prog
ExecStartPost=chmod www-data:www-data /tmp/my_prog.sock
ExecStopPost=rm -f /tmp/my_prog.sock

[Install]
WantedBy=multi-user.target

This version has two problems:

  • The owner of /tmp/my_prog.sock is never changed - always root:root.

  • /tmp/my_prog.sock is never removed after this service is stopped.

I guess it is commands chmod and rm getting executed too soon that gives me such unexpected results:

  • chmod runs before my_prog finishes creating the socket file, and
  • rm runs before my_program exits (my_program prohibits its socket file from being deleted when it is running?).

What follow are my second version, which fails to give me correct results, either:

file my_prog.service:

[Unit]
Description=My Program
After=network.target

[Service]
ExecStart=/usr/local/bin/my_prog

[Install]
WantedBy=multi-user.target

file my_prog-socket.path:

[Unit]
Description=My program - notify socket existence

[Path]
PathExists=/tmp/my_prog.sock

file my_prog-socket.service:

[Unit]
Description=My program - change owner and remove socket

[Service]
ExecStart=chown www-data:www-data /tmp/my_prog.sock
ExecStopPost=rm -f /tmp/my_prog.sock

I have run out of all tricks. What's wrong with my unit files? Is there more elegant design than above?

Thanks!

P.S.: For those who are interested, this legacy version /etc/init.d/my-prog works as expected, at least:

#!/bin/sh
### BEGIN INIT INFO
# Provides:         my-program
# Default-Start:    2 3 4 5
# Default-Stop:     0 1 6
# Short-Description:    My Program
# Description:      My Program
### END INIT INFO


PATH=/sbin:/bin:/usr/sbin:/usr/bin
DAEMON=/usr/local/bin/my_prog
NAME=my-program
DESC="My Program"
SCRIPTNAME=/etc/init.d/$NAME
PIDFILE=/var/run/my-prog.pid
SOCKET_FILE="/tmp/my-prog.sock"

test -x $DAEMON || exit 0

grant_socket_access()
{
    #Wait program to create socket.
    count=1
    while [ "$count" -lt "50" ]
    do
        if [ -S $SOCKET_FILE ]
        then
            chown www-data:www-data $SOCKET_FILE
            return 0
        fi
        sleep 0.2
        count=`expr $count + 1`
    done
    echo >&2 "$NAME fails to grant access to Unix socket file: $SOCKET_FILE"
    return 1
}

. /lib/lsb/init-functions

case "$1" in
    start)
        log_daemon_msg "Starting $DESC" $NAME
        rm -f $SOCKET_FILE
        if start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -b -m
        then
            grant_socket_access
        else
            rm -f $SOCKET_FILE
        fi
        log_end_msg $?
        ;;
    stop)
        log_daemon_msg "Stopping $DESC" $NAME
        rm -f $SOCKET_FILE
        if start-stop-daemon --stop --retry TERM/10/KILL/5 --quiet --pidfile $PIDFILE --exec $DAEMON --remove-pidfile
        then
            log_daemon_msg "$DESC" "$NAME stopped"
            log_end_msg 0
        else
            log_end_msg 1
        fi
        ;;
    reload|force-reload)
        log_daemon_msg "Reloading $DESC" $NAME
        rm -f $SOCKET_FILE
        if start-stop-daemon --stop --signal HUP --quiet --pidfile $PIDFILE --exec $DAEMON
        then
            grant_socket_access
            log_end_msg $?
        else
            log_end_msg 1
        fi
        ;;
    restart)
        log_daemon_msg "Restarting $DESC" $NAME
        $0 stop
        $0 start
        ;;
    status)
        status_of_proc -p "$PIDFILE" "$DAEMON" "$NAME" && exit 0 || exit $?
        ;;
    *)
        echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload|status}" >&2
        exit 1
        ;;
esac

exit 0

Edit

Following the recipe kindly provided by @Mark Stosberg, I have modified my original first version to have /bin/chown and /bin/rm just now.

I get the desired result from line ExecStopPost=/bin/rm -f /tmp/my_prog.sock in the new version. However, ExecStart=/bin/chown www-data:www-data /tmp/my_prog.sock outputs this error:

chown[8388]: /bin/chown: connot access '/tmp/my_prog.socket'

Solution

  • Running your script through your systemd-analyze verify ./your-file.service reveals the problems:

    [/home/mark/tmp/t.service:7] Executable path is not absolute, ignoring: chmod www-data:www-data /tmp/my_prog.sock                                   
    [/home/mark/tmp/t.service:8] Executable path is not absolute, ignoring: rm -f /tmp/my_prog.sock  
    

    The documentation in man systemd.service documents the requirement that the executable paths must be absolute.


    Your error refers to a file with a .socket extension, but your example shows a .sock extension. Confirm that you are using .sock or .socket consistently everywhere.


    If your service doesn't need to run as root, you can improve the security and avoid the need to chown, by using the User= and Group= directives to run the service as a different user. This will create a socket owned by that user instead of root.