Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Applying default/distros presets override sysadmin previous configuration #4830

Open
fbuihuu opened this issue Dec 5, 2016 · 43 comments
Open
Labels
install RFE 🎁 Request for Enhancement, i.e. a feature request

Comments

@fbuihuu
Copy link
Contributor

fbuihuu commented Dec 5, 2016

Hi,

This issue is not really new and there were some attempts to address it in the past already but unfortunately the work got lost somehow.

For example the latest work I found is the preset-transient one which is in my understanding about having symlinks created by the preset commands in /run/systemd/ thus having lower precedence than the symlinks created by the sysadmin in /etc/systemd/. However a part is still missing as disabling services should create null symlinks now in /etc/ and should override any corresponding symlinks directories with lower precedences.

There might another possibility though: basically when the user is enabling/disabling a service (via the EnableUnitFiles bus call), this operation would be recorded via a dedicated preset directive in a preset file located for example in /etc/systemd/system-preset/50-user-preferences.preset

For example if the user does systemctl enable foo.service, this operation would add "enable foo.service" in the dedicated preset file. A subsequent systemctl disable foo would replace the previous directive with "disable foo.service".

Since the preset file is located in /etc/systemd/systemd-preset, running systemd preset foo.service command would simply restore the user last preferences and would ignore the distro defaults as they are
now overridden.

However this wouldn't work if the sysadmin created the symlinks manually (using ln(1)) but in this case it might be acceptable to consider that creating symlinks in this way is considered as low-level operations and thus might be missing some functionalities (but of course its main purpose would still work).

@keszybz
Copy link
Member

keszybz commented Dec 6, 2016 via email

@fbuihuu
Copy link
Contributor Author

fbuihuu commented Dec 6, 2016

Sure:

It might be desired to re-apply a preset during a package upgrade because the default shipped during the installation was considered as boggus. But it would be nice to apply the new preset config without destroying the sysadmin's configuration (if any).

For example if the user decided to disable the service "foo" after giving it a try then even if the distro default is changed from "disabled" to "enabled" for that service later shouldn't have any effect since the user explicitly said "I want foo to be disabled".

You could also find a relevant discussion about this here: https://lists.freedesktop.org/archives/systemd-devel/2014-November/025288.html

@keszybz
Copy link
Member

keszybz commented Dec 7, 2016 via email

@fbuihuu
Copy link
Contributor Author

fbuihuu commented Dec 7, 2016 via email

@poettering
Copy link
Member

Yeah, I pretty much agree with @keszybz. Please file PRs for the /dev/null symlinks for deps, and stuff like that. But I must say making presets "dynamic" sounds wrong to me. Presets are supposed to be static really, and not record user configuration.

@poettering poettering added install RFE 🎁 Request for Enhancement, i.e. a feature request labels Dec 7, 2016
@keszybz
Copy link
Member

keszybz commented Dec 7, 2016

But another point of view would be that if the sysadmin hasn't configured anything then it means that he accepted to follow the distro defaults which might change in the future.

But the same reasoning about not being able to distinguish the case of implicit approval with just not caring applies here. I know Debian likes to record origin of various decisions and base subsequent changes based on that, but I don't think we want this complexity.

But that's the reason why I proposed a different approach which looks much simpler ( maybe too simpler ;) ). But unfortunately you haven't commented on that...

I still think it'd change current preset system to something preset-like, with an additional level of enablement: explicitly enabled by the user. I see the appeal, but I think it's just too complex and thus not worth it.

@martinpitt
Copy link
Contributor

