Solved Using rc.d script with /usr/sbin/daemon to start and stop a script

I am trying to write an rc.d script to start and stop a (Python) script. Starting the command seems to be working, but stopping it is not yet the way I want.

My goal is to ensure the script receives a SIGTERM signal when it should shut down; that seems not to be the case; I think only the daemon process is receiving the signal.

I used Practical rc.d scripting and Supervised FreeBSD rc.d script for a Go daemon as a reference when starting out to write the rc.d script.

Here is what I have come up with so far:

Bash:
#!/bin/sh
#
# PROVIDE: pmm
# REQUIRE: networking
# KEYWORD:
#set -v
#set -x

. /etc/rc.subr

name="pmm"
rcvar="${name}_enable"
pmm_user="memyselfandi"
pmm_command="/usr/home/memyselfandi/.virtualenvs/pmm-test/bin/pmm"  # NOT an rc.subr thingie, regular var
pidfile="/usr/local/var/run/${name}.pid"
command="/usr/sbin/daemon"
procname="daemon:"  # needed?

start_precmd="${name}_prestart"

load_rc_config $name
: ${pmm_enable:=no}
: ${pmm_config_file:=/usr/home/memyselfandi/etc/pmm_conf.py}
: ${pmm_log_file=""}

# command and command_args only used for "start", I think?
# command_args will be added to the command line after $pmm_flags
# do NOT add dashed options to command_args, use prestart and rc_flags, see
# https://www.freebsd.org/doc/en/articles/rc-scripting/article.html#rc-flags
#command_args="-P ${pidfile} -r -S -T pmm -o /tmp/pmm.log ${pmm_command} start --config-file=${pmm_config_file}"

pmm_prestart() {
    if [ -z "${pmm_log_file}" ]; then
        # pmm_log_file is empty/not set
        rc_flags="-P ${pidfile} -r -S -T ${name} ${pmm_command} ingest start --config-file=${pmm_config_file}"
    else
        rc_flags="-P ${pidfile} -r -o ${pmm_log_file} ${pmm_command} ingest start --config-file=${pmm_config_file}"
    fi
    debug "using config file: ${pmm_config_file}"
}

pmm_deep_status() {
    echo "Status:"
    ${pmm_command} status --config-file=${pmm_config_file}
    echo "Status done"
}

run_rc_command "$1"

I have tried to "play" with the -P/-p flag of daemon() to no avail.

The problem seems to be that the PID that ends up in PIDFILE (obviously) is the PID of the daemon() utility, not that of my script, and when the stop command is issued only that daemon PID is sent the SIGTERM signal, but not my script (which has a different PID).

How can I get daemon to "forward" the SIGTERM signal to my script?

EDIT:

To be exact: the script is terminated by issuing the stop command, it is just not receiving the SIGTERM signal.

When the script is running, ps shows:

Code:
# ps aux | grep pmm-test
memyselfandi 13454   3.0  0.2 49964 40944  -  S    10:42       0:00.85 /usr/home/memyselfandi/.virtualenvs/pmm-test/bin/python /usr/home/memyselfandi/.virtualenvs/pmm-test/bin/pmm ingest start --config-file=/usr/home/memyselfandi/etc/pmm_conf.py (python3.6)
memyselfandi 13453   0.0  0.0 11004  2392  -  Ss   10:42       0:00.00 daemon: /usr/home/memyselfandi/.virtualenvs/pmm-test/bin/pmm[13454] (daemon)
# service pmm onestatus
pmm is running as pid 13453.

And when I send the SIGTERM signal to PID 13454 I see the signal hander of my script fire. When using stop it does not (which is the same if I send the SIGTERM signal to 13453).
 
I don't know if this is an issue or not, but the username above looks like it should be "memyselfandi".
Thanks for pointing this out; I "faked" this for the post in this forum though (and fixed it now), that was just a typo in the post (not in the real script). Sorry!
 
I utilize the rc-script of www/searx which does handle the python script by the way of daemon (8) as well. I compared it with your script, and one major structural difference is, that it manually creates the pid file in the start_precmd. Actually, I wondered already, on why this is necessary, however, I didn’t care to have a deeper look, because this script does work for my purpose:
Code:
#!/bin/sh
# $FreeBSD: head/www/searx/files/searx.in 463944 2018-03-09 08:34:57Z yuri $

# PROVIDE: searx
# REQUIRE: DAEMON NETWORKING
# BEFORE: LOGIN
# KEYWORD: shutdown

# Add the following lines to /etc/rc.conf to enable searx:
# searx_enable="YES"
#
# searx_enable (bool):    Set to YES to enable searx
#                Default: NO
# searx_conf (str):        searx configuration file
#                Default: ${PREFIX}/etc/searx.conf
# searx_user (str):        searx daemon user
#                Default: searx
# searx_group (str):        searx daemon group
#                Default: searx
# searx_flags (str):        Extra flags passed to searx

. /etc/rc.subr

name="searx"
rcvar=searx_enable

: ${searx_enable:="NO"}
: ${searx_user:="www"}
: ${searx_group:="www"}
: ${searx_flags:=""}

