594167
From b3b45ed9385341e72edfc1bae08819026d841d46 Mon Sep 17 00:00:00 2001
594167
From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= <zbyszek@in.waw.pl>
594167
Date: Tue, 8 Mar 2022 12:08:00 +0100
594167
Subject: [PATCH] shared/specifier: provide proper error messages when
594167
 specifiers fail to read files
594167
594167
ENOENT is easily confused with the file that we're working on not being
594167
present, e.g. when the file contains %o or something else that requires
594167
os-release to be present. Let's use -EUNATCH instead to reduce that chances of
594167
confusion if the context of the error is lost.
594167
594167
And once we have pinpointed the reason, let's provide a proper error message:
594167
594167
+ build/systemctl --root=/tmp/systemctl-test.TO7Mcb enable some-some-link6@.socket
594167
/tmp/systemctl-test.TO7Mcb/etc/systemd/system/some-some-link6@.socket: Failed to resolve alias "target@A:%A.socket": Protocol driver not attached
594167
Failed to enable unit, cannot resolve specifiers in "target@A:%A.socket".
594167
594167
(cherry picked from commit 6ec4c852c910b1aca649e87ba3143841334f01fa)
594167
594167
Related: #2082131
594167
---
594167
 src/shared/install.c          | 31 +++++++++++++-------
594167
 src/shared/specifier.c        | 27 ++++++++++++-----
594167
 src/test/test-specifier.c     |  2 +-
594167
 test/test-systemctl-enable.sh | 55 ++++++++++++++++++++++++++---------
594167
 4 files changed, 82 insertions(+), 33 deletions(-)
594167
594167
diff --git a/src/shared/install.c b/src/shared/install.c
594167
index cbfe96b1e8..ea5bc36482 100644
594167
--- a/src/shared/install.c
594167
+++ b/src/shared/install.c
594167
@@ -374,6 +374,7 @@ void unit_file_dump_changes(int r, const char *verb, const UnitFileChange *chang
594167
                                         verb, changes[i].path);
594167
                         logged = true;
594167
                         break;
594167
+
594167
                 case -EADDRNOTAVAIL:
594167
                         log_error_errno(changes[i].type_or_errno, "Failed to %s unit, unit %s is transient or generated.",
594167
                                         verb, changes[i].path);