Indeed this was discussed several times already, last time IIRC on the second-to-last hackfest in Brussels. The /etc/systemd/system/ enablement symlinks are both a wart (as these aren't user configuration but usually distro defaults) and a nuisance (as you need to track separately which units were enabled/disabled by the admin and which by package defaults, so that you can do the right thing on upgrades).

One idea back then was to ship enablement symlinks in packages in /usr, and then make systemctl disable create a /dev/null symlink in /etc/ for this case -- then /etc/systemd/system/ would be empty as long as you stay with the distro defaults, and only have the explicit admin changes otherwise. This would be much cleaner, keep things upgradeable, and would also get rid of the separate tracking that we have to do in Debian.

The other aproach was @xnox' idea of "runtime presets" as @fbuihuu already mentioned. While these are more flexible, this means more boot-time overhead (you need to process more service files and create the links). I can't remember any more whether there was something conceptually wrong with this or @xnox just dropped this because of changing company.

@xnox
Copy link
Member

xnox commented Dec 9, 2016

@martinpitt there is code out there and I have used runtime presets for a while, however I ended up optimizing that distro to the point that we simply shipped pre-enabled services as symlinks in /usr/lib/systemd/system/*.wants/* and did nothing else. Giving option for people to "disable" services ended up being a non-issue, as mask/unmask was sufficient for that. Allowing to negate wants symlinks would be nice, but is not implemented.

@keszybz
Copy link
Member

keszybz commented Dec 9, 2016

I think what @martinpitt describes is an "incremental enhancement" to current code. It requires:

  1. systemd support for masking .wants symlinks, which everybody seems to agree would be desirable
  2. support in the package system to create symlinks in /usr/lib
    So there's no block to implementing this currently.

Later on, we could extend systemctl to allow creating symlinks in /usr/lib instead of /etc to make creation of packages easier. This would be simple change that would make the whole process easier, but isn't strictly necessary.

I like this approach much more. It requires only small changes, and is backwards compatible (in spirit and implementation) with current scheme.

@fbuihuu
Copy link
Contributor Author

fbuihuu commented Dec 10, 2016

Presets are supposed to be static really, and not record user configuration.

@poettering then I'm not sure to understand the point of allowing presets to be located in /etc/systemd/system-preset/

@fbuihuu
Copy link
Contributor Author

fbuihuu commented Dec 10, 2016 via email

@keszybz
Copy link
Member

keszybz commented Dec 10, 2016

Just to make sure: to create the /dev/null symlinks in /etc, one would use "systemctl disable", right ?

That's two separate steps really:

  1. support reading of such symlinks (created manually)
  2. support creation of such symlinks by systemctl

Definitely we should do both, but step two doesn't have to be done immediately.

Do you mean modify the rpm macros so they install symlinks in /usr/lib ?

Package %post scripts should not modify in anything in /usr/lib. If there are any symlinks in /usr/lib, they should be part of the contents of the package. Again, two steps:

  1. create those symlinks by hand (i.e. using ln -s in %install)
  2. teach systemctl to do this (systemctl enable foobar.service --root=%{buildroot} --vendor, where --vendor would tell systemctl to create the symlinks in /usr/lib instead of /etc).

Why not making "systemctl preset" create symlinks in /usr/lib and "systemctl enable" in /etc?

Because both preset and enable shouldn't modify anything in /usr (on a real system).

@arvidjaar
Copy link
Contributor

@keszybz

Because both preset and enable shouldn't modify anything in /usr (on a real system).

Then how exactly you suggest to distinguish between unit, enabled by preset, and unit, enabled by admin?

Making preset operate on /usr/lib and explicit enable/disable on /etc provides clean separation. It also goes in line with system hardening used by many distributions which modifies ownership/permissions of files under packaging control after installation. So you could switch between "permissive" and "locked down" presets without requiring package changes and still respecting current admin choice.

@fbuihuu
Copy link
Contributor Author

fbuihuu commented Dec 11, 2016

@keszybz

Package %post scripts should not modify in anything in /usr/lib. If there are any symlinks in /usr/lib, they should be part of the contents of the package. Again, two steps:

Moreover that sounds to defeat the whole purpose of the preset mechanism: keep the default enablement policies in one place that are under control of one single package which may be different according the distro flavor.

In the new scheme, "systemctl preset" really looks to be packaging material stuff only.

@keszybz
Copy link
Member

keszybz commented Dec 11, 2016

My proposal does not in any way limit what is currently possible and used, so it cannot "defeat the whole purpose of the preset mechanism". One can still use it exactly as before, if wanted. The only difference is that it adds a mechanism for the distribution to package its symlinks in /usr/lib in a clean way, so that if the local configuration exactly matches the distribution defaults, no symlinks are necessary in /etc. The symlinks created in the new scheme have exactly the same effect as what now would be created after installation in /etc, except that they are packaged statically and land in /usr/lib.

Then how exactly you suggest to distinguish between unit, enabled by preset, and unit, enabled by admin?

By the location of the enablement symlinks, see above.

In the new scheme, "systemctl preset" really looks to be packaging material stuff only.

No. Presets are mostly for packaging stuff, when doing local changes normally it's easier to just do enable/disable instead of providing local presets. Nevertheless, the admin can always do so. Those presets would still be honoured, exactly the same as before.

Making preset operate on /usr/lib and explicit enable/disable on /etc provides clean separation.

It doesn't, unless you disallow use of presets by the admin. If you can have local presets in /etc, it would totally mess up the separation if they influenced what is present in /usr/lib. I think it's crucial that any symlinks created after installation based on local configuration are in /etc.

So you could switch between "permissive" and "locked down" presets

"Locked down"? I think that's a completely different story or just a wrong term.

I feel I'm being misunderstood. Maybe an example would help:
Let's say I'm packaging foobar.service. In the package creation script I do (using rpm as an example, I'm most familiar with that):

%install
# install the service file as before, no change
install foobar.service -Dpm0644 -t %buildroot%_unitdir/
# precreate the enablement symlinks, I'm ignoring the implementation of service_should_be_enabled for now
%if service_should_be_enabled
    systemctl enable foobar.service --root=%{buildroot} --vendor
%endif

%files
%_unitdir/foobar.service
%if service_should_be_enabled
  %_unitdir/*.wants.d/foobar.service
%endif

%post
# no change here
%systemd_post foobar.service

%preun
# no change here
%systemd_preun foobar.service

As you can see, the postinstallatin/pre-uninstallation scriptlets are called as before, except that now, if the admin didn't override the distro config as supplied by service_should_be_enabled there are no symlinks in /etc, because the package provides equivalent ones in /usr/lib. If the local configuration deviates from the distro defaults, additional symlinks would be present in /etc. If the local configuration disables a service that was enabled by default, those would be the new /dev/null symlinks.

When writing this, I realized that to nicely implement service_should_be_enabled, we should query system presets. So service_should_be_enabled could be imlemented as:

%global service_should_be_enabled %(systemctl is-enabled --presets foobar.service && echo 1 || echo 0)

This assumes that --presets switch is added which allows querying of the presets configuration for a unit.

@martinpitt
Copy link
Contributor

@keszybz: Right, that's pretty much what I had in mind. Changing a package to ship an enablement symlink in /usr requires your (1) "allow masking of .wants symlinks in /usr/lib by /dev/null links in /etc" so that systemctl disable continues to behave as before, but nothing else.

So if @fbuihuu does not insist to use presets for this, but is only interested in the actual effect we can retitle this accordingly, like "enabling a unit by default should not require link in /etc"?

@fbuihuu
Copy link
Contributor Author

fbuihuu commented Dec 12, 2016 via email

@martinpitt
Copy link
Contributor

Well, it seems that systemctl preset --root=xxx --vendor foobar.service is exactly what you want here.

@fbuihuu is right in that regard, that's in fact what automatically generated maintainer scripts call, not enable. So conceptually this is "enable, but respect presets that the distro or admin might have set". So this opens up some interesting combinatorial cases such as "general distro default is to enable", "package enables a service by default" (and thus ships the .wants symlink in /usr), then both a preset that disables that service and a manual systemctl disable should disable the service -- the latter by making a /dev/null symlink in /etc/…/..wants/.

@keszybz
Copy link
Member

keszybz commented Dec 12, 2016

Ok so we mostly agree that presets should be used on package installation/upgrade only.

No, I never said that.

Well, it seems that
systemctl preset --root=xxx --vendor foobar.service
is exactly what you want here.

The issue that I was trying to solve here was how to initialize the preset policy in %{buildroot}. With my proposed version, the preset state would be retrieved from the "host" system. %{buildroot} is empty, it contains only files installed by the package being built, so it doesn't have any preset policy files. For preset --vendor to do something useful, those policy files would have to be installed into %{buildroot} prior to that command. But that's messy, since we don't want them to end up part of the package.

We need to query the distro preset policy for a unit, and then apply that preset policy in %{buildroot}.

@fbuihuu
Copy link
Contributor Author

fbuihuu commented Dec 12, 2016 via email

@keszybz
Copy link
Member

keszybz commented Dec 12, 2016

I think you still haven't explained why... and that's probably source of my confusion.

What @martinpitt said: "enabling a unit by default should not require link in /etc", while keeping to the other constraints: backwards compatibility, ability to perform local configuration both through enable/disable and presets, and that local configuration does not change /usr/lib.

Again I'm not sure to understand why you want to change that as it will require to rebuild each package for which the default policies are changed later and add some complexities in each packages

Well, I don't think you can have both: if the default configuration is embedded in the package, changing it requires the package to be changing.

Well simply replace "--root=%buildroot" with "--prefix=%buildroot"

I don't think this is enough, because we need to operate on two locations (if we don't copy the files into the installation root). I think it might be cleaner to simply allow the list of preset files to be explicitly specified (Something like systemctl --presets=/usr/lib/systemd/system-preset/*.preset preset --vendor --root=%{buildroot} foobar.service. --preset could then accept host-absolute paths and understand nspawn-style + prefix to operate under --root.

@fbuihuu
Copy link
Contributor Author

fbuihuu commented Dec 13, 2016 via email

@poettering
Copy link
Member

I am not sure I follow what this is about. But I am very much against making systemctl write to /usr, ever. That's just wrong, /usr is supposed to be a read-only thing where packages or "make install" or something like that drop files, but not something we make changes to with "systemctl".

presets are a concept that exists only so that a vendor-recommended enable/disable state can be queried at any time, and be applied automatically at package installation time. Yes, admins can also install their preset files, but that only makes sense if they build their own images, i.e. in a relatively advanced workflow, where the admins kinda take the position of the image vendor, and are not just administrators of it anymore. If they are simply administrators of it, they should just use "systemctl enable" and "systemctl disable" to deviate from the vendor defaults. And they can use "systemctl preset" to return to the vendor defaults.

However, any scheme that dynamically makes changes to the presets is just wrong in my opinion. If you want to change something dynamically, don't bother with the presets, just enable/disable your service you service directly.

if a unit file in package version 1 had a different [Install] section than in version 2, and at upgrade the new settings shall be applied, then the postinst scripts should execute something like: systemctl is-enabled foo && systemctl reenable foo, or something like that. But they should not consult presets, as that reflects vendor suggestions, not user settings.

In order to support stateless systems that want to carry nothing in /etc, I think it would make sense to add support for masking dependencies in /etc via /dev/null symlinks, as has been proposed. But that is mostly something for systems where presets are an undesirable concept, i.e. where the vendor default comes anyway already pre-applied in /usr, and hence the second level of vendor suggestions that presets encode are unnecessary.

Anyway, I am not following the discussion here I must say. systemctl should not be able to modify /usr, and it should not ever spit out preset files. Form the three levels of enablement:

  1. the symlinks in /usr
  2. the suggested enablement state in preset files
  3. the symlinks in /etc

it should only change the third level possibly taking the second level into consideration and definitely the first level.

Making systemctl modify the second level is wrong, and so is modifying the first level. if you want to modify the second level use a text editor when building your image. and when modifying the first level, then use a build script and "ln -s". systemctl should only be in the business of modifying the third level.

Or to say this differently: I think systemctl should be an end-user/admin tool, not a build tool.

@martinpitt
Copy link
Contributor

@poettering: Indeed, I think we violently agree here :-)

@martinpitt
Copy link
Contributor

I. e. the idea was that a downstream packaging tool (such as Debian's dh-systemd debhelper plugin) could decide to ship enablement symlinks in the .deb instead of calling systemctl enable in the post-installation script, to avoid cluttering /etc with symlinks which are essentially a distro default already. That does not mean that this needs to be done by systemctl enable.

@keszybz
Copy link
Member

keszybz commented Dec 13, 2016

But I am very much against making systemctl write to /usr, ever. That's just wrong, /usr is supposed to be a read-only thing where packages or "make install" or something like that drop files, but not something we make changes to with "systemctl".

The idea is that if you are creating symlinks in /usr/lib at packaging time, like we do during systemd installation btw, instead of doing it "manually" with ln -s, packages can use systemctl as a helper tool to do that. This would be just a nicety to make the process easier.

I think when you say that systemctl should not make changes to /usr, you mean on a real system. During package creation in a temporary installation directory, this should be fine.

systemctl ... should not ever spit out preset files

I never said anything about spitting out preset files. I have no idea where this came from.

Anyway, I think that we agree on some basic implementation bits, so we should start with those, and then hopefully the big picture will be clearer.

@poettering
Copy link
Member

@keszybz hmm, so if I get this right, then you want to use systemctl in a special "build-time mode", in which case it does what we in systemd currently do with "ln -s" in our Makefile? I am not totally opposed to that, but also I think "ln -s" is probably mostly fine for this purpose, no? i mean, building requires an additional level of expertise, and people do it with build scripts anyway, hence doing "ln -s" sounds OK, no?

I am mostly afraid that people would be more confused if we'd start exposing "systemctl --build" or so, and would begin misusing that for horrible things, and run it on an end-user systems or so, and they really shouldn't... if this option is really desirable, then maybe as a hidden env var or so, and with some extra checks (for example, require that --root= is also used...). Or maybe in a different tool "systemd-build" or so..

Anyway, i'd be very much onboard with:

  1. a way to mask /usr dependency symlinks via /dev/null symlinks in /etc
  2. a way to test what preset files say about a unit ("systemctl test-preset foo") or so
  3. some switch for "systemctl reenable" that turns it into "systemctl is-enabled foo && systemctl reenable foo", if you follow what I mean.

(I am not sure anyone actually asked for item 2 or 3 in this list, but I got the impression)

@keszybz
Copy link
Member

keszybz commented Dec 13, 2016

I'm not a big fan of modifying behaviour through environment variables. That's a lousy user interface.
I also don't believe that hiding options because they might be "misused" works: people can always do the wrong thing, and lack of documentation is not going to stop them.

Anyway, i'd be very much onboard with:
...
(I am not sure anyone actually asked for item 2 or 3 in this list, but I got the impression)

I did, above:

This assumes that --presets switch is added which allows querying of the presets configuration for a unit.

@stanislav-brabec
Copy link

@poettering

at upgrade the new settings shall be applied, then the postinst scripts should execute something like: systemctl is-enabled foo && systemctl reenable foo, or something like that.

That wouldn't work. "systemctl is-enabled foo" cannot differentiate between is-enabled-by-admin and is-enabled-by-preset.

Imagine that preset author decides to disable foo. It was enabled in past => this will re-enable it, exactly in opposite you need.

Me, as a package maintainer, do not care about exact implementation. I just need to implement:

IF the service was explicitly enabled/disabled by sysadmin THEN keep the state on upgrade, independently on preset change.

IF the service was enabled/disabled by preset THEN follow the preset

To implement that, we need to save sysadmin actions in a different way/place than enabling/disabling by preset.

If you will implement something like "systemctl is-enabled-by-admin foo" returning yes/no/default, then it would work.

If you will implement "systemctl vendor-preset" that will keep sysadmin's decision, it would work as well.

If you will implement dynamic preset file tracking sysadmin's "systemctl enable", it would work as well.

There are many ways to do it.

@poettering
Copy link
Member

@stanislav-brabec well, we don't really record who did what, and I am not convinced we should, because if the admin runs a script that includes "systemctl" invocations, I figure you want this to be recorded as "admin" actions. But if the same script is called from some postinst packaging scripts, i figure it should be recorded as "packaging" actions. So, distuingishing these two cases is hard, as it would require inheriting context, and I am not sure we should really try to cover that. Moreover, the distinction "admin ran systemctl" vs "packaging scripts ran systemctl" sounds like something where the next guy would show up asking for even further distinction, maybe "user script ran systemctl" or "app1 ran systemctl vs. app2 ran systemctl"...

Also, if a preset in version x of a distro said "enable service foo by default", and then the user upgrades to version y of the distro, which changed the preset to "disable service foo by default", then I doubt we should turn off foo.

"presets" are a tool to pick user defaults if none exist yet. But from the moment on the preset is applied it's really a user setting I think, and the distinction whether the user or the packaging script ultimately enabled it shouldn't matter.

Or to say this differently: I really not convinced that there should be any concept of "ownership of enablement" really in systemd.

@stanislav-brabec
Copy link

@poettering: I agree, the only really needed information is:

The default defined by the package was left, package upgrade script should keep the state despite the new default.

Also, if a preset in version x of a distro said "enable service foo by default", and then the user upgrades to version y of the distro, which changed the preset to "disable service foo by default", then I doubt we should turn off foo.

I think that we should. I can imagine that a service can be deprecated in the new version, and we want to turn it off for everybody who don't care. (But such change did not appear yet in SUSE. Only reverse requests: change off to on.)

But from the moment on the preset is applied it's really a user setting I think, and the distinction whether the user or the packaging script ultimately enabled it shouldn't matter.

I disagree. Such approach makes impossible to fix bugs saying: You did not enable service foo by default. You can easily fix it for new install (add a line for preset), but there is no way to fix it for upgrades:

  • Preset was already applied, don't apply on upgrade.
  • If you do "systemctl enable", you can change conscious sysadmin decision.

Or to say this differently: I really not convinced that there should be any concept of "ownership of enablement" really in systemd.

Me too. Package maintainer does not need "ownership". Package maintainer needs to be able to allow two states:

  • Service state should follow the preset on upgrade.
  • Service should be left explicitly enabled/disabled on upgrade.

Any explicit "systemctl enable/disable" (done by syadmin, by script, but not by "systemctl preset") should change state from the first to the second one.

@fbuihuu
Copy link
Contributor Author

fbuihuu commented Dec 15, 2016 via email

@fbuihuu
Copy link
Contributor Author

fbuihuu commented Jan 14, 2017

Hi all,

I took a look at this during this week to see how this could be done and it seems that most of the troubles are coming from aliases.

Let's consider this corner case which illustrates the problem.

There are: unit 'B', unit 'A' and its 2 aliases 'a1' and 'a2'.

'B' pulls in 'A' through different dropins as shown below:

/etc/systemd/system/A.service
/etc/systemd/system/a1.service -> A.service
/etc/systemd/system/a2.service -> A.service

/etc/systemd/system/B.service.wants/a1.service -> /dev/null
/run/systemd/system/B.service.wants/a2.service -> /etc/systemd/system/a2.service
/usr/lib/systemd/system/B.service.wants/A.service -> /etc/systemd/system/A.service

Dropin 'a1' has been masked.

Should 'A' still be pulled in through the other dropins or should the dropin mask also disable all other dropins pulling 'A' ?

Before going into this I'd like to be sure that this use case makes sense.

Thanks for the feedback.

@fsateler
Copy link
Member

Should 'A' still be pulled in through the other dropins or should the dropin mask also disable all other dropins pulling 'A' ?

I think yes, at least according to the discussion in #4122. Masks should apply upstream, but not downstream.

Also Related: #4917, #4706

@fbuihuu
Copy link
Contributor Author

fbuihuu commented Jan 19, 2017

@fsateler do you mean:

/etc/systemd/system/B.service.wants/a1.service -> /dev/null

should mask all Wants=a1.service (added by dropins) for B ?

And:

/etc/systemd/system/B.service.wants/A.service -> /dev/null

should mask all Wants=a1.service, Wants=a2.service and Wants=A.service (added by dropins) for B ?

@keszybz , WDYT ?

@fsateler
Copy link
Member

@fsateler do you mean:

/etc/systemd/system/B.service.wants/a1.service -> /dev/null

should mask all Wants=a1.service (added by dropins) for B ?

If the dropins are in /usr, yes. If the dropins are from /etc, I'm not sure what should happen.

And:

/etc/systemd/system/B.service.wants/A.service -> /dev/null

should mask all Wants=a1.service, Wants=a2.service and Wants=A.service (added by dropins) for B ?

Yes, as long as the dropins are from /usr. /run should override /etc, of course.

fbuihuu added a commit to fbuihuu/systemd that referenced this issue Jan 31, 2017
This patch allows masking of .wants symlinks in /usr/lib by /dev/null symlinks
in /etc. IOW it gives the possibility to mask a dependency symlink (in .wants
or .requires), assuming that the later being located in a directory having a
'lower' precedence.

As a consequence a dependency symlink mask in /etc/ will mask all equivalent
dependency symlinks located in directories such as /run, /usr/lib. And a
dependency symlink mask in /usr/lib will has no effect on a dropin symlink
located in /run or /etc.

Consider the following example:

 /etc/systemd/system/foo.service.wants/bar.service -> /dev/null
 /usr/lib/systemd/system/foo.service.wants/bar.service -> ../bar.service

service 'foo' was installed during a package installation with a dependency on
'bar' and later sysadmin decided to disable this dependency by adding a mask in
/etc/. This results in service 'bar' not being pulled in by 'bar' anymore.

The primary use case is to allow a clean separation between service
activation/deactivation done during package installations/updates and those
done by sysadmin.

Assuming that distro dependency symlinks happens in /usr/lib only, the sysadmin
is now able to disable persistently a service by creating a mask in /etc/. This
mask will take precedence over any distro policies (usually defined by presets)
defined in /usr/lib.

The distro policies (presets) are now free to be changed without interfering
with the configuration done by the sysadmin (if any). They will still be
preserved and will still be taken into account.

The assumption on distros creating symlinks in /usr/lib only is currently not
met since this happens in /etc/. However the dropin symlink mask is the first
step to achieve this clean separation. Later changes will ensure that dropin
symlinks created during package installations/updates will happen in /usr/lib
only. See issue systemd#4830.

In order to implement the masking of dependency symlinks properly, this patch
defers the handling of those dependencies until all involved units are fully
loaded. This way all units and especially their aliases are known and a mask
can be effective on a specific unit including all of its aliases.

This is due to the way we currently handle the unit aliases (how a unit and its
aliases are "lazily" merged) and also due to the fact that we can't remove
dependencies added to a unit (until a full daemon-reload happens).

As an example:

 /etc/.../a.service.wants/b1.service -> /dev/null
 /usr/.../a.service.wants/b.service -> ../b.service

and 'b1' is an alias of 'b'. In this example, dropin symlink dependencies
pulling 'b' or any of its aliases (including 'b1') will be masked.

This patch also changes a rather odd behavior that nobody sane should rely on:
a .wants symlink to /dev/null was masking the unit the symlink was refering
to. Therefore:

 /etc/.../a.wants/b.service -> /dev/null

would have masked 'b' service. Whereas now, it only masks the corresponding
dependency symlinks defined in a.wants/ directories.

It also changes the following weird case:

 /etc/.../a.service.wants/b.service -> ../c.service

where 'b' defines an alias for 'c'.

I'm not sure why symlinks were used to define dependencies in .wants/.requires
in the first place whereas a simple empty file would have been sufficient and
unambiguous. With this patch applied, no more aliases are defined this way.
@fbuihuu
Copy link
Contributor Author

fbuihuu commented Jan 31, 2017

Hi,

I tried to implement the mask thing in #5195. it would be nice if you could have a look.

Thanks.

@keszybz
Copy link
Member

keszybz commented Jan 31, 2017

(Sorry for the delay.)

/etc/systemd/system/B.service.wants/a1.service -> /dev/null

should mask all Wants=a1.service (added by dropins) for B ?

No. I think it should only add the dependency added by that specific file, i.e. that null link nullifies the effect of any B.service.wants/a1.service symlink, and nothing else.

And:

/etc/systemd/system/B.service.wants/A.service -> /dev/null

should mask all Wants=a1.service, Wants=a2.service and Wants=A.service (added by dropins) for B ?

No, for the same same reason as above.

In general, configuration in systemd is only additive. Allowing a mask to subtract configuration that is not defined in a masked file would go against that.

@fbuihuu
Copy link
Contributor Author

fbuihuu commented Feb 1, 2017

@keszybz I think there's a confusion here due to my poor wordings: I used the term "dropin dependency" or even "dropin" assuming that in this context they were referring to dependency symlinks put in .wants or .requires directories.

However "dropin" is the term used for all additional .conf files located in .service.d/, at least that is what you seem to assume.

So I'll change that in the [RFC] I posted, and will use "dependency symlinks".

Actually I'm even not sure this term is appropriate because we could only use an empty file instead of a symlink (except for masks) which would remove any ambiguities.

Actually doing touch /etc/systemd/system/a.service.wants/b.service also works just fine but remove any weird use cases such as unexpected alias definitions.

@keszybz
Copy link
Member

keszybz commented Feb 1, 2017

No, I don't think there was confusion like that. IIUC, we had a different interpretation of what exactly a mask file (e.g. symlink /etc/systemd/system/a.service.wants/b.service → /dev/null) should mask. In your interpretation, that'd be all Wants=b.service dependencies that a.service may have. In my interpretation that'd be just any /**/systemd/system/a.service.wants/b.service symlinks with lower priority).

