0

I have a systemd service which runs and does its thing. Periodically I need it to upgrade itself, which requires a shutdown and a restart of the service. For question purposes the upgrade script can be as simple as:

echo "Stopping service..."
systemctl stop myservice
echo "Doing some stuff..."
sleep 10s
echo "Starting service..."
systemctl start myservice

I want to call this within the service itself, preferably using boost::process:

boost::process::child instexe{
    boost::process::search_path("bash"), 
    std::vector<std::string>{"installerscript.sh"},
    boost::process::start_dir("/installer/folder"),
    boost::process::std_out > "/some/log/file.txt"
};
instexe.detach();

The problem is that as soon as the script calls systemctl stop myservice, the installer script is killed.

Is there a way I can do what I want to do with boost::process? Or how can I do it?

Addy
  • 2,414
  • 1
  • 23
  • 43

2 Answers2

0

If the upgrades are at predefined period you can think of using crontab. https://opensource.com/article/17/11/how-use-cron-linux

00 09-17 * * 1-5 /usr/local/bin/installerScript.sh

The above entry in crontab will make the program upgrade every hour between 9 am to 5pm from Monday to Friday. There are many combinations that you can think and configure.

Nik
  • 1,294
  • 10
  • 16
  • 1
    I was thinking of doing something like this, but it really needs to be on demand. Of course, I could probably have the code in my service create a cron, but that's here nor there. – Addy Sep 05 '22 at 11:30
0

Is there a way I can do what I want to do with boost::process? Or how can I do it?

If you have the child process killing the parent, there's always going to be a race condition by definition.

The quick hack is to put a sleep statement at the start of the installer script, but the correct solution is to explicitly synchronize with the child:

  1. have the installer script detect whether it's running interactively (ie, being run manually from a terminal instead of by your service)
  2. if it is non-interactive (your use case), have it wait for some input in stdin
  3. connect the stdin pipe when you create the child
  4. detach the child and then write something to tell the child it's safe

Other synchronization mechanisms are available, you could use a lockfile or a signal - you just need to make sure the child doesn't do anything until after the parent has detached it.

I turns out (from this question, which leads to the excellent-but-unfindable systemd.kill manpage) that systemd has four different ways of stopping a unit, controlled by the KillMode variable in your unit configuration:

  • control-group will send SIGTERM (by default, overridable with KillSignal) to every process in the unit's cgroup. That means both parent and child.
  • mixed will send SIGTERM (or KillSignal) to your main process and SIGKILL to the child.
  • process will kill only the main process and leave the child alone
  • none is not recommended, it will just run your ExecStop procedure

You can probably just set KillMode=process, but note that if SendSIGKill or SendSIGUP are true, those signals will still be delivered to your child after TimeoutStopSec.

It seems like it might be simpler to restart your service and have a launch script that can update it at startup, or to perform the update in your ExecStop procedure, than to persuade systemd to leave the child alone until the update is complete, without the risk of a hung child updater hanging around forever.

Either way, your remaining problems are exclusively with systemd rather than with boost.Process.

Useless
  • 64,155
  • 6
  • 88
  • 132
  • The only synchronization I need is handled by shutting down the service. The upgrade script blocks on the `systemctl stop` call. The only problem is that it also exits out. – Addy Sep 05 '22 at 23:39
  • No, you also need synchronization to prevent the race condition that is your whole problem. _Something_ must prevent the child process from running `systemctl stop` until _after_ the parent process has detached it. That something is a form of synchronization, since it's coordinating two asynchronous processes. – Useless Sep 06 '22 at 08:15
  • Though I still can't get it to work even with the sleep and confirming the parent's code to detach it has run. I think I need to type up some sample code of a service to see if someone can tell me what I'm doing wrong. – Addy Sep 06 '22 at 11:26
  • Did you put the child in a different process group, as I suggested? – Useless Sep 07 '22 at 13:05
  • Yes, I did. I tried with `boost::process::spawn` and `boost::process::group`, and I also tried with `fork()` and `setpgid()`. I'm not 100% sure I did it right, which is why I want to find time to make a little sample program and type up another question. – Addy Sep 07 '22 at 21:44
  • My working theory now is that I'm having problems because I'm using `systemctl`. I assumed `systemctl` was doing something like `kill -SIGHUP ` for the service's PID, but instead I think it's doing something like `pkill -SIGHUP `. If I ignore `SIGHUP` in the child process, the `systemctl stop` timesout, and I'm left with a single process. – Addy Sep 08 '22 at 10:34
  • Turns out there's a load of configuration around this, and that's probably where you need to focus. – Useless Sep 08 '22 at 12:32