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