@fbuihuu
Copy link
Contributor Author

fbuihuu commented Feb 1, 2017 via email

fbuihuu added a commit to fbuihuu/systemd that referenced this issue Feb 2, 2017
This patch allows masking of .wants symlinks in /usr/lib by /dev/null symlinks
in /etc. IOW it gives the possibility to mask a dependency symlink (in .wants
or .requires), assuming that the later being located in a directory having a
'lower' precedence.

As a consequence a dependency symlink mask in /etc/ will mask all equivalent
dependency symlinks located in directories such as /run, /usr/lib. And a
dependency symlink mask in /usr/lib will has no effect on a dropin symlink
located in /run or /etc.

Consider the following example:

 /etc/systemd/system/foo.service.wants/bar.service -> /dev/null
 /usr/lib/systemd/system/foo.service.wants/bar.service -> ../bar.service

service 'foo' was installed during a package installation with a dependency on
'bar' and later sysadmin decided to disable this dependency by adding a mask in
/etc/. This results in service 'bar' not being pulled in by 'bar' anymore.

The primary use case is to allow a clean separation between service
activation/deactivation done during package installations/updates and those
done by sysadmin.

Assuming that distro dependency symlinks happens in /usr/lib only, the sysadmin
is now able to disable persistently a service by creating a mask in /etc/. This
mask will take precedence over any distro policies (usually defined by presets)
defined in /usr/lib.

