Environment generators #5131

Merged
merged 29 commits into from Feb 21, 2017

Conversation

Projects
None yet
6 participants
Owner

keszybz commented Jan 23, 2017

This is an mutation of #3904 that implements the idea of "environment file generators" as described in #3904 (comment). There's a bunch of cleanups and scaffolding, but the actual code changes are pretty small.

Wrt. to @halfline's version:

  • enhanced variable expansion is removed, only straightforward substitutions ($VAR and ${VAR}) are supprted
  • the list of environment.d dirs is extended to include $XDG_RUNTIME_DIR/systemd/environment.d, and
    generators are executed by systemd --user to populate it.

@keszybz keszybz added the pid1 label Jan 23, 2017

@keszybz keszybz referenced this pull request Jan 23, 2017

Closed

Environment.d #3904

A couple of comments.

I am not overly enthusiastic about this, but OK.

What I really try to understand is why the sequential generators thing is a good idea. What's the real-life usecase for this? generators that patch around in each others generated data sounds wrong to me... The example used in the man page I don#t find overly convincing either...

I also wonder about the variable expansion within environment files. Do we really want to have that? I am pretty conservative on defining new magic macro formats like this I must say...

And if we need one of the above, do we really need both?

man/environment.d.xml
+ <para><filename>/run/user/<replaceable>uid</replaceable>/systemd/environment.d</filename></para>
+ <para><filename>/etc/environment.d/*.conf</filename></para>
+ <para><filename>/run/environment.d/*.conf</filename></para>
+ <para><filename>/usr/lib/environment.d/*.conf</filename></para>
@poettering

poettering Feb 2, 2017

Owner

should these files really have the .conf suffix? Maybe .env or so, instead?

man/environment.d.xml
+ <literal>KEY=VALUE</literal> environment variable assignments, separated
+ by newlines. The right hand side of these assignments may reference
+ previously defined environment variables, using the
+ <literal>${OTHER_KEY}</literal> format.</para>
@poettering

poettering Feb 2, 2017

Owner

what precisely is the usecase for variable expansion here?

This has been requested before for EnvironmentFile=, but I so far have been very conservative about it. If we do add this here, why not also in EnvironmentFile=?

Is this syntax shell compatible? environment files so far always have been considered something you could also source in...

tmpfiles.d/systemd.conf.m4
@@ -7,6 +7,8 @@
# See tmpfiles.d(5) for details
+d /run/environment.d 0755 root root -
@poettering

poettering Feb 2, 2017

Owner

why this? if you want to be able to put a file there you need privs anyway, and can hence create the dir... an there's nothing too special in the access mode tha twould prohibit that...

I don't think this file should exist, except when people actually need this...

Owner

keszybz commented Feb 2, 2017

What I really try to understand is why the sequential generators thing is a good idea. What's the real-life usecase for this? generators that patch around in each others generated data sounds wrong to me...

One common and special case if appending and prepending to variables, which may or may not be already defined. In particular, the following operations are needed:

  • provide a default value, i.e. define the variable only if is currently empty
  • add a suffix, after a separator, but omit the separator if the variable was previously empty
  • same for a suffix

This pattern of growing a list in a variable applies to $PATH, $PYTHONPATH, various $XDG_*_DIRs, possibly $CLASSPATH.

I can see three ways to provide this:

  • add shell-like variable manipulation syntax like #3904 did
  • add conditionals (something like #ifdefs and #ifs in Makefiles)
  • allow the generators to do it themselves

I think the first two solutions would have a tendency to add more features until they expand into a turing-complete language, just like shell and make did. The third solution allows for arbitrarily complex stuff, but leaves the syntax extremely simple.

I also wonder about the variable expansion within environment files.

Strictly speaking, no. If generators run sequentially, we can force them to perform any expansions internally. Nevertheless, I imaging that many (most?) generators will not need to look at the old values of the variables, but would want to make use of them. E.g. it's easier to say MY_RUNTIME_DIR=$XDG_RUNTIME_DIR/my, without figuring out what $XDG_RUNTIME_DIR really is.

There's precedent for allowing envvars to be expanded, without any other fanciness: our Environment= directive, pkgconfig files. I can drop this part, we can always add it later, but I think it makes the whole scheme a bit easier to user.

Please note that there's less ambiguity, since there is no splitting into arguments, so $VAR just expands to a string of zero or more characters.

should these files really have the .conf suffix? Maybe .env or so, instead?

This was discussed back and forth in #5904. No other files are planned in those directories, so no need to use a suffix to distinguish them. We have .conf in modprobe.d, tmpfiles.d, modules-load.d, sysctl.d, binfmt.d, sysusers.d. I prefer .conf because of consistency.

Is this syntax shell compatible? environment files so far always have been considered something you could also source in...

Yes. It's definitely supposed to be 100%-shell compatible. Doing . $XDG_RUNTIME_DIR/environment.d/* is supposed to yield proper values.

Owner

keszybz commented Feb 2, 2017

To add to the sequentiality question:
I think the tradeoffs here are different then for unit file generators. Unit files are usually largely independent, because they need to allow asynchronous starting and stopping, so generating them asynchronously is not an issue. And the number of unit files generators is pretty large and they do a fairly large amount of work (e.g. sysv generator). They also run in early boot, blocking other stuff. Thus running them in parallel is both easier and more beneficial. For environment generators, I think we'll end up with a few very simple ones, and their efficiency won't matter either way.

Owner

poettering commented Feb 2, 2017

well, but these generators would still block the login, and if that's shell, that's slow...

I still don't get why we need both the seq generators and env var expansion. Wouldn't it suffice to have async generators, and then simply make sure they put they output their fragments in the right order, so that they can extend what others have written without having to actually read and parse the env var snippets from other generators?

Owner

keszybz commented Feb 2, 2017

I still don't get why we need both the seq generators and env var expansion.

Let's say, for the sake of discussion, that env var expansion is removed.

Wouldn't it suffice to have async generators

No, unless there's some way I'm not seeing, of implementing the requirements described in #5131 (comment).

Owner

poettering commented Feb 2, 2017

hmm, but we get env vars from other sources too, such as PAM or login, or gdm and whatever else. how would such a generator take those into consideration?

I wonder if it wouldn't be more natural to change the generators here to simply build a pipeline: we pass in the env vars we get from PAM/login/whatever starts us, then each process reads them from stdin, manipulates them, spits them out to stdout, and the next one does the same...

I mean, unlikely unit generators we actually aren't interested at all in stuff that hits the file system, and instead of creating disjunct nodes all they do is manipulate an env var stream... I think this would make a lot of things nicer and easier....

(by pipeline i don't mean we necessarily have to implement this as actual shell-like pipeline where all processes run in parallel with actual pipes in between. I just mean that each one gets the output from the previous one, and spits it out again...)

Owner

keszybz commented Feb 2, 2017

Yeah, I thought about something like that too. I'll try to rework the code to do this.

I think we should still dump the final environment to a file somewhere, to allow external processes to pick it up easily. Not sure if we need environment.d for anything if this is implemented.

Contributor

halfline commented Feb 2, 2017

admins shouldn't have to write a program to change the environment system wide. Likewise, users shouldn't have to write a program to set an environment variable for their session (including user services). And, it would be nice if software like flatpak could just drop a file to augment XDG_DATA_DIRS (or PATH or LD_LIBRARY_PATH) without having to ship a whole program (although, it's not the end of the world if it does). Running a bunch of programs at startup for filtering the environment gives a lot of flexibility, but I hope it's flexibility we don't have to use commonly... I mean status quo is running a bunch of scripts at startup in /etc/profile.d...we're trying get away from that model. I like environment.d because it provides an answer for users and admins, and isvs.

Owner

keszybz commented Feb 2, 2017

Sure, a drop-in file is great, if it works. The problem with just allowing straightforward environment.d dir is that it's not powerful enough (or maybe it is powerful enough with "advanced" variable expansion, but I'm pretty sure that usecases where it's not powerful enough will crop up anyway).

Not sure if we need environment.d for anything if this is implemented.

On second thought, maybe we should have both:

  1. environment.d that only allows straightforward variable definitions and
  2. a "pipeline" of generators to tweak those variables and generate the final environment

In this scenario, the simplest cases would be handled by dropping a trivial X=Y file into the right place, and more complicated cases would be handled by a "program", but it'd still be quite trivial because it wouldn't need to parse anything, just pull some stuff out of its environment and print to stdout.

Contributor

halfline commented Feb 2, 2017

I think that's better, but it still sucks for a user that wants to add ~/bin to their PATH (or whatever). If a user has to write a program to do that, they're going to grumble.

Owner

keszybz commented Feb 2, 2017

Actually prepending stuff to $PATH could be trivially handled using the environment.d files. But even for more complicated cases, let's say you want to add something to path only if it's not already there:

#!/bin/sh
if ! echo $PATH | grep -E -q "(^|:)$HOME/bin(:|$)"; then echo PATH=$HOME/bin:$PATH; fi 

Not too ominous.

(I mean: it's not any harder then putting the same in .bashrc. There's a certain minimal level of complexity below which it's hard to go.)

Contributor

halfline commented Feb 3, 2017

Yea I guess $PATH was a bad example since it will always be set, and duplicates aren't a huge problem. A better example would be LD_LIBRARY_PATH, where if the environment.d snippet had:

LD_LIBRARY_PATH=${HOME}/lib:${LD_LIBRARY_PATH}

and LD_LIBRARY_PATH wasn't set, then current working directory would get added to LD_LIBRARY_PATH and potentially create a security problem.

I don't think your generator is quite what @poettering was proposing, though. My understanding is it would have to be

#!/bin/sh
while read environment_entry; do 
  if echo $environment_entry | grep -E -q "^PATH="; then
    eval $environment_entry
    if ! echo $PATH | grep -E -q "(^|:)$HOME/bin(:|$)"; then
     echo PATH=$HOME/bin:$PATH;
    else
      echo $environment_entry
    fi 
  else
    echo $environment_entry
  fi
done

right?

Owner

poettering commented Feb 6, 2017

I think we should still dump the final environment to a file somewhere, to allow external processes to pick it up easily. Not sure if we need environment.d for anything if this is implemented.

sure that makes sense to me.

Owner

poettering commented Feb 6, 2017

admins shouldn't have to write a program to change the environment system wide. Likewise, users shouldn't have to write a program to set an environment variable for their session (including user services). And, it would be nice if software like flatpak could just drop a file to augment XDG_DATA_DIRS (or PATH or LD_LIBRARY_PATH) without having to ship a whole program (although, it's not the end of the world if it does). Running a bunch of programs at startup for filtering the environment gives a lot of flexibility, but I hope it's flexibility we don't have to use commonly... I mean status quo is running a bunch of scripts at startup in /etc/profile.d...we're trying get away from that model. I like environment.d because it provides an answer for users and admins, and isvs.

I have the suspicion that desktops such as GNOME would anyway prefer configuring env vars from a different place than a plain text drop-in file? I mean, dconf or so? By only providing executable "plugins" for population of the env vars we permit people to hack things up the way they like, using any source they like. One option would even be for some project to ship an executable that populates the environment from an enviuronment file, but systemd wouldn't be in the business of implementing that.

Owner

poettering commented Feb 6, 2017

and yeah @halfline is right, i had in mind that env vars would be fed in via stdin, be processed and then written out via stdout again.

But I figure we could instead also pass them in via the usual env block, and then make the generate output simply an "override". However, that would mean there would be no way to unset an env var.... Not sure if that's a limitation though...

I'd be much more comfortable with supporting drop-in env var snippets btw, if we wouldn't do variable expansion on them. i.e. dumb env var files sound quite acceptable to me.

Contributor

halfline commented Feb 6, 2017

I have the suspicion that desktops such as GNOME would anyway prefer configuring env vars from a different place than a plain text drop-in file? I mean, dconf or so?

We pull specific environment variable settings from dconf. mainly locale related variables. But, only out of necessity. I don't think we'll ever have any dedicated general custom_environment dconf key or something.

By only providing executable "plugins" for population of the env vars we permit people to hack things up the way they like, using any source they like. One option would even be for some project to ship an executable that populates the environment from an enviuronment file, but systemd wouldn't be in the business of implementing that.

So a big part of the appeal of this change is having a standardized place that projects and users can rely on. If we can't have a standard place for this, I think software will just keep using /etc/profile.d which is the de facto answer now. Also, pushing handling of /etc/environment to systemd from pam_env would mean distros could have one less pam module running as root. /usr/share/gnome/environment.d just wouldn't be as generally useful.

From a flatpak perspective, having a little environment generator would probably be fine but it's not a great answer for sysadmins and users.

I'd be much more comfortable with supporting drop-in env var snippets btw, if we wouldn't do variable expansion on them. i.e. dumb env var files sound quite acceptable to me.

The problem is that some of the most common use cases wouldn't be covered without the flexibility of variable expansion (PATH, LD_LIBRARY_PATH, XDG_DATA_DIRS).

Owner

poettering commented Feb 6, 2017

So a big part of the appeal of this change is having a standardized place that projects and users can rely on. If we can't have a standard place for this, I think software will just keep using /etc/profile.d which is the de facto answer now.

Well, but key here is that there is a difference between projects and users. I think the "generators" concept would be perfectly fine for projects. Heck, in fact I think it's better suited for them, as only they really know what the right logic to extend/override their specific env vars is.

For the user case I think that ~/.profile is actually sufficient...

Contributor

halfline commented Feb 6, 2017

Heck, in fact I think it's better suited for them, as only they really know what the right logic to extend/override their specific env vars is.

It's less convenient and it means those projects may have to come up with some other answer for non-systemd systems, but fair enough (it's not like the standard i was gunning for is implemented anywhere yet). env generators are certainly more flexible and achieve the end goal okay (though I was hoping we could lower the number of tiny programs started during login, but doesn't really matter).

For the user case I think that ~/.profile is actually sufficient...

It's not really sufficient as it stands right now. I mean gnome wayland sessions didn't run a login shell at all until a couple of weeks ago... and what about systemd --user services ? I mean I guess we could ship an env generator with systemd that ran a login shell and printed the full environment?

At the moment, gnome-session will try to push the session environment up to systemd --user but that's racy with user services that start right at pam_open_session time, and really, races aside, the wrong way around imo.

Owner

poettering commented Feb 6, 2017

I mean gnome wayland sessions didn't run a login shell at all until a couple of weeks ago...

Uh, why would they even? Not following? Why would you run gnome-wayland from a any shell at all?

I mean I guess we could ship an env generator with systemd that ran a login shell and printed the full environment?

Wut? I'd leave ~/.profile for login shells, and not do that for anything else I must say. I seriously doubt the stuff set that way belongs anywhere else than in interactive shells by the user...

Contributor

halfline commented Feb 6, 2017

Uh, why would they even? Not following? Why would you run gnome-wayland from a any shell at all?``

Because users want their environment variables set ! See the gnome bug:
https://bugzilla.gnome.org/show_bug.cgi?id=736660

Wut? I'd leave ~/.profile for login shells, and not do that for anything else I must say. I seriously doubt the stuff set that way belongs anywhere else than in interactive shells by the user...

most terminal applications don't run a login shell by default, so ~/.profile isn't going to execute if not done earlier... and users want a way to set environment variables that affect all applications in the session, not just stuff they run in a terminal. for instance adding $HOME/bin to PATH , so they can run programs they compiled themselves, or putting G_MESSAGES_DEBUG=all so the session outputs debug information, or whatever.

Owner

poettering commented Feb 6, 2017

urks. shells in the clean code paths. that's just sick...

Anyway, I'd probably be OK if we'd ship an env var generator like using the proposed interface that does what @halfline wants: reads a bunch of drop-ins and does basic variable substitution on them. I mean, we ship also the ugliness that is rc-local-generator, and hence it's kinda hard to say no to that...

Still not liking it though, and I'd definitely prefer if such a generator would live somewhere else than in systemd, in some reasonably standard package. After all, if we have the generator concept, then people can provide generators easily externally...

maybe it's time to start an xdg project for standardizing ~/.environment/ or so, and then do a reference implementation that everybody can adopt that among hooks into other stuff also provides hookups into systemd by shipping a generator?

Contributor

halfline commented Feb 6, 2017

urks. shells in the clean code paths. that's just sick...

yea I tried to get away from it with wayland, but the backlash was too strong, since we don't have an alternative mechanism... (and thus #3904 and this pull request)

Anyway, I'd probably be OK if we'd ship an env var generator like using the proposed interface that does what @halfline wants: reads a bunch of drop-ins and does basic variable substitution on them. I mean, we ship also the ugliness that is rc-local-generator, and hence it's kinda hard to say no to that...

that'd be great!

Still not liking it though, and I'd definitely prefer if such a generator would live somewhere else than in systemd, in some reasonably standard package. After all, if we have the generator concept, then people can provide generators easily externally...

maybe it's time to start an xdg project for standardizing ~/.environment/ or so, and then do a reference implementation that everybody can adopt that among hooks into other stuff also provides hookups into systemd by shipping a generator?

I'm not super enthusiastic about maintaining another module…especially something dealing with environment variables (not saying i won't do it, but I don't want to do it)

I was thinking we'd just add /etc/environment.d to https://www.freedesktop.org/wiki/Software/systemd/InterfacePortabilityAndStabilityChart/ with Reimplementable Independently=yes

Owner

poettering commented Feb 6, 2017

@halfline: what about this: you put a brief spec together that can be hosted on fdo, and describes the drop-in dir, and the relevant supported variable expansions? we'd then add a generator implementing it to systemd, but wouldn't be the ones inventing this, and can point to fdo for this.

Or in other words: if you don't want to host the generator elsewhere, at least host the spec elsewhere, so that we can support this new "xdg-envvars" spec (or whatever you want to call it) the same way we already support "xdg-basedir" and suchlike?

Contributor

halfline commented Feb 6, 2017

0kay sounds good. Will report back (though I have a few unrelated deadlines to attend to first!).

Owner

keszybz commented Feb 7, 2017

@halfline before you start working on this, please wait a bit. I'm currently rehashing my PR.

Contributor

halfline commented Feb 7, 2017

Sure

keszybz added some commits Jan 22, 2017

manager: fix handling of failure in initialization
We would warn and continue after failure in manager_startup, but there's no
way we can continue. We must fail.
basic/util: move execute_directory() to separate file
It's a fairly specialized function. Let's make new files for it and the tests.
basic/exec-util: split out actual execution to a different function
This corrects an error in error handling: if execution fails, we should
never use return, but immediately _exit().
basic/conf-files: extend conf_files_list() to list unsuffixed files
5dd11ab did a similar change for conf_files_list_strv().
Here we do the same for conf_files_list() and conf_files_list_nulstr().

No change for existing users. Tests are added.
basic/exec-util: use conf_files_list_strv to list executables
Essentially the same logic as in conf_files_list() was independently implemented in
do_execute(). With previous commit, do_execute() can just call conf_files_list() to
get a list of executable paths.
basic/strv: allow NULLs to be inserted into strv
All callers of this function insert non-empty strings, so there's no functional
change.
Owner

keszybz commented Feb 12, 2017

I pushed a new version. I think this is ready for another round of review.

tl;dr: systemd manager instance will run generators from /usr/lib/systemd/environment-generators which can write new variable definitions on stdout. Each generator is started with the current set of variables in the environment block. One generator is implemented: it reads environment.d directories. Simple variable substitution is supported.

@poettering suggested creating a spec for environment.d through fdo. I think that's much more of an effort than it's worth. The generator in current patch set is 64 lines (according to sloccount), the documentation is ~100 lines. I don't think the final spec from fdo would be much different, but doing this through systemd ensures that we have a uniform solution that everybody can start using when 233 is released. In particular, releasing the environment generator support without the environment-d-generator doesn't seem useful, because it would provide half-broken functionality and force people to create generators when they could just use a drop-in in environment.d.

(There's the question of allowing variable expansion or not. I don't think it is necessary, at least for now. It's always possible to extend the syntax later and allow substitution. I think we should get some feedback from people if the current version covers their needs.)

Looks very good, but a few comments!

src/basic/env-util.c
@@ -274,6 +274,19 @@ _pure_ static bool env_match(const char *t, const char *pattern) {
return false;
}
+_pure_ static bool env_entry_has_name(const char *entry, const char *name) {
@poettering

poettering Feb 13, 2017

Owner

pure is kinda unnecessary if the function is static anyway, no? the compiler can figure this out on its own really..

@keszybz

keszybz Feb 17, 2017

Owner

Dropped.

src/basic/env-util.c
@@ -336,7 +349,7 @@ char **strv_env_unset(char **l, const char *p) {
for (f = t = l; *f; f++) {
- if (env_match(*f, p)) {
+ if (env_entry_has_name(*f, p)) {
@poettering

poettering Feb 13, 2017

Owner

is this change really the best idea? Previously, you could call strv_env_delete() with FOO=BAR and it would remove exactly FOO=BAR, but never FOO=QUUX. You could also call it with just FOO in which case FOO=BAR and FOO=QUUX are removed.

I think this behaviour makes a lot of sense, and we do expose it in our bus calls for seting the env vars, afaics. THis chnage doesn't look right to me hence?

@keszybz

keszybz Feb 18, 2017

Owner

OK, thanks for the explanation. I was wondering why this is like this.

All APIs to read the environment entries (except for manually iterating over the third argument to main, but that's special) assume that keys are unique. So we should always keep the keys unique.

I'll change the set and replace functions to do that.

src/basic/env-util.c
@@ -365,7 +378,7 @@ char **strv_env_unset_many(char **l, ...) {
va_start(ap, l);
while ((p = va_arg(ap, const char*))) {
- if (env_match(*f, p)) {
+ if (env_entry_has_name(*f, p)) {
src/basic/exec-util.h
#include "time-util.h"
-void execute_directories(const char* const* directories, usec_t timeout, char *argv[]);
+typedef int (*gather_stdout_callback_t) (int fd, void *arg);
@poettering

poettering Feb 13, 2017

Owner

For most other cases where we have a callback that takes arbitrary userdata ptrs we called that ptr userdata. I think we should do that here, too!

src/basic/exec-util.h
+typedef int (*gather_stdout_callback_t) (int fd, void *arg);
+
+typedef struct {
+ gather_stdout_callback_t one; /* from generators to helper process */
@poettering