# daemon
pidfile="/var/run/${name}.pid"
python="/usr/local/bin/python3.7"
script_py="/usr/local/lib/python3.7/site-packages/${name}/webapp.py"
command=/usr/sbin/daemon
procname="daemon"
command_args=" -c -f -P ${pidfile} ${python} ${script_py}"
start_precmd="searx_precmd"

searx_precmd()
{
    install -o ${searx_user} /dev/null ${pidfile}
}

load_rc_config $name
run_rc_command "$1"

Note, also here, the pid which makes it into the pid file is that of the daemon process. However, this matches my understanding on how things work. Because, if you terminate the child while daemon is still running, daemon would restart it because of your -r flag. So, it does not make any sense to put the pid of the child into the pid file.

I had a quick look into the source code of daemon (/usr/src/usr.sbin/daemon/daemon.c), and according to this, it would pass the signals which it receives to its child process, so the question still remains why your script handler does not receive the very signal from daemon.
 
In your pmm_prestart, you should always add rc_flags to itself just in case. It's not important here, but it's good practice.
ie, rc_flags="-p ${pidfile} ... ${rc_flags}"

When it starts up, what is written to the pidfile? Is it the parent (daemon) or the child (your script)?
I ask because you say it "seems to be" which is too vague. Either it does or it doesn't.

So, run your script again, and report back on the state of the pidfile.

Also, for consideration:

Add a pmm_poststop() function for removing the pidfile, otherwise you will have dangling pidfiles. Meaning you invoke service xxx stop and the pidfile remains.

You can also add a pmm_stop() function, test for your pidfile then kill SIGTERM the python script.
 
Thank you for your replies!

it manually creates the pid file in the start_precmd
I just tried with the same functionality in my script, but it did not change anything.
However, this matches my understanding on how things work. Because, if you terminate the child while daemon is still running, daemon would restart it because of your -r flag. So, it does not make any sense to put the pid of the child into the pid file.
Exactly!
always add rc_flags to itself
Thank you for the hint! I have done this now.
When it starts up, what is written to the pidfile? Is it the parent (daemon) or the child (your script)?
It works like advertised: when I use -P the PID file contains the parent's/daemon's PID, when I use -p it contains the child's/script's PID.
otherwise you will have dangling pidfiles
Hmm - that does not seem to be the case. The PID file is gone after the stop command is issued, also without me removing it in a pmm_poststop() function.
You can also add a pmm_stop() function, test for your pidfile then kill SIGTERM the python script.
That is actually the (only) solution I see at the moment. I would probably use both, -P and -p together, and then send the SIGTERM to the child in pmm_stop(). But I would need to make sure all the "other tasks" that stop is doing for me now (like removing the PID file for example) are also in my pmm_stop(). Thing is: I do not know what stop is doing for me...

I also wonder by now if the signal really is not forwarded or if I just do not realise it is... I would expect to see the exact same thing when daemon(8) is forwarding the signal to my code as when I manually send the SIGTERM signal to my script's (child's) PID, no?
 
Thank you for your replies!


I just tried with the same functionality in my script, but it did not change anything.

Exactly!

Thank you for the hint! I have done this now.

It works like advertised: when I use -P the PID file contains the parent's/daemon's PID, when I use -p it contains the child's/script's PID.

Hmm - that does not seem to be the case. The PID file is gone after the stop command is issued, also without me removing it in a pmm_poststop() function.

That is actually the (only) solution I see at the moment. I would probably use both, -P and -p together, and then send the SIGTERM to the child in pmm_stop(). But I would need to make sure all the "other tasks" that stop is doing for me now (like removing the PID file for example) are also in my pmm_stop(). Thing is: I do not know what stop is doing for me...

I also wonder by now if the signal really is not forwarded or if I just do not realise it is... I would expect to see the exact same thing when daemon(8) is forwarding the signal to my code as when I manually send the SIGTERM signal to my script's (child's) PID, no?
Another thing is to put the pidfile creation in your script and then the rc script will just use the one you've ceated; providing you give it the same name of course.. dah.:D
 
Maybe I made some progress with this. There are several hints that the issue might be related to forking/child processes or some sort of a subshell executing the python script (and the "subshell" swallowing the signal).

So I went and changed from using -P to using -p (i.e. write the child's PID to the pidfile instead of daemon(8)'s PID.

Bash:
rc_flags="-p ${pidfile} -S -T ${name} ${pmm_command} ingest start --config-file=${pmm_config_file}"

For this to work (e.g. to make service pmm status work), I also had to adapt the procname= variable (thanks to rc.d doesn't see my pid file):

Bash:
procname="/usr/home/memyselfandi/.virtualenvs/pmm-test/bin/python"

Now I can start and stop my script!

But, as you might have noticed, I had to remove the -r flag from the daemon(8) invocation. This is because of an issue that is explained in the man page (I think):
... as the -p option will give you the child's ID to signal when you attempt to stop the service, causing daemon to restart the child.
And that is exactly what happens: daemon always restarts my script if I want to terminate it.
So it seems that -p and -r are mutually exclusive?
 
Looks like a minor flaw: IMHO the correct place to store the $pidfile is /var/run. One system, one place for all the services' $pidfiles.
 
Back
Top