Use paths specified from environment variables for /etc configuration files

Some configuration files that need updates are directly under in /etc. To
update them atomically, we need write access to /etc. For Ubuntu Core this is
an issue as /etc is not writable. Only a selection of subdirectories can be
writable. The general solution is symlinks or bind mounts to writable places.
But for atomic writes in /etc, that does not work. So Ubuntu has had a patch
for that that did not age well.

Instead we would like to introduce some environment variables for alternate
paths.

 * SYSTEMD_ETC_HOSTNAME: /etc/hostname
 * SYSTEMD_ETC_MACHINE_INFO: /etc/machine-info
 * SYSTEMD_ETC_LOCALTIME: /etc/localtime
 * SYSTEMD_ETC_LOCALE_CONF: /etc/locale.conf
 * SYSTEMD_ETC_VCONSOLE_CONF: /etc/vconsole.conf
 * SYSTEMD_ETC_ADJTIME: /etc/adjtime

While it is for now expected that there is a symlink from the standard, we
still try to read them from that alternate path. This is important for
`/etc/localtime`, which is a symlink, so we cannot have an indirect symlink or
bind mount for it.

Since machine-id is typically written only once and not updated. This commit
does not cover it. An initrd can properly create it and bind mount it.
This commit is contained in:
Valentin David
2025-06-23 15:32:11 +02:00
committed by Lennart Poettering
parent fa34fda2eb
commit 0dc39dffbd
19 changed files with 376 additions and 48 deletions
+19 -1
View File
@@ -157,13 +157,31 @@ bool is_localhost(const char *hostname) {
endswith_no_case(hostname, ".localhost.localdomain.");
}
const char* etc_hostname(void) {
static const char *cached = NULL;
if (!cached)
cached = secure_getenv("SYSTEMD_ETC_HOSTNAME") ?: "/etc/hostname";
return cached;
}
const char* etc_machine_info(void) {
static const char *cached = NULL;
if (!cached)
cached = secure_getenv("SYSTEMD_ETC_MACHINE_INFO") ?: "/etc/machine-info";
return cached;
}
int get_pretty_hostname(char **ret) {
_cleanup_free_ char *n = NULL;
int r;
assert(ret);
r = parse_env_file(NULL, "/etc/machine-info", "PRETTY_HOSTNAME", &n);
r = parse_env_file(NULL, etc_machine_info(), "PRETTY_HOSTNAME", &n);
if (r < 0)
return r;
+3
View File
@@ -37,6 +37,9 @@ static inline bool is_dns_proxy_stub_hostname(const char *hostname) {
return STRCASE_IN_SET(hostname, "_localdnsproxy", "_localdnsproxy.");
}
const char* etc_hostname(void);
const char* etc_machine_info(void);
int get_pretty_hostname(char **ret);
int machine_spec_valid(const char *s);
+10 -1
View File
@@ -1607,7 +1607,7 @@ int get_timezone(char **ret) {
assert(ret);
r = readlink_malloc("/etc/localtime", &t);
r = readlink_malloc(etc_localtime(), &t);
if (r == -ENOENT)
/* If the symlink does not exist, assume "UTC", like glibc does */
return strdup_to(ret, "UTC");
@@ -1623,6 +1623,15 @@ int get_timezone(char **ret) {
return strdup_to(ret, e);
}
const char* etc_localtime(void) {
static const char *cached = NULL;
if (!cached)
cached = secure_getenv("SYSTEMD_ETC_LOCALTIME") ?: "/etc/localtime";
return cached;
}
int mktime_or_timegm_usec(
struct tm *tm, /* input + normalized output */
bool utc,
+1
View File
@@ -168,6 +168,7 @@ bool clock_supported(clockid_t clock);
usec_t usec_shift_clock(usec_t, clockid_t from, clockid_t to);
int get_timezone(char **ret);
const char* etc_localtime(void);
int mktime_or_timegm_usec(struct tm *tm, bool utc, usec_t *ret);
int localtime_or_gmtime_usec(usec_t t, bool utc, struct tm *ret);
+10 -4
View File
@@ -420,7 +420,7 @@ static int manager_read_timezone_stat(Manager *m) {
assert(m);
/* Read the current stat() data of /etc/localtime so that we detect changes */
if (lstat("/etc/localtime", &st) < 0) {
if (lstat(etc_localtime(), &st) < 0) {
log_debug_errno(errno, "Failed to stat /etc/localtime, ignoring: %m");
changed = m->etc_localtime_accessible;
m->etc_localtime_accessible = false;
@@ -457,14 +457,20 @@ static int manager_setup_timezone_change(Manager *m) {
* Note that we create the new event source first here, before releasing the old one. This should optimize
* behaviour as this way sd-event can reuse the old watch in case the inode didn't change. */
r = sd_event_add_inotify(m->event, &new_event, "/etc/localtime",
r = sd_event_add_inotify(m->event, &new_event, etc_localtime(),
IN_ATTRIB|IN_MOVE_SELF|IN_CLOSE_WRITE|IN_DONT_FOLLOW, manager_dispatch_timezone_change, m);
if (r == -ENOENT) {
/* If the file doesn't exist yet, subscribe to /etc instead, and wait until it is created either by
* O_CREATE or by rename() */
_cleanup_free_ char *localtime_dir = NULL;
log_debug_errno(r, "/etc/localtime doesn't exist yet, watching /etc instead.");
r = sd_event_add_inotify(m->event, &new_event, "/etc",
int dir_r = path_extract_directory(etc_localtime(), &localtime_dir);
if (dir_r < 0)
return log_error_errno(dir_r, "Failed to extract directory from path '%s': %m", etc_localtime());
log_debug_errno(r, "%s doesn't exist yet, watching %s instead.", etc_localtime(), localtime_dir);
r = sd_event_add_inotify(m->event, &new_event, localtime_dir,
IN_CREATE|IN_MOVED_TO|IN_ONLYDIR, manager_dispatch_timezone_change, m);
}
if (r < 0)
+18 -14
View File
@@ -31,6 +31,7 @@
#include "label.h"
#include "label-util.h"
#include "libcrypt-util.h"
#include "locale-setup.h"
#include "locale-util.h"
#include "lock-util.h"
#include "loop-util.h"
@@ -408,7 +409,7 @@ static int process_locale(int rfd) {
assert(rfd >= 0);
pfd = chase_and_open_parent_at(rfd, "/etc/locale.conf",
pfd = chase_and_open_parent_at(rfd, etc_locale_conf(),
CHASE_AT_RESOLVE_IN_ROOT|CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW,
&f);
if (pfd < 0)
@@ -425,7 +426,7 @@ static int process_locale(int rfd) {
return log_error_errno(r, "Failed to check if directory file descriptor is root: %m");
if (arg_copy_locale && r == 0) {
r = copy_file_atomic_at(AT_FDCWD, "/etc/locale.conf", pfd, f, 0644, COPY_REFLINK);
r = copy_file_atomic_at(AT_FDCWD, etc_locale_conf(), pfd, f, 0644, COPY_REFLINK);
if (r != -ENOENT) {
if (r < 0)
return log_error_errno(r, "Failed to copy host's /etc/locale.conf: %m");
@@ -520,7 +521,7 @@ static int process_keymap(int rfd) {
assert(rfd >= 0);
pfd = chase_and_open_parent_at(rfd, "/etc/vconsole.conf",
pfd = chase_and_open_parent_at(rfd, etc_vconsole_conf(),
CHASE_AT_RESOLVE_IN_ROOT|CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW,
&f);
if (pfd < 0)
@@ -537,7 +538,7 @@ static int process_keymap(int rfd) {
return log_error_errno(r, "Failed to check if directory file descriptor is root: %m");
if (arg_copy_keymap && r == 0) {
r = copy_file_atomic_at(AT_FDCWD, "/etc/vconsole.conf", pfd, f, 0644, COPY_REFLINK);
r = copy_file_atomic_at(AT_FDCWD, etc_vconsole_conf(), pfd, f, 0644, COPY_REFLINK);
if (r != -ENOENT) {
if (r < 0)
return log_error_errno(r, "Failed to copy host's /etc/vconsole.conf: %m");
@@ -617,13 +618,13 @@ static int prompt_timezone(int rfd) {
static int process_timezone(int rfd) {
_cleanup_close_ int pfd = -EBADF;
_cleanup_free_ char *f = NULL;
_cleanup_free_ char *f = NULL, *relpath = NULL;
const char *e;
int r;
assert(rfd >= 0);
pfd = chase_and_open_parent_at(rfd, "/etc/localtime",
pfd = chase_and_open_parent_at(rfd, etc_localtime(),
CHASE_AT_RESOLVE_IN_ROOT|CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW,
&f);
if (pfd < 0)
@@ -642,7 +643,7 @@ static int process_timezone(int rfd) {
if (arg_copy_timezone && r == 0) {
_cleanup_free_ char *s = NULL;
r = readlink_malloc("/etc/localtime", &s);
r = readlink_malloc(etc_localtime(), &s);
if (r != -ENOENT) {
if (r < 0)
return log_error_errno(r, "Failed to read host's /etc/localtime: %m");
@@ -663,9 +664,12 @@ static int process_timezone(int rfd) {
if (isempty(arg_timezone))
return 0;
e = strjoina("../usr/share/zoneinfo/", arg_timezone);
e = strjoina("/usr/share/zoneinfo/", arg_timezone);
r = path_make_relative_parent(etc_localtime(), e, &relpath);
if (r < 0)
return r;
r = symlinkat_atomic_full(e, pfd, f, SYMLINK_LABEL);
r = symlinkat_atomic_full(relpath, pfd, f, SYMLINK_LABEL);
if (r < 0)
return log_error_errno(r, "Failed to create /etc/localtime symlink: %m");
@@ -712,7 +716,7 @@ static int process_hostname(int rfd) {
assert(rfd >= 0);
pfd = chase_and_open_parent_at(rfd, "/etc/hostname",
pfd = chase_and_open_parent_at(rfd, etc_hostname(),
CHASE_AT_RESOLVE_IN_ROOT|CHASE_MKDIR_0755|CHASE_WARN,
&f);
if (pfd < 0)
@@ -1228,12 +1232,12 @@ static int process_reset(int rfd) {
return 0;
FOREACH_STRING(p,
"/etc/locale.conf",
"/etc/vconsole.conf",
"/etc/hostname",
etc_locale_conf(),
etc_vconsole_conf(),
etc_hostname(),
"/etc/machine-id",
"/etc/kernel/cmdline",
"/etc/localtime") {
etc_localtime()) {
r = reset_one(rfd, p);
if (r < 0)
return r;
+10 -10
View File
@@ -127,7 +127,7 @@ static void context_read_etc_hostname(Context *c) {
assert(c);
if (stat("/etc/hostname", &current_stat) >= 0 &&
if (stat(etc_hostname(), &current_stat) >= 0 &&
stat_inode_unmodified(&c->etc_hostname_stat, &current_stat))
return;
@@ -160,7 +160,7 @@ static void context_read_machine_info(Context *c) {
assert(c);
if (stat("/etc/machine-info", &current_stat) >= 0 &&
if (stat(etc_machine_info(), &current_stat) >= 0 &&
stat_inode_unmodified(&c->etc_machine_info_stat, &current_stat))
return;
@@ -175,7 +175,7 @@ static void context_read_machine_info(Context *c) {
(UINT64_C(1) << PROP_HARDWARE_SKU) |
(UINT64_C(1) << PROP_HARDWARE_VERSION));
r = parse_env_file(NULL, "/etc/machine-info",
r = parse_env_file(NULL, etc_machine_info(),
"PRETTY_HOSTNAME", &c->data[PROP_PRETTY_HOSTNAME],
"ICON_NAME", &c->data[PROP_ICON_NAME],
"CHASSIS", &c->data[PROP_CHASSIS],
@@ -806,14 +806,14 @@ static int context_write_data_static_hostname(Context *c) {
s = &c->etc_hostname_stat;
if (isempty(c->data[PROP_STATIC_HOSTNAME])) {
if (unlink("/etc/hostname") < 0 && errno != ENOENT)
if (unlink(etc_hostname()) < 0 && errno != ENOENT)
return -errno;
TAKE_PTR(s);
return 0;
}
r = write_string_file("/etc/hostname", c->data[PROP_STATIC_HOSTNAME], WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_LABEL);
r = write_string_file(etc_hostname(), c->data[PROP_STATIC_HOSTNAME], WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_LABEL);
if (r < 0)
return r;
@@ -839,7 +839,7 @@ static int context_write_data_machine_info(Context *c) {
* already, even if we can't make it hit the disk. */
s = &c->etc_machine_info_stat;
r = load_env_file(NULL, "/etc/machine-info", &l);
r = load_env_file(NULL, etc_machine_info(), &l);
if (r < 0 && r != -ENOENT)
return r;
@@ -852,7 +852,7 @@ static int context_write_data_machine_info(Context *c) {
}
if (strv_isempty(l)) {
if (unlink("/etc/machine-info") < 0 && errno != ENOENT)
if (unlink(etc_machine_info()) < 0 && errno != ENOENT)
return -errno;
TAKE_PTR(s);
@@ -861,8 +861,8 @@ static int context_write_data_machine_info(Context *c) {
r = write_env_file(
AT_FDCWD,
"/etc/machine-info",
/* headers= */ NULL,
etc_machine_info(),
/* headers= */NULL,
l,
WRITE_ENV_FILE_LABEL);
if (r < 0)
@@ -1687,7 +1687,7 @@ static int build_describe_response(Context *c, bool privileged, sd_json_variant
(void) vsock_get_local_cid(&local_cid);
(void) load_os_release_pairs(/* root= */ NULL, &os_release_pairs);
(void) load_env_file_pairs(/* f=*/ NULL, "/etc/machine-info", &machine_info_pairs);
(void) load_env_file_pairs(/* f=*/ NULL, etc_machine_info(), &machine_info_pairs);
r = sd_json_buildo(
&v,
+5 -5
View File
@@ -152,7 +152,7 @@ int vconsole_read_data(Context *c, sd_bus_message *m) {
c->vc_cache = sd_bus_message_ref(m);
}
fd = RET_NERRNO(open("/etc/vconsole.conf", O_CLOEXEC | O_PATH));
fd = RET_NERRNO(open(etc_vconsole_conf(), O_CLOEXEC | O_PATH));
if (fd == -ENOENT) {
c->vc_stat = (struct stat) {};
vc_context_clear(&c->vc);
@@ -174,7 +174,7 @@ int vconsole_read_data(Context *c, sd_bus_message *m) {
x11_context_clear(&c->x11_from_vc);
r = parse_env_file_fd(
fd, "/etc/vconsole.conf",
fd, etc_vconsole_conf(),
"KEYMAP", &c->vc.keymap,
"KEYMAP_TOGGLE", &c->vc.toggle,
"XKBLAYOUT", &c->x11_from_vc.layout,
@@ -298,7 +298,7 @@ int vconsole_write_data(Context *c) {
xc = context_get_x11_context(c);
r = load_env_file(NULL, "/etc/vconsole.conf", &l);
r = load_env_file(NULL, etc_vconsole_conf(), &l);
if (r < 0 && r != -ENOENT)
return r;
@@ -307,7 +307,7 @@ int vconsole_write_data(Context *c) {
return r;
if (strv_isempty(l)) {
if (unlink("/etc/vconsole.conf") < 0)
if (unlink(etc_vconsole_conf()) < 0)
return errno == ENOENT ? 0 : -errno;
c->vc_stat = (struct stat) {};
@@ -318,7 +318,7 @@ int vconsole_write_data(Context *c) {
if (r < 0)
return r;
if (stat("/etc/vconsole.conf", &c->vc_stat) < 0)
if (stat(etc_vconsole_conf(), &c->vc_stat) < 0)
return -errno;
return 0;
+36
View File
@@ -0,0 +1,36 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <sys/stat.h>
#include "env-file.h"
#include "env-file-label.h"
#include "locale-setup.h"
#include "selinux-util.h"
int write_env_file_label(int dir_fd, const char *fname, char **headers, char **l) {
int r;
r = mac_selinux_create_file_prepare(fname, S_IFREG);
if (r < 0)
return r;
r = write_env_file(dir_fd, fname, headers, l);
mac_selinux_create_file_clear();
return r;
}
int write_vconsole_conf_label(char **l) {
int r;
r = mac_selinux_create_file_prepare(etc_vconsole_conf(), S_IFREG);
if (r < 0)
return r;
r = write_vconsole_conf(AT_FDCWD, etc_vconsole_conf(), l);
mac_selinux_create_file_clear();
return r;
}
+1 -1
View File
@@ -139,7 +139,7 @@ int read_etc_hostname(const char *path, bool substitute_wildcards, char **ret) {
assert(ret);
if (!path)
path = "/etc/hostname";
path = etc_hostname();
f = fopen(path, "re");
if (!f)
+23 -5
View File
@@ -67,7 +67,7 @@ static int locale_context_load_conf(LocaleContext *c, LocaleLoadFlag flag) {
if (!FLAGS_SET(flag, LOCALE_LOAD_LOCALE_CONF))
return 0;
fd = RET_NERRNO(open("/etc/locale.conf", O_CLOEXEC | O_PATH));
fd = RET_NERRNO(open(etc_locale_conf(), O_CLOEXEC | O_PATH));
if (fd == -ENOENT)
return 0;
if (fd < 0)
@@ -83,7 +83,7 @@ static int locale_context_load_conf(LocaleContext *c, LocaleLoadFlag flag) {
c->st = st;
locale_context_clear(c);
r = parse_env_file_fd(fd, "/etc/locale.conf",
r = parse_env_file_fd(fd, etc_locale_conf(),
"LANG", &c->locale[VARIABLE_LANG],
"LANGUAGE", &c->locale[VARIABLE_LANGUAGE],
"LC_CTYPE", &c->locale[VARIABLE_LC_CTYPE],
@@ -199,7 +199,7 @@ int locale_context_save(LocaleContext *c, char ***ret_set, char ***ret_unset) {
return r;
if (strv_isempty(set)) {
if (unlink("/etc/locale.conf") < 0)
if (unlink(etc_locale_conf()) < 0)
return errno == ENOENT ? 0 : -errno;
c->st = (struct stat) {};
@@ -213,14 +213,14 @@ int locale_context_save(LocaleContext *c, char ***ret_set, char ***ret_unset) {
r = write_env_file(
AT_FDCWD,
"/etc/locale.conf",
etc_locale_conf(),
/* headers= */ NULL,
set,
WRITE_ENV_FILE_LABEL);
if (r < 0)
return r;
if (stat("/etc/locale.conf", &c->st) < 0)
if (stat(etc_locale_conf(), &c->st) < 0)
return -errno;
if (ret_set)
@@ -300,3 +300,21 @@ int locale_setup(char ***environment) {
return 0;
}
const char* etc_locale_conf(void) {
static const char *cached = NULL;
if (!cached)
cached = secure_getenv("SYSTEMD_ETC_LOCALE_CONF") ?: "/etc/locale.conf";
return cached;
}
const char* etc_vconsole_conf(void) {
static const char *cached = NULL;
if (!cached)
cached = secure_getenv("SYSTEMD_ETC_VCONSOLE_CONF") ?: "/etc/vconsole.conf";
return cached;
}
+3
View File
@@ -28,3 +28,6 @@ void locale_context_take(LocaleContext *c, char *l[_VARIABLE_LC_MAX]);
bool locale_context_equal(const LocaleContext *c, char *l[_VARIABLE_LC_MAX]);
int locale_setup(char ***environment);
const char* etc_locale_conf(void);
const char* etc_vconsole_conf(void);
+17 -7
View File
@@ -292,22 +292,32 @@ static int context_write_data_timezone(Context *c) {
if (access("/usr/share/zoneinfo/UTC", F_OK) < 0) {
if (unlink("/etc/localtime") < 0 && errno != ENOENT)
if (unlink(etc_localtime()) < 0 && errno != ENOENT)
return -errno;
return 0;
}
source = "../usr/share/zoneinfo/UTC";
source = "/usr/share/zoneinfo/UTC";
} else {
p = path_join("../usr/share/zoneinfo", c->zone);
p = path_join("/usr/share/zoneinfo", c->zone);
if (!p)
return -ENOMEM;
source = p;
}
return symlink_atomic(source, "/etc/localtime");
return symlinkat_atomic_full(source, AT_FDCWD, etc_localtime(),
!secure_getenv("SYSTEMD_ETC_LOCALTIME"));
}
static const char* etc_adjtime(void) {
static const char *cached = NULL;
if (!cached)
cached = secure_getenv("SYSTEMD_ETC_ADJTIME") ?: "/etc/adjtime";
return cached;
}
static int context_write_data_local_rtc(Context *c) {
@@ -316,7 +326,7 @@ static int context_write_data_local_rtc(Context *c) {
assert(c);
r = read_full_file("/etc/adjtime", &s, NULL);
r = read_full_file(etc_adjtime(), &s, NULL);
if (r < 0) {
if (r != -ENOENT)
return r;
@@ -368,7 +378,7 @@ static int context_write_data_local_rtc(Context *c) {
*mempcpy_typesafe(stpcpy(stpcpy(mempcpy(w, s, a), prepend), c->local_rtc ? "LOCAL" : "UTC"), e, b) = 0;
if (streq(w, NULL_ADJTIME_UTC)) {
if (unlink("/etc/adjtime") < 0)
if (unlink(etc_adjtime()) < 0)
if (errno != ENOENT)
return -errno;
@@ -380,7 +390,7 @@ static int context_write_data_local_rtc(Context *c) {
if (r < 0)
return r;
return write_string_file("/etc/adjtime", w, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_LABEL);
return write_string_file(etc_adjtime(), w, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_LABEL);
}
static int context_update_ntp_status(Context *c, sd_bus *bus, sd_bus_message *m) {