poettering Feb 13, 2017

Owner

in comments, maybe say explicitly which component wrote to the fds passed here before, from which context (and how often) this is called and what the callback probably should do with it. i.e. something maybe like:

Called once for each generator after the generator ran. Gets an fd passed that contains the generator's stdout output. Will be called from a process that is the child of the process invoking execute_directory(). Should process the output as necessary and then propagate whatever it wants to propagate to its own stdout, which will then be read by the next callback ("two").

Or something like that.

src/basic/exec-util.h
+ void *two_arg;
+ gather_stdout_callback_t three; /* process data in main process */
+ void *three_arg;
+} gather_stdout_callbacks;
@poettering

poettering Feb 13, 2017

Owner

for exported structures I think it would be nicer to use CamelCase naming.

@poettering

poettering Feb 13, 2017

Owner

Also, when I see something like this I wonder if this should better be a structure containing an array of callback/userdata pairs. After all you are even numerically indexing the entries by calling them "one", "two" and "three".

You could then define an enum with named indexes. Something like this:

enum {
        STDOUT_GENERATE,
        STDOUT_COLLECT,
        STDOUT_CONSUME,
};

I think naming the three callbacks like this would make a lot of sense. Of course, the names aren't entirely self-explanatory, but I think "generate", "collect" and "consume" still are better than "one", "to", "three" ;-)