The distro policies (presets) are now free to be changed without interfering
with the configuration done by the sysadmin (if any). They will still be
preserved and will still be taken into account.

The assumption on distros creating symlinks in /usr/lib only is currently not
met since this happens in /etc/. However the dropin symlink mask is the first
step to achieve this clean separation. Later changes will ensure that dropin
symlinks created during package installations/updates will happen in /usr/lib
only. See issue systemd#4830.

In order to implement the masking of dependency symlinks properly, this patch
defers the handling of those dependencies until all involved units are fully
loaded. This way all units and especially their aliases are known and a mask
can be effective on a specific unit including all of its aliases.

This is due to the way we currently handle the unit aliases (how a unit and its
aliases are "lazily" merged) and also due to the fact that we can't remove
dependencies added to a unit (until a full daemon-reload happens).

As an example:

 /etc/.../a.service.wants/b1.service -> /dev/null
 /usr/.../a.service.wants/b.service -> ../b.service

and 'b1' is an alias of 'b'. In this example, dropin symlink dependencies
pulling 'b' or any of its aliases (including 'b1') will be masked.

This patch also changes a rather odd behavior that nobody sane should rely on:
it's possible to define an alias for unit 'c' via a dependency symlink:

 /etc/.../a.service.wants/alias-for-c.service -> ../c.service