594167
@@ -401,6 +402,12 @@ void unit_file_dump_changes(int r, const char *verb, const UnitFileChange *chang
594167
                         logged = true;
594167
                         break;
594167
 
594167
+                case -EUNATCH:
594167
+                        log_error_errno(changes[i].type_or_errno, "Failed to %s unit, cannot resolve specifiers in \"%s\".",
594167
+                                        verb, changes[i].path);
594167
+                        logged = true;
594167
+                        break;
594167
+
594167
                 default:
594167
                         assert(changes[i].type_or_errno < 0);
594167
                         log_error_errno(changes[i].type_or_errno, "Failed to %s unit, file \"%s\": %m",
594167
@@ -1154,7 +1161,8 @@ static int config_parse_also(
594167
 
594167
                 r = install_name_printf(info, word, info->root, &printed);
594167
                 if (r < 0)
594167
-                        return r;
594167
+                        return log_syntax(unit, LOG_WARNING, filename, line, r,
594167
+                                          "Failed to resolve unit name in Also=\"%s\": %m", word);
594167
 
594167
                 r = install_info_add(c, printed, NULL, info->root, /* auxiliary= */ true, NULL);
594167
                 if (r < 0)
594167
@@ -1201,14 +1209,13 @@ static int config_parse_default_instance(
594167
 
594167
         r = install_name_printf(i, rvalue, i->root, &printed);
594167
         if (r < 0)
594167
-                return r;
594167
+                return log_syntax(unit, LOG_WARNING, filename, line, r,
594167
+                                  "Failed to resolve instance name in DefaultInstance=\"%s\": %m", rvalue);
594167
 
594167
-        if (isempty(printed)) {
594167
-                i->default_instance = mfree(i->default_instance);
594167
-                return 0;
594167
-        }
594167
+        if (isempty(printed))
594167
+                printed = mfree(printed);
594167
 
594167
-        if (!unit_instance_is_valid(printed))
594167
+        if (printed && !unit_instance_is_valid(printed))
594167
                 return log_syntax(unit, LOG_WARNING, filename, line, SYNTHETIC_ERRNO(EINVAL),
594167
                                   "Invalid DefaultInstance= value \"%s\".", printed);
594167
 
594167
@@ -1776,8 +1783,10 @@ static int install_info_symlink_alias(
594167
                 _cleanup_free_ char *alias_path = NULL, *dst = NULL, *dst_updated = NULL;
594167
 
594167
                 q = install_name_printf(i, *s, i->root, &dst);
594167
-                if (q < 0)
594167
+                if (q < 0) {
594167
+                        unit_file_changes_add(changes, n_changes, q, *s, NULL);
594167
                         return q;
594167
+                }
594167
 
594167
                 q = unit_file_verify_alias(i, dst, &dst_updated);
594167
                 if (q < 0)
594167
@@ -1861,8 +1870,10 @@ static int install_info_symlink_wants(
594167
                 _cleanup_free_ char *path = NULL, *dst = NULL;
594167
 
594167
                 q = install_name_printf(i, *s, i->root, &dst);
594167
-                if (q < 0)
594167
+                if (q < 0) {
594167
+                        unit_file_changes_add(changes, n_changes, q, *s, NULL);
594167
                         return q;
594167
+                }
594167
 
594167
                 if (!unit_name_is_valid(dst, valid_dst_type)) {
594167
                         /* Generate a proper error here: EUCLEAN if the name is generally bad, EIDRM if the
594167
@@ -3332,7 +3343,7 @@ int unit_file_preset_all(
594167
 
594167
                         r = preset_prepare_one(scope, &plus, &minus, &lp, de->d_name, &presets, changes, n_changes);
594167
                         if (r < 0 &&
594167
-                            !IN_SET(r, -EEXIST, -ERFKILL, -EADDRNOTAVAIL, -EIDRM, -EUCLEAN, -ELOOP, -ENOENT))
594167
+                            !IN_SET(r, -EEXIST, -ERFKILL, -EADDRNOTAVAIL, -EIDRM, -EUCLEAN, -ELOOP, -ENOENT, -EUNATCH))
594167
                                 /* Ignore generated/transient/missing/invalid units when applying preset, propagate other errors.
594167
                                  * Coordinate with unit_file_dump_changes() above. */
594167
                                 return r;
594167
diff --git a/src/shared/specifier.c b/src/shared/specifier.c
594167
index c26628975c..a917378427 100644
594167
--- a/src/shared/specifier.c
594167
+++ b/src/shared/specifier.c
594167
@@ -131,7 +131,8 @@ int specifier_machine_id(char specifier, const void *data, const char *root, con
594167
 
594167
                 fd = chase_symlinks_and_open("/etc/machine-id", root, CHASE_PREFIX_ROOT, O_RDONLY|O_CLOEXEC|O_NOCTTY, NULL);
594167
                 if (fd < 0)
594167
-                        return fd;
594167
+                        /* Translate error for missing os-release file to EUNATCH. */
594167
+                        return fd == -ENOENT ? -EUNATCH : fd;
594167
 
594167
                 r = id128_read_fd(fd, ID128_PLAIN, &id;;
594167
         } else
594167
@@ -214,31 +215,41 @@ int specifier_architecture(char specifier, const void *data, const char *root, c
594167
 
594167
 /* Note: fields in /etc/os-release might quite possibly be missing, even if everything is entirely valid
594167
  * otherwise. We'll return an empty value or NULL in that case from the functions below. But if the
594167
- * os-release file is missing, we'll return -ENOENT. This means that something is seriously wrong with the
594167
+ * os-release file is missing, we'll return -EUNATCH. This means that something is seriously wrong with the
594167
  * installation. */
594167
 
594167
+static int parse_os_release_specifier(const char *root, const char *id, char **ret) {
594167
+        int r;
594167
+
594167
+        assert(ret);
594167
+
594167
+        /* Translate error for missing os-release file to EUNATCH. */
594167
+        r = parse_os_release(root, id, ret);
594167
+        return r == -ENOENT ? -EUNATCH : r;
594167
+}
594167
+
594167
 int specifier_os_id(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
594167
-        return parse_os_release(root, "ID", ret);
594167
+        return parse_os_release_specifier(root, "ID", ret);
594167
 }
594167
 
594167
 int specifier_os_version_id(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
594167
-        return parse_os_release(root, "VERSION_ID", ret);
594167
+        return parse_os_release_specifier(root, "VERSION_ID", ret);
594167
 }
594167
 
594167
 int specifier_os_build_id(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
594167
-        return parse_os_release(root, "BUILD_ID", ret);
594167
+        return parse_os_release_specifier(root, "BUILD_ID", ret);
594167
 }
594167
 
594167
 int specifier_os_variant_id(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
594167
-        return parse_os_release(root, "VARIANT_ID", ret);
594167
+        return parse_os_release_specifier(root, "VARIANT_ID", ret);
594167
 }
594167
 
594167
 int specifier_os_image_id(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
594167
-        return parse_os_release(root, "IMAGE_ID", ret);
594167
+        return parse_os_release_specifier(root, "IMAGE_ID", ret);
594167
 }
594167
 
594167
 int specifier_os_image_version(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
594167
-        return parse_os_release(root, "IMAGE_VERSION", ret);
594167
+        return parse_os_release_specifier(root, "IMAGE_VERSION", ret);
594167
 }
594167
 
594167
 int specifier_group_name(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
594167
diff --git a/src/test/test-specifier.c b/src/test/test-specifier.c
594167
index 790f0252d7..a45d1bd0b9 100644
594167
--- a/src/test/test-specifier.c
594167
+++ b/src/test/test-specifier.c
594167
@@ -104,7 +104,7 @@ TEST(specifiers_missing_data_ok) {
594167
         assert_se(streq(resolved, "-----"));
594167
 
594167
         assert_se(setenv("SYSTEMD_OS_RELEASE", "/nosuchfileordirectory", 1) == 0);
594167
-        assert_se(specifier_printf("%A-%B-%M-%o-%w-%W", SIZE_MAX, specifier_table, NULL, NULL, &resolved) == -ENOENT);
594167
+        assert_se(specifier_printf("%A-%B-%M-%o-%w-%W", SIZE_MAX, specifier_table, NULL, NULL, &resolved) == -EUNATCH);
594167
         assert_se(streq(resolved, "-----"));
594167
 
594167
         assert_se(unsetenv("SYSTEMD_OS_RELEASE") == 0);
594167
diff --git a/test/test-systemctl-enable.sh b/test/test-systemctl-enable.sh
594167
index 769341129c..43a2c0a0fb 100644
594167
--- a/test/test-systemctl-enable.sh
594167
+++ b/test/test-systemctl-enable.sh
594167
@@ -445,12 +445,45 @@ EOF
594167
 
594167
 check_alias a "$(uname -m | tr '_' '-')"
594167
 
594167
-# FIXME: when os-release is not found, we fail we a cryptic error
594167
-# Alias=target@%A.socket
594167
+test ! -e "$root/etc/os-release"
594167
+test ! -e "$root/usr/lib/os-release"
594167
+
594167
+check_alias A '' && { echo "Expected failure" >&2; exit 1; }
594167
+check_alias B '' && { echo "Expected failure" >&2; exit 1; }
594167
+check_alias M '' && { echo "Expected failure" >&2; exit 1; }
594167
+check_alias o '' && { echo "Expected failure" >&2; exit 1; }
594167
+check_alias w '' && { echo "Expected failure" >&2; exit 1; }
594167
+check_alias W '' && { echo "Expected failure" >&2; exit 1; }
594167
+
594167
+cat >"$root/etc/os-release" <
594167
+# empty
594167
+EOF
594167
 
594167
-check_alias b "$(systemd-id128 boot-id)"
594167
+check_alias A ''
594167
+check_alias B ''
594167
+check_alias M ''
594167
+check_alias o ''
594167
+check_alias w ''
594167
+check_alias W ''
594167
+
594167
+cat >"$root/etc/os-release" <
594167
+ID='the-id'
594167
+VERSION_ID=39a
594167
+BUILD_ID=build-id
594167
+VARIANT_ID=wrong
594167
+VARIANT_ID=right
594167
+IMAGE_ID="foobar"
594167
+IMAGE_VERSION='1-2-3'
594167
+EOF
594167
 
594167
-# Alias=target@%B.socket
594167
+check_alias A '1-2-3'
594167
+check_alias B 'build-id'
594167
+check_alias M 'foobar'
594167
+check_alias o 'the-id'
594167
+check_alias w '39a'
594167
+check_alias W 'right'
594167
+
594167
+check_alias b "$(systemd-id128 boot-id)"
594167
 
594167
 # FIXME: Failed to enable: Invalid slot.
594167
 # Alias=target@%C.socket
594167
@@ -479,18 +512,15 @@ check_alias l "$(uname -n | sed 's/\..*//')"
594167
 # FIXME: Failed to enable: Invalid slot.
594167
 # Alias=target@%L.socket
594167
 
594167
-# FIXME: Failed to enable: No such file or directory.
594167
-# Alias=target@%m.socket
594167
+test ! -e "$root/etc/machine-id"
594167
+check_alias m '' && { echo "Expected failure" >&2; exit 1; }
594167
 
594167
-# FIXME: Failed to enable: No such file or directory.
594167
-# Alias=target@%M.socket
594167
+systemd-id128 new >"$root/etc/machine-id"
594167
+check_alias m "$(cat "$root/etc/machine-id")"
594167
 
594167
 check_alias n 'some-some-link6@.socket'
594167
 check_alias N 'some-some-link6@'
594167
 
594167
-# FIXME: Failed to enable: No such file or directory.
594167
-# Alias=target@%o.socket
594167
-
594167
 check_alias p 'some-some-link6'
594167
 
594167
 # FIXME: Failed to enable: Invalid slot.
594167
@@ -509,9 +539,6 @@ check_alias v "$(uname -r)"
594167
 # FIXME: Failed to enable: Invalid slot.
594167
 # Alias=target@%V.socket
594167
 
594167
-# Alias=target@%w.socket
594167
-# Alias=target@%W.socket
594167
-
594167
 check_alias % '%' && { echo "Expected failure because % is not legal in unit name" >&2; exit 1; }
594167
 
594167
 check_alias z 'z' && { echo "Expected failure because %z is not known" >&2; exit 1; }