src/basic/fileio.c
+ path = getpid() == 1 ? "/run/systemd" : "/tmp";
+ fd = open_tmpfile_unlinkable(path, O_RDWR|O_CLOEXEC);
+ if (fd < 0)
+ return -errno;
@poettering

poettering Feb 13, 2017

Owner

should return fd here... (not a bug in your change, this apparently was a pre-existing bug)

@poettering

poettering Feb 20, 2017

Owner

hmm, fixed? the patch looks the same still?

src/core/manager.c
+ };
+
+ if (!generator_path_any(environment_generator_binary_paths))
+ return 0;
@poettering

poettering Feb 13, 2017

Owner

hmm, so what's the story here? this is called for both --system and --user mode the way this is right now? Is this really the intention?

If this is the intention, shouldn't the dirs be called "system-environment-generators" and "user-environment-generators"? If that's not the intention, shouldn't the dir be called "user-environment-generators" anyway, simply that the door is open to maybe one day add this for the system generators too? (not saying htat would be a good idea, just want to keep the avenue open...) or am i missing something?

@keszybz

keszybz Feb 14, 2017

Owner

There's a duality: environment.d dirs are only for users, but the generators themselves run for all manager instances, and are supposed to just do nothing in cases where they should do nothing. My motivation for this was that: a) it's quite possible to want to set the same set of variables for all environments, and having install two generators would be a bit more work, b) it's fairly easy to check if running as a user, c) doing just one dir simplifies the implementation and documentation.