In this case 'alias-for-c' is an alias of 'c' assuming that 'alias-for-c' unit
doesn't exist.

I'm not sure why symlinks were used to define dependencies in .wants/.requires
in the first place whereas a simple empty file would have been sufficient and
unambiguous. With this patch applied, no more aliases are defined this way.

Fixes: systemd#1169
Fixes: systemd#5179
Fixes: systemd#4830
fbuihuu added a commit to fbuihuu/systemd that referenced this issue Feb 3, 2017
This patch allows masking of .wants symlinks in /usr/lib by /dev/null symlinks
in /etc. IOW it gives the possibility to mask a dependency symlink (in .wants
or .requires), assuming that the later being located in a directory having a
'lower' precedence.

As a consequence a dependency symlink mask in /etc/ will mask all equivalent
dependency symlinks located in directories such as /run, /usr/lib. And a
dependency symlink mask in /usr/lib will has no effect on a dropin symlink
located in /run or /etc.

Consider the following example:

 /etc/systemd/system/foo.service.wants/bar.service -> /dev/null
 /usr/lib/systemd/system/foo.service.wants/bar.service -> ../bar.service

service 'foo' was installed during a package installation with a dependency on
'bar' and later sysadmin decided to disable this dependency by adding a mask in
/etc/. This results in service 'bar' not being pulled in by 'bar' anymore.

