934ef6a522
Upon an incoming connection for an accepting socket, we'd create a unit like foo@0.service, then figure out that the instance name should be e.g. "0-41-0", and then add the name foo@0-41-0.service to the unit. This obviously violates the rule that any service needs to have a constance instance part. So let's reverse the order: we first determine the instance name and then create the unit with the correct name from the start. There are two cases where we don't know the instance name: - analyze-verify: we just do a quick check that the instance unit can be created. So let's use a bogus instance string. - selinux: the code wants to load the service unit to extract the ExecStart path and query it for the selinux label. Do the same as above. Note that in both cases it is possible that the real unit that is loaded could be different than the one with the bogus instance value, for example if there is a dropin for a specific instance name. We can't do much about this, since we can't figure out the instance name in advance. The old code had the same shortcoming.
284 lines
8.1 KiB
C
284 lines
8.1 KiB
C
/* SPDX-License-Identifier: LGPL-2.1+ */
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include "alloc-util.h"
|
|
#include "all-units.h"
|
|
#include "analyze-verify.h"
|
|
#include "bus-error.h"
|
|
#include "bus-util.h"
|
|
#include "log.h"
|
|
#include "manager.h"
|
|
#include "pager.h"
|
|
#include "path-util.h"
|
|
#include "strv.h"
|
|
#include "unit-name.h"
|
|
|
|
static int prepare_filename(const char *filename, char **ret) {
|
|
int r;
|
|
const char *name;
|
|
_cleanup_free_ char *abspath = NULL;
|
|
_cleanup_free_ char *dir = NULL;
|
|
_cleanup_free_ char *with_instance = NULL;
|
|
char *c;
|
|
|
|
assert(filename);
|
|
assert(ret);
|
|
|
|
r = path_make_absolute_cwd(filename, &abspath);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
name = basename(abspath);
|
|
if (!unit_name_is_valid(name, UNIT_NAME_ANY))
|
|
return -EINVAL;
|
|
|
|
if (unit_name_is_valid(name, UNIT_NAME_TEMPLATE)) {
|
|
r = unit_name_replace_instance(name, "i", &with_instance);
|
|
if (r < 0)
|
|
return r;
|
|
}
|
|
|
|
dir = dirname_malloc(abspath);
|
|
if (!dir)
|
|
return -ENOMEM;
|
|
|
|
c = path_join(dir, with_instance ?: name);
|
|
if (!c)
|
|
return -ENOMEM;
|
|
|
|
*ret = c;
|
|
return 0;
|
|
}
|
|
|
|
static int generate_path(char **var, char **filenames) {
|
|
const char *old;
|
|
char **filename;
|
|
|
|
_cleanup_strv_free_ char **ans = NULL;
|
|
int r;
|
|
|
|
STRV_FOREACH(filename, filenames) {
|
|
char *t;
|
|
|
|
t = dirname_malloc(*filename);
|
|
if (!t)
|
|
return -ENOMEM;
|
|
|
|
r = strv_consume(&ans, t);
|
|
if (r < 0)
|
|
return r;
|
|
}
|
|
|
|
assert_se(strv_uniq(ans));
|
|
|
|
/* First, prepend our directories. Second, if some path was specified, use that, and
|
|
* otherwise use the defaults. Any duplicates will be filtered out in path-lookup.c.
|
|
* Treat explicit empty path to mean that nothing should be appended.
|
|
*/
|
|
old = getenv("SYSTEMD_UNIT_PATH");
|
|
if (!streq_ptr(old, "")) {
|
|
if (!old)
|
|
old = ":";
|
|
|
|
r = strv_extend(&ans, old);
|
|
if (r < 0)
|
|
return r;
|
|
}
|
|
|
|
*var = strv_join(ans, ":");
|
|
if (!*var)
|
|
return -ENOMEM;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int verify_socket(Unit *u) {
|
|
Unit *service;
|
|
int r;
|
|
|
|
assert(u);
|
|
|
|
if (u->type != UNIT_SOCKET)
|
|
return 0;
|
|
|
|
r = socket_load_service_unit(SOCKET(u), -1, &service);
|
|
if (r < 0)
|
|
return log_unit_error_errno(u, r, "service unit for the socket cannot be loaded: %m");
|
|
|
|
if (service->load_state != UNIT_LOADED)
|
|
return log_unit_error_errno(u, SYNTHETIC_ERRNO(ENOENT),
|
|
"service %s not loaded, socket cannot be started.", service->id);
|
|
|
|
log_unit_debug(u, "using service unit %s.", service->id);
|
|
return 0;
|
|
}
|
|
|
|
int verify_executable(Unit *u, const ExecCommand *exec) {
|
|
if (!exec)
|
|
return 0;
|
|
|
|
if (exec->flags & EXEC_COMMAND_IGNORE_FAILURE)
|
|
return 0;
|
|
|
|
if (access(exec->path, X_OK) < 0)
|
|
return log_unit_error_errno(u, errno, "Command %s is not executable: %m", exec->path);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int verify_executables(Unit *u) {
|
|
ExecCommand *exec;
|
|
int r = 0, k;
|
|
unsigned i;
|
|
|
|
assert(u);
|
|
|
|
exec = u->type == UNIT_SOCKET ? SOCKET(u)->control_command :
|
|
u->type == UNIT_MOUNT ? MOUNT(u)->control_command :
|
|
u->type == UNIT_SWAP ? SWAP(u)->control_command : NULL;
|
|
k = verify_executable(u, exec);
|
|
if (k < 0 && r == 0)
|
|
r = k;
|
|
|
|
if (u->type == UNIT_SERVICE)
|
|
for (i = 0; i < ELEMENTSOF(SERVICE(u)->exec_command); i++) {
|
|
k = verify_executable(u, SERVICE(u)->exec_command[i]);
|
|
if (k < 0 && r == 0)
|
|
r = k;
|
|
}
|
|
|
|
if (u->type == UNIT_SOCKET)
|
|
for (i = 0; i < ELEMENTSOF(SOCKET(u)->exec_command); i++) {
|
|
k = verify_executable(u, SOCKET(u)->exec_command[i]);
|
|
if (k < 0 && r == 0)
|
|
r = k;
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
static int verify_documentation(Unit *u, bool check_man) {
|
|
char **p;
|
|
int r = 0, k;
|
|
|
|
STRV_FOREACH(p, u->documentation) {
|
|
log_unit_debug(u, "Found documentation item: %s", *p);
|
|
|
|
if (check_man && startswith(*p, "man:")) {
|
|
k = show_man_page(*p + 4, true);
|
|
if (k != 0) {
|
|
if (k < 0)
|
|
log_unit_error_errno(u, k, "Can't show %s: %m", *p + 4);
|
|
else {
|
|
log_unit_error(u, "Command 'man %s' failed with code %d", *p + 4, k);
|
|
k = -ENOEXEC;
|
|
}
|
|
if (r == 0)
|
|
r = k;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Check remote URLs? */
|
|
|
|
return r;
|
|
}
|
|
|
|
static int verify_unit(Unit *u, bool check_man) {
|
|
_cleanup_(sd_bus_error_free) sd_bus_error err = SD_BUS_ERROR_NULL;
|
|
int r, k;
|
|
|
|
assert(u);
|
|
|
|
if (DEBUG_LOGGING)
|
|
unit_dump(u, stdout, "\t");
|
|
|
|
log_unit_debug(u, "Creating %s/start job", u->id);
|
|
r = manager_add_job(u->manager, JOB_START, u, JOB_REPLACE, NULL, &err, NULL);
|
|
if (r < 0)
|
|
log_unit_error_errno(u, r, "Failed to create %s/start: %s", u->id, bus_error_message(&err, r));
|
|
|
|
k = verify_socket(u);
|
|
if (k < 0 && r == 0)
|
|
r = k;
|
|
|
|
k = verify_executables(u);
|
|
if (k < 0 && r == 0)
|
|
r = k;
|
|
|
|
k = verify_documentation(u, check_man);
|
|
if (k < 0 && r == 0)
|
|
r = k;
|
|
|
|
return r;
|
|
}
|
|
|
|
int verify_units(char **filenames, UnitFileScope scope, bool check_man, bool run_generators) {
|
|
const ManagerTestRunFlags flags =
|
|
MANAGER_TEST_RUN_BASIC |
|
|
MANAGER_TEST_RUN_ENV_GENERATORS |
|
|
run_generators * MANAGER_TEST_RUN_GENERATORS;
|
|
|
|
_cleanup_(manager_freep) Manager *m = NULL;
|
|
Unit *units[strv_length(filenames)];
|
|
_cleanup_free_ char *var = NULL;
|
|
int r = 0, k, i, count = 0;
|
|
char **filename;
|
|
|
|
if (strv_isempty(filenames))
|
|
return 0;
|
|
|
|
/* set the path */
|
|
r = generate_path(&var, filenames);
|
|
if (r < 0)
|
|
return log_error_errno(r, "Failed to generate unit load path: %m");
|
|
|
|
assert_se(set_unit_path(var) >= 0);
|
|
|
|
r = manager_new(scope, flags, &m);
|
|
if (r < 0)
|
|
return log_error_errno(r, "Failed to initialize manager: %m");
|
|
|
|
log_debug("Starting manager...");
|
|
|
|
r = manager_startup(m, NULL, NULL);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
manager_clear_jobs(m);
|
|
|
|
log_debug("Loading remaining units from the command line...");
|
|
|
|
STRV_FOREACH(filename, filenames) {
|
|
_cleanup_free_ char *prepared = NULL;
|
|
|
|
log_debug("Handling %s...", *filename);
|
|
|
|
k = prepare_filename(*filename, &prepared);
|
|
if (k < 0) {
|
|
log_error_errno(k, "Failed to prepare filename %s: %m", *filename);
|
|
if (r == 0)
|
|
r = k;
|
|
continue;
|
|
}
|
|
|
|
k = manager_load_startable_unit_or_warn(m, NULL, prepared, &units[count]);
|
|
if (k < 0) {
|
|
if (r == 0)
|
|
r = k;
|
|
continue;
|
|
}
|
|
|
|
count++;
|
|
}
|
|
|
|
for (i = 0; i < count; i++) {
|
|
k = verify_unit(units[i], check_man);
|
|
if (k < 0 && r == 0)
|
|
r = k;
|
|
}
|
|
|
|
return r;
|
|
}
|