But on second thought, it's not so obvious: in environment-d-generator, I used getuid, but it seems that environment.d directories should be also supported for the root's --user manager. In this case, that check is wrong. Instead, we should check if $USER is set.

I you think that there should be two separate sets of generators, I can change that. I still think that simply checking if $USER is set is simpler, but I don't feel particularly strongly about that.

@poettering

poettering Feb 14, 2017

Owner

hmm, so I am torn about this... I mean, I think that both uid==0 or $USER being set aren't the best choices for detecting in which context we are called. Using getppid() == 1 might be better, but breaks in we should ever want to run this from "systemd --system --test" or so...

another option would be to set $SYSTEMD_CONTEXT=user or so, and suggesting people use that for discerning the mode of invocation. That of course sucks, given that the whole concept of environment generators is to alter the environment, hence it might be a bit nasty to use the environment for this, too... Another option would be to use do what sd_bus_open() uses to figure out in which context it is run and hence to which bus to connect to: whether cg_pid_get_owner_uid() succeeds.

But the other thing, is of more philosophical nature: our units generators so far where placed in two distinct directories, and so where our static units, and everything else we did. I am tempted to say we should just continue to do it that way, simply to keep things reasonably uniform. And if people really want to use the same generator in both contexts, they are welcome to by symlinking the generator into both...

