Bug 29535

Summary: One service cannot disable a subsequent service during boot
Product: systemd Reporter: Adam Williamson <adamw>
Component: generalAssignee: Lennart Poettering <lennart>
Status: RESOLVED NOTABUG QA Contact:
Severity: normal    
Priority: medium    
Version: unspecified   
Hardware: Other   
OS: All   
Whiteboard:
i915 platform: i915 features:

Description Adam Williamson 2010-08-12 09:31:41 UTC
It's fairly common, for one reason or another, to want to have one service during the boot sequence disable a service which otherwise would have started later in the same boot sequence. For instance, on Fedora live images, two special services started early, 'livesys' and 'livesys-late', do preparation for the live boot, including disabling several services which it doesn't make sense to start in a live environment. It doesn't seem possible to do this with systemd (7): if you create two services, one of which is listed as running Before the second and which runs a command to disable the second, the second service still runs during that boot sequence and is only disabled on *subsequent* boots.

Here's a Fedora test package for this:

http://www.happyassassin.net/extras/systemd-disable-test-1.0-1.fc14.noarch.rpm

it creates two systemd services, test-first.service and test-last.service . test-last.service calls a script (/bin/test-last.sh) which writes 'started at (date started)' to the file /var/log/test-last.log . test-first.service runs a script which attempts to disable test-last.service. test-first.service has 'Before=test-last.service' specified.

To test, enable both services, and boot. You should observe that the 'started at (date)' message gets written to /var/log/test-last.log , indicating that even though test-first.service should have run before test-last.service and disabled it, test-last.service was in fact run. If you boot a second time, you should see that /var/log/test-last.log is not updated.
Comment 1 Lennart Poettering 2010-08-16 19:51:06 UTC
OK, I spent a while tonight to reproduce this, and then was surprised to find out that systemd actually does the right thing. ;-)

There are two reasons this doesn't work as expected:

- The initial transaction is calculated when systemd starts-up. systemd then compiles a list of things to do during bootup, looking at all dependencies. Those are then called "pending jobs" and kept around and bit by bit processed. When you disable a unit while we are processing the pending jobs (which does not make it completely unavailable, just causes it not to be considered when a transaction is compiled), then this has no influence on the jobs already pending. There's a very simple way to make sure that systemd also drops the queued jobs: just call "systemctl stop foobar.service" in addition to "systemctl disable foobar.service" and not only will the unit not be considered for future transactions anymore, but also a new transaction be schedule which contradicts the existing jobs and which hence causes them to be removed from the list of pending jobs. 

- The two .service files did not carry a service type field (i.e. Type= setting in [Service]). That means Type=simple was implied. In that case using ordering dependencies on it does not help for synchronization: systemd will just fork off the script and immediately continue with the next one. To do what you want to do you must make sure that systemd actually waits until all processes spawned for the service finished: use Type=oneshot for that (that's the systemd v8 name for it, in systemd v7 it was called Type=finish). Unfortunately the user must configure the type of the service manually, since we cannot autodect this. i.e. there is stuff like getty which just gets forked off, there's stuff like fsck, where we need to wait for completion and there's even stuff like traditional sysv daemons where we need to wait until the process we started exited and something was forked off in the background.

by making these two changes (i.e. placing "systemctl stop test-last.service" in /bin/test-first.sh and Type=oneshot in both .service files) things started to work as intended.

Thanks a lot for putting together this test case. Much appreciated!

I wonder if we can make this more discoverable, but tbh I am actually kinda convinced that the current behaviour of systemd here is pretty OK. What do you think?
Comment 2 Adam Williamson 2010-08-17 00:58:58 UTC
Ah, yeah, that explains a lot. I kind of thought the problem was something like the initial transaction list you describe, but didn't think to try a 'stop' command as a way to deal with it. I also thought the service type may be important but couldn't quite figure out what to set it to either :)

Frankly I'm fine with this as long as there *is* a way to do it and it works. It would probably help to note it somewhere, maybe in that tips page johann's working on.

the only remaining issue is if we can do this with legacy services - in the case i'm mostly interested in, live boot, most of the service we want to disable are (currently) legacy sysv services. So the advice here doesn't map exactly. Would we do 'chkconfig foobar off' and then 'service foobar stop'? 'chkconfig foobar off' and then 'systemctl foobar.service stop'?
Comment 3 Lennart Poettering 2010-08-23 16:18:03 UTC
(In reply to comment #2)

> the only remaining issue is if we can do this with legacy services - in the
> case i'm mostly interested in, live boot, most of the service we want to
> disable are (currently) legacy sysv services. So the advice here doesn't map
> exactly. Would we do 'chkconfig foobar off' and then 'service foobar stop'?
> 'chkconfig foobar off' and then 'systemctl foobar.service stop'?

My recommendation is to always disable stuff first and then shut it down. It fixes a tiny race that something might get activated again in the time between it was stopped and disabled.

So my recommendation is something like this:

<snip>
systemctl disable foo.service
chkconfig foobar off
systemctl stop foo.service
service foobar stop
</snip>

In this order. And of course all of this directed to 2>&1 > /dev/null.
Comment 4 Adam Williamson 2011-09-18 21:38:56 UTC
for the record, for anyone else who finds this bug in future, there's a key tweak needed to lennart's suggestion:

systemctl --no-reload disable foo.service
chkconfig foobar off
systemctl stop foo.service
service foobar stop

without --no-reload, the disable will trigger a reload of systemd, which can really screw boot up.

Use of freedesktop.org services, including Bugzilla, is subject to our Code of Conduct. How we collect and use information is described in our Privacy Policy.