/* SPDX-License-Identifier: LGPL-2.1+ */
#include "macro.h"
+#include "string-util.h"
#include "unit-file.h"
bool unit_type_may_alias(UnitType type) {
UNIT_TIMER,
UNIT_PATH);
}
+
+int unit_validate_alias_symlink_and_warn(const char *filename, const char *target) {
+ const char *src, *dst;
+ _cleanup_free_ char *src_instance = NULL, *dst_instance = NULL;
+ UnitType src_unit_type, dst_unit_type;
+ int src_name_type, dst_name_type;
+
+ /* Check if the *alias* symlink is valid. This applies to symlinks like
+ * /etc/systemd/system/dbus.service → dbus-broker.service, but not to .wants or .requires symlinks
+ * and such. Neither does this apply to symlinks which *link* units, i.e. symlinks to outside of the
+ * unit lookup path.
+ *
+ * -EINVAL is returned if the something is wrong with the source filename or the source unit type is
+ * not allowed to symlink,
+ * -EXDEV if the target filename is not a valid unit name or doesn't match the source.
+ */
+
+ src = basename(filename);
+ dst = basename(target);
+
+ /* src checks */
+
+ src_name_type = unit_name_to_instance(src, &src_instance);
+ if (src_name_type < 0)
+ return log_notice_errno(src_name_type,
+ "%s: not a valid unit name \"%s\": %m", filename, src);
+
+ src_unit_type = unit_name_to_type(src);
+ assert(src_unit_type >= 0); /* unit_name_to_instance() checked the suffix already */
+
+ if (!unit_type_may_alias(src_unit_type))
+ return log_notice_errno(SYNTHETIC_ERRNO(EINVAL),
+ "%s: symlinks are not allowed for units of this type, rejecting.",
+ filename);
+
+ if (src_name_type != UNIT_NAME_PLAIN &&
+ !unit_type_may_template(src_unit_type))
+ return log_notice_errno(SYNTHETIC_ERRNO(EINVAL),
+ "%s: templates not allowed for %s units, rejecting.",
+ filename, unit_type_to_string(src_unit_type));
+
+ /* dst checks */
+
+ dst_name_type = unit_name_to_instance(dst, &dst_instance);
+ if (dst_name_type < 0)
+ return log_notice_errno(dst_name_type == -EINVAL ? SYNTHETIC_ERRNO(EXDEV) : dst_name_type,
+ "%s points to \"%s\" which is not a valid unit name: %m",
+ filename, dst);
+
+ if (!(dst_name_type == src_name_type ||
+ (src_name_type == UNIT_NAME_INSTANCE && dst_name_type == UNIT_NAME_TEMPLATE)))
+ return log_notice_errno(SYNTHETIC_ERRNO(EXDEV),
+ "%s: symlink target name type \"%s\" does not match source, rejecting.",
+ filename, dst);
+
+ if (dst_name_type == UNIT_NAME_INSTANCE) {
+ assert(src_instance);
+ assert(dst_instance);
+ if (!streq(src_instance, dst_instance))
+ return log_notice_errno(SYNTHETIC_ERRNO(EXDEV),
+ "%s: unit symlink target \"%s\" instance name doesn't match, rejecting.",
+ filename, dst);
+ }
+
+ dst_unit_type = unit_name_to_type(dst);
+ if (dst_unit_type != src_unit_type)
+ return log_notice_errno(SYNTHETIC_ERRNO(EXDEV),
+ "%s: symlink target \"%s\" has incompatible suffix, rejecting.",
+ filename, dst);
+
+ return 0;
+}
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "path-lookup.h"
+#include "strv.h"
+#include "tests.h"
+#include "unit-file.h"
+
+static void test_unit_validate_alias_symlink_and_warn(void) {
+ log_info("/* %s */", __func__);
+
+ assert_se(unit_validate_alias_symlink_and_warn("/path/a.service", "/other/b.service") == 0);
+ assert_se(unit_validate_alias_symlink_and_warn("/path/a.service", "/other/b.socket") == -EXDEV);
+ assert_se(unit_validate_alias_symlink_and_warn("/path/a.service", "/other/b.foobar") == -EXDEV);
+ assert_se(unit_validate_alias_symlink_and_warn("/path/a@.service", "/other/b@.service") == 0);
+ assert_se(unit_validate_alias_symlink_and_warn("/path/a@.service", "/other/b@.socket") == -EXDEV);
+ assert_se(unit_validate_alias_symlink_and_warn("/path/a@XXX.service", "/other/b@YYY.service") == -EXDEV);
+ assert_se(unit_validate_alias_symlink_and_warn("/path/a@XXX.service", "/other/b@YYY.socket") == -EXDEV);
+ assert_se(unit_validate_alias_symlink_and_warn("/path/a@.service", "/other/b@YYY.service") == -EXDEV);
+ assert_se(unit_validate_alias_symlink_and_warn("/path/a@XXX.service", "/other/b@XXX.service") == 0);
+ assert_se(unit_validate_alias_symlink_and_warn("/path/a@XXX.service", "/other/b@.service") == 0);
+ assert_se(unit_validate_alias_symlink_and_warn("/path/a@.service", "/other/b.service") == -EXDEV);
+ assert_se(unit_validate_alias_symlink_and_warn("/path/a.service", "/other/b@.service") == -EXDEV);
+ assert_se(unit_validate_alias_symlink_and_warn("/path/a@.slice", "/other/b.slice") == -EINVAL);
+ assert_se(unit_validate_alias_symlink_and_warn("/path/a.slice", "/other/b.slice") == -EINVAL);
+}
+
+int main(int argc, char **argv) {
+ test_setup_logging(LOG_DEBUG);
+
+ test_unit_validate_alias_symlink_and_warn();
+
+ return 0;
+}