@keszybz

keszybz Feb 14, 2017

Owner

We could also use an argument in argv[1]. Currently argv is not used at all.

@keszybz

keszybz Feb 17, 2017

Owner

I changed the code to support two different dirs.

systemd manager instance will run generators from /usr/lib/systemd/environment-generators ... One generator is implemented: it reads environment.d directories.
...
There's the question of allowing variable expansion or not. I don't think it is necessary, at least for now.

As I've followed the issue through from the Gnome bug before Fedora enabled Wayland by default: what I want is a way to add directories to $PATH for my whole user session. I would also like it to be possible for an installation routine running as non-root to add to $PATH for the user. As I understand it, this wouldn't be possible if the environment.d mechanism does not support variable substitution/expansion:

  • From your description, generators must be installed systemwide. (Was that a shorthand and it will actually look in user-writable directories too?)
  • You can't add to $PATH in an environment file that doesn't have the ability to expand $PATH.

It's possibly unfortunate that several important environment variables function as lists rather than atomic values, but I'd like it to be practical to manipulate these.

Owner

keszybz commented Feb 14, 2017

Nah, expanding $PATH is quite well supported. It's even in one of the examples in the new man page.

Owner

keszybz commented Feb 18, 2017

Updated.

I fixed the stuff you mentioned, and added another commit on top to tighten handling of unexpected syntax.