The primary use case is to allow a clean separation between service
activation/deactivation done during package installations/updates and those
done by sysadmin.

Assuming that distro dependency symlinks happens in /usr/lib only, the sysadmin
is now able to disable persistently a service by creating a mask in /etc/. This
mask will take precedence over any distro policies (usually defined by presets)
defined in /usr/lib.

The distro policies (presets) are now free to be changed without interfering
with the configuration done by the sysadmin (if any). They will still be
preserved and will still be taken into account.

The assumption on distros creating symlinks in /usr/lib only is currently not
met since this happens in /etc/. However the dropin symlink mask is the first
step to achieve this clean separation. Later changes will ensure that dropin
symlinks created during package installations/updates will happen in /usr/lib
only. See issue systemd#4830.

In order to implement the masking of dependency symlinks properly, this patch
defers the handling of those dependencies until all involved units are fully
loaded. This way all units and especially their aliases are known and a mask
can be effective on a specific unit including all of its aliases.

This is due to the way we currently handle the unit aliases (how a unit and its
aliases are "lazily" merged) and also due to the fact that we can't remove
dependencies added to a unit (until a full daemon-reload happens).

As an example:

 /etc/.../a.service.wants/b1.service -> /dev/null
 /usr/.../a.service.wants/b.service -> ../b.service

