OpenRC / openrc

The OpenRC init system
BSD 2-Clause "Simplified" License
1.48k stars 248 forks source link

symlinked conf.d file loading and naming can clash with runlevel specific conf.d #286

Open gyakovlev opened 5 years ago

gyakovlev commented 5 years ago

https://github.com/OpenRC/openrc/blob/3eef6e91274f2e07bd566f206e89d9b0b9c45fb9/sh/openrc-run.sh.in#L223-L237

consider user having a file /etc/init.d/app and a symlink to above file ^ /etc/init.d/app.default

and it's added to runlevel default

openrc will try to source runlevel-specific config first, which will be /etc/conf.d/app.default.default and on next try will try to source /etc/conf.d/app because bash string %% regex is greedy _c=${RC_SVCNAME%%.*}

so basically there need to be some logic handling this case.

or regex not as greedy (single %), but not sure how it will affect existing services or symlinks.

as I understand intention is to load specific file first and fallback to generic one if it fails.

but there is funky behavior if user creates a postfix that clashes with runlevel name.

gyakovlev commented 5 years ago

reported by user gmturner on irc

gmt commented 5 years ago

irc gmturner here :)

I wasn't 100% sure whether https://github.com/OpenRC/openrc/blob/master/sh/openrc-run.sh.in#L227-L229:

if ! sourcex -e "$_conf_d/$_c.$RC_RUNLEVEL"; then
    sourcex -e "$_conf_d/$_c"
fi

and https://github.com/OpenRC/openrc/blob/master/sh/openrc-run.sh.in#L234-L236:

if ! sourcex -e "$_conf_d/$RC_SVCNAME.$RC_RUNLEVEL"; then
    sourcex -e "$_conf_d/$RC_SVCNAME"
fi

are correct, absent any corner case? Put in concrete terms, in runlevel default, should the presence of conf.d/sshd.default prevent the evaluation of conf.d/sshd entirely, by design?

Assuming Yes

Then we have an ambiguity about a script like conf.d/net.default if we are working on behalf of init.d/net.default in the default runlevel. An argument could be made, assuming both that the above code is not totally wrong, and that there is a policy to resolve such ambiguities about the meaning of a conf.d/net.default in favor of treating it as a runlevel-specific script, that the current behavior is correct, specifically, something like:

If openrc finds a file conf.d/net.default in the default runlevel, it is going to treat that as the runlevel-targeted file, not the default-variant-targeted configuration for the multiplexed net script-family. Therefore, it must ignore conf.d/net.

I think it's pretty counterintuitive though. In my case, I didn't even know, until gyakovlev set me straight on IRC, that the runlevel-specific conf.d/ files were a thing, and was quite confused by this. Also that policy would strongly suggest we should be ignoring conf.d/net.default when we are not in the default runlevel, on any system which /has/ a default runlevel, which we clearly aren't actually doing.

I think the more intuitive policy would be the opposite one; we treat this file as the regular demultiplexed conf.d/ file specific to ${RC_SVCNAME}, regardless of runlevel, and if the user really wants a runlevel-specific multiplexed conf.d file, tough cookies (they could still put ${RC_RUNLEVEL}-conditional code into conf.d/${RC_SVCNAME}, after all and they can still put demultiplexed, runlevel-targeted settings in the usual place, i.e., conf.d/net.default.default, although they would need to unset variables from conf.d/net.default if they didn't want them to apply).

Assuming No

Then the behavior is totally buggy and this needs rewriting. If we are openrc-run trying to find conf.d scripts to source, all of:

these() {
    case "${RC_SVCNAME}" in
    *.*)
        _family=${RC_SVCNAME%%.*}
        # yes, the second one is just conf.d/${RC_SVCNAME}.
        # spelled out this way for clarity
        echo conf.d/${_family}
        echo conf.d/${_family}.${RC_SVCNAME#${_family}.}
        case "${RC_SVCNAME#${_family}.}" in
        ${RC_RUNLEVEL})
            # no point in sourcing it twice...
            :;
            ;;
        *) 
            echo conf.d/${_family}.${RC_RUNLEVEL}
            ;;
        esac
        echo conf.d/${_family}.${RC_SVCNAME#${_family}.}.${RC_RUNLEVEL}
        ;;
    *)
        echo conf.d/${RC_SVCNAME}
        echo conf.d/${RC_SVCNAME}.${RC_RUNLEVEL}
        ;;
    esac
}

candidates=$(these)

need sourcing, which isn't at all what's happening since any time we find a suitable ${foo}.${RC_RUNLEVEL} we fail to source ${foo}, including when [ "${RC_RUNLEVEL}" = "${_family}" ]

In that case, it's possible the user doesn't know anything about all these quirks, but simply tried to make a conf.d file for their regular init.d/foo.bar script which happened to have a dot in the name (essentially a supported use-case, even though this may technically have created a multiplexed singleton script).

tldr

The existing behavior in the edge case where a runlevel shares a name with a multiplexed variant (i.e., runlevel eth0) seems at best extremely counter-intuitive -- but far more likely buggy.

This is perhaps a less obscure problem than it seems at first glance. For example, creating "lo" and "hi" runlevels could wreak havoc on a netifrc box that required extra addresses assigned to the "127/8" address-space. Less plausibly, but more inscrutably, an "idmapd" or "pipefs" runlevel could give an NFS sysadmin a very confusing several hours of head-scratching while all his users waited for services to be restored.

I hope all my pseudo-code is not too buggy and some of the foregoing was comprehensible :) @gyakovlev and I discussed, as a potential solution to the underlying semantic deficiency, changing the .${RC_RUNLEVEL} suffix to _${RC_RUNLEVEL}, or making that character configurable, which might or might not create a big maintainability problem (and potentially creates a backward-incompatibility, if done unilaterally and without corresponding package-manager fixups).