Owner

keszybz commented Feb 18, 2017

I made some small fixes and added /usr/lib/environment.d/00-xdg-basedir.conf to predefine the $XDG_* vars. This way it's possible to append to them later on without having to repeat the defaults and use variable expansion tricks.

Hmm looks good in general, but I am really not convinced about the xdg variable drop-in now. The spec explicitly say the env vars should be used when set, but apps should have fall backs in place with the listed paths. I really doesn't look right to me to always "pollute" the env block with these env vars which are almost never necessary. I mean, there are only a handful of people in this world who want to change these paths, and I am sure if they are adventurous enough to set things up like that they are also smart enough to deal with set the fallbacks correctly.

REally, I don't think we should have that in place by default, it appears entirely redundant to me.

src/basic/fileio.c
+ path = getpid() == 1 ? "/run/systemd" : "/tmp";
+ fd = open_tmpfile_unlinkable(path, O_RDWR|O_CLOEXEC);
+ if (fd < 0)
+ return -errno;
@poettering

poettering Feb 13, 2017

Owner

should return fd here... (not a bug in your change, this apparently was a pre-existing bug)

@poettering

poettering Feb 20, 2017

Owner

hmm, fixed? the patch looks the same still?

Contributor

halfline commented Feb 20, 2017

thanks for putting so much work into this.

I still think it's a little unfortunate that users wanting to set LD_LIBRARY_PATH have to write a generator to prevent inadvertently adding working directory to the library path list. I feel like there's a high probability that users will get it wrong, and just drop a file with LD_LIBRARY_PATH=$HOME/lib:$LD_LIBRARY_PATH in ~/.config/environment.d which will seemingly work but then open them up to potential security problems when they cd into ~/Download or whatever. On the other hand, a lot of users probably get it wrong today, too, in .bashrc… So I guess it's not really any worse than status quo.

Having /usr/lib/environment.d/00-xdg-basedir.conf has the benefit of flatpak not needing a generator, which is nice. I do see @poettering 's point about avoiding unnecessary environment variables if we can, though. Ultimately, we should be shooting to stop using them altogether!

Honestly, my preference would be to just allow the extended substitution syntax. Then we can avoid unnecessarily adding XDG_DATA_DIRS to the environment block, let users set LD_LIBRARY_PATH without a generator, and still have generators for more complicated cases than the big 3 (PATH/LD_LIBRARY_PATH/XDG_DATA_DIRS).

Anyway, regardless, this pull request puts us in good shape, and I'm happy.

keszybz and others added some commits Feb 11, 2017

core/manager: split out creation of serialization fd out to a helper
There is a slight change in behaviour: the user manager for root will create a
temporary file in /run/systemd, not /tmp. I don't think this matters, but
simplifies implementation.
basic/exec-util: add support for synchronous (ordered) execution
The output of processes can be gathered, and passed back to the callee.
(This commit just implements the basic functionality and tests.)

After the preparation in previous commits, the change in functionality is
relatively simple. For coding convenience, alarm is prepared *before* any
children are executed, and not before. This shouldn't matter usually, since
just forking of the children should be pretty quick. One could also argue that
this is more correct, because we will also catch the case when (for whatever
reason), forking itself is slow.

Three callback functions and three levels of serialization are used:
- from individual generator processes to the generator forker
- from the forker back to the main process
- deserialization in the main process

v2:
- replace an structure with an indexed array of callbacks
build-sys,man: load /etc/environment and describe the new environment…
….d syntax

Add support for /etc/environment and document the changes to the user manager
to automatically import environment *.conf files from:

        ~/.config/environment.d/
        /etc/environment.d/
        /run/environment.d/
        /usr/local/lib/environment.d/
        /usr/lib/environment.d/
        /etc/environment