and 'b1' is an alias of 'b'. In this example, dropin symlink dependencies
pulling 'b' or any of its aliases (including 'b1') will be masked.

This patch also changes a rather odd behavior that nobody sane should rely on:
it's possible to define an alias for unit 'c' via a dependency symlink:

 /etc/.../a.service.wants/alias-for-c.service -> ../c.service

In this case 'alias-for-c' is an alias of 'c' assuming that 'alias-for-c' unit
doesn't exist.

I'm not sure why symlinks were used to define dependencies in .wants/.requires
in the first place whereas a simple empty file would have been sufficient and
unambiguous. With this patch applied, no more aliases are defined this way.

Fixes: systemd#1169
Fixes: systemd#5179
Fixes: systemd#4830
keszybz added a commit to keszybz/systemd that referenced this issue Feb 5, 2017
…conf dropins

Essentially, instead of sequentially adding deps based on all symlinks
encountered in .wants and .requires dirs for each name and each unit file load
path, iteratate over the load paths and unit names gathering symlinks,
then order them based on priority, filter our masked entries, and then
iterate over the final list, adding dependencies.

This patch doesn't change the logic too much, except of course the added
ability to mask entires in .wants and .requires. Adding additional filtering on
the symlinks is left for later patches.

Fixes systemd#1169.
Fixes systemd#4830.

Example log errors:
Feb 04 22:13:28 systemd[1462]: foo.service: Wants dependency on empty_file.service is masked by /home/zbyszek/.config/systemd/user/foo.service.wants/empty_file.service, ignoring
Feb 04 22:13:28 systemd[1462]: foo.service: Wants dependency on masked.service is masked by /home/zbyszek/.config/systemd/user/foo.service.wants/masked.service, ignoring
@keszybz
Copy link
Member

keszybz commented Feb 5, 2017

Alt version: #5231.

keszybz added a commit that referenced this issue Feb 5, 2017
…conf dropins

Essentially, instead of sequentially adding deps based on all symlinks
encountered in .wants and .requires dirs for each name and each unit file load
path, iteratate over the load paths and unit names gathering symlinks,
then order them based on priority, filter our masked entries, and then
iterate over the final list, adding dependencies.

This patch doesn't change the logic too much, except of course the added
ability to mask entires in .wants and .requires. Adding additional filtering on
the symlinks is left for later patches.

Fixes #1169.
Fixes #4830.

Example log errors:
Feb 04 22:13:28 systemd[1462]: foo.service: Wants dependency on empty_file.service is masked by /home/zbyszek/.config/systemd/user/foo.service.wants/empty_file.service, ignoring
Feb 04 22:13:28 systemd[1462]: foo.service: Wants dependency on masked.service is masked by /home/zbyszek/.config/systemd/user/foo.service.wants/masked.service, ignoring
@fbuihuu
Copy link
Contributor Author

fbuihuu commented Feb 9, 2017

@poettering : I don't think this one can be closed yet.

Some pieces are still missing before distros can fully create symlinks in /usr/lib/systemd/*, see #4830 (comment)

We also still need to adapt systemctl disable/enable/is-enabled/... with the new dropin mask feature.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
install RFE 🎁 Request for Enhancement, i.e. a feature request
Development

No branches or pull requests

8 participants