* move documentation about our common env vars (SYSTEMD_LOG_LEVEL,
SYSTEMD_PAGER, …) into a man page of its own, and just link it from our
- various man pages that so far embedd the whole list again and again, in an
+ various man pages that so far embed the whole list again and again, in an
attempt to reduce clutter and noise a bid.
* vmspawn switch default swtpm PCR bank to SHA384-only (away from SHA256), at
also have to be configured manually for correct ordering and conflicting. For example:</para>
<programlisting>[Unit]
-Description=My surviving service
+Description=My Surviving Service
SurviveFinalKillSignal=yes
IgnoreOnIsolate=yes
DefaultDependencies=no
After=basic.target
-Conflicts=reboot.target
-Before=reboot.target
-Conflicts=kexec.target
-Before=kexec.target
-Conflicts=poweroff.target
-Before=poweroff.target
-Conflicts=halt.target
-Before=halt.target
-Conflicts=rescue.target
-Before=rescue.target
-Conflicts=emergency.target
-Before=emergency.target
+Conflicts=reboot.target kexec.target poweroff.target halt.target rescue.target emergency.target
+Before=reboot.target kexec.target poweroff.target halt.target rescue.target emergency.target
[Service]
Type=oneshot
-ExecStart=sleep infinity
- </programlisting>
+ExecStart=sleep infinity</programlisting>
</listitem>
<listitem><para>File system mounts may remain mounted during the transition, and complex storage
conf.set_quoted('VERSION_TAG', version_tag)
vcs_tag = get_option('vcs-tag')
-if vcs_tag and fs.is_dir(project_source_root / '.git')
- version_h = vcs_tag(
- input : 'src/version/version.h.in',
- output : 'version.h',
- fallback : '',
- command : ['sh', '-c', 'echo "-g$(git -C . describe --abbrev=7 --match="" --always --dirty=^)"'],
- )
-else
- version_h = configure_file(
- input : 'src/version/version.h.in',
- output : 'version.h',
- configuration : configuration_data({'VCS_TAG' : ''}),
- )
-endif
+command = ['sh', '-c',
+ vcs_tag and fs.exists(project_source_root / '.git') ?
+ 'echo "-g$(git -C . describe --abbrev=7 --match="" --always --dirty=^)"' : ':']
+version_h = vcs_tag(
+ input : 'src/version/version.h.in',
+ output : 'version.h',
+ fallback : '',
+ command : command,
+)
shared_lib_tag = get_option('shared-lib-tag')
if shared_lib_tag == ''
return TAKE_FD(fd);
}
+
+int link_fd(int fd, int newdirfd, const char *newpath) {
+ int r;
+
+ assert(fd >= 0);
+ assert(newdirfd >= 0 || newdirfd == AT_FDCWD);
+ assert(newpath);
+
+ /* Try linking via /proc/self/fd/ first. */
+ r = RET_NERRNO(linkat(AT_FDCWD, FORMAT_PROC_FD_PATH(fd), newdirfd, newpath, AT_SYMLINK_FOLLOW));
+ if (r != -ENOENT)
+ return r;
+
+ /* Fall back to symlinking via AT_EMPTY_PATH as fallback (this requires CAP_DAC_READ_SEARCH and a
+ * more recent kernel, but does not require /proc/ mounted) */
+ if (proc_mounted() != 0)
+ return r;
+
+ return RET_NERRNO(linkat(fd, "", newdirfd, newpath, AT_EMPTY_PATH));
+}
+
+int linkat_replace(int olddirfd, const char *oldpath, int newdirfd, const char *newpath) {
+ _cleanup_close_ int old_fd = -EBADF;
+ int r;
+
+ assert(olddirfd >= 0 || olddirfd == AT_FDCWD);
+ assert(newdirfd >= 0 || newdirfd == AT_FDCWD);
+ assert(!isempty(newpath)); /* source path is optional, but the target path is not */
+
+ /* Like linkat() but replaces the target if needed. Is a NOP if source and target already share the
+ * same inode. */
+
+ if (olddirfd == AT_FDCWD && isempty(oldpath)) /* Refuse operating on the cwd (which is a dir, and dirs can't be hardlinked) */
+ return -EISDIR;
+
+ if (path_implies_directory(oldpath)) /* Refuse these definite directories early */
+ return -EISDIR;
+
+ if (path_implies_directory(newpath))
+ return -EISDIR;
+
+ /* First, try to link this directly */
+ if (oldpath)
+ r = RET_NERRNO(linkat(olddirfd, oldpath, newdirfd, newpath, 0));
+ else
+ r = link_fd(olddirfd, newdirfd, newpath);
+ if (r >= 0)
+ return 0;
+ if (r != -EEXIST)
+ return r;
+
+ old_fd = xopenat(olddirfd, oldpath, O_PATH|O_CLOEXEC);
+ if (old_fd < 0)
+ return old_fd;
+
+ struct stat old_st;
+ if (fstat(old_fd, &old_st) < 0)
+ return -errno;
+
+ if (S_ISDIR(old_st.st_mode)) /* Don't bother if we are operating on a directory */
+ return -EISDIR;
+
+ struct stat new_st;
+ if (fstatat(newdirfd, newpath, &new_st, AT_SYMLINK_NOFOLLOW) < 0)
+ return -errno;
+
+ if (S_ISDIR(new_st.st_mode)) /* Refuse replacing directories */
+ return -EEXIST;
+
+ if (stat_inode_same(&old_st, &new_st)) /* Already the same inode? Then shortcut this */
+ return 0;
+
+ _cleanup_free_ char *tmp_path = NULL;
+ r = tempfn_random(newpath, /* extra= */ NULL, &tmp_path);
+ if (r < 0)
+ return r;
+
+ r = link_fd(old_fd, newdirfd, tmp_path);
+ if (r < 0) {
+ if (!ERRNO_IS_PRIVILEGE(r))
+ return r;
+
+ /* If that didn't work due to permissions then go via the path of the dentry */
+ r = RET_NERRNO(linkat(olddirfd, oldpath, newdirfd, tmp_path, 0));
+ if (r < 0)
+ return r;
+ }
+
+ r = RET_NERRNO(renameat(newdirfd, tmp_path, newdirfd, newpath));
+ if (r < 0) {
+ (void) unlinkat(newdirfd, tmp_path, /* flags= */ 0);
+ return r;
+ }
+
+ return 0;
+}
static inline int xopenat_lock(int dir_fd, const char *path, int open_flags, LockType locktype, int operation) {
return xopenat_lock_full(dir_fd, path, open_flags, 0, 0, locktype, operation);
}
+
+int link_fd(int fd, int newdirfd, const char *newpath);
+
+int linkat_replace(int olddirfd, const char *oldpath, int newdirfd, const char *newpath);
return path[2] == 0;
}
+bool path_implies_directory(const char *path) {
+
+ /* Sometimes, if we look at a path we already know it must refer to a directory, because it is
+ * suffixed with a slash, or its last component is "." or ".." */
+
+ if (!path)
+ return false;
+
+ if (dot_or_dot_dot(path))
+ return true;
+
+ return ENDSWITH_SET(path, "/", "/.", "/..");
+}
+
bool empty_or_root(const char *path) {
/* For operations relative to some root directory, returns true if the specified root directory is
bool dot_or_dot_dot(const char *path);
+bool path_implies_directory(const char *path);
+
static inline const char *skip_dev_prefix(const char *p) {
const char *e;
return "sock";
}
- /* Note anonmyous inodes in the kernel will have a zero type. Hence fstat() of an eventfd() will
+ /* Note anonymous inodes in the kernel will have a zero type. Hence fstat() of an eventfd() will
* return an .st_mode where we'll return NULL here! */
return NULL;
}
return 0;
}
-static int link_fd(int fd, int newdirfd, const char *newpath) {
- int r;
-
- assert(fd >= 0);
- assert(newdirfd >= 0 || newdirfd == AT_FDCWD);
- assert(newpath);
-
- /* Try symlinking via /proc/fd/ first. */
- r = RET_NERRNO(linkat(AT_FDCWD, FORMAT_PROC_FD_PATH(fd), newdirfd, newpath, AT_SYMLINK_FOLLOW));
- if (r != -ENOENT)
- return r;
-
- /* Fall back to symlinking via AT_EMPTY_PATH as fallback (this requires CAP_DAC_READ_SEARCH and a
- * more recent kernel, but does not require /proc/ mounted) */
- if (proc_mounted() != 0)
- return r;
-
- return RET_NERRNO(linkat(fd, "", newdirfd, newpath, AT_EMPTY_PATH));
-}
-
int link_tmpfile_at(int fd, int dir_fd, const char *path, const char *target, LinkTmpfileFlags flags) {
- _cleanup_free_ char *tmp = NULL;
int r;
assert(fd >= 0);
r = RET_NERRNO(renameat(dir_fd, path, dir_fd, target));
else
r = rename_noreplace(dir_fd, path, dir_fd, target);
- if (r < 0)
- return r;
} else {
-
- r = link_fd(fd, dir_fd, target);
- if (r != -EEXIST || !FLAGS_SET(flags, LINK_TMPFILE_REPLACE))
- return r;
-
- /* So the target already exists and we were asked to replace it. That sucks a bit, since the kernel's
- * linkat() logic does not allow that. We work-around this by linking the file to a random name
- * first, and then renaming that to the final name. This reintroduces the race O_TMPFILE kinda is
- * trying to fix, but at least the vulnerability window (i.e. where the file is linked into the file
- * system under a temporary name) is very short. */
-
- r = tempfn_random(target, NULL, &tmp);
- if (r < 0)
- return r;
-
- if (link_fd(fd, dir_fd, tmp) < 0)
- return -EEXIST; /* propagate original error */
-
- r = RET_NERRNO(renameat(dir_fd, tmp, dir_fd, target));
- if (r < 0) {
- (void) unlinkat(dir_fd, tmp, 0);
- return r;
- }
+ if (FLAGS_SET(flags, LINK_TMPFILE_REPLACE))
+ r = linkat_replace(fd, /* oldpath= */ NULL, dir_fd, target);
+ else
+ r = link_fd(fd, dir_fd, target);
}
+ if (r < 0)
+ return r;
if (FLAGS_SET(flags, LINK_TMPFILE_SYNC)) {
r = fsync_full(fd);
#include "random-util.h"
#include "sparse-endian.h"
#include "stat-util.h"
+#include "tmpfile-util.h"
#include "tpm2-util.h"
#include "user-util.h"
#include "varlink.h"
assert(dfd >= 0);
assert(fn);
- /* For non-root users creating a temporary file using the openat(2) over "." will fail later, in the
- * linkat(2) step at the end. The reason is that linkat(2) requires the CAP_DAC_READ_SEARCH
- * capability when it uses the AT_EMPTY_PATH flag. */
- if (have_effective_cap(CAP_DAC_READ_SEARCH) > 0) {
- fd = openat(dfd, ".", O_CLOEXEC|O_WRONLY|O_TMPFILE, 0400);
- if (fd < 0)
- log_debug_errno(errno, "Failed to create temporary credential file with O_TMPFILE, proceeding without: %m");
- }
- if (fd < 0) {
- if (asprintf(&t, "credential.secret.%016" PRIx64, random_u64()) < 0)
- return -ENOMEM;
-
- fd = openat(dfd, t, O_CLOEXEC|O_WRONLY|O_CREAT|O_EXCL|O_NOFOLLOW, 0400);
- if (fd < 0)
- return -errno;
- }
+ fd = open_tmpfile_linkable_at(dfd, fn, O_CLOEXEC|O_WRONLY, &t);
+ if (fd < 0)
+ return log_debug_errno(fd, "Failed to create temporary file for credential host secret: %m");
r = chattr_secret(fd, 0);
if (r < 0)
if (r < 0)
goto fail;
- if (fsync(fd) < 0) {
+ if (fchmod(fd, 0400) < 0) {
r = -errno;
goto fail;
}
- warn_not_encrypted(fd, flags, dirname, fn);
-
- if (t) {
- r = rename_noreplace(dfd, t, dfd, fn);
- if (r < 0)
- goto fail;
-
- t = mfree(t);
- } else if (linkat(fd, "", dfd, fn, AT_EMPTY_PATH) < 0) {
+ if (fsync(fd) < 0) {
r = -errno;
goto fail;
}
- if (fsync(dfd) < 0) {
- r = -errno;
+ warn_not_encrypted(fd, flags, dirname, fn);
+
+ r = link_tmpfile_at(fd, dfd, t, fn, LINK_TMPFILE_SYNC);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to link host key into place: %m");
goto fail;
}
void *copy;
copy = memdup(buf.data, sizeof(buf.data));
- if (!copy) {
- r = -ENOMEM;
- goto fail;
- }
+ if (!copy)
+ return -ENOMEM;
*ret = IOVEC_MAKE(copy, sizeof(buf.data));
}
if (!f->background_color)
return 0;
- /* When we see a carriage return (ASCII 13) this this sets only the background */
+ /* When we see a carriage return (ASCII 13) then this sets only the background */
s = background_color_sequence(f);
if (!s)
assert_se(xopenat_lock_full(tfd, "def", O_DIRECTORY, 0, 0755, LOCK_POSIX, LOCK_EX) == -EBADF);
}
+TEST(linkat_replace) {
+ _cleanup_(rm_rf_physical_and_freep) char *t = NULL;
+ _cleanup_close_ int tfd = -EBADF;
+
+ assert_se((tfd = mkdtemp_open(NULL, 0, &t)) >= 0);
+
+ _cleanup_close_ int fd1 = openat(tfd, "foo", O_CREAT|O_RDWR|O_CLOEXEC, 0600);
+ assert_se(fd1 >= 0);
+
+ assert_se(linkat_replace(tfd, "foo", tfd, "bar") >= 0);
+ assert_se(linkat_replace(tfd, "foo", tfd, "bar") >= 0);
+
+ _cleanup_close_ int fd1_check = openat(tfd, "bar", O_RDWR|O_CLOEXEC);
+ assert_se(fd1_check >= 0);
+
+ assert_se(inode_same_at(fd1, NULL, fd1_check, NULL, AT_EMPTY_PATH) > 0);
+
+ _cleanup_close_ int fd2 = openat(tfd, "baz", O_CREAT|O_RDWR|O_CLOEXEC, 0600);
+ assert_se(fd2 >= 0);
+
+ assert_se(inode_same_at(fd1, NULL, fd2, NULL, AT_EMPTY_PATH) == 0);
+
+ assert_se(linkat_replace(tfd, "foo", tfd, "baz") >= 0);
+
+ _cleanup_close_ int fd2_check = openat(tfd, "baz", O_RDWR|O_CLOEXEC);
+
+ assert_se(inode_same_at(fd2, NULL, fd2_check, NULL, AT_EMPTY_PATH) == 0);
+ assert_se(inode_same_at(fd1, NULL, fd2_check, NULL, AT_EMPTY_PATH) > 0);
+}
+
static int intro(void) {
arg_test_dir = saved_argv[1];
return EXIT_SUCCESS;
assert_cc(FILENAME_MAX == PATH_MAX);
}
+TEST(path_implies_directory) {
+ assert_se(!path_implies_directory(NULL));
+ assert_se(!path_implies_directory(""));
+ assert_se(path_implies_directory("/"));
+ assert_se(path_implies_directory("////"));
+ assert_se(path_implies_directory("////.///"));
+ assert_se(path_implies_directory("////./"));
+ assert_se(path_implies_directory("////."));
+ assert_se(path_implies_directory("."));
+ assert_se(path_implies_directory("./"));
+ assert_se(path_implies_directory("/."));
+ assert_se(path_implies_directory(".."));
+ assert_se(path_implies_directory("../"));
+ assert_se(path_implies_directory("/.."));
+ assert_se(!path_implies_directory("a"));
+ assert_se(!path_implies_directory("ab"));
+ assert_se(path_implies_directory("ab/"));
+ assert_se(!path_implies_directory("ab/a"));
+ assert_se(path_implies_directory("ab/a/"));
+ assert_se(path_implies_directory("ab/a/.."));
+ assert_se(path_implies_directory("ab/a/."));
+ assert_se(path_implies_directory("ab/a//"));
+}
+
DEFINE_TEST_MAIN(LOG_DEBUG);