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

add support for passing arguments to services #386

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 29 additions & 3 deletions doc/manpages/dinit-service.5.m4
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,22 @@ Dinit service description files
.SH DESCRIPTION
.\"
The service description files for \fBDinit\fR each describe a service. The name
of the file corresponds to the name of the service it describes.
of the file corresponds to the name of the service it describes, minus its argument.
.LP
Service description files specify the various attributes of a service. A
service description file is named after the service it represents, and is
a plain-text file with simple key-value format.
service description file is named after the service it represents (without
its argument), and is a plain-text file with simple key-value format.
The description files are located in a service description directory;
See \fBdinit\fR(8) for more details of the default service description directories,
and how and when service descriptions are loaded.
.LP
The full name of the service includes its argument, such as \fIservice@argument\fR.
The argument is optional, so you can also invoke just \fIservice\fR. Each instance
of a service, i.e. with different arguments, is separate, including loading. This
means every time you invoke the service with a different argument, it is loaded
separately. Empty argument is not the same as missing argument, as this affects
variable substitution (see \fBVARIABLE SUBSTITUTION\fR).
.LP
All services have a \fItype\fR and a set of \fIdependencies\fR. These are discussed
in the following subsections. The type, dependencies, and other attributes are
specified via property settings, the format of which are documented in the
Expand Down Expand Up @@ -269,6 +276,8 @@ This service depends on the named service.
Starting this service will start the named service; the command to start this service will not be executed
until the named service has started.
If the named service stops then this service will also be stopped.
The \fIservice-name\fR is subject to minimal variable substitution
(see \fBVARIABLE SUBSTITUTION\fR).
.TP
\fBdepends\-ms\fR: \fIservice-name\fR
This service has a "milestone" dependency on the named service. Starting this
Expand All @@ -277,13 +286,15 @@ named service has started, and will fail to start if the named service does
not start.
Once the named (dependent) service reaches the started state, however, the
dependency may stop without affecting the dependent service.
The name is likewise subject to minimal variable substitution.
.TP
\fBwaits\-for\fR: \fIservice-name\fR
When this service is started, wait for the named service to finish starting
(or to fail starting) before commencing the start procedure for this service.
Starting this service will automatically start the named service.
If the named service fails to start, this service will start as usual (subject to
other dependencies being met).
The name is likewise subject to minimal variable substitution.
.TP
\fBdepends\-on.d\fR: \fIdirectory-path\fR
For each file name in \fIdirectory-path\fR which does not begin with a dot,
Expand All @@ -296,6 +307,7 @@ is not considered fatal.
.IP
The directory path, if not absolute, is relative to the directory containing the service
description file.
No variable substitution is done for path dependencies.
.TP
\fBdepends\-ms.d\fR: \fIdirectory-path\fR
As for \fBdepends-on.d\fR, but with dependency type \fBdepends\-ms\fR.
Expand All @@ -311,13 +323,17 @@ starting this service will not cause it to start (nor wait for it in that case).
It does not by itself cause the named service to be loaded (if loaded later, the "after"
relationship will be enforced from that point).
.TP
The name is subject to minimal variable substitution.
.TP
\fBbefore\fR: \fIservice-name\fR
When starting the named service, if this service is also starting, wait for this service
to finish starting before bringing the named service up. This is largely equivalent to specifying
an \fBafter\fR relationship to this service from the named service.
However, it does not by itself cause the named service to be loaded (if loaded later, the "before"
relationship will be enforced from that point).
.TP
The name is subject to minimal variable substitution.
.TP
\fBchain\-to\fR = \fIservice-name\fR
When this service terminates (i.e. starts successfully, and then stops of its
own accord), the named service should be started.
Expand All @@ -338,6 +354,8 @@ stopped due to a dependency stopping (for any reason), if it will restart
abnormally or with an exit status indicating an error.
However, if the \fBalways-chain\fR option is set the chain is started regardless of the
reason and the status of this service termination.
.IP
The name is subject to minimal variable substitution.
.TP
\fBsocket\-listen\fR = \fIsocket-path\fR
Pre-open a socket for the service and pass it to the service using the
Expand Down Expand Up @@ -711,6 +729,10 @@ set and non\-empty), and `\fB${NAME+word}\fR' (substitute `\fBword\fR' if variab
Unlike in shell expansion, the substituted \fBword\fR does not itself undergo expansion and
cannot contain closing brace characters or whitespace, even if quoted.
.LP
To substitute the service argument, the `\fB$1\fR' syntax may be used. The complete syntax
of the substitution is supported here. Services without an argument are treated as if the
variable was unset, which affects some of the curly brace syntax variants.
.LP
Note that by default, command-line variable substitution occurs after splitting the line into
separate arguments and so
a single environment variable cannot be used to add multiple arguments to a command line.
Expand Down Expand Up @@ -741,6 +763,10 @@ used for substitution, if they have been changed in the meantime.
Using environment variable values in service commands and parameters can be used as means to
provide easily-adjustable service configuration, but is not ideal for this purpose and alternatives
should be considered.
.LP
In dependency fields, including \fIbefore\fR and similar, minimal version of variable
substitution may happen. Only the service argument may be substituted. No curly brace
syntax is supported. Escaping to avoid substitution is still supported.
.\"
.SS META-COMMANDS
.\"
Expand Down
2 changes: 1 addition & 1 deletion doc/manpages/dinit.8.m4
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ Specifies the name of a service that should be started (along with its
dependencies).
If none are specified, defaults to \fIboot\fR (which requires that a suitable service description
for the \fIboot\fR service exists). Multiple services can be specified in which case they will each
be started.
be started. An argument to the service may be included here.
.sp
\fBNote:\fR on Linux, if \fBdinit\fR is running as PID 1 and with UID 0, it may ignore "naked"
service names (without preceding \fB\-\-service\fR/\fB\-t\fR) provided on the command line.
Expand Down
3 changes: 2 additions & 1 deletion doc/manpages/dinitctl.8.m4
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,8 @@ success.
Clear the log buffer for the service after displaying it.
.TP
\fIservice-name\fR
Specifies the name of the service to which the command applies.
Specifies the name of the service to which the command applies. It may have an argument that is passed
to the service.
.\"
.SH COMMAND DESCRIPTIONS
.\"
Expand Down
4 changes: 2 additions & 2 deletions src/dinitcheck.cc
Original file line number Diff line number Diff line change
Expand Up @@ -671,7 +671,7 @@ service_record *load_service(service_set_t &services, const std::string &name,
};

try {
process_service_line(settings, name.c_str(), line, input_pos, setting, op, i, end,
process_service_line(settings, name.c_str(), nullptr, line, input_pos, setting, op, i, end,
load_service_n, process_dep_dir_n);
}
catch (service_description_exc &exc) {
Expand Down Expand Up @@ -745,7 +745,7 @@ service_record *load_service(service_set_t &services, const std::string &name,
return resolve_env_var(name, envmap);
};

settings.finalise(report_err, renvmap, report_err, resolve_var);
settings.finalise(report_err, renvmap, nullptr, report_err, resolve_var);

if (!settings.working_dir.empty()) {
service_wdir = settings.working_dir;
Expand Down
24 changes: 23 additions & 1 deletion src/igr-tests/igr-runner.cc
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ void catlog_test();
void offline_enable_test();
void xdg_config_test();
void cycles_test();
void svc_arg_test();

int main(int argc, char **argv)
{
Expand All @@ -55,7 +56,8 @@ int main(int argc, char **argv)
{ "pseudo-cycle", pseudo_cycle_test }, { "before-after", before_after_test},
{ "before-after2", before_after2_test }, { "log-via-pipe", log_via_pipe_test },
{ "catlog", catlog_test }, { "offline-enable", offline_enable_test },
{ "xdg-config", xdg_config_test }, { "cycles", cycles_test } };
{ "xdg-config", xdg_config_test }, { "cycles", cycles_test },
{ "svc-arg", svc_arg_test } };
constexpr int num_tests = sizeof(tests) / sizeof(tests[0]);

dinit_bindir = "../..";
Expand Down Expand Up @@ -974,3 +976,23 @@ void cycles_test()
igr_assert_eq(read_file_contents(igr_input_basedir + "/cycles/expected-after_self"),
dinitctl_p.get_stderr());
}

void svc_arg_test()
{
igr_test_setup setup("svc-arg");
std::string output_file = setup.prep_output_file("svc-arg-record");
std::string socket_path = setup.prep_socket_path();

igr_env_var_setup env_output("OUTPUT", output_file.c_str());

dinit_proc dinit_p;
dinit_p.start("svc-arg", {"-u", "-d", "sd", "-p", socket_path, "-q", "checkarg@foo"});
dinit_p.wait_for_term({1,0} /* max 1 second */);

check_file_contents(output_file, std::string() +
"test-$1\n" +
"test-hello\n" +
"hello\n" +
"foo\n" +
"foo\n");
}
3 changes: 3 additions & 0 deletions src/igr-tests/svc-arg/checkarg.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/sh

echo "$1" >> "$OUTPUT"
4 changes: 4 additions & 0 deletions src/igr-tests/svc-arg/sd/checkarg
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
type = process
waits-for = checkarg2@$1
command = ../checkarg.sh $1
restart = false
3 changes: 3 additions & 0 deletions src/igr-tests/svc-arg/sd/checkarg2
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
type = scripted
waits-for = checkarg3@hello
command = ../checkarg.sh $1
3 changes: 3 additions & 0 deletions src/igr-tests/svc-arg/sd/checkarg3
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
type = scripted
waits-for = checkarg4@test-$1
command = ../checkarg.sh $1
3 changes: 3 additions & 0 deletions src/igr-tests/svc-arg/sd/checkarg4
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
type = scripted
waits-for = checkarg5@test-$$1
command = ../checkarg.sh $1
2 changes: 2 additions & 0 deletions src/igr-tests/svc-arg/sd/checkarg5
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
type = scripted
command = ../checkarg.sh $1
62 changes: 44 additions & 18 deletions src/includes/load-service.h
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,27 @@ inline string read_setting_value(file_pos_ref input_pos, string_iterator &i, str
return rval;
}

// Reads a dependency name while performing minimal argument expansion in it.
inline string read_dependency_value(file_pos_ref input_pos, string_iterator &i, string_iterator end,
const char *argval)
{
string rval;
if (!argval) argval = "";
read_setting_value(rval, setting_op_t::ASSIGN, input_pos, i, end, nullptr);
for (auto v = rval.find('$'); v != rval.npos && v != (rval.size() - 1); v = rval.find('$', v + 1)) {
switch (rval[v + 1]) {
case '$':
rval.erase(v, 1);
break;
case '1':
rval.replace(v, 2, argval);
v += strlen(argval) - 1;
continue;
}
}
return rval;
}

inline void fill_environment_userinfo(uid_t uid, const std::string &service_name, environment &env)
{
if (uid == (uid_t)-1) {
Expand Down Expand Up @@ -1031,7 +1052,7 @@ inline const char *resolve_env_var(const string &name, const environment::env_ma
template <typename T>
static void value_var_subst(const char *setting_name, std::string &line,
std::list<std::pair<unsigned,unsigned>> &offsets, T &var_resolve,
environment::env_map const &envmap)
environment::env_map const &envmap, const char *argval = nullptr)
{
auto dindx = line.find('$');
if (dindx == string::npos) {
Expand Down Expand Up @@ -1074,11 +1095,16 @@ static void value_var_subst(const char *setting_name, std::string &line,
bool brace = line[spos] == '{';
if (brace) ++spos;
auto j = std::next(line.begin(), spos);
// may be a service argument
bool is_arg = j != token_end && *j == '1';
// read environment variable name
string name = read_config_name(j, token_end, true);
if (name.empty()) {
throw service_description_exc(setting_name, "invalid/missing variable name after '$'");
}
string name;
if (!is_arg) {
name = read_config_name(j, token_end, true);
if (name.empty()) {
throw service_description_exc(setting_name, "invalid/missing variable name after '$'");
}
} else ++j;
char altmode = '\0';
bool colon = false;
auto altbeg = j, altend = j;
Expand Down Expand Up @@ -1106,7 +1132,7 @@ static void value_var_subst(const char *setting_name, std::string &line,
}
size_t line_len_before = r_line.size();
string_view resolved_vw;
auto *resolved = var_resolve(name, envmap);
auto *resolved = is_arg ? argval : var_resolve(name, envmap);
if (resolved) {
resolved_vw = resolved;
}
Expand Down Expand Up @@ -1300,7 +1326,7 @@ class service_settings_wrapper
// var_subst - functor to resolve environment variable values
template <bool propagate_sde = false, typename T, typename U = decltype(dummy_lint), typename V = decltype(resolve_env_var),
bool do_report_lint = !std::is_same<U, decltype(dummy_lint)>::value>
void finalise(T &report_error, environment::env_map const &envmap, U &report_lint = dummy_lint, V &var_subst = resolve_env_var)
void finalise(T &report_error, environment::env_map const &envmap, const char *argval, U &report_lint = dummy_lint, V &var_subst = resolve_env_var)
{
if (service_type == service_type_t::PROCESS || service_type == service_type_t::BGPROCESS
|| service_type == service_type_t::SCRIPTED) {
Expand Down Expand Up @@ -1384,7 +1410,7 @@ class service_settings_wrapper
try {
offsets.front().first = 0;
offsets.front().second = setting_value.size();
value_var_subst(setting_name, setting_value, offsets, var_subst, envmap);
value_var_subst(setting_name, setting_value, offsets, var_subst, envmap, argval);
}
catch (service_description_exc &exc) {
if (propagate_sde) throw;
Expand Down Expand Up @@ -1460,7 +1486,7 @@ class service_settings_wrapper
template <typename settings_wrapper,
typename load_service_t,
typename process_dep_dir_t>
void process_service_line(settings_wrapper &settings, const char *name, string &line,
void process_service_line(settings_wrapper &settings, const char *name, const char *arg, string &line,
file_pos_ref input_pos, string &setting, setting_op_t setting_op, string::iterator &i,
string::iterator &end, load_service_t load_service,
process_dep_dir_t process_dep_dir)
Expand Down Expand Up @@ -1524,20 +1550,21 @@ void process_service_line(settings_wrapper &settings, const char *name, string &
break;
case setting_id_t::DEPENDS_ON:
{
string dependency_name = read_setting_value(input_pos, i, end);
settings.depends.emplace_back(load_service(dependency_name.c_str()), dependency_type::REGULAR);
string dependency_name = read_dependency_value(input_pos, i, end, arg);
settings.depends.emplace_back(load_service(dependency_name.c_str()),
dependency_type::REGULAR);
break;
}
case setting_id_t::DEPENDS_MS:
{
string dependency_name = read_setting_value(input_pos, i, end);
string dependency_name = read_dependency_value(input_pos, i, end, arg);
settings.depends.emplace_back(load_service(dependency_name.c_str()),
dependency_type::MILESTONE);
break;
}
case setting_id_t::WAITS_FOR:
{
string dependency_name = read_setting_value(input_pos, i, end);
string dependency_name = read_dependency_value(input_pos, i, end, arg);
settings.depends.emplace_back(load_service(dependency_name.c_str()),
dependency_type::WAITS_FOR);
break;
Expand All @@ -1556,20 +1583,19 @@ void process_service_line(settings_wrapper &settings, const char *name, string &
}
case setting_id_t::DEPENDS_MS_D:
{
string dependency_name = read_setting_value(input_pos, i, end);
settings.depends.emplace_back(load_service(dependency_name.c_str()),
dependency_type::MILESTONE);
string depends_ms_d = read_setting_value(input_pos, i, end);
process_dep_dir(settings.depends, depends_ms_d, dependency_type::MILESTONE);
break;
}
case setting_id_t::AFTER:
{
string after_name = read_setting_value(input_pos, i, end);
string after_name = read_dependency_value(input_pos, i, end, arg);
settings.after_svcs.emplace_back(std::move(after_name));
break;
}
case setting_id_t::BEFORE:
{
string before_name = read_setting_value(input_pos, i, end);
string before_name = read_dependency_value(input_pos, i, end, arg);
settings.before_svcs.emplace_back(std::move(before_name));
break;
}
Expand Down
Loading