diff --git a/TODO b/TODO
index 073e10dc7d..8c0922815d 100644
--- a/TODO
+++ b/TODO
@@ -119,11 +119,9 @@ Deprecations and removals:
Features:
-* systemd-measure: only require private key to be set when signing. iiuc we can
- generate the public key from it anyway.
-
-* automatically propagate LUKS password credential into cryptsetup from host,
- so that one can unlock LUKS via VM hypervisor supplied password.
+* automatically propagate LUKS password credential into cryptsetup from host
+ (i.e. SMBIOS type #11, …), so that one can unlock LUKS via VM hypervisor
+ supplied password.
* add ability to path_is_valid() to classify paths that refer to a dir from
those which may refer to anything, and use that in various places to filter
@@ -166,9 +164,6 @@ Features:
systemd.import_encrypted_creds=foobar.waldo,tmpfiles.extra to protect locked
down kernels from credentials generated on the host with a weak kernel
-* tmpfiles: currently if we fail to create an inode, we stat it first, and only
- then O_PATH open it. Reverse that.
-
* Add support for extra verity configuration options to systemd-repart (FEC,
hash type, etc)
@@ -218,8 +213,6 @@ Features:
* sd-bus: document that sd_bus_process() only returns messages that non of the
filters/handlers installed on the connection took possession of.
-* sd-device: add an API for opening a child device, given a device object
-
* sd-device: add an API for acquiring list of child devices, given a device
objects (i.e. all child dirents that dirs or symlinks to dirs)
@@ -236,9 +229,6 @@ Features:
portabled/… up to udev to watch block devices coming up with the flags set, and
use it.
-* portabled: read a credential "portable.extra" or so, that takes a list of
- file system paths to enable on start.
-
* sd-boot should look for information what to boot in SMBIOS, too, so that VM
managers can tell sd-boot what to boot into and suchlike
@@ -277,27 +267,34 @@ Features:
this to remove auxiliary files, and never remove them explicitly. Benefit:
resources such as initrds/kernels/dtb can be shared between entries.
-* networkd/udevd: add a way to define additional .link, .network, .netdev files
- via the credentials logic.
-
-* fstab-generator: allow defining additional fstab-like mounts via
- credentials (similar: crypttab-generator, verity-generator,
- integrity-generator)
-
-* getty-generator: allow defining additional getty instances via a credential
-
-* run-generator: allow defining additional commands to run via a credential
-
-* resolved: allow defining additional /etc/hosts entries via a credential (it
- might make sense to then synthesize a new combined /etc/hosts file in /run
- and bind mount it on /etc/hosts for other clients that want to read it.
- Similar, allow picking up DNS server IP addresses from credential.
-
-* repart: allow defining additional partitions via credential
-
-* tmpfiles: add snippet that provisions /root/.ssh/authorized_keys from credential
-
-* timesyncd: pick NTP server info from credential
+* Process credentials in:
+ • networkd/udevd: add a way to define additional .link, .network, .netdev files
+ via the credentials logic.
+ • fstab-generator: allow defining additional fstab-like mounts via
+ credentials (similar: crypttab-generator, verity-generator,
+ integrity-generator)
+ • getty-generator: allow defining additional getty instances via a credential
+ • run-generator: allow defining additional commands to run via a credential
+ • resolved: allow defining additional /etc/hosts entries via a credential (it
+ might make sense to then synthesize a new combined /etc/hosts file in /run
+ and bind mount it on /etc/hosts for other clients that want to read it.
+ Similar, allow picking up DNS server IP addresses from credential.
+ • repart: allow defining additional partitions via credential
+ • timesyncd: pick NTP server info from credential
+ • portabled: read a credential "portable.extra" or so, that takes a list of
+ file system paths to enable on start.
+ • make systemd-fstab-generator look for a system credential encoding root= or
+ usr=
+ • systemd-homed: when initializing, look for a credential
+ systemd.homed.register or so with JSON user records to automatically
+ register if not registered yet. Usecase: deploy a system, and add an
+ account one can directly log into.
+ • initialize machine ID from systemd credential picked up from the ESP via
+ sd-stub, so that machine ID is stable even on systems where unified kernels
+ are used, and hence kernel cmdline cannot be modified locally
+ • in gpt-auto-generator: check partition uuids against such uuids supplied via
+ sd-stub credentials. That way, we can support parallel OS installations with
+ pre-built kernels.
* define a JSON format for units, separating out unit definitions from unit
runtime state. Then, expose it:
@@ -326,9 +323,6 @@ Features:
UEFI firmware (for example, ovmf supports that via qemu cmdline option), and
use it to load stuff from the ESP.
-* make tmpfiles read lines from creds, so that we can provision SSH host keys
- via creds. Similar: sysusers, sysctl, homed
-
* mount /var/ from initrd, so that we can apply sysext and stuff before the
initrd transition. Specifically:
1. There should be a var= kernel cmdline option, matching root= and usr=
@@ -361,9 +355,6 @@ Features:
comes from, but we can still derive that from the stdin socket its output
came from. We apparently don't do that right now.
-* make systemd-fstab-generator look for a system credential encoding root= or
- usr=
-
* add ability to set hostname with suffix derived from machine id at boot
* ask dracut to generate usr= on the kernel cmdline so that we don't need to
@@ -393,10 +384,6 @@ Features:
inode first, then connect to /proc/self/fd/XYZ. When binding, create symlink
to target dir in /tmp, and bind through it.
-* systemd-homed: when initializing, look for a credential sysemd.homed.register
- or so with JSON user records to automatically register if not registered yet.
- Usecase: deploy a system, and add an account one can directly log into.
-
* add a proper concept of a "developer" mode, i.e. where cryptographic
protections of the root OS are weakened after interactive confirmation, to
allow hackers to allow their own stuff. idea: allow entering developer mode
@@ -541,14 +528,6 @@ Features:
the real kernel. benefit: downloading these stubs would be tiny and quick,
hence cheap for enumeration.
-* initialize machine ID from systemd credential picked up from the ESP via
- sd-stub, so that machine ID is stable even on systems where unified kernels
- are used, and hence kernel cmdline cannot be modified locally
-
-* in gpt-auto-generator: check partition uuids against such uuids supplied via
- sd-stub credentials. That way, we can support parallel OS installations with
- pre-built kernels.
-
* sysext: measure all activated sysext into a TPM PCR
* maybe add a "syscfg" concept, that is almost entirely identical to "sysext",
@@ -624,7 +603,7 @@ Features:
* systemd-dissect: show GPT disk UUID in output
-* Enable RestricFileSystems= for all our long-running services (similar:
+* Enable RestrictFileSystems= for all our long-running services (similar:
RestrictNetworkInterfaces=)
* Add systemd-analyze security checks for RestrictFileSystems= and
@@ -644,9 +623,6 @@ Features:
such as masking out /usr/lib/ or so. We should probably refuse if existing
inodes are replaced by other types of inodes or so.
-* sysext: ensure one can build a sysext that can safely apply to *any* system
- (because it contains only static go binaries in /opt/ or so)
-
* userdb: when synthesizing NSS records, pick "best" password from defined
passwords, not just the first. i.e. if there are multiple defined, prefer
unlocked over locked and prefer non-empty over empty.
@@ -1270,7 +1246,8 @@ Features:
"systemd-gdb" for attaching to the start-up of any system service in its
natural habitat.
-* gpt-auto logic: support encrypted swap, add kernel cmdline option to force it, and honour a gpt bit about it, plus maybe a configuration file
+* gpt-auto logic: support encrypted swap, add kernel cmdline option to force
+ it, and honour a gpt bit about it, plus maybe a configuration file
* add a percentage syntax for TimeoutStopSec=, e.g. TimeoutStopSec=150%, and
then use that for the setting used in user@.service. It should be understood
@@ -1609,11 +1586,6 @@ Features:
* mount: turn dependency information from /proc/self/mountinfo into dependency information between systemd units.
-* firstboot: allow provisioning of /etc/hosts entries, so that we can via the
- credentials logic insert host name to resolve into containers/hosts. Usecase:
- fork a container, and make it ping some specific address which is defined by
- the host on invocation
-
* systemd-firstboot: make sure to always use chase_symlinks() before
reading/writing files
diff --git a/man/rules/meson.build b/man/rules/meson.build
index a250326a4d..2925dadc1e 100644
--- a/man/rules/meson.build
+++ b/man/rules/meson.build
@@ -1086,6 +1086,7 @@ manpages = [
['systemd.special', '7', [], ''],
['systemd.swap', '5', [], ''],
['systemd.syntax', '7', [], ''],
+ ['systemd.system-credentials', '7', [], ''],
['systemd.target', '5', [], ''],
['systemd.time', '7', [], ''],
['systemd.timer', '5', [], ''],
diff --git a/man/systemd.system-credentials.xml b/man/systemd.system-credentials.xml
new file mode 100644
index 0000000000..3ec7ae8d4f
--- /dev/null
+++ b/man/systemd.system-credentials.xml
@@ -0,0 +1,192 @@
+
+
+
+
+
+
+
+ systemd.system-credentials
+ systemd
+
+
+
+ systemd.system-credentials
+ 7
+
+
+
+ systemd.system-credentials
+ System Credentials
+
+
+
+ Description
+
+ System and Service Credentials are data objects
+ that may be passed into booted systems or system services as they are invoked. They can be acquired from
+ various external sources, and propagated into the system and from there into system services. Credentials
+ may optionally be encrypted with a machine-specific key and/or locked to the local TPM2 device, and are
+ only decrypted when the consuming service is invoked.
+
+ System credentials may be used to provision and configure various aspects of the system. Depending
+ on the consuming component credentials are only used on initial invocations or are needed for all
+ invocations.
+
+ Credentials may be used for any kind of data, binary or text, and may carry passwords, secrets,
+ certificates, cryptographic key material, identity information, configuration, and more.
+
+
+
+ Well known system credentials
+
+
+
+ firstboot.keymap
+
+ The console key mapping to set (e.g. de). Read by
+ systemd-firstboot1,
+ and only honoured if no console keymap has been configured before.
+
+
+
+
+ firstboot.locale
+ firstboot.locale-message
+
+ The system locale to set (e.g. de_DE.UTF-8). Read by
+ systemd-firstboot1,
+ and only honoured if no locale has been configured before. firstboot.locale sets
+ LANG, while firstboot.locale-message sets
+ LC_MESSAGES.
+
+
+
+
+ firstboot.timezone
+
+ The system timezone to set (e.g. Europe/Berlin). Read by
+ systemd-firstboot1,
+ and only honoured if no system timezone has been configured before.
+
+
+
+
+ login.issue
+
+ The data of this credential is written to
+ /etc/issue.d/50-provision.conf, if the file doesn't exist
+ yet. agetty8
+ reads this file and shows its contents at the login prompt of terminal logins. See issue5 for
+ details.
+
+ Consumed by /usr/lib/tmpfiles.d/provision.conf, see
+ tmpfiles.d5.
+
+
+
+
+ login.motd
+
+ The data of this credential is written to /etc/motd.d/50-provision.conf,
+ if the file doesn't exist
+ yet. pam_motd8
+ reads this file and shows its contents as "message of the day" during terminal logins. See
+ motd5 for
+ details.
+
+ Consumed by /usr/lib/tmpfiles.d/provision.conf, see
+ tmpfiles.d5.
+
+
+
+
+ network.hosts
+
+ The data of this credential is written to /etc/hosts, if the file
+ doesn't exist yet. See hosts5 for
+ details.
+
+ Consumed by /usr/lib/tmpfiles.d/provision.conf, see
+ tmpfiles.d5.
+
+
+
+
+ passwd.hashed-password.root
+ passwd.plaintext-password.root
+
+ May contain the password (either in UNIX hashed format, or in plaintext) for the root users.
+ Read by both
+ systemd-firstboot1
+ and
+ systemd-sysusers1,
+ and only honoured if no root password has been configured before.
+
+
+
+
+ passwd.shell.root
+
+ The path to the shell program (e.g. /bin/bash) for the root user. Read by
+ both
+ systemd-firstboot1
+ and
+ systemd-sysusers1,
+ and only honoured if no root shell has been configured before.
+
+
+
+
+ ssh.authorized_keys.root
+
+ The data of this credential is written to /root/.ssh/authorized_keys, if
+ the file doesn't exist yet. This allows provisioning SSH access for the system's root user.
+
+ Consumed by /usr/lib/tmpfiles.d/provision.conf, see
+ tmpfiles.d5.
+
+
+
+
+ sysusers.extra
+
+ Additional
+ sysusers.d5
+ lines to process during boot.
+
+
+
+
+ sysctl.extra
+
+ Additional
+ sysctl.d5 lines
+ to process during boot.
+
+
+
+
+ tmpfiles.extra
+
+ Additional
+ tmpfiles.d5
+ lines to process during boot.
+
+
+
+
+
+
+
+ See Also
+
+ systemd1,
+ kernel-command-line7
+
+
+
+
diff --git a/man/tmpfiles.d.xml b/man/tmpfiles.d.xml
index 15785d1bf2..4ede01c82d 100644
--- a/man/tmpfiles.d.xml
+++ b/man/tmpfiles.d.xml
@@ -531,27 +531,24 @@ w- /proc/sys/vm/swappiness - - - - 10
Mode
- The file access mode to use when creating this file or
- directory. If omitted or when set to -, the
- default is used: 0755 for directories, 0644 for all other file
- objects. For z, Z lines,
- if omitted or when set to -, the file access
- mode will not be modified. This parameter is ignored for
- x, r,
- R, L, t,
- and a lines.
+ The file access mode to use when creating this file or directory. If omitted or when set to
+ -, the default is used: 0755 for directories, 0644 for all other file objects. For
+ z, Z lines, if omitted or when set to -, the
+ file access mode will not be modified. This parameter is ignored for x,
+ r, R, L, t, and
+ a lines.
- Optionally, if prefixed with ~, the
- access mode is masked based on the already set access bits for
- existing file or directories: if the existing file has all
- executable bits unset, all executable bits are removed from the
- new access mode, too. Similarly, if all read bits are removed
- from the old access mode, they will be removed from the new
- access mode too, and if all write bits are removed, they will be
- removed from the new access mode too. In addition, the
- sticky/SUID/SGID bit is removed unless applied to a
- directory. This functionality is particularly useful in
- conjunction with Z.
+ Optionally, if prefixed with ~, the access mode is masked based on the already
+ set access bits for existing file or directories: if the existing file has all executable bits unset,
+ all executable bits are removed from the new access mode, too. Similarly, if all read bits are removed
+ from the old access mode, they will be removed from the new access mode too, and if all write bits are
+ removed, they will be removed from the new access mode too. In addition, the sticky/SUID/SGID bit is
+ removed unless applied to a directory. This functionality is particularly useful in conjunction with
+ Z.
+
+ Optionally, if prefixed with :, the configured access mode is only used when
+ creating new inodes. If the inode the line refers to already exists, its access mode is left in place
+ unmodified.
@@ -571,6 +568,10 @@ w- /proc/sys/vm/swappiness - - - - 10
url="https://systemd.io/UIDS-GIDS/#notes-on-resolvability-of-user-and-group-names">Notes on
Resolvability of User and Group Names for more information on requirements on system user/group
definitions.
+
+ Optionally, if prefixed with :, the configured user/group information is only
+ used when creating new inodes. If the inode the line refers to already exists, its user/group is left
+ in place unmodified.
diff --git a/src/basic/fs-util.c b/src/basic/fs-util.c
index cbab59b777..6b757bd570 100644
--- a/src/basic/fs-util.c
+++ b/src/basic/fs-util.c
@@ -432,7 +432,7 @@ int symlink_idempotent(const char *from, const char *to, bool make_relative) {
return 0;
}
-int symlink_atomic_full(const char *from, const char *to, bool make_relative) {
+int symlinkat_atomic_full(const char *from, int atfd, const char *to, bool make_relative) {
_cleanup_free_ char *relpath = NULL, *t = NULL;
int r;
@@ -451,18 +451,19 @@ int symlink_atomic_full(const char *from, const char *to, bool make_relative) {
if (r < 0)
return r;
- if (symlink(from, t) < 0)
+ if (symlinkat(from, atfd, t) < 0)
return -errno;
- if (rename(t, to) < 0) {
- unlink_noerrno(t);
- return -errno;
+ r = RET_NERRNO(renameat(atfd, t, atfd, to));
+ if (r < 0) {
+ (void) unlinkat(atfd, t, 0);
+ return r;
}
return 0;
}
-int mknod_atomic(const char *path, mode_t mode, dev_t dev) {
+int mknodat_atomic(int atfd, const char *path, mode_t mode, dev_t dev) {
_cleanup_free_ char *t = NULL;
int r;
@@ -472,58 +473,36 @@ int mknod_atomic(const char *path, mode_t mode, dev_t dev) {
if (r < 0)
return r;
- if (mknod(t, mode, dev) < 0)
+ if (mknodat(atfd, t, mode, dev) < 0)
return -errno;
- if (rename(t, path) < 0) {
- unlink_noerrno(t);
- return -errno;
- }
-
- return 0;
-}
-
-int mkfifo_atomic(const char *path, mode_t mode) {
- _cleanup_free_ char *t = NULL;
- int r;
-
- assert(path);
-
- r = tempfn_random(path, NULL, &t);
- if (r < 0)
+ r = RET_NERRNO(renameat(atfd, t, atfd, path));
+ if (r < 0) {
+ (void) unlinkat(atfd, t, 0);
return r;
-
- if (mkfifo(t, mode) < 0)
- return -errno;
-
- if (rename(t, path) < 0) {
- unlink_noerrno(t);
- return -errno;
}
return 0;
}
-int mkfifoat_atomic(int dirfd, const char *path, mode_t mode) {
+int mkfifoat_atomic(int atfd, const char *path, mode_t mode) {
_cleanup_free_ char *t = NULL;
int r;
assert(path);
- if (path_is_absolute(path))
- return mkfifo_atomic(path, mode);
-
/* We're only interested in the (random) filename. */
- r = tempfn_random_child("", NULL, &t);
+ r = tempfn_random(path, NULL, &t);
if (r < 0)
return r;
- if (mkfifoat(dirfd, t, mode) < 0)
+ if (mkfifoat(atfd, t, mode) < 0)
return -errno;
- if (renameat(dirfd, t, dirfd, path) < 0) {
- unlink_noerrno(t);
- return -errno;
+ r = RET_NERRNO(renameat(atfd, t, atfd, path));
+ if (r < 0) {
+ (void) unlinkat(atfd, t, 0);
+ return r;
}
return 0;
diff --git a/src/basic/fs-util.h b/src/basic/fs-util.h
index f2174af15d..c4dffc48f3 100644
--- a/src/basic/fs-util.h
+++ b/src/basic/fs-util.h
@@ -58,13 +58,20 @@ static inline int touch(const char *path) {
int symlink_idempotent(const char *from, const char *to, bool make_relative);
-int symlink_atomic_full(const char *from, const char *to, bool make_relative);
+int symlinkat_atomic_full(const char *from, int atfd, const char *to, bool make_relative);
static inline int symlink_atomic(const char *from, const char *to) {
- return symlink_atomic_full(from, to, false);
+ return symlinkat_atomic_full(from, AT_FDCWD, to, false);
}
-int mknod_atomic(const char *path, mode_t mode, dev_t dev);
-int mkfifo_atomic(const char *path, mode_t mode);
+
+int mknodat_atomic(int atfd, const char *path, mode_t mode, dev_t dev);
+static inline int mknod_atomic(const char *path, mode_t mode, dev_t dev) {
+ return mknodat_atomic(AT_FDCWD, path, mode, dev);
+}
+
int mkfifoat_atomic(int dir_fd, const char *path, mode_t mode);
+static inline int mkfifo_atomic(const char *path, mode_t mode) {
+ return mkfifoat_atomic(AT_FDCWD, path, mode);
+}
int get_files_in_directory(const char *path, char ***list);
diff --git a/src/shared/label.c b/src/shared/label.c
index d00158a258..66fcc0a31f 100644
--- a/src/shared/label.c
+++ b/src/shared/label.c
@@ -71,7 +71,7 @@ int symlink_atomic_full_label(const char *from, const char *to, bool make_relati
if (r < 0)
return r;
- r = symlink_atomic_full(from, to, make_relative);
+ r = symlinkat_atomic_full(from, AT_FDCWD, to, make_relative);
mac_selinux_create_file_clear();
if (r < 0)
diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c
index 7e99921db9..0031b6ee49 100644
--- a/src/tmpfiles/tmpfiles.c
+++ b/src/tmpfiles/tmpfiles.c
@@ -31,6 +31,7 @@
#include "dirent-util.h"
#include "dissect-image.h"
#include "env-util.h"
+#include "errno-util.h"
#include "escape.h"
#include "fd-util.h"
#include "fileio.h"
@@ -149,6 +150,9 @@ typedef struct Item {
bool uid_set:1;
bool gid_set:1;
bool mode_set:1;
+ bool uid_only_create:1;
+ bool gid_only_create:1;
+ bool mode_only_create:1;
bool age_set:1;
bool mask_perms:1;
bool attribute_set:1;
@@ -180,6 +184,14 @@ typedef enum DirectoryType {
_DIRECTORY_TYPE_MAX,
} DirectoryType;
+typedef enum {
+ CREATION_NORMAL,
+ CREATION_EXISTING,
+ CREATION_FORCE,
+ _CREATION_MODE_MAX,
+ _CREATION_MODE_INVALID = -EINVAL,
+} CreationMode;
+
static bool arg_cat_config = false;
static bool arg_user = false;
static OperationMask arg_operation = 0;
@@ -205,6 +217,14 @@ STATIC_DESTRUCTOR_REGISTER(arg_exclude_prefixes, freep);
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
+static const char *const creation_mode_verb_table[_CREATION_MODE_MAX] = {
+ [CREATION_NORMAL] = "Created",
+ [CREATION_EXISTING] = "Found existing",
+ [CREATION_FORCE] = "Created replacement",
+};
+
+DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(creation_mode_verb, CreationMode);
+
static int specifier_machine_id_safe(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
int r;
@@ -844,10 +864,18 @@ static mode_t process_mask_perms(mode_t mode, mode_t current) {
return mode;
}
-static int fd_set_perms(Item *i, int fd, const char *path, const struct stat *st) {
+static int fd_set_perms(
+ Item *i,
+ int fd,
+ const char *path,
+ const struct stat *st,
+ CreationMode creation) {
+
+ bool do_chown, do_chmod;
struct stat stbuf;
mode_t new_mode;
- bool do_chown;
+ uid_t new_uid;
+ gid_t new_gid;
int r;
assert(i);
@@ -867,19 +895,20 @@ static int fd_set_perms(Item *i, int fd, const char *path, const struct stat *st
return log_error_errno(SYNTHETIC_ERRNO(EPERM),
"Refusing to set permissions on hardlinked file %s while the fs.protected_hardlinks sysctl is turned off.",
path);
+ new_uid = i->uid_set && (creation != CREATION_EXISTING || !i->uid_only_create) ? i->uid : st->st_uid;
+ new_gid = i->gid_set && (creation != CREATION_EXISTING || !i->gid_only_create) ? i->gid : st->st_gid;
/* Do we need a chown()? */
- do_chown =
- (i->uid_set && i->uid != st->st_uid) ||
- (i->gid_set && i->gid != st->st_gid);
+ do_chown = (new_uid != st->st_uid) || (new_gid != st->st_gid);
/* Calculate the mode to apply */
- new_mode = i->mode_set ? (i->mask_perms ?
- process_mask_perms(i->mode, st->st_mode) :
- i->mode) :
- (st->st_mode & 07777);
+ new_mode = i->mode_set && (creation != CREATION_EXISTING || !i->mode_only_create) ?
+ (i->mask_perms ? process_mask_perms(i->mode, st->st_mode) : i->mode) :
+ (st->st_mode & 07777);
- if (i->mode_set && do_chown) {
+ do_chmod = ((new_mode ^ st->st_mode) & 07777) != 0;
+
+ if (do_chmod && do_chown) {
/* Before we issue the chmod() let's reduce the access mode to the common bits of the old and
* the new mode. That way there's no time window where the file exists under the old owner
* with more than the old access modes — and not under the new owner with more than the new
@@ -902,35 +931,25 @@ static int fd_set_perms(Item *i, int fd, const char *path, const struct stat *st
}
if (do_chown) {
- log_debug("Changing \"%s\" to owner "UID_FMT":"GID_FMT,
- path,
- i->uid_set ? i->uid : UID_INVALID,
- i->gid_set ? i->gid : GID_INVALID);
+ log_debug("Changing \"%s\" to owner "UID_FMT":"GID_FMT, path, new_uid, new_gid);
- if (fchownat(fd,
- "",
- i->uid_set ? i->uid : UID_INVALID,
- i->gid_set ? i->gid : GID_INVALID,
+ if (fchownat(fd, "",
+ new_uid != st->st_uid ? new_uid : UID_INVALID,
+ new_gid != st->st_gid ? new_gid : GID_INVALID,
AT_EMPTY_PATH) < 0)
return log_error_errno(errno, "fchownat() of %s failed: %m", path);
}
/* Now, apply the final mode. We do this in two cases: when the user set a mode explicitly, or after a
* chown(), since chown()'s mangle the access mode in regards to sgid/suid in some conditions. */
- if (i->mode_set || do_chown) {
+ if (do_chmod || do_chown) {
if (S_ISLNK(st->st_mode))
log_debug("Skipping mode fix for symlink %s.", path);
else {
- /* Check if the chmod() is unnecessary. Note that if we did a chown() before we always
- * chmod() here again, since it might have mangled the bits. */
- if (!do_chown && ((new_mode ^ st->st_mode) & 07777) == 0)
- log_debug("\"%s\" matches mode %o already.", path, new_mode);
- else {
- log_debug("Changing \"%s\" to mode %o.", path, new_mode);
- r = fchmod_opath(fd, new_mode);
- if (r < 0)
- return log_error_errno(r, "fchmod() of %s failed: %m", path);
- }
+ log_debug("Changing \"%s\" to mode %o.", path, new_mode);
+ r = fchmod_opath(fd, new_mode);
+ if (r < 0)
+ return log_error_errno(r, "fchmod() of %s failed: %m", path);
}
}
@@ -979,7 +998,11 @@ static int path_open_safe(const char *path) {
return fd;
}
-static int path_set_perms(Item *i, const char *path) {
+static int path_set_perms(
+ Item *i,
+ const char *path,
+ CreationMode creation) {
+
_cleanup_close_ int fd = -1;
assert(i);
@@ -989,7 +1012,7 @@ static int path_set_perms(Item *i, const char *path) {
if (fd < 0)
return fd;
- return fd_set_perms(i, fd, path, NULL);
+ return fd_set_perms(i, fd, path, /* st= */ NULL, creation);
}
static int parse_xattrs_from_arg(Item *i) {
@@ -1028,7 +1051,13 @@ static int parse_xattrs_from_arg(Item *i) {
return 0;
}
-static int fd_set_xattrs(Item *i, int fd, const char *path, const struct stat *st) {
+static int fd_set_xattrs(
+ Item *i,
+ int fd,
+ const char *path,
+ const struct stat *st,
+ CreationMode creation) {
+
assert(i);
assert(fd >= 0);
assert(path);
@@ -1042,7 +1071,11 @@ static int fd_set_xattrs(Item *i, int fd, const char *path, const struct stat *s
return 0;
}
-static int path_set_xattrs(Item *i, const char *path) {
+static int path_set_xattrs(
+ Item *i,
+ const char *path,
+ CreationMode creation) {
+
_cleanup_close_ int fd = -1;
assert(i);
@@ -1052,7 +1085,7 @@ static int path_set_xattrs(Item *i, const char *path) {
if (fd < 0)
return fd;
- return fd_set_xattrs(i, fd, path, NULL);
+ return fd_set_xattrs(i, fd, path, /* st = */ NULL, creation);
}
static int parse_acls_from_arg(Item *item) {
@@ -1075,7 +1108,13 @@ static int parse_acls_from_arg(Item *item) {
}
#if HAVE_ACL
-static int path_set_acl(const char *path, const char *pretty, acl_type_t type, acl_t acl, bool modify) {
+static int path_set_acl(
+ const char *path,
+ const char *pretty,
+ acl_type_t type,
+ acl_t acl,
+ bool modify) {
+
_cleanup_(acl_free_charpp) char *t = NULL;
_cleanup_(acl_freep) acl_t dup = NULL;
int r;
@@ -1124,7 +1163,13 @@ static int path_set_acl(const char *path, const char *pretty, acl_type_t type, a
}
#endif
-static int fd_set_acls(Item *item, int fd, const char *path, const struct stat *st) {
+static int fd_set_acls(
+ Item *item,
+ int fd,
+ const char *path,
+ const struct stat *st,
+ CreationMode creation) {
+
int r = 0;
#if HAVE_ACL
struct stat stbuf;
@@ -1174,7 +1219,7 @@ static int fd_set_acls(Item *item, int fd, const char *path, const struct stat *
return r;
}
-static int path_set_acls(Item *item, const char *path) {
+static int path_set_acls(Item *item, const char *path, CreationMode creation) {
int r = 0;
#if HAVE_ACL
_cleanup_close_ int fd = -1;
@@ -1186,7 +1231,7 @@ static int path_set_acls(Item *item, const char *path) {
if (fd < 0)
return fd;
- r = fd_set_acls(item, fd, path, NULL);
+ r = fd_set_acls(item, fd, path, /* st= */ NULL, creation);
#endif
return r;
}
@@ -1275,7 +1320,13 @@ static int parse_attribute_from_arg(Item *item) {
return 0;
}
-static int fd_set_attribute(Item *item, int fd, const char *path, const struct stat *st) {
+static int fd_set_attribute(
+ Item *item,
+ int fd,
+ const char *path,
+ const struct stat *st,
+ CreationMode creation) {
+
_cleanup_close_ int procfs_fd = -1;
struct stat stbuf;
unsigned f;
@@ -1294,9 +1345,8 @@ static int fd_set_attribute(Item *item, int fd, const char *path, const struct s
st = &stbuf;
}
- /* Issuing the file attribute ioctls on device nodes is not
- * safe, as that will be delivered to the drivers, not the
- * file system containing the device node. */
+ /* Issuing the file attribute ioctls on device nodes is not safe, as that will be delivered to the
+ * drivers, not the file system containing the device node. */
if (!S_ISREG(st->st_mode) && !S_ISDIR(st->st_mode))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Setting file flags is only supported on regular files and directories, cannot set on '%s'.",
@@ -1326,7 +1376,7 @@ static int fd_set_attribute(Item *item, int fd, const char *path, const struct s
return 0;
}
-static int path_set_attribute(Item *item, const char *path) {
+static int path_set_attribute(Item *item, const char *path, CreationMode creation) {
_cleanup_close_ int fd = -1;
if (!item->attribute_set || item->attribute_mask == 0)
@@ -1336,7 +1386,7 @@ static int path_set_attribute(Item *item, const char *path) {
if (fd < 0)
return fd;
- return fd_set_attribute(item, fd, path, NULL);
+ return fd_set_attribute(item, fd, path, /* st= */ NULL, creation);
}
static int write_argument_data(Item *i, int fd, const char *path) {
@@ -1360,7 +1410,7 @@ static int write_argument_data(Item *i, int fd, const char *path) {
return 0;
}
-static int write_one_file(Item *i, const char *path) {
+static int write_one_file(Item *i, const char *path, CreationMode creation) {
_cleanup_close_ int fd = -1, dir_fd = -1;
_cleanup_free_ char *bn = NULL;
int r;
@@ -1403,13 +1453,14 @@ static int write_one_file(Item *i, const char *path) {
if (r < 0)
return r;
- return fd_set_perms(i, fd, path, NULL);
+ return fd_set_perms(i, fd, path, NULL, creation);
}
static int create_file(Item *i, const char *path) {
_cleanup_close_ int fd = -1, dir_fd = -1;
_cleanup_free_ char *bn = NULL;
struct stat stbuf, *st = NULL;
+ CreationMode creation;
int r = 0;
assert(i);
@@ -1437,17 +1488,14 @@ static int create_file(Item *i, const char *path) {
}
if (fd < 0) {
- /* Even on a read-only filesystem, open(2) returns EEXIST if the
- * file already exists. It returns EROFS only if it needs to
- * create the file. */
+ /* Even on a read-only filesystem, open(2) returns EEXIST if the file already exists. It
+ * returns EROFS only if it needs to create the file. */
if (fd != -EEXIST)
return log_error_errno(fd, "Failed to create file %s: %m", path);
- /* Re-open the file. At that point it must exist since open(2)
- * failed with EEXIST. We still need to check if the perms/mode
- * need to be changed. For read-only filesystems, we let
- * fd_set_perms() report the error if the perms need to be
- * modified. */
+ /* Re-open the file. At that point it must exist since open(2) failed with EEXIST. We still
+ * need to check if the perms/mode need to be changed. For read-only filesystems, we let
+ * fd_set_perms() report the error if the perms need to be modified. */
fd = openat(dir_fd, bn, O_NOFOLLOW|O_CLOEXEC|O_PATH, i->mode);
if (fd < 0)
return log_error_errno(errno, "Failed to re-open file %s: %m", path);
@@ -1461,19 +1509,23 @@ static int create_file(Item *i, const char *path) {
path);
st = &stbuf;
- } else if (item_binary_argument(i)) {
+ creation = CREATION_EXISTING;
+ } else {
r = write_argument_data(i, fd, path);
if (r < 0)
return r;
+
+ creation = CREATION_NORMAL;
}
- return fd_set_perms(i, fd, path, st);
+ return fd_set_perms(i, fd, path, st, creation);
}
static int truncate_file(Item *i, const char *path) {
_cleanup_close_ int fd = -1, dir_fd = -1;
_cleanup_free_ char *bn = NULL;
struct stat stbuf, *st = NULL;
+ CreationMode creation;
bool erofs = false;
int r = 0;
@@ -1497,21 +1549,25 @@ static int truncate_file(Item *i, const char *path) {
if (dir_fd < 0)
return dir_fd;
- RUN_WITH_UMASK(0000) {
- mac_selinux_create_file_prepare(path, S_IFREG);
- fd = RET_NERRNO(openat(dir_fd, bn, O_CREAT|O_NOFOLLOW|O_NONBLOCK|O_CLOEXEC|O_WRONLY|O_NOCTTY, i->mode));
- mac_selinux_create_file_clear();
+ creation = CREATION_EXISTING;
+ fd = RET_NERRNO(openat(dir_fd, bn, O_NOFOLLOW|O_NONBLOCK|O_CLOEXEC|O_WRONLY|O_NOCTTY, i->mode));
+ if (fd == -ENOENT) {
+ creation = CREATION_NORMAL; /* Didn't work without O_CREATE, try again with */
+
+ RUN_WITH_UMASK(0000) {
+ mac_selinux_create_file_prepare(path, S_IFREG);
+ fd = RET_NERRNO(openat(dir_fd, bn, O_CREAT|O_NOFOLLOW|O_NONBLOCK|O_CLOEXEC|O_WRONLY|O_NOCTTY, i->mode));
+ mac_selinux_create_file_clear();
+ }
}
if (fd < 0) {
if (fd != -EROFS)
return log_error_errno(fd, "Failed to open/create file %s: %m", path);
- /* On a read-only filesystem, we don't want to fail if the
- * target is already empty and the perms are set. So we still
- * proceed with the sanity checks and let the remaining
- * operations fail with EROFS if they try to modify the target
- * file. */
+ /* On a read-only filesystem, we don't want to fail if the target is already empty and the
+ * perms are set. So we still proceed with the sanity checks and let the remaining operations
+ * fail with EROFS if they try to modify the target file. */
fd = openat(dir_fd, bn, O_NOFOLLOW|O_CLOEXEC|O_PATH, i->mode);
if (fd < 0) {
@@ -1524,6 +1580,7 @@ static int truncate_file(Item *i, const char *path) {
}
erofs = true;
+ creation = CREATION_EXISTING;
}
if (fstat(fd, &stbuf) < 0)
@@ -1550,12 +1607,13 @@ static int truncate_file(Item *i, const char *path) {
return r;
}
- return fd_set_perms(i, fd, path, st);
+ return fd_set_perms(i, fd, path, st, creation);
}
static int copy_files(Item *i) {
_cleanup_close_ int dfd = -1, fd = -1;
_cleanup_free_ char *bn = NULL;
+ struct stat st, a;
int r;
log_debug("Copying tree \"%s\" to \"%s\".", i->argument, i->path);
@@ -1575,64 +1633,44 @@ static int copy_files(Item *i) {
i->uid_set ? i->uid : UID_INVALID,
i->gid_set ? i->gid : GID_INVALID,
COPY_REFLINK | COPY_MERGE_EMPTY | COPY_MAC_CREATE | COPY_HARDLINKS);
- if (r < 0) {
- struct stat a, b;
-
- /* If the target already exists on read-only filesystems, trying
- * to create the target will not fail with EEXIST but with
- * EROFS. */
- if (r == -EROFS && faccessat(dfd, bn, F_OK, AT_SYMLINK_NOFOLLOW) == 0)
- r = -EEXIST;
-
- if (r != -EEXIST)
- return log_error_errno(r, "Failed to copy files to %s: %m", i->path);
-
- if (stat(i->argument, &a) < 0)
- return log_error_errno(errno, "stat(%s) failed: %m", i->argument);
-
- if (fstatat(dfd, bn, &b, AT_SYMLINK_NOFOLLOW) < 0)
- return log_error_errno(errno, "stat(%s) failed: %m", i->path);
-
- if ((a.st_mode ^ b.st_mode) & S_IFMT) {
- log_debug("Can't copy to %s, file exists already and is of different type", i->path);
- return 0;
- }
- }
fd = openat(dfd, bn, O_NOFOLLOW|O_CLOEXEC|O_PATH);
- if (fd < 0)
- return log_error_errno(errno, "Failed to openat(%s): %m", i->path);
+ if (fd < 0) {
+ if (r < 0) /* Look at original error first */
+ return log_error_errno(r, "Failed to copy files to %s: %m", i->path);
- return fd_set_perms(i, fd, i->path, NULL);
+ return log_error_errno(errno, "Failed to openat(%s): %m", i->path);
+ }
+
+ if (fstat(fd, &st) < 0)
+ return log_error_errno(errno, "Failed to fstat(%s): %m", i->path);
+
+ if (stat(i->argument, &a) < 0)
+ return log_error_errno(errno, "Failed to stat(%s): %m", i->argument);
+
+ if (((st.st_mode ^ a.st_mode) & S_IFMT) != 0) {
+ log_debug("Can't copy to %s, file exists already and is of different type", i->path);
+ return 0;
+ }
+
+ return fd_set_perms(i, fd, i->path, &st, _CREATION_MODE_INVALID);
}
-typedef enum {
- CREATION_NORMAL,
- CREATION_EXISTING,
- CREATION_FORCE,
- _CREATION_MODE_MAX,
- _CREATION_MODE_INVALID = -EINVAL,
-} CreationMode;
+static int create_directory_or_subvolume(
+ const char *path,
+ mode_t mode,
+ bool subvol,
+ struct stat *ret_st,
+ CreationMode *ret_creation) {
-static const char *const creation_mode_verb_table[_CREATION_MODE_MAX] = {
- [CREATION_NORMAL] = "Created",
- [CREATION_EXISTING] = "Found existing",
- [CREATION_FORCE] = "Created replacement",
-};
-
-DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(creation_mode_verb, CreationMode);
-
-static int create_directory_or_subvolume(const char *path, mode_t mode, bool subvol, CreationMode *creation) {
_cleanup_free_ char *bn = NULL;
_cleanup_close_ int pfd = -1;
- CreationMode c;
- int r;
+ CreationMode creation;
+ struct stat st;
+ int r, fd;
assert(path);
- if (!creation)
- creation = &c;
-
r = path_extract_filename(path, &bn);
if (r < 0)
return log_error_errno(r, "Failed to extract filename from path '%s': %m", path);
@@ -1648,7 +1686,7 @@ static int create_directory_or_subvolume(const char *path, mode_t mode, bool sub
log_warning_errno(r, "Cannot parse value of $SYSTEMD_TMPFILES_FORCE_SUBVOL, ignoring.");
r = btrfs_is_subvol(empty_to_root(arg_root)) > 0;
}
- if (!r)
+ if (r == 0)
/* Don't create a subvolume unless the root directory is one, too. We do this under
* the assumption that if the root directory is just a plain directory (i.e. very
* light-weight), we shouldn't try to split it up into subvolumes (i.e. more
@@ -1664,62 +1702,67 @@ static int create_directory_or_subvolume(const char *path, mode_t mode, bool sub
} else
r = 0;
- if (!subvol || r == -ENOTTY)
+ if (!subvol || ERRNO_IS_NOT_SUPPORTED(r))
RUN_WITH_UMASK(0000)
r = mkdirat_label(pfd, bn, mode);
- if (r < 0) {
- int k;
+ creation = r >= 0 ? CREATION_NORMAL : CREATION_EXISTING;
- if (!IN_SET(r, -EEXIST, -EROFS))
+ fd = openat(pfd, bn, O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH);
+ if (fd < 0) {
+ /* We couldn't open it because it is not actually a directory? */
+ if (errno == ENOTDIR)
+ return log_error_errno(SYNTHETIC_ERRNO(EEXIST), "\"%s\" already exists and is not a directory.", path);
+
+ /* Then look at the original error */
+ if (r < 0)
return log_error_errno(r, "Failed to create directory or subvolume \"%s\": %m", path);
- k = is_dir_full(pfd, bn, /* follow= */ false);
- if (k == -ENOENT && r == -EROFS)
- return log_error_errno(r, "%s does not exist and cannot be created as the file system is read-only.", path);
- if (k < 0)
- return log_error_errno(k, "Failed to check if %s exists: %m", path);
- if (!k)
- return log_warning_errno(SYNTHETIC_ERRNO(EEXIST),
- "\"%s\" already exists and is not a directory.", path);
+ return log_error_errno(errno, "Failed to open directory/subvolume we just created '%s': %m", path);
+ }
- *creation = CREATION_EXISTING;
- } else
- *creation = CREATION_NORMAL;
+ if (fstat(fd, &st) < 0)
+ return log_error_errno(errno, "Failed to fstat(%s): %m", path);
- log_debug("%s directory \"%s\".", creation_mode_verb_to_string(*creation), path);
+ assert(S_ISDIR(st.st_mode)); /* we used O_DIRECTORY above */
- r = openat(pfd, bn, O_NOCTTY|O_CLOEXEC|O_DIRECTORY);
- if (r < 0)
- return log_error_errno(errno, "Failed to open directory '%s': %m", bn);
+ log_debug("%s directory \"%s\".", creation_mode_verb_to_string(creation), path);
- return r;
+ if (ret_st)
+ *ret_st = st;
+ if (ret_creation)
+ *ret_creation = creation;
+
+ return fd;
}
static int create_directory(Item *i, const char *path) {
_cleanup_close_ int fd = -1;
+ CreationMode creation;
+ struct stat st;
assert(i);
assert(IN_SET(i->type, CREATE_DIRECTORY, TRUNCATE_DIRECTORY));
- fd = create_directory_or_subvolume(path, i->mode, false, NULL);
+ fd = create_directory_or_subvolume(path, i->mode, /* subvol= */ false, &st, &creation);
if (fd == -EEXIST)
return 0;
if (fd < 0)
return fd;
- return fd_set_perms(i, fd, path, NULL);
+ return fd_set_perms(i, fd, path, &st, creation);
}
static int create_subvolume(Item *i, const char *path) {
_cleanup_close_ int fd = -1;
CreationMode creation;
+ struct stat st;
int r, q = 0;
assert(i);
assert(IN_SET(i->type, CREATE_SUBVOLUME, CREATE_SUBVOLUME_NEW_QUOTA, CREATE_SUBVOLUME_INHERIT_QUOTA));
- fd = create_directory_or_subvolume(path, i->mode, true, &creation);
+ fd = create_directory_or_subvolume(path, i->mode, /* subvol = */ true, &st, &creation);
if (fd == -EEXIST)
return 0;
if (fd < 0)
@@ -1742,43 +1785,52 @@ static int create_subvolume(Item *i, const char *path) {
log_debug("Quota for subvolume \"%s\" already in place, no change made.", i->path);
}
- r = fd_set_perms(i, fd, path, NULL);
+ r = fd_set_perms(i, fd, path, &st, creation);
if (q < 0) /* prefer the quota change error from above */
return q;
return r;
}
-static int empty_directory(Item *i, const char *path) {
+static int empty_directory(Item *i, const char *path, CreationMode creation) {
+ _cleanup_close_ int fd = -1;
+ struct stat st;
int r;
assert(i);
assert(i->type == EMPTY_DIRECTORY);
- r = is_dir(path, false);
+ r = chase_symlinks(path, arg_root, CHASE_SAFE|CHASE_WARN, NULL, &fd);
+ if (r == -ENOLINK) /* Unsafe symlink: already covered by CHASE_WARN */
+ return fd;
if (r == -ENOENT) {
- /* Option "e" operates only on existing objects. Do not
- * print errors about non-existent files or directories */
- log_debug("Skipping missing directory: %s", path);
+ /* Option "e" operates only on existing objects. Do not print errors about non-existent files
+ * or directories */
+ log_debug_errno(r, "Skipping missing directory: %s", path);
return 0;
}
if (r < 0)
- return log_error_errno(r, "is_dir() failed on path %s: %m", path);
- if (r == 0) {
- log_warning("\"%s\" already exists and is not a directory.", path);
+ return log_error_errno(r, "Failed to open directory '%s': %m", path);
+
+ if (fstat(fd, &st) < 0)
+ return log_error_errno(errno, "Failed to fstat(%s): %m", path);
+ if (!S_ISDIR(st.st_mode)) {
+ log_warning("'%s' already exists and is not a directory.", path);
return 0;
}
- return path_set_perms(i, path);
+ return fd_set_perms(i, fd, path, &st, creation);
}
static int create_device(Item *i, mode_t file_type) {
_cleanup_close_ int dfd = -1, fd = -1;
_cleanup_free_ char *bn = NULL;
CreationMode creation;
+ struct stat st;
int r;
assert(i);
+ assert(IN_SET(i->type, CREATE_BLOCK_DEVICE|CREATE_CHAR_DEVICE));
assert(IN_SET(file_type, S_IFBLK, S_IFCHR));
r = path_extract_filename(i->path, &bn);
@@ -1798,122 +1850,267 @@ static int create_device(Item *i, mode_t file_type) {
r = RET_NERRNO(mknodat(dfd, bn, i->mode | file_type, i->major_minor));
mac_selinux_create_file_clear();
}
+ creation = r >= 0 ? CREATION_NORMAL : CREATION_EXISTING;
- if (r < 0) {
- struct stat st;
+ /* Try to open the inode via O_PATH, regardless if we could create it or not. Maybe everything is in
+ * order anyway and we hence can ignore the error to create the device node */
+ fd = openat(dfd, bn, O_NOFOLLOW|O_CLOEXEC|O_PATH);
+ if (fd < 0) {
+ /* OK, so opening the inode failed, let's look at the original error then. */
- if (r == -EPERM) {
- log_debug_errno(r,
- "We lack permissions, possibly because of cgroup configuration; "
- "skipping creation of device node %s.", i->path);
- return 0;
+ if (r < 0) {
+ if (ERRNO_IS_PRIVILEGE(r))
+ goto handle_privilege;
+
+ return log_error_errno(r, "Failed to create device node '%s': %m", i->path);
}
- if (r != -EEXIST)
- return log_error_errno(r, "Failed to create device node %s: %m", i->path);
+ return log_error_errno(errno, "Failed to open device node '%s' we just created: %m", i->path);
+ }
- if (fstatat(dfd, bn, &st, 0) < 0)
- return log_error_errno(errno, "stat(%s) failed: %m", i->path);
+ if (fstat(fd, &st) < 0)
+ return log_error_errno(errno, "Failed to fstat(%s): %m", i->path);
- if ((st.st_mode & S_IFMT) != file_type) {
+ if (((st.st_mode ^ file_type) & S_IFMT) != 0) {
- if (i->append_or_force) {
+ if (i->append_or_force) {
+ fd = safe_close(fd);
- RUN_WITH_UMASK(0000) {
- mac_selinux_create_file_prepare(i->path, file_type);
- /* FIXME: need to introduce mknodat_atomic() */
- r = mknod_atomic(i->path, i->mode | file_type, i->major_minor);
- mac_selinux_create_file_clear();
- }
-
- if (r < 0)
- return log_error_errno(r, "Failed to create device node \"%s\": %m", i->path);
- creation = CREATION_FORCE;
- } else {
- log_warning("\"%s\" already exists is not a device node.", i->path);
- return 0;
+ RUN_WITH_UMASK(0000) {
+ mac_selinux_create_file_prepare(i->path, file_type);
+ r = mknodat_atomic(dfd, bn, i->mode | file_type, i->major_minor);
+ mac_selinux_create_file_clear();
}
- } else
- creation = CREATION_EXISTING;
- } else
- creation = CREATION_NORMAL;
+ if (ERRNO_IS_PRIVILEGE(r))
+ goto handle_privilege;
+ if (IN_SET(r, -EISDIR, -EEXIST, -ENOTEMPTY)) {
+ r = rm_rf_child(dfd, bn, REMOVE_PHYSICAL);
+ if (r < 0)
+ return log_error_errno(r, "rm -rf %s failed: %m", i->path);
+
+ mac_selinux_create_file_prepare(i->path, file_type);
+ r = RET_NERRNO(mknodat(dfd, bn, i->mode | file_type, i->major_minor));
+ mac_selinux_create_file_clear();
+ }
+ if (r < 0)
+ return log_error_errno(r, "Failed to create device node '%s': %m", i->path);
+
+ fd = openat(dfd, bn, O_NOFOLLOW|O_CLOEXEC|O_PATH);
+ if (fd < 0)
+ return log_error_errno(errno, "Failed to open device node we just created '%s': %m", i->path);
+
+ /* Validate type before change ownership below */
+ if (fstat(fd, &st) < 0)
+ return log_error_errno(errno, "Failed to fstat(%s): %m", i->path);
+
+ if (((st.st_mode ^ file_type) & S_IFMT) != 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EBADF), "Device node we just created is not a device node, refusing.");
+
+ creation = CREATION_FORCE;
+ } else {
+ log_warning("\"%s\" already exists and is not a device node.", i->path);
+ return 0;
+ }
+ }
log_debug("%s %s device node \"%s\" %u:%u.",
creation_mode_verb_to_string(creation),
i->type == CREATE_BLOCK_DEVICE ? "block" : "char",
i->path, major(i->mode), minor(i->mode));
- fd = openat(dfd, bn, O_NOFOLLOW|O_CLOEXEC|O_PATH);
- if (fd < 0)
- return log_error_errno(errno, "Failed to openat(%s): %m", i->path);
+ return fd_set_perms(i, fd, i->path, &st, creation);
- return fd_set_perms(i, fd, i->path, NULL);
+handle_privilege:
+ log_debug_errno(r,
+ "We lack permissions, possibly because of cgroup configuration; "
+ "skipping creation of device node '%s'.", i->path);
+ return 0;
}
-static int create_fifo(Item *i, const char *path) {
+static int create_fifo(Item *i) {
_cleanup_close_ int pfd = -1, fd = -1;
_cleanup_free_ char *bn = NULL;
CreationMode creation;
struct stat st;
int r;
+ assert(i);
+ assert(i->type == CREATE_FIFO);
+
r = path_extract_filename(i->path, &bn);
if (r < 0)
- return log_error_errno(r, "Failed to extract filename from path '%s': %m", path);
+ return log_error_errno(r, "Failed to extract filename from path '%s': %m", i->path);
if (r == O_DIRECTORY)
- return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Cannot open path '%s' for creating FIFO, is a directory.", path);
+ return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Cannot open path '%s' for creating FIFO, is a directory.", i->path);
- pfd = path_open_parent_safe(path);
+ pfd = path_open_parent_safe(i->path);
if (pfd < 0)
return pfd;
RUN_WITH_UMASK(0000) {
- mac_selinux_create_file_prepare(path, S_IFIFO);
+ mac_selinux_create_file_prepare(i->path, S_IFIFO);
r = RET_NERRNO(mkfifoat(pfd, bn, i->mode));
mac_selinux_create_file_clear();
}
- if (r < 0) {
- if (r != -EEXIST)
- return log_error_errno(r, "Failed to create fifo %s: %m", path);
-
- if (fstatat(pfd, bn, &st, AT_SYMLINK_NOFOLLOW) < 0)
- return log_error_errno(errno, "stat(%s) failed: %m", path);
-
- if (!S_ISFIFO(st.st_mode)) {
-
- if (i->append_or_force) {
- RUN_WITH_UMASK(0000) {
- mac_selinux_create_file_prepare(path, S_IFIFO);
- r = mkfifoat_atomic(pfd, bn, i->mode);
- mac_selinux_create_file_clear();
- }
-
- if (r < 0)
- return log_error_errno(r, "Failed to create fifo %s: %m", path);
- creation = CREATION_FORCE;
- } else {
- log_warning("\"%s\" already exists and is not a fifo.", path);
- return 0;
- }
- } else
- creation = CREATION_EXISTING;
- } else
- creation = CREATION_NORMAL;
-
- log_debug("%s fifo \"%s\".", creation_mode_verb_to_string(creation), path);
+ creation = r >= 0 ? CREATION_NORMAL : CREATION_EXISTING;
+ /* Open the inode via O_PATH, regardless if we managed to create it or not. Maybe it is is already the FIFO we want */
fd = openat(pfd, bn, O_NOFOLLOW|O_CLOEXEC|O_PATH);
- if (fd < 0)
- return log_error_errno(errno, "Failed to openat(%s): %m", path);
+ if (fd < 0) {
+ if (r < 0)
+ return log_error_errno(r, "Failed to create FIFO %s: %m", i->path); /* original error! */
- return fd_set_perms(i, fd, i->path, NULL);
+ return log_error_errno(errno, "Failed to open FIFO we just created %s: %m", i->path);
+ }
+
+ if (fstat(fd, &st) < 0)
+ return log_error_errno(errno, "Failed to fstat(%s): %m", i->path);
+
+ if (!S_ISFIFO(st.st_mode)) {
+
+ if (i->append_or_force) {
+ fd = safe_close(fd);
+
+ RUN_WITH_UMASK(0000) {
+ mac_selinux_create_file_prepare(i->path, S_IFIFO);
+ r = mkfifoat_atomic(pfd, bn, i->mode);
+ mac_selinux_create_file_clear();
+ }
+ if (IN_SET(r, -EISDIR, -EEXIST, -ENOTEMPTY)) {
+ r = rm_rf_child(pfd, bn, REMOVE_PHYSICAL);
+ if (r < 0)
+ return log_error_errno(r, "rm -rf %s failed: %m", i->path);
+
+ mac_selinux_create_file_prepare(i->path, S_IFIFO);
+ r = RET_NERRNO(mkfifoat(pfd, bn, i->mode));
+ mac_selinux_create_file_clear();
+ }
+ if (r < 0)
+ return log_error_errno(r, "Failed to create FIFO %s: %m", i->path);
+
+ fd = openat(pfd, bn, O_NOFOLLOW|O_CLOEXEC|O_PATH);
+ if (fd < 0)
+ return log_error_errno(errno, "Failed to open FIFO we just created '%s': %m", i->path);
+
+ /* Validate type before change ownership below */
+ if (fstat(fd, &st) < 0)
+ return log_error_errno(errno, "Failed to fstat(%s): %m", i->path);
+
+ if (!S_ISFIFO(st.st_mode))
+ return log_error_errno(SYNTHETIC_ERRNO(EBADF), "FIFO inode we just created is not a FIFO, refusing.");
+
+ creation = CREATION_FORCE;
+ } else {
+ log_warning("\"%s\" already exists and is not a FIFO.", i->path);
+ return 0;
+ }
+ }
+
+ log_debug("%s fifo \"%s\".", creation_mode_verb_to_string(creation), i->path);
+
+ return fd_set_perms(i, fd, i->path, &st, creation);
}
-typedef int (*action_t)(Item *i, const char *path);
-typedef int (*fdaction_t)(Item *i, int fd, const char *path, const struct stat *st);
+static int create_symlink(Item *i) {
+ _cleanup_close_ int pfd = -1, fd = -1;
+ _cleanup_free_ char *bn = NULL;
+ CreationMode creation;
+ struct stat st;
+ bool good = false;
+ int r;
+
+ assert(i);
+
+ r = path_extract_filename(i->path, &bn);
+ if (r < 0)
+ return log_error_errno(r, "Failed to extract filename from path '%s': %m", i->path);
+ if (r == O_DIRECTORY)
+ return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Cannot open path '%s' for creating FIFO, is a directory.", i->path);
+
+ pfd = path_open_parent_safe(i->path);
+ if (pfd < 0)
+ return pfd;
+
+ mac_selinux_create_file_prepare(i->path, S_IFLNK);
+ r = RET_NERRNO(symlinkat(i->argument, pfd, bn));
+ mac_selinux_create_file_clear();
+
+ creation = r >= 0 ? CREATION_NORMAL : CREATION_EXISTING;
+
+ fd = openat(pfd, bn, O_NOFOLLOW|O_CLOEXEC|O_PATH);
+ if (fd < 0) {
+ if (r < 0)
+ return log_error_errno(r, "Failed to create symlink '%s': %m", i->path); /* original error! */
+
+ return log_error_errno(errno, "Failed to open symlink we just created '%s': %m", i->path);
+ }
+
+ if (fstat(fd, &st) < 0)
+ return log_error_errno(errno, "Failed to fstat(%s): %m", i->path);
+
+ if (S_ISLNK(st.st_mode)) {
+ _cleanup_free_ char *x = NULL;
+
+ r = readlinkat_malloc(fd, "", &x);
+ if (r < 0)
+ return log_error_errno(r, "readlinkat(%s) failed: %m", i->path);
+
+ good = streq(x, i->argument);
+ } else
+ good = false;
+
+ if (!good) {
+ if (!i->append_or_force) {
+ log_debug("\"%s\" is not a symlink or does not point to the correct path.", i->path);
+ return 0;
+ }
+
+ fd = safe_close(fd);
+
+ mac_selinux_create_file_prepare(i->path, S_IFLNK);
+ r = symlinkat_atomic_full(i->argument, pfd, bn, /* make_relative= */ false);
+ mac_selinux_create_file_clear();
+ if (IN_SET(r, -EISDIR, -EEXIST, -ENOTEMPTY)) {
+ r = rm_rf_child(pfd, bn, REMOVE_PHYSICAL);
+ if (r < 0)
+ return log_error_errno(r, "rm -rf %s failed: %m", i->path);
+
+ mac_selinux_create_file_prepare(i->path, S_IFLNK);
+ r = RET_NERRNO(symlinkat(i->argument, pfd, i->path));
+ mac_selinux_create_file_clear();
+ }
+ if (r < 0)
+ return log_error_errno(r, "symlink(%s, %s) failed: %m", i->argument, i->path);
+
+ fd = openat(pfd, bn, O_NOFOLLOW|O_CLOEXEC|O_PATH);
+ if (fd < 0)
+ return log_error_errno(errno, "Failed to open symlink we just created '%s': %m", i->path);
+
+ /* Validate type before change ownership below */
+ if (fstat(fd, &st) < 0)
+ return log_error_errno(errno, "Failed to fstat(%s): %m", i->path);
+
+ if (!S_ISLNK(st.st_mode))
+ return log_error_errno(SYNTHETIC_ERRNO(EBADF), "Symlink we just created is not a symlink, refusing.");
+
+ creation = CREATION_FORCE;
+ }
+
+ log_debug("%s symlink \"%s\".", creation_mode_verb_to_string(creation), i->path);
+ return fd_set_perms(i, fd, i->path, &st, creation);
+}
+
+typedef int (*action_t)(Item *i, const char *path, CreationMode creation);
+typedef int (*fdaction_t)(Item *i, int fd, const char *path, const struct stat *st, CreationMode creation);
+
+static int item_do(
+ Item *i,
+ int fd,
+ const char *path,
+ CreationMode creation,
+ fdaction_t action) {
-static int item_do(Item *i, int fd, const char *path, fdaction_t action) {
struct stat st;
int r = 0, q;
@@ -1926,9 +2123,8 @@ static int item_do(Item *i, int fd, const char *path, fdaction_t action) {
goto finish;
}
- /* This returns the first error we run into, but nevertheless
- * tries to go on */
- r = action(i, fd, path, &st);
+ /* This returns the first error we run into, but nevertheless tries to go on */
+ r = action(i, fd, path, &st, creation);
if (S_ISDIR(st.st_mode)) {
_cleanup_closedir_ DIR *d = NULL;
@@ -1960,7 +2156,7 @@ static int item_do(Item *i, int fd, const char *path, fdaction_t action) {
q = log_oom();
else
/* Pass ownership of dirent fd over */
- q = item_do(i, de_fd, de_path, action);
+ q = item_do(i, de_fd, de_path, CREATION_EXISTING, action);
}
if (q < 0 && r == 0)
@@ -1983,7 +2179,8 @@ static int glob_item(Item *i, action_t action) {
return log_error_errno(k, "glob(%s) failed: %m", i->path);
STRV_FOREACH(fn, g.gl_pathv) {
- k = action(i, *fn);
+ /* We pass CREATION_EXISTING here, since if we are globbing for it, it always has to exist */
+ k = action(i, *fn, CREATION_EXISTING);
if (k < 0 && r == 0)
r = k;
}
@@ -2017,7 +2214,7 @@ static int glob_item_recursively(Item *i, fdaction_t action) {
continue;
}
- k = item_do(i, fd, *fn, action);
+ k = item_do(i, fd, *fn, CREATION_EXISTING, action);
if (k < 0 && r == 0)
r = k;
@@ -2202,8 +2399,7 @@ static int mkdir_parents_item(Item *i, mode_t child_mode) {
}
static int create_item(Item *i) {
- CreationMode creation;
- int r = 0;
+ int r;
assert(i);
@@ -2227,7 +2423,6 @@ static int create_item(Item *i) {
r = truncate_file(i, i->path);
else
r = create_file(i, i->path);
-
if (r < 0)
return r;
break;
@@ -2283,59 +2478,21 @@ static int create_item(Item *i) {
if (r < 0)
return r;
- r = create_fifo(i, i->path);
+ r = create_fifo(i);
if (r < 0)
return r;
break;
- case CREATE_SYMLINK: {
+ case CREATE_SYMLINK:
r = mkdir_parents_item(i, S_IFLNK);
if (r < 0)
return r;
- mac_selinux_create_file_prepare(i->path, S_IFLNK);
- r = RET_NERRNO(symlink(i->argument, i->path));
- mac_selinux_create_file_clear();
+ r = create_symlink(i);
+ if (r < 0)
+ return r;
- if (r < 0) {
- _cleanup_free_ char *x = NULL;
-
- if (r != -EEXIST)
- return log_error_errno(r, "symlink(%s, %s) failed: %m", i->argument, i->path);
-
- r = readlink_malloc(i->path, &x);
- if (r < 0 || !streq(i->argument, x)) {
-
- if (i->append_or_force) {
- mac_selinux_create_file_prepare(i->path, S_IFLNK);
- r = symlink_atomic(i->argument, i->path);
- mac_selinux_create_file_clear();
-
- if (IN_SET(r, -EISDIR, -EEXIST, -ENOTEMPTY)) {
- r = rm_rf(i->path, REMOVE_ROOT|REMOVE_PHYSICAL);
- if (r < 0)
- return log_error_errno(r, "rm -fr %s failed: %m", i->path);
-
- mac_selinux_create_file_prepare(i->path, S_IFLNK);
- r = RET_NERRNO(symlink(i->argument, i->path));
- mac_selinux_create_file_clear();
- }
- if (r < 0)
- return log_error_errno(r, "symlink(%s, %s) failed: %m", i->argument, i->path);
-
- creation = CREATION_FORCE;
- } else {
- log_debug("\"%s\" is not a symlink or does not point to the correct path.", i->path);
- return 0;
- }
- } else
- creation = CREATION_EXISTING;
- } else
-
- creation = CREATION_NORMAL;
- log_debug("%s symlink \"%s\".", creation_mode_verb_to_string(creation), i->path);
break;
- }
case CREATE_BLOCK_DEVICE:
case CREATE_CHAR_DEVICE:
@@ -2410,7 +2567,7 @@ static int create_item(Item *i) {
return 0;
}
-static int remove_item_instance(Item *i, const char *instance) {
+static int remove_item_instance(Item *i, const char *instance, CreationMode creation) {
int r;
assert(i);
@@ -2483,7 +2640,11 @@ static char *age_by_to_string(AgeBy ab, bool is_dir) {
return ret;
}
-static int clean_item_instance(Item *i, const char* instance) {
+static int clean_item_instance(
+ Item *i,
+ const char* instance,
+ CreationMode creation) {
+
_cleanup_closedir_ DIR *d = NULL;
STRUCT_STATX_DEFINE(sx);
int mountpoint, r;
@@ -2568,7 +2729,7 @@ static int clean_item(Item *i) {
case TRUNCATE_DIRECTORY:
case IGNORE_PATH:
case COPY_FILES:
- clean_item_instance(i, i->path);
+ clean_item_instance(i, i->path, CREATION_EXISTING);
return 0;
case EMPTY_DIRECTORY:
case IGNORE_DIRECTORY_PATH:
@@ -2710,12 +2871,15 @@ static bool item_compatible(const Item *a, const Item *b) {
a->uid_set == b->uid_set &&
a->uid == b->uid &&
+ a->uid_only_create == b->uid_only_create &&
a->gid_set == b->gid_set &&
a->gid == b->gid &&
+ a->gid_only_create == b->gid_only_create &&
a->mode_set == b->mode_set &&
a->mode == b->mode &&
+ a->mode_only_create == b->mode_only_create &&
a->age_set == b->age_set &&
a->age == b->age &&
@@ -3303,32 +3467,52 @@ static int parse_line(
}
if (!empty_or_dash(user)) {
- r = find_uid(user, &i.uid, uid_cache);
+ const char *u;
+
+ u = startswith(user, ":");
+ if (u)
+ i.uid_only_create = true;
+ else
+ u = user;
+
+ r = find_uid(u, &i.uid, uid_cache);
if (r < 0) {
*invalid_config = true;
- return log_syntax(NULL, LOG_ERR, fname, line, r, "Failed to resolve user '%s': %m", user);
+ return log_syntax(NULL, LOG_ERR, fname, line, r, "Failed to resolve user '%s': %m", u);
}
i.uid_set = true;
}
if (!empty_or_dash(group)) {
- r = find_gid(group, &i.gid, gid_cache);
+ const char *g;
+
+ g = startswith(group, ":");
+ if (g)
+ i.gid_only_create = true;
+ else
+ g = group;
+
+ r = find_gid(g, &i.gid, gid_cache);
if (r < 0) {
*invalid_config = true;
- return log_syntax(NULL, LOG_ERR, fname, line, r, "Failed to resolve group '%s'.", group);
+ return log_syntax(NULL, LOG_ERR, fname, line, r, "Failed to resolve group '%s'.", g);
}
i.gid_set = true;
}
if (!empty_or_dash(mode)) {
- const char *mm = mode;
+ const char *mm;
unsigned m;
- if (*mm == '~') {
- i.mask_perms = true;
- mm++;
+ for (mm = mode;; mm++) {
+ if (*mm == '~')
+ i.mask_perms = true;
+ else if (*mm == ':')
+ i.mode_only_create = true;
+ else
+ break;
}
r = parse_mode(mm, &m);
diff --git a/test/units/testsuite-22.14.sh b/test/units/testsuite-22.14.sh
new file mode 100755
index 0000000000..2132de7a32
--- /dev/null
+++ b/test/units/testsuite-22.14.sh
@@ -0,0 +1,37 @@
+#!/bin/bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# Tests for the ":" uid/gid/mode modifier
+#
+set -eux
+
+rm -rf /tmp/someinode
+
+systemd-tmpfiles --create - <