basic: fix strv_env_get_n for unclean arrays
If an environment array has duplicates, strv_env_get_n returns
the results for the first match. This is wrong, because later
entries in the environment are supposed to replace earlier
entries.
basic: add new merge_env_file function
merge_env_file is a new function, that's like load_env_file, but takes a
pre-existing environment as an input argument. New environment entries are
merged. Variable expansion is performed.

Falling back to the process environment is supported (when a flag is set).
Alternatively this could be implemented as passing an additional fallback
environment array, but later on we're adding another flag to allow braceless
expansion, and the two flags can be combined in one arg, so there's less
stuff to pass around.
basic: drop unnecessary strempty() call in replace_env
strempty() converts a NULL value to empty string, so
that it can be passed on to functions that don't support NULL.

replace_env calls strempty before passing its value on to strappend.

strappend supports NULL just fine, though, so this commit drops the
strempty call.
manager: run environment generators
Environment file generators are a lot like unit file generators, but not
exactly:

1. environment file generators are run for each manager instance, and their
   output is (or at least can be) individualized.

   The generators themselves are system-wide, the same for all users.

2. environment file generators are run sequentially, in priority order.

Thus, the lifetime of those files is tied to lifecycle of the manager
instance. Because generators are run sequentially, later generators can use or
modify the output of earlier generators.

Each generator is run with no arguments, and the whole state is stored in the
environment variables. The generator can echo a set of variable assignments to
standard output:

  VAR_A=something
  VAR_B=something else

This output is parsed, and the next and subsequent generators run with those
updated variables in the environment. After the last generator is done, the
environment that the manager itself exports is updated.

Each generator must return 0, otherwise the output is ignored.

The generators in */user-env-generator are for the user session managers,
including root, and the ones in */system-env-generator are for pid1.
man: add systemd.environment-generator(7) with two examples
v2:
  - add example files to EXTRA_DIST
v3:
  - rework for the new scheme where nothing is written to disk
v4:
  - use separate dirs for system and user env generators
core/manager: move environment serialization out to basic/env-util.c
This protocol is generally useful, we might just as well reuse it for the
env. generators.

The implementation is changed a bit: instead of making a new strv and freeing
the old one, just mutate the original. This is much faster with larger arrays,
while in fact atomicity is preserved, since we only either insert the new
entry or not, without being in inconsistent state.

v2:
- fix confusion with return value
exec-util: implement a set of callbacks to pass variables around
Only tests are added, otherwise the new code is unused.
env-util,fileio: immediately replace variables in load_env_file_push()
strv_env_replace was calling env_match(), which in effect allowed multiple
values for the same key to be inserted into the environment block. That's
pointless, because APIs to access variables only return a single value (the
latest entry), so it's better to keep the block clean, i.e. with just a single
entry for each key.

Add a new helper function that simply tests if the part before '=' is equal in
two strings and use that in strv_env_replace.

In load_env_file_push, use strv_env_replace to immediately replace the previous
assignment with a matching name.

Afaict, none of the callers are materially affected by this change, but it
seems like some pointless work was being done, if the same value was set
multiple times. We'd go through parsing and assigning the value for each
entry. With this change, we handle just the last one.
environment-generator: new generator to peruse environment.d
Why the strange name: the prefix is necessary to follow our own advice that
environment generators should have numerical prefixes. I also put -d- in the
name because otherwise the name was very easy to mistake with
systemd.environment-generator. This additional letter clarifies that this
on special generator that supports environment.d files.
Owner

keszybz commented Feb 20, 2017

Yeah, I think @halfline was right all along ;) I think current version would do the job, but somehow it doesn't seem a good fit: we'd either need to write generators for each and every thing, or make sure that all possible variables of list type are predefined. Both are just not satisfactory.

I'll prep another version which restores the extended substitution syntax.

keszybz and others added some commits Feb 11, 2017

Allow braceless variables to be expanded
(Only in environment.d files.)

We have only basic compatibility with shell syntax, but specifying variables
without using braces is probably more common, and I think a lot of people would
be surprised if this didn't work.
basic: add replace_env_n function
It's like replace_env, but lets you pass in a substring.
basic: support default and alternate values for env expansion
Sometimes it's useful to provide a default value during an environment
expansion, if the environment variable isn't already set.

For instance $XDG_DATA_DIRS is suppose to default to:

/usr/local/share/:/usr/share/

if it's not yet set. That means callers wishing to augment
XDG_DATA_DIRS need to manually add those two values.

This commit changes replace_env to support the following shell
compatible default value syntax:

XDG_DATA_DIRS=/foo:${XDG_DATA_DIRS:-/usr/local/share/:/usr/share}

Likewise, it's useful to provide an alternate value during an
environment expansion, if the environment variable isn't already set.

For instance, $LD_LIBRARY_PATH will inadvertently search the current
working directory if it starts or ends with a colon, so the following
is usually wrong:

LD_LIBRARY_PATH=/foo/lib:${LD_LIBRARY_PATH}

To address that, this changes replace_env to support the following
shell compatible alternate value syntax:

LD_LIBRARY_PATH=/foo/lib${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}

[zj: gate the new syntax under REPLACE_ENV_ALLOW_EXTENDED switch, so
existing callers are not modified.]
Tighten checking for variable validity
In the future we might want to allow additional syntax (for example
"unset VAR". But let's check that the data we're getting does not contain
anything unexpected.
build-sys: make environment.d support conditional
We have ./configure switches for various parts of non-essential functionality,
let's add one for this new stuff too. Support for environment generators is
not conditional — if you don't want them, just don't install any.
test-env-util: add more tests for "extended syntax"
This is only the tip of the iceberg. It would be great to test all kinds of nesting, handling
of invalid syntax, etc., but I'm leaving that for later.
Owner

keszybz commented Feb 21, 2017

Here's another version. I forward-ported @halfline original patch to add "extended" syntax, but hooked it up just for the environment-d-generator. There are some tests, but just enough to confirm that things work if the correct syntax is used. If this approach is accepted, I'll add more tests for invalid syntax and various edge cases.

Owner

keszybz commented Feb 21, 2017

Another big question is how to hook this up to login sessions. Having the environment in the user manager is a nice step, but it isn't terribly useful if login, ssh, and graphical sessions are not using this environment.

Owner

poettering commented Feb 21, 2017

Another big question is how to hook this up to login sessions. Having the environment in the user manager is a nice step, but it isn't terribly useful if login, ssh, and graphical sessions are not using this environment.

That's why I was requesting an XDG spec for the drop-ins. (not for the generators though)

@poettering poettering merged commit a4dde27 into systemd:master Feb 21, 2017

5 checks passed

default Build finished.
Details
semaphoreci The build passed on Semaphore.
Details
xenial-amd64 autopkgtest finished (success)
Details
xenial-i386 autopkgtest finished (success)
Details
xenial-s390x autopkgtest finished (success)
Details
Member

evverx commented Feb 21, 2017

Hm

FAIL: test-env-util
===================


=================================================================
==17489==ERROR: LeakSanitizer: detected memory leaks

Direct leak of 16 byte(s) in 1 object(s) allocated from:
    #0 0x7fd06fa1de60 in malloc (/lib64/libasan.so.3+0xc6e60)
    #1 0x7fd06ef1c5ec in malloc_multiply src/basic/alloc-util.h:70
    #2 0x7fd06ef1dc76 in strnappend src/basic/string-util.c:206
    #3 0x7fd06ef7efb0 in replace_env_n src/basic/env-util.c:694
    #4 0x55a386b954a7 in replace_env src/basic/env-util.h:42
    #5 0x55a386b978ba in test_replace_env src/test/test-env-util.c:160
    #6 0x55a386b99b24 in main src/test/test-env-util.c:324
    #7 0x7fd06d7e5400 in __libc_start_main (/lib64/libc.so.6+0x20400)

Direct leak of 9 byte(s) in 1 object(s) allocated from:
    #0 0x7fd06fa1de60 in malloc (/lib64/libasan.so.3+0xc6e60)
    #1 0x7fd06ef1c5ec in malloc_multiply src/basic/alloc-util.h:70
    #2 0x7fd06ef1dc76 in strnappend src/basic/string-util.c:206
    #3 0x7fd06ef7efb0 in replace_env_n src/basic/env-util.c:694
    #4 0x55a386b954a7 in replace_env src/basic/env-util.h:42
    #5 0x55a386b978ba in test_replace_env src/test/test-env-util.c:160
    #6 0x55a386b99b2e in main src/test/test-env-util.c:325
    #7 0x7fd06d7e5400 in __libc_start_main (/lib64/libc.so.6+0x20400)

SUMMARY: AddressSanitizer: 25 byte(s) leaked in 2 allocation(s).
FAIL test-env-util (exit status: 1)
FAIL: test-fileio
=================
...
==18689==ERROR: LeakSanitizer: detected memory leaks

Direct leak of 64 byte(s) in 1 object(s) allocated from:
    #0 0x7f181a73e210 in realloc (/lib64/libasan.so.3+0xc7210)
    #1 0x7f1819d20c23 in greedy_realloc src/basic/alloc-util.c:57
    #2 0x7f1819cd7431 in parse_env_file_internal src/basic/fileio.c:415
    #3 0x7f1819cd93ae in merge_env_file src/basic/fileio.c:807
    #4 0x5634db7dc370 in test_merge_env_file_invalid src/test/test-fileio.c:308
    #5 0x5634db7e0af8 in main src/test/test-fileio.c:674
    #6 0x7f1818505400 in __libc_start_main (/lib64/libc.so.6+0x20400)

SUMMARY: AddressSanitizer: 64 byte(s) leaked in 1 allocation(s).
FAIL test-fileio (exit status: 1)
- v = strdup(value);
- if (!v)
- return -ENOMEM;
+ if (value) {
@benjarobin

benjarobin Feb 21, 2017

Contributor

Sorry (I am to curious) to ask that, but what is the use case to allow inserting NULL ? You use it in environment_dirs() but I don't really follow.

@keszybz

keszybz Feb 21, 2017

Owner

Oh, I think this isn't used any more. It was used to insert an empty slot at the beginning of an argv array in some earlier version of the patchset.

@benjarobin

benjarobin Feb 21, 2017

Contributor

@keszybz Shouldn't this commit be reverted ? I personally think this is kind of dangerous to allow this kind of operation, we may leak element...

@poettering

poettering Feb 21, 2017

Owner

I agree, we should drop that again, if it's not acually needed.

@poettering

poettering Feb 21, 2017

Owner

I posted a patch reverting this as part of PR #5411

@keszybz keszybz deleted the keszybz:environment-generators branch Feb 21, 2017

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment