1
0
mirror of https://github.com/systemd/systemd synced 2026-03-30 11:44:49 +02:00

Compare commits

...

27 Commits

Author SHA1 Message Date
Lennart Poettering
de3ef2524e
Merge pull request #20968 from poettering/homed-pin
homed: pin+lock homes while logged in + keep trying to unmount on logging out + optionally drop caches on logging out
2021-10-11 23:11:03 +02:00
Lennart Poettering
5c791053e3
Merge pull request #20776 from medhefgo/boot-timeout
sd-boot: Allow disabling timeout
2021-10-11 23:05:37 +02:00
Yu Watanabe
f533135c6c
Merge pull request #20981 from poettering/glibc-less-internal
various clean-ups: use less glibc internal symbols, modernize some other stuff
2021-10-12 02:10:49 +09:00
Yu Watanabe
10285219ea
Merge pull request #20965 from poettering/getdents
recurse-dir: use getdents64()
2021-10-12 02:03:12 +09:00
Lennart Poettering
c4fb47365c update TODO 2021-10-11 16:00:34 +02:00
Lennart Poettering
2700fecdb3 homed: allow overriding the root directory for home dirs via env var (i.e. use a different path than /home/)
This is a debugging feature. It's sometimes incredibly useful to be able
to run a second instance of homed that operates on another dir than
/home/.

Specifically, if you build homed from the source tree you can now run an
instance of it pretty reasonably directly from the build tree via:

  sudo SYSTEMD_HOME_DEBUG_SUFFIX=foo SYSTEMD_HOMEWORK_PATH=$(pwd)/build/systemd-homework SYSTEMD_HOME_ROOT=/home/foo ./build/systemd-homed

And then talk to it via

  sudo SYSTEMD_HOME_DEBUG_SUFFIX=foo homectl …

(you might need to tweak your dbus policy for this to work fully though)
2021-10-11 16:00:34 +02:00
Lennart Poettering
86019efa44 homed: optionally, drop caches on logout
Fixes: #20857
2021-10-11 16:00:34 +02:00
Lennart Poettering
2aaf565a2d homed: take BSD file lock on LUKS file while activated
Fixes: #19758
2021-10-11 16:00:34 +02:00
Lennart Poettering
23cff6d4fe homed: retry deactivation every 15s until successful
Fixes: #17445
2021-10-11 16:00:34 +02:00
Lennart Poettering
0c71e3ef24 homed: keep "pinning" fd open while home dir active
The pin fd keeps the mount busy, ensuring that unmount requests need to
go through us.

Note that this doesn't change too much IRL, since a logged in user
generally has processes keeping the home dir busy anyway. However, in
some corner cases it is safer to protect from accidental unmounts this
way. (e.g. if user manually called "homectl activate" first).
2021-10-11 16:00:34 +02:00
Lennart Poettering
bdfe7ada0d rm-rf: optionally fsync() after removing directory tree 2021-10-11 16:00:34 +02:00
Lennart Poettering
678ca2133c varlink: make one more parameter const 2021-10-11 15:37:59 +02:00
Lennart Poettering
a995ce4768 util: define initializer for 'struct ucred' that properly invalidates all fields
i.e. let's make sure to invalid uid/gid to UID_INVAID + GID_INVALID
instead of zero.
2021-10-11 15:37:37 +02:00
Lennart Poettering
d8f1673700 sort-util: avoid using glibc's internal __compar_d_fn_t type 2021-10-11 14:33:02 +02:00
Lennart Poettering
6393b847f4 recuse-dir: rework to use getdents64() instead of readdir()
Let's use the underlying Linux API directly, instead of
opendir()/readdir(). This makes it possible for us to do a single memory
allocation for all directory entries in common cases, instead of one for
each entry.
2021-10-11 14:31:34 +02:00
Lennart Poettering
25d7a71774 test-recurse-dir: output some simple timing info, comparing recurse_dir() and nftw() 2021-10-11 14:31:34 +02:00
Lennart Poettering
aab35b1e59 missing: add getdents64() syscall wrapper
glibc 2.30 (Aug 2019) added a wrapper for getdents64(). For older
versions let's define our own.

(This syscall exists since Linux 2.4, hence should be safe to use for
us)
2021-10-11 14:31:34 +02:00
Lennart Poettering
11c8b1f103 localed: use PROJECT_FILE rather than __FILE__ for logging
All our log.h code uses PROJECT_FILE for this, let's hence use it here
too.
2021-10-11 14:10:48 +02:00
Lennart Poettering
95fe7b28d3 ethtool-util: let's use userspace types in userspace code
Using kernel types __u32 is fine for headers shared by the kernel, but
if we define something in userspace and only use it in userspace, in our
own .c files, let's stick to userspace fixed-length types.
2021-10-11 14:10:44 +02:00
Lennart Poettering
7fbae5b706 tree-wide: use C99 __func__ rather than obsolete __FUNCTION__
We use __func__ almost everywhere, but there are some holdouts. Fix
that.
2021-10-11 14:10:39 +02:00
Lennart Poettering
fe92eb795b network: use official bswap_32() rather than inofficial __bswap_32()
The former is a macro for the latter, but let's use the official API
(the one that has an API).
2021-10-11 14:10:07 +02:00
Lennart Poettering
899c1c0a34 macro: also use trailing __ for alignof use in attributes
While the underscore is optional, the docs say we should suffix and we
do that everywher else. Do so here too.
2021-10-11 14:09:33 +02:00
Lennart Poettering
2ccd598635 stub: also move magic string in stub into .sdmagic PE section
We already did that for sd-boot, hence do it for sd-stub the same way.

Also, move the __attribute__ stuff to the beginning of the statement,
rather than the middle. Mostly just because we usually put it first for
implementations for identifiers (for prototypes we put it last).
2021-10-11 14:09:28 +02:00
Lennart Poettering
f0c4f94453 sort-util: use comparison_fn_t instead of __compar_fn_t
Let's avoid using the internal type of glibc, and rather use the one
they officially export.

https://www.gnu.org/software/libc/manual/html_node/Comparison-Functions.html
2021-10-11 14:09:18 +02:00
Lennart Poettering
f8cc16fd53 signal-util: don't introduce symbols with double underscores
ANSI C reserves identifiers beginning with an underscore for compiler
internal stuff. We already invade that namespace plenty and probably
should not. But even going for the doubly underscore prefixed namespace
is a bit too much. Let's just rename the offending table as
"static_signal_table[]", since it lists the static defined signals
rather than the "dynamic" RTSIGMIN/RTSIGMAX signals.
2021-10-11 14:08:58 +02:00
Jan Janssen
39ddc32a86 bootctl: Add set-timeout verb
Fixes: #18766
2021-10-08 15:32:50 +02:00
Jan Janssen
52b6b35643 sd-boot: Allow disabling timeout 2021-10-08 15:26:55 +02:00
51 changed files with 951 additions and 330 deletions

4
TODO
View File

@ -1248,10 +1248,6 @@ Features:
fallback logic to get a regular user created on uninitialized systems.
- store PKCS#11 + FIDO2 token info in LUKS2 header, compatible with
systemd-cryptsetup, so that it can unlock homed volumes
- try to unmount in regular intervals when home dir was busy when we
tried because idle.
- keep an fd to the homedir open at all times, to keep the fs pinned
(autofs and such) while user is logged in.
* add a new switch --auto-definitions=yes/no or so to systemd-repart. If
specified, synthesize a definition automatically if we can: enlarge last

View File

@ -116,6 +116,21 @@
</para></listitem>
</varlistentry>
<varlistentry>
<term><option>set-timeout</option> <replaceable>TIMEOUT</replaceable></term>
<term><option>set-timeout-oneshot</option> <replaceable>TIMEOUT</replaceable></term>
<listitem><para>Sets the boot loader menu timeout in seconds. The <option>set-timeout-oneshot</option>
command will set the timeout only for the next boot. See
<citerefentry><refentrytitle>systemd.time</refentrytitle><manvolnum>7</manvolnum></citerefentry>
for details about the syntax of time spans.</para>
<para>If this is set to <option>menu-hidden</option> or <option>0</option> no menu is shown and
the default entry will be booted immediately, while setting this to <option>menu-force</option>
disables the timeout while always showing the menu. When an empty string ("") is specified the
bootloader will revert to its default menu timeout.</para></listitem>
</varlistentry>
</variablelist>
</refsect1>

View File

@ -610,6 +610,17 @@
node is not allowed if any of the other storage backends are used.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--drop-caches=</option><replaceable>BOOL</replaceable></term>
<listitem><para>Automatically flush OS file system caches on logout. This is useful in combination
with the fscrypt storage backend to ensure the OS does not keep decrypted versions of the files and
directories in memory (and accessible) after logout. This option is also supported on other backends,
but should not bring any benefit there. Defaults to off, except if the selected storage backend is
fscrypt, where it defaults to on. Note that flushing OS caches will negatively influence performance
of the OS shortly after logout.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--fs-type=</option><replaceable>TYPE</replaceable></term>

View File

@ -106,9 +106,10 @@
will be stored as an EFI variable in that case, overriding this option.
</para>
<para>If the timeout is disabled, the default entry will be booted
immediately. The menu can be shown by pressing and holding a key before
systemd-boot is launched.</para>
<para>If set to <literal>menu-hidden</literal> or <literal>0</literal> no menu
is shown and the default entry will be booted immediately. The menu can be shown
by pressing and holding a key before systemd-boot is launched. Setting this to
<literal>menu-force</literal> disables the timeout while always showing the menu.</para>
</listitem>
</varlistentry>

View File

@ -546,6 +546,7 @@ foreach ident : [
['mount_setattr', '''#include <sys/mount.h>'''],
['move_mount', '''#include <sys/mount.h>'''],
['open_tree', '''#include <sys/mount.h>'''],
['getdents64', '''#include <dirent.h>'''],
]
have = cc.has_function(ident[0], prefix : ident[1], args : '-D_GNU_SOURCE')

View File

@ -57,7 +57,7 @@ _bootctl() {
local -A VERBS=(
# systemd-efi-options takes an argument, but it is free-form, so we cannot complete it
[STANDALONE]='help status install update remove is-installed random-seed systemd-efi-options list'
[STANDALONE]='help status install update remove is-installed random-seed systemd-efi-options list set-timeout set-timeout-oneshot'
[BOOTENTRY]='set-default set-oneshot'
[BOOLEAN]='reboot-to-firmware'
)

View File

@ -46,6 +46,8 @@ _bootctl_reboot-to-firmware() {
"list:List boot loader entries"
"set-default:Set the default boot loader entry"
"set-oneshot:Set the default boot loader entry only for the next boot"
"set-timeout:Set the menu timeout"
"set-timeout-oneshot:Set the menu timeout for the next boot only"
)
if (( CURRENT == 1 )); then
_describe -t commands 'bootctl command' _bootctl_cmds || compadd "$@"

View File

@ -25,7 +25,7 @@
#define _public_ __attribute__((__visibility__("default")))
#define _hidden_ __attribute__((__visibility__("hidden")))
#define _weakref_(x) __attribute__((__weakref__(#x)))
#define _alignas_(x) __attribute__((__aligned__(__alignof(x))))
#define _alignas_(x) __attribute__((__aligned__(__alignof__(x))))
#define _alignptr_ __attribute__((__aligned__(sizeof(void*))))
#define _warn_unused_result_ __attribute__((__warn_unused_result__))

View File

@ -540,3 +540,19 @@ static inline int missing_move_mount(
# define move_mount missing_move_mount
#endif
/* ======================================================================= */
#if !HAVE_GETDENTS64
static inline ssize_t missing_getdents64(int fd, void *buffer, size_t length) {
# if defined __NR_getdents64 && __NR_getdents64 >= 0
return syscall(__NR_getdents64, fd, buffer, length);
# else
errno = ENOSYS;
return -1;
# endif
}
# define getdents64 missing_getdents64
#endif

View File

@ -4,6 +4,7 @@
#include "dirent-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "missing_syscall.h"
#include "mountpoint-util.h"
#include "recurse-dir.h"
#include "sort-util.h"
@ -14,76 +15,130 @@ static int sort_func(struct dirent * const *a, struct dirent * const *b) {
return strcmp((*a)->d_name, (*b)->d_name);
}
struct dirent** readdir_all_free(struct dirent **array) {
static bool ignore_dirent(const struct dirent *de, RecurseDirFlags flags) {
assert(de);
/* Destructor that relies on the fact that the array of dirent structure pointers is NULL
* terminated */
/* Depending on flag either ignore everything starting with ".", or just "." itself and ".." */
if (!array)
return NULL;
for (struct dirent **i = array; *i; i++)
free(*i);
return mfree(array);
return FLAGS_SET(flags, RECURSE_DIR_IGNORE_DOT) ?
de->d_name[0] == '.' :
dot_or_dot_dot(de->d_name);
}
int readdir_all(DIR *d,
/* Maximum space one direent structure might require at most */
#define DIRENT_SIZE_MAX MAX(sizeof(struct dirent), offsetof(struct dirent, d_name) + NAME_MAX + 1)
int readdir_all(int dir_fd,
RecurseDirFlags flags,
struct dirent ***ret) {
DirectoryEntries **ret) {
_cleanup_(readdir_all_freep) struct dirent **de_array = NULL;
size_t n_de = 0;
_cleanup_free_ DirectoryEntries *de = NULL;
DirectoryEntries *nde;
size_t add, sz, j;
assert(d);
assert(dir_fd >= 0);
/* Returns an array with pointers to "struct dirent" directory entries, optionally sorted. Free the
* array with readdir_all_freep(). */
for (;;) {
_cleanup_free_ struct dirent *copy = NULL;
struct dirent *de;
/* Only if 64bit off_t is enabled struct dirent + struct dirent64 are actually the same. We require
* this, and we want them to be interchangable, hence verify that. */
assert_cc(_FILE_OFFSET_BITS == 64);
assert_cc(sizeof(struct dirent) == sizeof(struct dirent64));
assert_cc(offsetof(struct dirent, d_ino) == offsetof(struct dirent64, d_ino));
assert_cc(sizeof(((struct dirent*) NULL)->d_ino) == sizeof(((struct dirent64*) NULL)->d_ino));
assert_cc(offsetof(struct dirent, d_off) == offsetof(struct dirent64, d_off));
assert_cc(sizeof(((struct dirent*) NULL)->d_off) == sizeof(((struct dirent64*) NULL)->d_off));
assert_cc(offsetof(struct dirent, d_reclen) == offsetof(struct dirent64, d_reclen));
assert_cc(sizeof(((struct dirent*) NULL)->d_reclen) == sizeof(((struct dirent64*) NULL)->d_reclen));
assert_cc(offsetof(struct dirent, d_type) == offsetof(struct dirent64, d_type));
assert_cc(sizeof(((struct dirent*) NULL)->d_type) == sizeof(((struct dirent64*) NULL)->d_type));
assert_cc(offsetof(struct dirent, d_name) == offsetof(struct dirent64, d_name));
assert_cc(sizeof(((struct dirent*) NULL)->d_name) == sizeof(((struct dirent64*) NULL)->d_name));
errno = 0;
de = readdir(d);
if (!de) {
if (errno == 0)
/* Start with space for up to 8 directory entries. We expect at least 2 ("." + ".."), hence hopefully
* 8 will cover most cases comprehensively. (Note that most likely a lot more entries will actually
* fit in the buffer, given we calculate maximum file name length here.) */
de = malloc(offsetof(DirectoryEntries, buffer) + DIRENT_SIZE_MAX * 8);
if (!de)
return -ENOMEM;
de->buffer_size = 0;
for (;;) {
size_t bs;
ssize_t n;
bs = MIN(MALLOC_SIZEOF_SAFE(de) - offsetof(DirectoryEntries, buffer), (size_t) SSIZE_MAX);
assert(bs > de->buffer_size);
n = getdents64(dir_fd, de->buffer + de->buffer_size, bs - de->buffer_size);
if (n < 0)
return -errno;
if (n == 0)
break;
return -errno;
}
de->buffer_size += n;
/* Depending on flag either ignore everything starting with ".", or just "." itself and ".." */
if (FLAGS_SET(flags, RECURSE_DIR_IGNORE_DOT) ?
de->d_name[0] == '.' :
dot_or_dot_dot(de->d_name))
if (de->buffer_size < bs - DIRENT_SIZE_MAX) /* Still room for one more entry, then try to
* fill it up without growing the structure. */
continue;
if (n_de >= INT_MAX) /* Make sure we can return the number as 'int' return value */
return -ERANGE;
if (bs >= SSIZE_MAX - offsetof(DirectoryEntries, buffer))
return -EFBIG;
bs = bs >= (SSIZE_MAX - offsetof(DirectoryEntries, buffer))/2 ? SSIZE_MAX - offsetof(DirectoryEntries, buffer) : bs * 2;
if (!GREEDY_REALLOC(de_array, n_de+2))
nde = realloc(de, bs);
if (!nde)
return -ENOMEM;
copy = memdup(de, de->d_reclen);
if (!copy)
de = nde;
}
de->n_entries = 0;
for (struct dirent *entry = (struct dirent*) de->buffer;
(uint8_t*) entry < de->buffer + de->buffer_size;
entry = (struct dirent*) ((uint8_t*) entry + entry->d_reclen)) {
if (ignore_dirent(entry, flags))
continue;
de->n_entries++;
}
sz = ALIGN(offsetof(DirectoryEntries, buffer) + de->buffer_size);
add = sizeof(struct dirent*) * de->n_entries;
if (add > SIZE_MAX - add)
return -ENOMEM;
de_array[n_de++] = TAKE_PTR(copy);
de_array[n_de] = NULL; /* guarantee array remains NUL terminated */
nde = realloc(de, sz + add);
if (!nde)
return -ENOMEM;
de = nde;
de->entries = (struct dirent**) ((uint8_t*) de + ALIGN(offsetof(DirectoryEntries, buffer) + de->buffer_size));
j = 0;
for (struct dirent *entry = (struct dirent*) de->buffer;
(uint8_t*) entry < de->buffer + de->buffer_size;
entry = (struct dirent*) ((uint8_t*) entry + entry->d_reclen)) {
if (ignore_dirent(entry, flags))
continue;
de->entries[j++] = entry;
}
if (FLAGS_SET(flags, RECURSE_DIR_SORT))
typesafe_qsort(de_array, n_de, sort_func);
typesafe_qsort(de->entries, de->n_entries, sort_func);
if (ret)
*ret = TAKE_PTR(de_array);
*ret = TAKE_PTR(de);
return (int) n_de;
return 0;
}
int recurse_dir(
DIR *d,
int dir_fd,
const char *path,
unsigned statx_mask,
unsigned n_depth_max,
@ -91,51 +146,50 @@ int recurse_dir(
recurse_dir_func_t func,
void *userdata) {
_cleanup_(readdir_all_freep) struct dirent **de = NULL;
int r, n;
_cleanup_free_ DirectoryEntries *de = NULL;
int r;
assert(d);
assert(dir_fd >= 0);
assert(func);
/* This is a lot like ftw()/nftw(), but a lot more modern, i.e. built around openat()/statx(), and
* under the assumption that fds are not as 'expensive' as they used to be. */
/* This is a lot like ftw()/nftw(), but a lot more modern, i.e. built around openat()/statx()/O_PATH,
* and under the assumption that fds are not as 'expensive' as they used to be. */
if (n_depth_max == 0)
return -EOVERFLOW;
if (n_depth_max == UINT_MAX) /* special marker for "default" */
n_depth_max = DEFAULT_RECURSION_MAX;
n = readdir_all(d, flags, &de);
if (n < 0)
return n;
r = readdir_all(dir_fd, flags, &de);
if (r < 0)
return r;
for (int i = 0; i < n; i++) {
for (size_t i = 0; i < de->n_entries; i++) {
_cleanup_close_ int inode_fd = -1, subdir_fd = -1;
_cleanup_free_ char *joined = NULL;
_cleanup_closedir_ DIR *subdir = NULL;
_cleanup_close_ int inode_fd = -1;
STRUCT_STATX_DEFINE(sx);
bool sx_valid = false;
const char *p;
/* For each directory entry we'll do one of the following:
*
* 1) If the entry refers to a directory, we'll open it as O_DIRECTORY 'subdir' and then statx() the opened directory if requested
* 2) Otherwise and RECURSE_DIR_INODE_FD is set we'll open O_PATH 'inode_fd' and then statx() the opened inode
* 3) Otherwise we'll statx() the directory entry via the directory we are currently looking at
* 1) If the entry refers to a directory, we'll open it as O_DIRECTORY 'subdir_fd' and then statx() the opened directory via that new fd (if requested)
* 2) Otherwise, if RECURSE_DIR_INODE_FD is set we'll open it as O_PATH 'inode_fd' and then statx() the opened inode via that new fd (if requested)
* 3) Otherwise, we'll statx() the directory entry via the directory fd we are currently looking at (if requested)
*/
if (path) {
joined = path_join(path, de[i]->d_name);
joined = path_join(path, de->entries[i]->d_name);
if (!joined)
return -ENOMEM;
p = joined;
} else
p = de[i]->d_name;
p = de->entries[i]->d_name;
if (IN_SET(de[i]->d_type, DT_UNKNOWN, DT_DIR)) {
subdir = xopendirat(dirfd(d), de[i]->d_name, O_NOFOLLOW);
if (!subdir) {
if (IN_SET(de->entries[i]->d_type, DT_UNKNOWN, DT_DIR)) {
subdir_fd = openat(dir_fd, de->entries[i]->d_name, O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
if (subdir_fd < 0) {
if (errno == ENOENT) /* Vanished by now, go for next file immediately */
continue;
@ -147,9 +201,9 @@ int recurse_dir(
r = func(RECURSE_DIR_SKIP_OPEN_DIR_ERROR_BASE + errno,
p,
dirfd(d),
dir_fd,
-1,
de[i],
de->entries[i],
NULL,
userdata);
if (r == RECURSE_DIR_LEAVE_DIRECTORY)
@ -164,10 +218,10 @@ int recurse_dir(
} else {
/* If we managed to get a DIR* off the inode, it's definitely a directory. */
de[i]->d_type = DT_DIR;
de->entries[i]->d_type = DT_DIR;
if (statx_mask != 0 || (flags & RECURSE_DIR_SAME_MOUNT)) {
r = statx_fallback(dirfd(subdir), "", AT_EMPTY_PATH, statx_mask, &sx);
r = statx_fallback(subdir_fd, "", AT_EMPTY_PATH, statx_mask, &sx);
if (r < 0)
return r;
@ -176,12 +230,12 @@ int recurse_dir(
}
}
if (!subdir) {
if (subdir_fd < 0) {
/* It's not a subdirectory. */
if (flags & RECURSE_DIR_INODE_FD) {
inode_fd = openat(dirfd(d), de[i]->d_name, O_PATH|O_NOFOLLOW|O_CLOEXEC);
inode_fd = openat(dir_fd, de->entries[i]->d_name, O_PATH|O_NOFOLLOW|O_CLOEXEC);
if (inode_fd < 0) {
if (errno == ENOENT) /* Vanished by now, go for next file immediately */
continue;
@ -192,9 +246,9 @@ int recurse_dir(
r = func(RECURSE_DIR_SKIP_OPEN_INODE_ERROR_BASE + errno,
p,
dirfd(d),
dir_fd,
-1,
de[i],
de->entries[i],
NULL,
userdata);
if (r == RECURSE_DIR_LEAVE_DIRECTORY)
@ -222,16 +276,16 @@ int recurse_dir(
* directory fd which sould be riskless now that we pinned the
* inode. */
subdir = xopendirat(AT_FDCWD, FORMAT_PROC_FD_PATH(inode_fd), 0);
if (!subdir)
subdir_fd = openat(AT_FDCWD, FORMAT_PROC_FD_PATH(inode_fd), O_DIRECTORY|O_CLOEXEC);
if (subdir_fd < 0)
return -errno;
inode_fd = safe_close(inode_fd);
}
} else if (statx_mask != 0 || (de[i]->d_type == DT_UNKNOWN && (flags & RECURSE_DIR_ENSURE_TYPE))) {
} else if (statx_mask != 0 || (de->entries[i]->d_type == DT_UNKNOWN && (flags & RECURSE_DIR_ENSURE_TYPE))) {
r = statx_fallback(dirfd(d), de[i]->d_name, AT_SYMLINK_NOFOLLOW, statx_mask | STATX_TYPE, &sx);
r = statx_fallback(dir_fd, de->entries[i]->d_name, AT_SYMLINK_NOFOLLOW, statx_mask | STATX_TYPE, &sx);
if (r == -ENOENT) /* Vanished by now? Go for next file immediately */
continue;
if (r < 0) {
@ -241,9 +295,9 @@ int recurse_dir(
r = func(RECURSE_DIR_SKIP_STAT_INODE_ERROR_BASE + -r,
p,
dirfd(d),
dir_fd,
-1,
de[i],
de->entries[i],
NULL,
userdata);
if (r == RECURSE_DIR_LEAVE_DIRECTORY)
@ -271,9 +325,9 @@ int recurse_dir(
r = func(RECURSE_DIR_SKIP_STAT_INODE_ERROR_BASE + EISDIR,
p,
dirfd(d),
dir_fd,
-1,
de[i],
de->entries[i],
NULL,
userdata);
if (r == RECURSE_DIR_LEAVE_DIRECTORY)
@ -289,22 +343,22 @@ int recurse_dir(
if (sx_valid) {
/* Copy over the data we acquired through statx() if we acquired any */
if (sx.stx_mask & STATX_TYPE) {
assert(!!subdir == !!S_ISDIR(sx.stx_mode));
de[i]->d_type = IFTODT(sx.stx_mode);
assert((subdir_fd < 0) == !S_ISDIR(sx.stx_mode));
de->entries[i]->d_type = IFTODT(sx.stx_mode);
}
if (sx.stx_mask & STATX_INO)
de[i]->d_ino = sx.stx_ino;
de->entries[i]->d_ino = sx.stx_ino;
}
if (subdir) {
if (subdir_fd >= 0) {
if (FLAGS_SET(flags, RECURSE_DIR_SAME_MOUNT)) {
bool is_mount;
if (sx_valid && FLAGS_SET(sx.stx_attributes_mask, STATX_ATTR_MOUNT_ROOT))
is_mount = FLAGS_SET(sx.stx_attributes, STATX_ATTR_MOUNT_ROOT);
else {
r = fd_is_mount_point(dirfd(d), de[i]->d_name, 0);
r = fd_is_mount_point(dir_fd, de->entries[i]->d_name, 0);
if (r < 0)
log_debug_errno(r, "Failed to determine whether %s is a submount, assuming not: %m", p);
@ -314,9 +368,9 @@ int recurse_dir(
if (is_mount) {
r = func(RECURSE_DIR_SKIP_MOUNT,
p,
dirfd(d),
dirfd(subdir),
de[i],
dir_fd,
subdir_fd,
de->entries[i],
statx_mask != 0 ? &sx : NULL, /* only pass sx if user asked for it */
userdata);
if (r == RECURSE_DIR_LEAVE_DIRECTORY)
@ -333,9 +387,9 @@ int recurse_dir(
r = func(RECURSE_DIR_SKIP_DEPTH,
p,
dirfd(d),
dirfd(subdir),
de[i],
dir_fd,
subdir_fd,
de->entries[i],
statx_mask != 0 ? &sx : NULL, /* only pass sx if user asked for it */
userdata);
if (r == RECURSE_DIR_LEAVE_DIRECTORY)
@ -348,9 +402,9 @@ int recurse_dir(
r = func(RECURSE_DIR_ENTER,
p,
dirfd(d),
dirfd(subdir),
de[i],
dir_fd,
subdir_fd,
de->entries[i],
statx_mask != 0 ? &sx : NULL, /* only pass sx if user asked for it */
userdata);
if (r == RECURSE_DIR_LEAVE_DIRECTORY)
@ -360,7 +414,7 @@ int recurse_dir(
if (r != RECURSE_DIR_CONTINUE)
return r;
r = recurse_dir(subdir,
r = recurse_dir(subdir_fd,
p,
statx_mask,
n_depth_max - 1,
@ -372,18 +426,18 @@ int recurse_dir(
r = func(RECURSE_DIR_LEAVE,
p,
dirfd(d),
dirfd(subdir),
de[i],
dir_fd,
subdir_fd,
de->entries[i],
statx_mask != 0 ? &sx : NULL, /* only pass sx if user asked for it */
userdata);
} else
/* Non-directory inode */
r = func(RECURSE_DIR_ENTRY,
p,
dirfd(d),
dir_fd,
inode_fd,
de[i],
de->entries[i],
statx_mask != 0 ? &sx : NULL, /* only pass sx if user asked for it */
userdata);
@ -406,11 +460,17 @@ int recurse_dir_at(
recurse_dir_func_t func,
void *userdata) {
_cleanup_closedir_ DIR *d = NULL;
_cleanup_close_ int fd = -1;
d = xopendirat(atfd, path, 0);
if (!d)
assert(atfd >= 0 || atfd == AT_FDCWD);
assert(func);
if (!path)
path = ".";
fd = openat(atfd, path, O_DIRECTORY|O_CLOEXEC);
if (fd < 0)
return -errno;
return recurse_dir(d, path, statx_mask, n_depth_max, flags, func, userdata);
return recurse_dir(fd, path, statx_mask, n_depth_max, flags, func, userdata);
}

View File

@ -67,9 +67,14 @@ typedef enum RecurseDirFlags {
RECURSE_DIR_INODE_FD = 1 << 4, /* passes an opened inode fd (O_DIRECTORY fd in case of dirs, O_PATH otherwise) */
} RecurseDirFlags;
struct dirent** readdir_all_free(struct dirent **array);
DEFINE_TRIVIAL_CLEANUP_FUNC(struct dirent **, readdir_all_free);
int readdir_all(DIR *d, RecurseDirFlags flags, struct dirent ***ret);
typedef struct DirectoryEntries {
size_t n_entries;
struct dirent** entries;
size_t buffer_size;
uint8_t buffer[] _alignas_(struct dirent);
} DirectoryEntries;
int recurse_dir(DIR *d, const char *path, unsigned statx_mask, unsigned n_depth_max, RecurseDirFlags flags, recurse_dir_func_t func, void *userdata);
int readdir_all(int dir_fd, RecurseDirFlags flags, DirectoryEntries **ret);
int recurse_dir(int dir_fd, const char *path, unsigned statx_mask, unsigned n_depth_max, RecurseDirFlags flags, recurse_dir_func_t func, void *userdata);
int recurse_dir_at(int atfd, const char *path, unsigned statx_mask, unsigned n_depth_max, RecurseDirFlags flags, recurse_dir_func_t func, void *userdata);

View File

@ -119,7 +119,7 @@ int sigprocmask_many(int how, sigset_t *old, ...) {
return 0;
}
static const char *const __signal_table[] = {
static const char *const static_signal_table[] = {
[SIGHUP] = "HUP",
[SIGINT] = "INT",
[SIGQUIT] = "QUIT",
@ -155,13 +155,13 @@ static const char *const __signal_table[] = {
[SIGSYS] = "SYS"
};
DEFINE_PRIVATE_STRING_TABLE_LOOKUP(__signal, int);
DEFINE_PRIVATE_STRING_TABLE_LOOKUP(static_signal, int);
const char *signal_to_string(int signo) {
static thread_local char buf[STRLEN("RTMIN+") + DECIMAL_STR_MAX(int)];
const char *name;
name = __signal_to_string(signo);
name = static_signal_to_string(signo);
if (name)
return name;
@ -190,7 +190,7 @@ int signal_from_string(const char *s) {
s += 3;
/* Check that the input is a signal name. */
signo = __signal_from_string(s);
signo = static_signal_from_string(s);
if (signo > 0)
return signo;

View File

@ -551,7 +551,7 @@ int getpeername_pretty(int fd, bool include_port, char **ret) {
return -errno;
if (sa.sa.sa_family == AF_UNIX) {
struct ucred ucred = {};
struct ucred ucred = UCRED_INVALID;
/* UNIX connection sockets are anonymous, so let's use
* PID/UID as pretty credentials instead */

View File

@ -327,3 +327,6 @@ static inline int socket_set_recvfragsize(int fd, int af, bool b) {
}
int socket_get_mtu(int fd, int af, size_t *ret);
/* an initializer for struct ucred that initialized all fields to the invalid value appropriate for each */
#define UCRED_INVALID { .pid = 0, .uid = UID_INVALID, .gid = GID_INVALID }

View File

@ -5,7 +5,7 @@
/* hey glibc, APIs with callbacks without a user pointer are so useless */
void *xbsearch_r(const void *key, const void *base, size_t nmemb, size_t size,
__compar_d_fn_t compar, void *arg) {
comparison_userdata_fn_t compar, void *arg) {
size_t l, u, idx;
const void *p;
int comparison;

View File

@ -5,14 +5,20 @@
#include "macro.h"
/* This is the same as glibc's internal __compar_d_fn_t type. glibc exports a public comparison_fn_t, for the
* external type __compar_fn_t, but doesn't do anything similar for __compar_d_fn_t. Let's hence do that
* ourselves, picking a name that is obvious, but likely enough to not clash with glibc's choice of naming if
* they should ever add one. */
typedef int (*comparison_userdata_fn_t)(const void *, const void *, void *);
void *xbsearch_r(const void *key, const void *base, size_t nmemb, size_t size,
__compar_d_fn_t compar, void *arg);
comparison_userdata_fn_t compar, void *arg);
#define typesafe_bsearch_r(k, b, n, func, userdata) \
({ \
const typeof(b[0]) *_k = k; \
int (*_func_)(const typeof(b[0])*, const typeof(b[0])*, typeof(userdata)) = func; \
xbsearch_r((const void*) _k, (b), (n), sizeof((b)[0]), (__compar_d_fn_t) _func_, userdata); \
xbsearch_r((const void*) _k, (b), (n), sizeof((b)[0]), (comparison_userdata_fn_t) _func_, userdata); \
})
/**
@ -20,7 +26,7 @@ void *xbsearch_r(const void *key, const void *base, size_t nmemb, size_t size,
* that only if nmemb > 0.
*/
static inline void* bsearch_safe(const void *key, const void *base,
size_t nmemb, size_t size, __compar_fn_t compar) {
size_t nmemb, size_t size, comparison_fn_t compar) {
if (nmemb <= 0)
return NULL;
@ -32,14 +38,14 @@ static inline void* bsearch_safe(const void *key, const void *base,
({ \
const typeof(b[0]) *_k = k; \
int (*_func_)(const typeof(b[0])*, const typeof(b[0])*) = func; \
bsearch_safe((const void*) _k, (b), (n), sizeof((b)[0]), (__compar_fn_t) _func_); \
bsearch_safe((const void*) _k, (b), (n), sizeof((b)[0]), (comparison_fn_t) _func_); \
})
/**
* Normal qsort requires base to be nonnull. Here were require
* that only if nmemb > 0.
*/
static inline void _qsort_safe(void *base, size_t nmemb, size_t size, __compar_fn_t compar) {
static inline void _qsort_safe(void *base, size_t nmemb, size_t size, comparison_fn_t compar) {
if (nmemb <= 1)
return;
@ -52,10 +58,10 @@ static inline void _qsort_safe(void *base, size_t nmemb, size_t size, __compar_f
#define typesafe_qsort(p, n, func) \
({ \
int (*_func_)(const typeof(p[0])*, const typeof(p[0])*) = func; \
_qsort_safe((p), (n), sizeof((p)[0]), (__compar_fn_t) _func_); \
_qsort_safe((p), (n), sizeof((p)[0]), (comparison_fn_t) _func_); \
})
static inline void qsort_r_safe(void *base, size_t nmemb, size_t size, __compar_d_fn_t compar, void *userdata) {
static inline void qsort_r_safe(void *base, size_t nmemb, size_t size, comparison_userdata_fn_t compar, void *userdata) {
if (nmemb <= 1)
return;
@ -66,7 +72,7 @@ static inline void qsort_r_safe(void *base, size_t nmemb, size_t size, __compar_
#define typesafe_qsort_r(p, n, func, userdata) \
({ \
int (*_func_)(const typeof(p[0])*, const typeof(p[0])*, typeof(userdata)) = func; \
qsort_r_safe((p), (n), sizeof((p)[0]), (__compar_d_fn_t) _func_, userdata); \
qsort_r_safe((p), (n), sizeof((p)[0]), (comparison_userdata_fn_t) _func_, userdata); \
})
int cmp_int(const int *a, const int *b);

View File

@ -55,9 +55,9 @@ typedef uint64_t __sd_bitwise be64_t;
#undef le64toh
#if __BYTE_ORDER == __LITTLE_ENDIAN
#define bswap_16_on_le(x) __bswap_16(x)
#define bswap_32_on_le(x) __bswap_32(x)
#define bswap_64_on_le(x) __bswap_64(x)
#define bswap_16_on_le(x) bswap_16(x)
#define bswap_32_on_le(x) bswap_32(x)
#define bswap_64_on_le(x) bswap_64(x)
#define bswap_16_on_be(x) (x)
#define bswap_32_on_be(x) (x)
#define bswap_64_on_be(x) (x)
@ -65,9 +65,9 @@ typedef uint64_t __sd_bitwise be64_t;
#define bswap_16_on_le(x) (x)
#define bswap_32_on_le(x) (x)
#define bswap_64_on_le(x) (x)
#define bswap_16_on_be(x) __bswap_16(x)
#define bswap_32_on_be(x) __bswap_32(x)
#define bswap_64_on_be(x) __bswap_64(x)
#define bswap_16_on_be(x) bswap_16(x)
#define bswap_32_on_be(x) bswap_32(x)
#define bswap_64_on_be(x) bswap_64(x)
#endif
static inline le16_t htole16(uint16_t value) { return (le16_t __sd_force) bswap_16_on_be(value); }

View File

@ -1085,3 +1085,14 @@ int is_this_me(const char *username) {
return uid == getuid();
}
const char *get_home_root(void) {
const char *e;
/* For debug purposes allow overriding where we look for home dirs */
e = secure_getenv("SYSTEMD_HOME_ROOT");
if (e && path_is_absolute(e) && path_is_normalized(e))
return e;
return "/home";
}

View File

@ -112,6 +112,8 @@ bool is_nologin_shell(const char *shell);
int is_this_me(const char *username);
const char *get_home_root(void);
/* A locked *and* invalid password for "struct spwd"'s .sp_pwdp and "struct passwd"'s .pw_passwd field */
#define PASSWORD_LOCKED_AND_INVALID "!*"

View File

@ -1099,6 +1099,9 @@ static int help(int argc, char *argv[], void *userdata) {
" list List boot loader entries\n"
" set-default ID Set default boot loader entry\n"
" set-oneshot ID Set default boot loader entry, for next boot only\n"
" set-timeout SECONDS Set the menu timeout\n"
" set-timeout-oneshot SECONDS\n"
" Set the menu timeout for the next boot only\n"
"\n%3$ssystemd-boot Commands:%4$s\n"
" install Install systemd-boot to the ESP and EFI variables\n"
" update Update systemd-boot in the ESP and EFI variables\n"
@ -1773,6 +1776,37 @@ static int verb_is_installed(int argc, char *argv[], void *userdata) {
return EXIT_SUCCESS;
}
static int parse_timeout(const char *arg1, char16_t **ret_timeout, size_t *ret_timeout_size) {
char utf8[DECIMAL_STR_MAX(usec_t)];
char16_t *encoded;
usec_t timeout;
int r;
assert(arg1);
assert(ret_timeout);
assert(ret_timeout_size);
if (streq(arg1, "menu-force"))
timeout = USEC_INFINITY;
else if (streq(arg1, "menu-hidden"))
timeout = 0;
else {
r = parse_time(arg1, &timeout, USEC_PER_SEC);
if (r < 0)
return log_error_errno(r, "Failed to parse timeout '%s': %m", arg1);
if (timeout != USEC_INFINITY && timeout > UINT32_MAX * USEC_PER_SEC)
log_warning("Timeout is too long and will be treated as 'menu-force' instead.");
}
xsprintf(utf8, USEC_FMT, MIN(timeout / USEC_PER_SEC, UINT32_MAX));
encoded = utf8_to_utf16(utf8, strlen(utf8));
if (!encoded)
return log_oom();
*ret_timeout = encoded;
*ret_timeout_size = char16_strlen(encoded) * 2 + 2;
return 0;
}
static int parse_loader_entry_target_arg(const char *arg1, char16_t **ret_target, size_t *ret_target_size) {
int r;
if (streq(arg1, "@current")) {
@ -1798,7 +1832,7 @@ static int parse_loader_entry_target_arg(const char *arg1, char16_t **ret_target
return 0;
}
static int verb_set_default(int argc, char *argv[], void *userdata) {
static int verb_set_efivar(int argc, char *argv[], void *userdata) {
int r;
if (!is_efi_boot())
@ -1821,24 +1855,39 @@ static int verb_set_default(int argc, char *argv[], void *userdata) {
if (!arg_touch_variables)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"'%s' operation cannot be combined with --touch-variables=no.",
"'%s' operation cannot be combined with --no-variables.",
argv[0]);
const char *variable = streq(argv[0], "set-default") ?
EFI_LOADER_VARIABLE(LoaderEntryDefault) : EFI_LOADER_VARIABLE(LoaderEntryOneShot);
const char *variable;
int (* arg_parser)(const char *, char16_t **, size_t *);
if (streq(argv[0], "set-default")) {
variable = EFI_LOADER_VARIABLE(LoaderEntryDefault);
arg_parser = parse_loader_entry_target_arg;
} else if (streq(argv[0], "set-oneshot")) {
variable = EFI_LOADER_VARIABLE(LoaderEntryOneShot);
arg_parser = parse_loader_entry_target_arg;
} else if (streq(argv[0], "set-timeout")) {
variable = EFI_LOADER_VARIABLE(LoaderConfigTimeout);
arg_parser = parse_timeout;
} else if (streq(argv[0], "set-timeout-oneshot")) {
variable = EFI_LOADER_VARIABLE(LoaderConfigTimeoutOneShot);
arg_parser = parse_timeout;
} else
assert_not_reached();
if (isempty(argv[1])) {
r = efi_set_variable(variable, NULL, 0);
if (r < 0 && r != -ENOENT)
return log_error_errno(r, "Failed to remove EFI variable '%s': %m", variable);
} else {
_cleanup_free_ char16_t *target = NULL;
size_t target_size = 0;
_cleanup_free_ char16_t *value = NULL;
size_t value_size = 0;
r = parse_loader_entry_target_arg(argv[1], &target, &target_size);
r = arg_parser(argv[1], &value, &value_size);
if (r < 0)
return r;
r = efi_set_variable(variable, target, target_size);
r = efi_set_variable(variable, value, value_size);
if (r < 0)
return log_error_errno(r, "Failed to update EFI variable '%s': %m", variable);
}
@ -1943,8 +1992,10 @@ static int bootctl_main(int argc, char *argv[]) {
{ "remove", VERB_ANY, 1, 0, verb_remove },
{ "is-installed", VERB_ANY, 1, 0, verb_is_installed },
{ "list", VERB_ANY, 1, 0, verb_list },
{ "set-default", 2, 2, 0, verb_set_default },
{ "set-oneshot", 2, 2, 0, verb_set_default },
{ "set-default", 2, 2, 0, verb_set_efivar },
{ "set-oneshot", 2, 2, 0, verb_set_efivar },
{ "set-timeout", 2, 2, 0, verb_set_efivar },
{ "set-timeout-oneshot", 2, 2, 0, verb_set_efivar },
{ "random-seed", VERB_ANY, 1, 0, verb_random_seed },
{ "systemd-efi-options", VERB_ANY, 2, 0, verb_systemd_efi_options },
{ "reboot-to-firmware", VERB_ANY, 2, 0, verb_reboot_to_firmware },

View File

@ -25,7 +25,7 @@
#define TEXT_ATTR_SWAP(c) EFI_TEXT_ATTR(((c) & 0b11110000) >> 4, (c) & 0b1111)
/* magic string to find in the binary image */
static const char _used_ _section_(".sdmagic") magic[] = "#### LoaderInfo: systemd-boot " GIT_VERSION " ####";
_used_ _section_(".sdmagic") static const char magic[] = "#### LoaderInfo: systemd-boot " GIT_VERSION " ####";
enum loader_type {
LOADER_UNDEFINED,
@ -60,9 +60,9 @@ typedef struct {
UINTN entry_count;
INTN idx_default;
INTN idx_default_efivar;
UINTN timeout_sec;
UINTN timeout_sec_config;
INTN timeout_sec_efivar;
UINT32 timeout_sec; /* Actual timeout used (efi_main() override > efivar > config). */
UINT32 timeout_sec_config;
UINT32 timeout_sec_efivar;
CHAR16 *entry_default_pattern;
CHAR16 *entry_oneshot;
CHAR16 *options_edit;
@ -75,6 +75,18 @@ typedef struct {
RandomSeedMode random_seed_mode;
} Config;
/* These values have been chosen so that the transitions the user sees could
* employ unsigned over-/underflow like this:
* efivar unset force menu no timeout/skip menu 1 s 2 s */
enum {
TIMEOUT_MIN = 1,
TIMEOUT_MAX = UINT32_MAX - 2U,
TIMEOUT_UNSET = UINT32_MAX - 1U,
TIMEOUT_MENU_FORCE = UINT32_MAX,
TIMEOUT_MENU_HIDDEN = 0,
TIMEOUT_TYPE_MAX = UINT32_MAX,
};
static VOID cursor_left(UINTN *cursor, UINTN *first) {
assert(cursor);
assert(first);
@ -366,6 +378,38 @@ static UINTN entry_lookup_key(Config *config, UINTN start, CHAR16 key) {
return -1;
}
static CHAR16 *update_timeout_efivar(UINT32 *t, BOOLEAN inc) {
assert(t);
switch (*t) {
case TIMEOUT_MAX:
*t = inc ? TIMEOUT_MAX : (*t - 1);
break;
case TIMEOUT_UNSET:
*t = inc ? TIMEOUT_MENU_FORCE : TIMEOUT_UNSET;
break;
case TIMEOUT_MENU_FORCE:
*t = inc ? TIMEOUT_MENU_HIDDEN : TIMEOUT_UNSET;
break;
case TIMEOUT_MENU_HIDDEN:
*t = inc ? TIMEOUT_MIN : TIMEOUT_MENU_FORCE;
break;
default:
*t += inc ? 1 : -1;
}
switch (*t) {
case TIMEOUT_UNSET:
return StrDuplicate(L"Menu timeout defined by configuration file.");
case TIMEOUT_MENU_FORCE:
return StrDuplicate(L"Timeout disabled, menu will always be shown.");
case TIMEOUT_MENU_HIDDEN:
return StrDuplicate(L"Menu disabled. Hold down key at bootup to show menu.");
default:
return PoolPrint(L"Menu timeout set to %u s.", *t);
}
}
static VOID print_status(Config *config, CHAR16 *loaded_image_path) {
UINT64 key;
UINTN timeout;
@ -400,10 +444,11 @@ static VOID print_status(Config *config, CHAR16 *loaded_image_path) {
Print(L"\n--- press key ---\n\n");
console_key_read(&key, 0);
Print(L"timeout: %u\n", config->timeout_sec);
if (config->timeout_sec_efivar >= 0)
Print(L"timeout (EFI var): %d\n", config->timeout_sec_efivar);
Print(L"timeout (config): %u\n", config->timeout_sec_config);
Print(L"timeout: %u s\n", config->timeout_sec);
if (config->timeout_sec_efivar != TIMEOUT_UNSET)
Print(L"timeout (EFI var): %u s\n", config->timeout_sec_efivar);
if (config->timeout_sec_config != TIMEOUT_UNSET)
Print(L"timeout (config): %u s\n", config->timeout_sec_config);
if (config->entry_default_pattern)
Print(L"default pattern: '%s'\n", config->entry_default_pattern);
Print(L"editor: %s\n", yes_no(config->editor));
@ -519,7 +564,8 @@ static BOOLEAN menu_run(
UINTN x_max, y_max;
CHAR16 **lines = NULL;
_cleanup_freepool_ CHAR16 *clearline = NULL, *status = NULL;
UINTN timeout_remain = config->timeout_sec;
UINT32 timeout_efivar_saved = config->timeout_sec_efivar;
UINT32 timeout_remain = config->timeout_sec == TIMEOUT_MENU_FORCE ? 0 : config->timeout_sec;
INT16 idx;
BOOLEAN exit = FALSE, run = TRUE;
INT64 console_mode_initial = ST->ConOut->Mode->Mode, console_mode_efivar_saved = config->console_mode_efivar;
@ -640,7 +686,7 @@ static BOOLEAN menu_run(
if (timeout_remain > 0) {
FreePool(status);
status = PoolPrint(L"Boot in %d s.", timeout_remain);
status = PoolPrint(L"Boot in %u s.", timeout_remain);
}
/* print status at last line of screen */
@ -768,44 +814,12 @@ static BOOLEAN menu_run(
case KEYPRESS(0, 0, '-'):
case KEYPRESS(0, 0, 'T'):
if (config->timeout_sec_efivar > 0) {
config->timeout_sec_efivar--;
efivar_set_uint_string(
LOADER_GUID,
L"LoaderConfigTimeout",
config->timeout_sec_efivar,
EFI_VARIABLE_NON_VOLATILE);
if (config->timeout_sec_efivar > 0)
status = PoolPrint(L"Menu timeout set to %d s.", config->timeout_sec_efivar);
else
status = StrDuplicate(L"Menu disabled. Hold down key at bootup to show menu.");
} else if (config->timeout_sec_efivar <= 0){
config->timeout_sec_efivar = -1;
efivar_set(
LOADER_GUID, L"LoaderConfigTimeout", NULL, EFI_VARIABLE_NON_VOLATILE);
if (config->timeout_sec_config > 0)
status = PoolPrint(L"Menu timeout of %d s is defined by configuration file.",
config->timeout_sec_config);
else
status = StrDuplicate(L"Menu disabled. Hold down key at bootup to show menu.");
}
status = update_timeout_efivar(&config->timeout_sec_efivar, FALSE);
break;
case KEYPRESS(0, 0, '+'):
case KEYPRESS(0, 0, 't'):
if (config->timeout_sec_efivar == -1 && config->timeout_sec_config == 0)
config->timeout_sec_efivar++;
config->timeout_sec_efivar++;
efivar_set_uint_string(
LOADER_GUID,
L"LoaderConfigTimeout",
config->timeout_sec_efivar,
EFI_VARIABLE_NON_VOLATILE);
if (config->timeout_sec_efivar > 0)
status = PoolPrint(L"Menu timeout set to %d s.",
config->timeout_sec_efivar);
else
status = StrDuplicate(L"Menu disabled. Hold down key at bootup to show menu.");
status = update_timeout_efivar(&config->timeout_sec_efivar, TRUE);
break;
case KEYPRESS(0, 0, 'e'):
@ -888,9 +902,8 @@ static BOOLEAN menu_run(
*chosen_entry = config->entries[idx_highlight];
/* The user is likely to cycle through several modes before
* deciding to keep one. Therefore, we update the EFI var after
* we left the menu to reduce nvram writes. */
/* Update EFI vars after we left the menu to reduce NVRAM writes. */
if (console_mode_efivar_saved != config->console_mode_efivar) {
if (config->console_mode_efivar == CONSOLE_MODE_KEEP)
efivar_set(LOADER_GUID, L"LoaderConfigConsoleMode", NULL, EFI_VARIABLE_NON_VOLATILE);
@ -899,6 +912,14 @@ static BOOLEAN menu_run(
config->console_mode_efivar, EFI_VARIABLE_NON_VOLATILE);
}
if (timeout_efivar_saved != config->timeout_sec_efivar) {
if (config->timeout_sec_efivar == TIMEOUT_UNSET)
efivar_set(LOADER_GUID, L"LoaderConfigTimeout", NULL, EFI_VARIABLE_NON_VOLATILE);
else
efivar_set_uint_string(LOADER_GUID, L"LoaderConfigTimeout",
config->timeout_sec_efivar, EFI_VARIABLE_NON_VOLATILE);
}
for (UINTN i = 0; i < config->entry_count; i++)
FreePool(lines[i]);
FreePool(lines);
@ -1023,10 +1044,16 @@ static VOID config_defaults_load_from_file(Config *config, CHAR8 *content) {
while ((line = line_get_key_value(content, (CHAR8 *)" \t", &pos, &key, &value))) {
if (strcmpa((CHAR8 *)"timeout", key) == 0) {
if (strcmpa((CHAR8*) "menu-force", value) == 0)
config->timeout_sec_config = TIMEOUT_MENU_FORCE;
else if (strcmpa((CHAR8*) "menu-hidden", value) == 0)
config->timeout_sec_config = TIMEOUT_MENU_HIDDEN;
else {
_cleanup_freepool_ CHAR16 *s = NULL;
s = stra_to_str(value);
config->timeout_sec_config = Atoi(s);
config->timeout_sec_config = MIN(Atoi(s), TIMEOUT_TYPE_MAX);
}
config->timeout_sec = config->timeout_sec_config;
continue;
}
@ -1439,6 +1466,8 @@ static VOID config_load_defaults(Config *config, EFI_FILE *root_dir) {
.idx_default_efivar = -1,
.console_mode = CONSOLE_MODE_KEEP,
.console_mode_efivar = CONSOLE_MODE_KEEP,
.timeout_sec_config = TIMEOUT_UNSET,
.timeout_sec_efivar = TIMEOUT_UNSET,
};
err = file_read(root_dir, L"\\loader\\loader.conf", 0, 0, &content, NULL);
@ -1447,17 +1476,16 @@ static VOID config_load_defaults(Config *config, EFI_FILE *root_dir) {
err = efivar_get_uint_string(LOADER_GUID, L"LoaderConfigTimeout", &value);
if (!EFI_ERROR(err)) {
config->timeout_sec_efivar = value > INTN_MAX ? INTN_MAX : value;
config->timeout_sec = value;
} else
config->timeout_sec_efivar = -1;
config->timeout_sec_efivar = MIN(value, TIMEOUT_TYPE_MAX);
config->timeout_sec = config->timeout_sec_efivar;
}
err = efivar_get_uint_string(LOADER_GUID, L"LoaderConfigTimeoutOneShot", &value);
if (!EFI_ERROR(err)) {
/* Unset variable now, after all it's "one shot". */
(void) efivar_set(LOADER_GUID, L"LoaderConfigTimeoutOneShot", NULL, EFI_VARIABLE_NON_VOLATILE);
config->timeout_sec = value;
config->timeout_sec = MIN(value, TIMEOUT_TYPE_MAX);
config->force_menu = TRUE; /* force the menu when this is set */
}

View File

@ -14,7 +14,7 @@
#include "util.h"
/* magic string to find in the binary image */
static const char __attribute__((used)) magic[] = "#### LoaderInfo: systemd-stub " GIT_VERSION " ####";
_used_ _section_(".sdmagic") static const char magic[] = "#### LoaderInfo: systemd-stub " GIT_VERSION " ####";
static EFI_STATUS combine_initrd(
EFI_PHYSICAL_ADDRESS initrd_base, UINTN initrd_size,

View File

@ -126,7 +126,7 @@ _printf_(2, 3) static int log_callback(int type, const char *fmt, ...) {
DISABLE_WARNING_FORMAT_NONLITERAL;
log_internalv(LOG_AUTH | callback_type_to_priority(type),
0, PROJECT_FILE, __LINE__, __FUNCTION__,
0, PROJECT_FILE, __LINE__, __func__,
fmt2, ap);
REENABLE_WARNING;
va_end(ap);

View File

@ -2131,6 +2131,7 @@ static int help(int argc, char *argv[], void *userdata) {
" --storage=STORAGE Storage type to use (luks, fscrypt, directory,\n"
" subvolume, cifs)\n"
" --image-path=PATH Path to image file/directory\n"
" --drop-caches=BOOL Whether to automatically drop caches on logout\n"
"\n%4$sLUKS Storage User Record Properties:%5$s\n"
" --fs-type=TYPE File system type to use in case of luks\n"
" storage (btrfs, ext4, xfs)\n"
@ -2245,6 +2246,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_RECOVERY_KEY,
ARG_AND_RESIZE,
ARG_AND_CHANGE_PASSWORD,
ARG_DROP_CACHES,
};
static const struct option options[] = {
@ -2327,6 +2329,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "recovery-key", required_argument, NULL, ARG_RECOVERY_KEY },
{ "and-resize", required_argument, NULL, ARG_AND_RESIZE },
{ "and-change-password", required_argument, NULL, ARG_AND_CHANGE_PASSWORD },
{ "drop-caches", required_argument, NULL, ARG_DROP_CACHES },
{}
};
@ -3450,6 +3453,26 @@ static int parse_argv(int argc, char *argv[]) {
arg_and_change_password = true;
break;
case ARG_DROP_CACHES: {
bool drop_caches;
if (isempty(optarg)) {
r = drop_from_identity("dropCaches");
if (r < 0)
return r;
}
r = parse_boolean_argument("--drop-caches=", optarg, &drop_caches);
if (r < 0)
return r;
r = json_variant_set_field_boolean(&arg_identity_extra, "dropCaches", r);
if (r < 0)
return log_error_errno(r, "Failed to set drop caches field: %m");
break;
}
case '?':
return -EINVAL;

View File

@ -38,6 +38,9 @@
#include "user-record.h"
#include "user-util.h"
/* Retry to deactivate home directories again and again every 15s until it works */
#define RETRY_DEACTIVATE_USEC (15U * USEC_PER_SEC)
#define HOME_USERS_MAX 500
#define PENDING_OPERATIONS_MAX 100
@ -130,6 +133,8 @@ int home_new(Manager *m, UserRecord *hr, const char *sysfs, Home **ret) {
.worker_stdout_fd = -1,
.sysfs = TAKE_PTR(ns),
.signed_locally = -1,
.pin_fd = -1,
.luks_lock_fd = -1,
};
r = hashmap_put(m->homes_by_name, home->user_name, home);
@ -203,6 +208,11 @@ Home *home_free(Home *h) {
h->current_operation = operation_unref(h->current_operation);
safe_close(h->pin_fd);
safe_close(h->luks_lock_fd);
h->retry_deactivate_event_source = sd_event_source_disable_unref(h->retry_deactivate_event_source);
return mfree(h);
}
@ -317,6 +327,141 @@ int home_unlink_record(Home *h) {
return 0;
}
static void home_unpin(Home *h) {
assert(h);
if (h->pin_fd < 0)
return;
h->pin_fd = safe_close(h->pin_fd);
log_debug("Successfully closed pin fd on home for %s.", h->user_name);
}
static void home_pin(Home *h) {
const char *path;
assert(h);
if (h->pin_fd >= 0) /* Already pinned? */
return;
path = user_record_home_directory(h->record);
if (!path) {
log_warning("No home directory path to pin for %s, ignoring.", h->user_name);
return;
}
h->pin_fd = open(path, O_RDONLY|O_DIRECTORY|O_CLOEXEC);
if (h->pin_fd < 0) {
log_warning_errno(errno, "Couldn't open home directory '%s' for pinning, ignoring: %m", path);
return;
}
log_debug("Successfully pinned home directory '%s'.", path);
}
static void home_update_pin_fd(Home *h, HomeState state) {
assert(h);
if (state < 0)
state = home_get_state(h);
return HOME_STATE_SHALL_PIN(state) ? home_pin(h) : home_unpin(h);
}
static void home_maybe_close_luks_lock_fd(Home *h, HomeState state) {
assert(h);
if (h->luks_lock_fd < 0)
return;
if (state < 0)
state = home_get_state(h);
/* Keep the lock as long as the home dir is active or has some operation going */
if (HOME_STATE_IS_EXECUTING_OPERATION(state) || HOME_STATE_IS_ACTIVE(state) || state == HOME_LOCKED)
return;
h->luks_lock_fd = safe_close(h->luks_lock_fd);
log_debug("Successfully closed LUKS backing file lock for %s.", h->user_name);
}
static void home_maybe_stop_retry_deactivate(Home *h, HomeState state) {
assert(h);
/* Free the deactivation retry event source if we won't need it anymore. Specifically, we'll free the
* event source whenever the home directory is already deactivated (and we thus where successful) or
* if we start executing an operation that indicates that the home directory is going to be used or
* operated on again. Also, if the home is referenced again stop the timer */
if (HOME_STATE_MAY_RETRY_DEACTIVATE(state) &&
!h->ref_event_source_dont_suspend &&
!h->ref_event_source_please_suspend)
return;
h->retry_deactivate_event_source = sd_event_source_disable_unref(h->retry_deactivate_event_source);
}
static int home_deactivate_internal(Home *h, bool force, sd_bus_error *error);
static void home_start_retry_deactivate(Home *h);
static int home_on_retry_deactivate(sd_event_source *s, uint64_t usec, void *userdata) {
Home *h = userdata;
HomeState state;
assert(s);
assert(h);
/* 15s after the last attempt to deactivate the home directory passed. Let's try it one more time. */
h->retry_deactivate_event_source = sd_event_source_disable_unref(h->retry_deactivate_event_source);
state = home_get_state(h);
if (!HOME_STATE_MAY_RETRY_DEACTIVATE(state))
return 0;
if (IN_SET(state, HOME_ACTIVE, HOME_LINGERING)) {
log_info("Again trying to deactivate home directory.");
/* If we are not executing any operation, let's start deactivating now. Note that this will
* restart our timer again, we are gonna be called again if this doesn't work. */
(void) home_deactivate_internal(h, /* force= */ false, NULL);
} else
/* if we are executing an operation (specifically, area already running a deactivation
* operation), then simply reque the timer, so that we retry again. */
home_start_retry_deactivate(h);
return 0;
}
static void home_start_retry_deactivate(Home *h) {
int r;
assert(h);
assert(h->manager);
/* Alrady allocated? */
if (h->retry_deactivate_event_source)
return;
/* If the home directory is being used now don't start the timer */
if (h->ref_event_source_dont_suspend || h->ref_event_source_please_suspend)
return;
r = sd_event_add_time_relative(
h->manager->event,
&h->retry_deactivate_event_source,
CLOCK_MONOTONIC,
RETRY_DEACTIVATE_USEC,
1*USEC_PER_MINUTE,
home_on_retry_deactivate,
h);
if (r < 0)
return (void) log_warning_errno(r, "Failed to install retry-deactivate event source, ignoring: %m");
(void) sd_event_source_set_description(h->retry_deactivate_event_source, "retry-deactivate");
}
static void home_set_state(Home *h, HomeState state) {
HomeState old_state, new_state;
@ -331,6 +476,10 @@ static void home_set_state(Home *h, HomeState state) {
home_state_to_string(old_state),
home_state_to_string(new_state));
home_update_pin_fd(h, new_state);
home_maybe_close_luks_lock_fd(h, new_state);
home_maybe_stop_retry_deactivate(h, new_state);
if (HOME_STATE_IS_EXECUTING_OPERATION(old_state) && !HOME_STATE_IS_EXECUTING_OPERATION(new_state)) {
/* If we just finished executing some operation, process the queue of pending operations. And
* enqueue it for GC too. */
@ -483,6 +632,8 @@ static int convert_worker_errno(Home *h, int e, sd_bus_error *error) {
return sd_bus_error_setf(error, BUS_ERROR_NO_DISK_SPACE, "Not enough disk space for home %s", h->user_name);
case -EKEYREVOKED:
return sd_bus_error_setf(error, BUS_ERROR_HOME_CANT_AUTHENTICATE, "Home %s has no password or other authentication mechanism defined.", h->user_name);
case -EADDRINUSE:
return sd_bus_error_setf(error, BUS_ERROR_HOME_IN_USE, "Home %s is currently being used elsewhere.", h->user_name);
}
return 0;
@ -1029,6 +1180,12 @@ static int home_start_work(Home *h, const char *verb, UserRecord *hr, UserRecord
_exit(EXIT_FAILURE);
}
/* If we haven't locked the device yet, ask for a lock to be taken and be passed back to us via sd_notify(). */
if (setenv("SYSTEMD_LUKS_LOCK", one_zero(h->luks_lock_fd < 0), 1) < 0) {
log_error_errno(errno, "Failed to set $SYSTEMD_LUKS_LOCK: %m");
_exit(EXIT_FAILURE);
}
if (h->manager->default_storage >= 0)
if (setenv("SYSTEMD_HOME_DEFAULT_STORAGE", user_storage_to_string(h->manager->default_storage), 1) < 0) {
log_error_errno(errno, "Failed to set $SYSTEMD_HOME_DEFAULT_STORAGE: %m");
@ -1149,6 +1306,7 @@ int home_fixate(Home *h, UserRecord *secret, sd_bus_error *error) {
case HOME_INACTIVE:
case HOME_DIRTY:
case HOME_ACTIVE:
case HOME_LINGERING:
case HOME_LOCKED:
return sd_bus_error_setf(error, BUS_ERROR_HOME_ALREADY_FIXATED, "Home %s is already fixated.", h->user_name);
case HOME_UNFIXATED:
@ -1190,6 +1348,11 @@ int home_activate(Home *h, UserRecord *secret, sd_bus_error *error) {
return sd_bus_error_setf(error, BUS_ERROR_HOME_ABSENT, "Home %s is currently missing or not plugged in.", h->user_name);
case HOME_ACTIVE:
return sd_bus_error_setf(error, BUS_ERROR_HOME_ALREADY_ACTIVE, "Home %s is already active.", h->user_name);
case HOME_LINGERING:
/* If we are lingering, i.e. active but are supposed to be deactivated, then cancel this
* timer if the user explicitly asks us to be active */
h->retry_deactivate_event_source = sd_event_source_disable_unref(h->retry_deactivate_event_source);
return 0;
case HOME_LOCKED:
return sd_bus_error_setf(error, BUS_ERROR_HOME_LOCKED, "Home %s is currently locked.", h->user_name);
case HOME_INACTIVE:
@ -1236,6 +1399,7 @@ int home_authenticate(Home *h, UserRecord *secret, sd_bus_error *error) {
case HOME_INACTIVE:
case HOME_DIRTY:
case HOME_ACTIVE:
case HOME_LINGERING:
break;
default:
return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "An operation on home %s is currently being executed.", h->user_name);
@ -1245,7 +1409,7 @@ int home_authenticate(Home *h, UserRecord *secret, sd_bus_error *error) {
if (r < 0)
return r;
return home_authenticate_internal(h, secret, state == HOME_ACTIVE ? HOME_AUTHENTICATING_WHILE_ACTIVE : HOME_AUTHENTICATING, error);
return home_authenticate_internal(h, secret, HOME_STATE_IS_ACTIVE(state) ? HOME_AUTHENTICATING_WHILE_ACTIVE : HOME_AUTHENTICATING, error);
}
static int home_deactivate_internal(Home *h, bool force, sd_bus_error *error) {
@ -1253,12 +1417,22 @@ static int home_deactivate_internal(Home *h, bool force, sd_bus_error *error) {
assert(h);
home_unpin(h); /* unpin so that we can deactivate */
r = home_start_work(h, force ? "deactivate-force" : "deactivate", h->record, NULL);
if (r < 0)
return r;
/* Operation failed before it even started, reacquire pin fd, if state still dictates so */
home_update_pin_fd(h, _HOME_STATE_INVALID);
else {
home_set_state(h, HOME_DEACTIVATING);
return 0;
r = 0;
}
/* Let's start a timer to retry deactivation in 15. We'll stop the timer once we manage to deactivate
* the home directory again, or we we start any other operation. */
home_start_retry_deactivate(h);
return r;
}
int home_deactivate(Home *h, bool force, sd_bus_error *error) {
@ -1273,6 +1447,7 @@ int home_deactivate(Home *h, bool force, sd_bus_error *error) {
case HOME_LOCKED:
return sd_bus_error_setf(error, BUS_ERROR_HOME_LOCKED, "Home %s is currently locked.", h->user_name);
case HOME_ACTIVE:
case HOME_LINGERING:
break;
default:
return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "An operation on home %s is currently being executed.", h->user_name);
@ -1309,6 +1484,7 @@ int home_create(Home *h, UserRecord *secret, sd_bus_error *error) {
case HOME_ABSENT:
break;
case HOME_ACTIVE:
case HOME_LINGERING:
case HOME_LOCKED:
default:
return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "Home %s is currently being used, or an operation on home %s is currently being executed.", h->user_name, h->user_name);
@ -1347,6 +1523,7 @@ int home_remove(Home *h, sd_bus_error *error) {
case HOME_DIRTY:
break;
case HOME_ACTIVE:
case HOME_LINGERING:
default:
return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "Home %s is currently being used, or an operation on home %s is currently being executed.", h->user_name, h->user_name);
}
@ -1485,6 +1662,7 @@ int home_update(Home *h, UserRecord *hr, sd_bus_error *error) {
case HOME_INACTIVE:
case HOME_DIRTY:
case HOME_ACTIVE:
case HOME_LINGERING:
break;
default:
return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "An operation on home %s is currently being executed.", h->user_name);
@ -1498,7 +1676,7 @@ int home_update(Home *h, UserRecord *hr, sd_bus_error *error) {
if (r < 0)
return r;
home_set_state(h, state == HOME_ACTIVE ? HOME_UPDATING_WHILE_ACTIVE : HOME_UPDATING);
home_set_state(h, HOME_STATE_IS_ACTIVE(state) ? HOME_UPDATING_WHILE_ACTIVE : HOME_UPDATING);
return 0;
}
@ -1520,6 +1698,7 @@ int home_resize(Home *h, uint64_t disk_size, UserRecord *secret, sd_bus_error *e
case HOME_INACTIVE:
case HOME_DIRTY:
case HOME_ACTIVE:
case HOME_LINGERING:
break;
default:
return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "An operation on home %s is currently being executed.", h->user_name);
@ -1568,7 +1747,7 @@ int home_resize(Home *h, uint64_t disk_size, UserRecord *secret, sd_bus_error *e
if (r < 0)
return r;
home_set_state(h, state == HOME_ACTIVE ? HOME_RESIZING_WHILE_ACTIVE : HOME_RESIZING);
home_set_state(h, HOME_STATE_IS_ACTIVE(state) ? HOME_RESIZING_WHILE_ACTIVE : HOME_RESIZING);
return 0;
}
@ -1616,6 +1795,7 @@ int home_passwd(Home *h,
case HOME_INACTIVE:
case HOME_DIRTY:
case HOME_ACTIVE:
case HOME_LINGERING:
break;
default:
return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "An operation on home %s is currently being executed.", h->user_name);
@ -1681,7 +1861,7 @@ int home_passwd(Home *h,
if (r < 0)
return r;
home_set_state(h, state == HOME_ACTIVE ? HOME_PASSWD_WHILE_ACTIVE : HOME_PASSWD);
home_set_state(h, HOME_STATE_IS_ACTIVE(state) ? HOME_PASSWD_WHILE_ACTIVE : HOME_PASSWD);
return 0;
}
@ -1700,6 +1880,7 @@ int home_unregister(Home *h, sd_bus_error *error) {
case HOME_DIRTY:
break;
case HOME_ACTIVE:
case HOME_LINGERING:
default:
return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "Home %s is currently being used, or an operation on home %s is currently being executed.", h->user_name, h->user_name);
}
@ -1727,6 +1908,7 @@ int home_lock(Home *h, sd_bus_error *error) {
case HOME_LOCKED:
return sd_bus_error_setf(error, BUS_ERROR_HOME_LOCKED, "Home %s is already locked.", h->user_name);
case HOME_ACTIVE:
case HOME_LINGERING:
break;
default:
return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "An operation on home %s is currently being executed.", h->user_name);
@ -1767,6 +1949,7 @@ int home_unlock(Home *h, UserRecord *secret, sd_bus_error *error) {
case HOME_ABSENT:
case HOME_INACTIVE:
case HOME_ACTIVE:
case HOME_LINGERING:
case HOME_DIRTY:
return sd_bus_error_setf(error, BUS_ERROR_HOME_NOT_LOCKED, "Home %s is not locked.", h->user_name);
case HOME_LOCKED:
@ -1789,7 +1972,7 @@ HomeState home_get_state(Home *h) {
/* Otherwise, let's see if the home directory is mounted. If so, we assume for sure the home
* directory is active */
if (user_record_test_home_directory(h->record) == USER_TEST_MOUNTED)
return HOME_ACTIVE;
return h->retry_deactivate_event_source ? HOME_LINGERING : HOME_ACTIVE;
/* And if we see the image being gone, we report this as absent */
r = user_record_test_image_path(h->record);
@ -1802,28 +1985,49 @@ HomeState home_get_state(Home *h) {
return HOME_INACTIVE;
}
void home_process_notify(Home *h, char **l) {
void home_process_notify(Home *h, char **l, int fd) {
_cleanup_close_ int taken_fd = TAKE_FD(fd);
const char *e;
int error;
int r;
assert(h);
e = strv_env_get(l, "ERRNO");
if (!e) {
log_debug("Got notify message lacking ERRNO= field, ignoring.");
e = strv_env_get(l, "SYSTEMD_LUKS_LOCK_FD");
if (e) {
r = parse_boolean(e);
if (r < 0)
return (void) log_debug_errno(r, "Failed to parse SYSTEMD_LUKS_LOCK_FD value: %m");
if (r > 0) {
if (taken_fd < 0)
return (void) log_debug("Got notify message with SYSTEMD_LUKS_LOCK_FD=1 but no fd passed, ignoring: %m");
safe_close(h->luks_lock_fd);
h->luks_lock_fd = TAKE_FD(taken_fd);
log_debug("Successfully acquired LUKS lock fd from worker.");
/* Immediately check if we actually want to keep it */
home_maybe_close_luks_lock_fd(h, _HOME_STATE_INVALID);
} else {
if (taken_fd >= 0)
return (void) log_debug("Got notify message with SYSTEMD_LUKS_LOCK_FD=0 but fd passed, ignoring: %m");
h->luks_lock_fd = safe_close(h->luks_lock_fd);
}
return;
}
e = strv_env_get(l, "ERRNO");
if (!e)
return (void) log_debug("Got notify message lacking both ERRNO= and SYSTEMD_LUKS_LOCK_FD= field, ignoring.");
r = safe_atoi(e, &error);
if (r < 0) {
log_debug_errno(r, "Failed to parse received error number, ignoring: %s", e);
return;
}
if (error <= 0) {
log_debug("Error number is out of range: %i", error);
return;
}
if (r < 0)
return (void) log_debug_errno(r, "Failed to parse received error number, ignoring: %s", e);
if (error <= 0)
return (void) log_debug("Error number is out of range: %i", error);
h->worker_error_code = error;
}
@ -2372,6 +2576,7 @@ static int home_dispatch_acquire(Home *h, Operation *o) {
break;
case HOME_ACTIVE:
case HOME_LINGERING:
for_state = HOME_AUTHENTICATING_FOR_ACQUIRE;
call = home_authenticate_internal;
break;
@ -2426,6 +2631,7 @@ static int home_dispatch_release(Home *h, Operation *o) {
break;
case HOME_ACTIVE:
case HOME_LINGERING:
r = home_deactivate_internal(h, false, &error);
break;
@ -2470,6 +2676,7 @@ static int home_dispatch_lock_all(Home *h, Operation *o) {
break;
case HOME_ACTIVE:
case HOME_LINGERING:
log_info("Locking home %s.", h->user_name);
r = home_lock(h, &error);
break;
@ -2514,6 +2721,7 @@ static int home_dispatch_deactivate_all(Home *h, Operation *o) {
break;
case HOME_ACTIVE:
case HOME_LINGERING:
log_info("Deactivating home %s.", h->user_name);
r = home_deactivate_internal(h, false, &error);
break;
@ -2559,6 +2767,7 @@ static int home_dispatch_pipe_eof(Home *h, Operation *o) {
break;
case HOME_ACTIVE:
case HOME_LINGERING:
r = home_deactivate_internal(h, false, &error);
if (r < 0)
log_warning_errno(r, "Failed to deactivate %s, ignoring: %s", h->user_name, bus_error_message(&error, r));
@ -2600,6 +2809,7 @@ static int home_dispatch_deactivate_force(Home *h, Operation *o) {
case HOME_ACTIVE:
case HOME_LOCKED:
case HOME_LINGERING:
r = home_deactivate_internal(h, true, &error);
if (r < 0)
log_warning_errno(r, "Failed to forcibly deactivate %s, ignoring: %s", h->user_name, bus_error_message(&error, r));
@ -2820,6 +3030,7 @@ static const char* const home_state_table[_HOME_STATE_MAX] = {
[HOME_ACTIVATING_FOR_ACQUIRE] = "activating-for-acquire",
[HOME_DEACTIVATING] = "deactivating",
[HOME_ACTIVE] = "active",
[HOME_LINGERING] = "lingering",
[HOME_LOCKING] = "locking",
[HOME_LOCKED] = "locked",
[HOME_UNLOCKING] = "unlocking",

View File

@ -21,6 +21,7 @@ typedef enum HomeState {
HOME_ACTIVATING_FOR_ACQUIRE, /* activating because Acquire() was called */
HOME_DEACTIVATING,
HOME_ACTIVE, /* logged in right now */
HOME_LINGERING, /* not logged in anymore, but we didn't manage to deactivate (because some process keeps it busy?) but we'll keep trying */
HOME_LOCKING,
HOME_LOCKED,
HOME_UNLOCKING,
@ -43,6 +44,7 @@ typedef enum HomeState {
static inline bool HOME_STATE_IS_ACTIVE(HomeState state) {
return IN_SET(state,
HOME_ACTIVE,
HOME_LINGERING,
HOME_UPDATING_WHILE_ACTIVE,
HOME_RESIZING_WHILE_ACTIVE,
HOME_PASSWD_WHILE_ACTIVE,
@ -74,6 +76,33 @@ static inline bool HOME_STATE_IS_EXECUTING_OPERATION(HomeState state) {
HOME_AUTHENTICATING_FOR_ACQUIRE);
}
static inline bool HOME_STATE_SHALL_PIN(HomeState state) {
/* Like HOME_STATE_IS_ACTIVE() but HOME_LINGERING is missing! */
return IN_SET(state,
HOME_ACTIVE,
HOME_UPDATING_WHILE_ACTIVE,
HOME_RESIZING_WHILE_ACTIVE,
HOME_PASSWD_WHILE_ACTIVE,
HOME_AUTHENTICATING_WHILE_ACTIVE,
HOME_AUTHENTICATING_FOR_ACQUIRE);
}
static inline bool HOME_STATE_MAY_RETRY_DEACTIVATE(HomeState state) {
/* Indicates when to leave the deactivate retry timer active */
return IN_SET(state,
HOME_ACTIVE,
HOME_LINGERING,
HOME_DEACTIVATING,
HOME_LOCKING,
HOME_UNLOCKING,
HOME_UNLOCKING_FOR_ACQUIRE,
HOME_UPDATING_WHILE_ACTIVE,
HOME_RESIZING_WHILE_ACTIVE,
HOME_PASSWD_WHILE_ACTIVE,
HOME_AUTHENTICATING_WHILE_ACTIVE,
HOME_AUTHENTICATING_FOR_ACQUIRE);
}
struct Home {
Manager *manager;
char *user_name;
@ -126,6 +155,15 @@ struct Home {
/* Used to coalesce bus PropertiesChanged events */
sd_event_source *deferred_change_event_source;
/* An fd to the top-level home directory we keep while logged in, to keep the dir busy */
int pin_fd;
/* A time event used to repeatedly try to unmount home dir after use if it didn't work on first try */
sd_event_source *retry_deactivate_event_source;
/* An fd that locks the backing file of LUKS home dirs with a BSD lock. */
int luks_lock_fd;
};
int home_new(Manager *m, UserRecord *hr, const char *sysfs, Home **ret);
@ -152,7 +190,7 @@ int home_unlock(Home *h, UserRecord *secret, sd_bus_error *error);
HomeState home_get_state(Home *h);
void home_process_notify(Home *h, char **l);
void home_process_notify(Home *h, char **l, int fd);
int home_killall(Home *h);

View File

@ -83,35 +83,38 @@ static void manager_watch_home(Manager *m) {
m->inotify_event_source = sd_event_source_disable_unref(m->inotify_event_source);
m->scan_slash_home = false;
if (statfs("/home/", &sfs) < 0) {
if (statfs(get_home_root(), &sfs) < 0) {
log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_WARNING, errno,
"Failed to statfs() /home/ directory, disabling automatic scanning.");
"Failed to statfs() %s directory, disabling automatic scanning.", get_home_root());
return;
}
if (is_network_fs(&sfs)) {
log_info("/home/ is a network file system, disabling automatic scanning.");
log_info("%s is a network file system, disabling automatic scanning.", get_home_root());
return;
}
if (is_fs_type(&sfs, AUTOFS_SUPER_MAGIC)) {
log_info("/home/ is on autofs, disabling automatic scanning.");
log_info("%s is on autofs, disabling automatic scanning.", get_home_root());
return;
}
m->scan_slash_home = true;
r = sd_event_add_inotify(m->event, &m->inotify_event_source, "/home/",
r = sd_event_add_inotify(m->event, &m->inotify_event_source, get_home_root(),
IN_CREATE|IN_CLOSE_WRITE|IN_DELETE_SELF|IN_MOVE_SELF|IN_ONLYDIR|IN_MOVED_TO|IN_MOVED_FROM|IN_DELETE,
on_home_inotify, m);
if (r < 0)
log_full_errno(r == -ENOENT ? LOG_DEBUG : LOG_WARNING, r,
"Failed to create inotify watch on /home/, ignoring.");
"Failed to create inotify watch on %s, ignoring.", get_home_root());
(void) sd_event_source_set_description(m->inotify_event_source, "home-inotify");
log_info("Watching %s.", get_home_root());
}
static int on_home_inotify(sd_event_source *s, const struct inotify_event *event, void *userdata) {
_cleanup_free_ char *j = NULL;
Manager *m = userdata;
const char *e, *n;
@ -121,15 +124,15 @@ static int on_home_inotify(sd_event_source *s, const struct inotify_event *event
if ((event->mask & (IN_Q_OVERFLOW|IN_MOVE_SELF|IN_DELETE_SELF|IN_IGNORED|IN_UNMOUNT)) != 0) {
if (FLAGS_SET(event->mask, IN_Q_OVERFLOW))
log_debug("/home/ inotify queue overflow, rescanning.");
log_debug("%s inotify queue overflow, rescanning.", get_home_root());
else if (FLAGS_SET(event->mask, IN_MOVE_SELF))
log_info("/home/ moved or renamed, recreating watch and rescanning.");
log_info("%s moved or renamed, recreating watch and rescanning.", get_home_root());
else if (FLAGS_SET(event->mask, IN_DELETE_SELF))
log_info("/home/ deleted, recreating watch and rescanning.");
log_info("%s deleted, recreating watch and rescanning.", get_home_root());
else if (FLAGS_SET(event->mask, IN_UNMOUNT))
log_info("/home/ unmounted, recreating watch and rescanning.");
log_info("%s unmounted, recreating watch and rescanning.", get_home_root());
else if (FLAGS_SET(event->mask, IN_IGNORED))
log_info("/home/ watch invalidated, recreating watch and rescanning.");
log_info("%s watch invalidated, recreating watch and rescanning.", get_home_root());
manager_watch_home(m);
(void) manager_gc_images(m);
@ -150,15 +153,19 @@ static int on_home_inotify(sd_event_source *s, const struct inotify_event *event
if (!suitable_user_name(n))
return 0;
j = path_join(get_home_root(), event->name);
if (!j)
return log_oom();
if ((event->mask & (IN_CREATE|IN_CLOSE_WRITE|IN_MOVED_TO)) != 0) {
if (FLAGS_SET(event->mask, IN_CREATE))
log_debug("/home/%s has been created, having a look.", event->name);
log_debug("%s has been created, having a look.", j);
else if (FLAGS_SET(event->mask, IN_CLOSE_WRITE))
log_debug("/home/%s has been modified, having a look.", event->name);
log_debug("%s has been modified, having a look.", j);
else if (FLAGS_SET(event->mask, IN_MOVED_TO))
log_debug("/home/%s has been moved in, having a look.", event->name);
log_debug("%s has been moved in, having a look.", j);
(void) manager_assess_image(m, -1, "/home/", event->name);
(void) manager_assess_image(m, -1, get_home_root(), event->name);
(void) bus_manager_emit_auto_login_changed(m);
}
@ -166,11 +173,11 @@ static int on_home_inotify(sd_event_source *s, const struct inotify_event *event
Home *h;
if (FLAGS_SET(event->mask, IN_DELETE))
log_debug("/home/%s has been deleted, revalidating.", event->name);
log_debug("%s has been deleted, revalidating.", j);
else if (FLAGS_SET(event->mask, IN_CLOSE_WRITE))
log_debug("/home/%s has been closed after writing, revalidating.", event->name);
log_debug("%s has been closed after writing, revalidating.", j);
else if (FLAGS_SET(event->mask, IN_MOVED_FROM))
log_debug("/home/%s has been moved away, revalidating.", event->name);
log_debug("%s has been moved away, revalidating.", j);
h = hashmap_get(m->homes_by_name, n);
if (h) {
@ -487,7 +494,7 @@ static int search_quota(uid_t uid, const char *exclude_quota_path) {
* comprehensive, but should cover most cases. Note that in an ideal world every user would be
* registered in NSS and avoid our own UID range, but for all other cases, it's a good idea to be
* paranoid and check quota if we can. */
FOREACH_STRING(where, "/home/", "/tmp/", "/var/", "/var/mail/", "/var/tmp/", "/var/spool/") {
FOREACH_STRING(where, get_home_root(), "/tmp/", "/var/", "/var/mail/", "/var/tmp/", "/var/spool/") {
struct dqblk req;
struct stat st;
@ -914,13 +921,13 @@ int manager_enumerate_images(Manager *m) {
if (!m->scan_slash_home)
return 0;
d = opendir("/home/");
d = opendir(get_home_root());
if (!d)
return log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_ERR, errno,
"Failed to open /home/: %m");
"Failed to open %s: %m", get_home_root());
FOREACH_DIRENT(de, d, return log_error_errno(errno, "Failed to read /home/ directory: %m"))
(void) manager_assess_image(m, dirfd(d), "/home", de->d_name);
FOREACH_DIRENT(de, d, return log_error_errno(errno, "Failed to read %s directory: %m", get_home_root()))
(void) manager_assess_image(m, dirfd(d), get_home_root(), de->d_name);
return 0;
}
@ -1010,13 +1017,25 @@ static int manager_bind_varlink(Manager *m) {
return 0;
}
static ssize_t read_datagram(int fd, struct ucred *ret_sender, void **ret) {
static ssize_t read_datagram(
int fd,
struct ucred *ret_sender,
void **ret,
int *ret_passed_fd) {
CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(struct ucred)) + CMSG_SPACE(sizeof(int))) control;
_cleanup_free_ void *buffer = NULL;
_cleanup_close_ int passed_fd = -1;
struct ucred *sender = NULL;
struct cmsghdr *cmsg;
struct msghdr mh;
struct iovec iov;
ssize_t n, m;
assert(fd >= 0);
assert(ret_sender);
assert(ret);
assert(ret_passed_fd);
n = next_datagram_size_fd(fd);
if (n < 0)
@ -1026,13 +1045,6 @@ static ssize_t read_datagram(int fd, struct ucred *ret_sender, void **ret) {
if (!buffer)
return -ENOMEM;
if (ret_sender) {
CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(struct ucred))) control;
bool found_ucred = false;
struct cmsghdr *cmsg;
struct msghdr mh;
struct iovec iov;
/* Pass one extra byte, as a size check */
iov = IOVEC_MAKE(buffer, n + 1);
@ -1047,37 +1059,40 @@ static ssize_t read_datagram(int fd, struct ucred *ret_sender, void **ret) {
if (m < 0)
return m;
cmsg_close_all(&mh);
/* Ensure the size matches what we determined before */
if (m != n)
if (m != n) {
cmsg_close_all(&mh);
return -EMSGSIZE;
}
CMSG_FOREACH(cmsg, &mh)
CMSG_FOREACH(cmsg, &mh) {
if (cmsg->cmsg_level == SOL_SOCKET &&
cmsg->cmsg_type == SCM_CREDENTIALS &&
cmsg->cmsg_len == CMSG_LEN(sizeof(struct ucred))) {
memcpy(ret_sender, CMSG_DATA(cmsg), sizeof(struct ucred));
found_ucred = true;
assert(!sender);
sender = (struct ucred*) CMSG_DATA(cmsg);
}
if (!found_ucred)
*ret_sender = (struct ucred) {
.pid = 0,
.uid = UID_INVALID,
.gid = GID_INVALID,
};
} else {
m = recv(fd, buffer, n + 1, MSG_DONTWAIT);
if (m < 0)
return -errno;
if (cmsg->cmsg_level == SOL_SOCKET &&
cmsg->cmsg_type == SCM_RIGHTS) {
/* Ensure the size matches what we determined before */
if (m != n)
if (cmsg->cmsg_len != CMSG_LEN(sizeof(int))) {
cmsg_close_all(&mh);
return -EMSGSIZE;
}
assert(passed_fd < 0);
passed_fd = *(int*) CMSG_DATA(cmsg);
}
}
if (sender)
*ret_sender = *sender;
else
*ret_sender = (struct ucred) UCRED_INVALID;
*ret_passed_fd = TAKE_FD(passed_fd);
/* For safety reasons: let's always NUL terminate. */
((char*) buffer)[n] = 0;
*ret = TAKE_PTR(buffer);
@ -1088,7 +1103,8 @@ static ssize_t read_datagram(int fd, struct ucred *ret_sender, void **ret) {
static int on_notify_socket(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
_cleanup_strv_free_ char **l = NULL;
_cleanup_free_ void *datagram = NULL;
struct ucred sender;
_cleanup_close_ int passed_fd = -1;
struct ucred sender = UCRED_INVALID;
Manager *m = userdata;
ssize_t n;
Home *h;
@ -1096,7 +1112,7 @@ static int on_notify_socket(sd_event_source *s, int fd, uint32_t revents, void *
assert(s);
assert(m);
n = read_datagram(fd, &sender, &datagram);
n = read_datagram(fd, &sender, &datagram, &passed_fd);
if (IN_SET(n, -EAGAIN, -EINTR))
return 0;
if (n < 0)
@ -1117,7 +1133,7 @@ static int on_notify_socket(sd_event_source *s, int fd, uint32_t revents, void *
if (!l)
return log_oom();
home_process_notify(h, l);
home_process_notify(h, l, TAKE_FD(passed_fd));
return 0;
}

View File

@ -8,11 +8,14 @@
#include <sys/mount.h>
#include <sys/xattr.h>
#include "sd-daemon.h"
#include "blkid-util.h"
#include "blockdev-util.h"
#include "btrfs-util.h"
#include "chattr-util.h"
#include "dm-util.h"
#include "env-util.h"
#include "errno-util.h"
#include "fd-util.h"
#include "fileio.h"
@ -1042,6 +1045,40 @@ int run_fallocate_by_path(const char *backing_path) {
return run_fallocate(backing_fd, NULL);
}
static int lock_image_fd(int image_fd, const char *ip) {
int r;
/* If the $SYSTEMD_LUKS_LOCK environment variable is set we'll take an exclusive BSD lock on the
* image file, and send it to our parent. homed will keep it open to ensure no other instance of
* homed (across the network or such) will also mount the file. */
r = getenv_bool("SYSTEMD_LUKS_LOCK");
if (r == -ENXIO)
return 0;
if (r < 0)
return log_error_errno(r, "Failed to parse $SYSTEMD_LUKS_LOCK environment variable: %m");
if (r > 0) {
if (flock(image_fd, LOCK_EX|LOCK_NB) < 0) {
if (errno == EWOULDBLOCK)
log_error_errno(errno, "Image file '%s' already locked, can't use.", ip);
else
log_error_errno(errno, "Failed to lock image file '%s': %m", ip);
return errno != EWOULDBLOCK ? -errno : -EADDRINUSE; /* Make error recognizable */
}
log_info("Successfully locked image file '%s'.", ip);
/* Now send it to our parent to keep safe while the home dir is active */
r = sd_pid_notify_with_fds(0, false, "SYSTEMD_LUKS_LOCK_FD=1", &image_fd, 1);
if (r < 0)
log_warning_errno(r, "Failed to send LUKS lock fd to parent, ignoring: %m");
}
return 0;
}
int home_prepare_luks(
UserRecord *h,
bool already_activated,
@ -1176,6 +1213,10 @@ int home_prepare_luks(
S_ISDIR(st.st_mode) ? SYNTHETIC_ERRNO(EISDIR) : SYNTHETIC_ERRNO(EBADFD),
"Image file %s is not a regular file or block device: %m", ip);
r = lock_image_fd(image_fd, ip);
if (r < 0)
return r;
r = luks_validate(image_fd, user_record_user_name_and_realm(h), h->partition_uuid, &found_partition_uuid, &offset, &size);
if (r < 0)
return log_error_errno(r, "Failed to validate disk label: %m");

View File

@ -28,6 +28,7 @@
#include "rm-rf.h"
#include "stat-util.h"
#include "strv.h"
#include "sync-util.h"
#include "tmpfile-util.h"
#include "user-util.h"
#include "virt.h"
@ -283,6 +284,20 @@ int user_record_authenticate(
return 0;
}
static void drop_caches_now(void) {
int r;
/* Drop file system caches now. See https://www.kernel.org/doc/Documentation/sysctl/vm.txt for
* details. We write "2" into /proc/sys/vm/drop_caches to ensure dentries/inodes are flushed, but not
* more. */
r = write_string_file("/proc/sys/vm/drop_caches", "2\n", WRITE_STRING_FILE_DISABLE_BUFFER);
if (r < 0)
log_warning_errno(r, "Failed to drop caches, ignoring: %m");
else
log_debug("Dropped caches.");
}
int home_setup_undo(HomeSetup *setup) {
int r = 0, q;
@ -295,6 +310,9 @@ int home_setup_undo(HomeSetup *setup) {
r = q;
}
if (syncfs(setup->root_fd) < 0)
log_debug_errno(errno, "Failed to synchronize home directory, ignoring: %m");
setup->root_fd = safe_close(setup->root_fd);
}
@ -345,6 +363,9 @@ int home_setup_undo(HomeSetup *setup) {
setup->volume_key = mfree(setup->volume_key);
setup->volume_key_size = 0;
if (setup->do_drop_caches)
drop_caches_now();
return r;
}
@ -367,6 +388,9 @@ int home_prepare(
/* Makes a home directory accessible (through the root_fd file descriptor, not by path!). */
if (!already_activated) /* If we set up the directory, we should also drop caches once we are done */
setup->do_drop_caches = setup->do_drop_caches || user_record_drop_caches(h);
switch (user_record_storage(h)) {
case USER_LUKS:
@ -827,6 +851,13 @@ static int home_deactivate(UserRecord *h, bool force) {
return r;
}
/* Sync explicitly, so that the drop caches logic below can work as documented */
r = syncfs_path(AT_FDCWD, user_record_home_directory(h));
if (r < 0)
log_debug_errno(r, "Failed to synchronize home directory, ignoring: %m");
else
log_info("Syncing completed.");
if (umount2(user_record_home_directory(h), UMOUNT_NOFOLLOW | (force ? MNT_FORCE|MNT_DETACH : 0)) < 0)
return log_error_errno(errno, "Failed to unmount %s: %m", user_record_home_directory(h));
@ -846,6 +877,9 @@ static int home_deactivate(UserRecord *h, bool force) {
if (!done)
return log_error_errno(SYNTHETIC_ERRNO(ENOEXEC), "Home is not active.");
if (user_record_drop_caches(h))
drop_caches_now();
log_info("Everything completed.");
return 0;
}
@ -1095,12 +1129,12 @@ static int determine_default_storage(UserStorage *ret) {
if (r < 0)
return log_error_errno(r, "Failed to determine whether we are in a container: %m");
if (r == 0) {
r = path_is_encrypted("/home");
r = path_is_encrypted(get_home_root());
if (r > 0)
log_info("/home is encrypted, not using '%s' storage, in order to avoid double encryption.", user_storage_to_string(USER_LUKS));
log_info("%s is encrypted, not using '%s' storage, in order to avoid double encryption.", get_home_root(), user_storage_to_string(USER_LUKS));
else {
if (r < 0)
log_warning_errno(r, "Failed to determine if /home is encrypted, ignoring: %m");
log_warning_errno(r, "Failed to determine if %s is encrypted, ignoring: %m", get_home_root());
r = dlopen_cryptsetup();
if (r < 0)
@ -1114,14 +1148,14 @@ static int determine_default_storage(UserStorage *ret) {
} else
log_info("Running in container, not using '%s' storage.", user_storage_to_string(USER_LUKS));
r = path_is_fs_type("/home", BTRFS_SUPER_MAGIC);
r = path_is_fs_type(get_home_root(), BTRFS_SUPER_MAGIC);
if (r < 0)
log_warning_errno(r, "Failed to determine file system of /home, ignoring: %m");
log_warning_errno(r, "Failed to determine file system of %s, ignoring: %m", get_home_root());
if (r > 0) {
log_info("/home is on btrfs, using '%s' as storage.", user_storage_to_string(USER_SUBVOLUME));
log_info("%s is on btrfs, using '%s' as storage.", get_home_root(), user_storage_to_string(USER_SUBVOLUME));
*ret = USER_SUBVOLUME;
} else {
log_info("/home is on simple file system, using '%s' as storage.", user_storage_to_string(USER_DIRECTORY));
log_info("%s is on simple file system, using '%s' as storage.", get_home_root(), user_storage_to_string(USER_DIRECTORY));
*ret = USER_DIRECTORY;
}
@ -1268,9 +1302,21 @@ static int home_remove(UserRecord *h) {
if (unlink(ip) < 0) {
if (errno != ENOENT)
return log_error_errno(errno, "Failed to remove %s: %m", ip);
} else
} else {
_cleanup_free_ char *parent = NULL;
deleted = true;
r = path_extract_directory(ip, &parent);
if (r < 0)
log_debug_errno(r, "Failed to determine parent directory of '%s': %m", ip);
else {
r = fsync_path_at(AT_FDCWD, parent);
if (r < 0)
log_debug_errno(r, "Failed to synchronize disk after deleting '%s', ignoring: %m", ip);
}
}
} else if (S_ISBLK(st.st_mode))
log_info("Not removing file system on block device %s.", ip);
else
@ -1285,7 +1331,7 @@ static int home_remove(UserRecord *h) {
case USER_FSCRYPT:
assert(ip);
r = rm_rf(ip, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME);
r = rm_rf(ip, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME|REMOVE_SYNCFS);
if (r < 0) {
if (r != -ENOENT)
return log_warning_errno(r, "Failed to remove %s: %m", ip);
@ -1316,9 +1362,12 @@ static int home_remove(UserRecord *h) {
deleted = true;
}
if (deleted)
if (deleted) {
if (user_record_drop_caches(h))
drop_caches_now();
log_info("Everything completed.");
else
} else
return log_notice_errno(SYNTHETIC_ERRNO(EALREADY),
"Nothing to remove.");
@ -1706,6 +1755,7 @@ static int run(int argc, char *argv[]) {
* ENOEXEC file system is currently not active
* ENOSPC not enough disk space for operation
* EKEYREVOKED user record has not suitable hashed password or pkcs#11 entry, we cannot authenticate
* EADDRINUSE home image is already used elsewhere (lock taken)
*/
if (streq(argv[1], "activate"))

View File

@ -32,6 +32,7 @@ typedef struct HomeSetup {
bool do_offline_fitrim;
bool do_offline_fallocate;
bool do_mark_clean;
bool do_drop_caches;
uint64_t partition_offset;
uint64_t partition_size;

View File

@ -75,7 +75,7 @@ int user_record_synthesize(
if (!ip)
return -ENOMEM;
hd = path_join("/home/", user_name);
hd = path_join(get_home_root(), user_name);
if (!hd)
return -ENOMEM;

View File

@ -36,6 +36,7 @@
#include "syslog-util.h"
#include "tmpfile-util.h"
#include "unit-name.h"
#include "user-util.h"
#define STDOUT_STREAMS_MAX 4096
@ -663,6 +664,7 @@ int stdout_stream_install(Server *s, int fd, StdoutStream **ret) {
*stream = (StdoutStream) {
.fd = -1,
.priority = LOG_INFO,
.ucred = UCRED_INVALID,
};
xsprintf(stream->id_field, "_STREAM_ID=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(id));
@ -727,9 +729,9 @@ static int stdout_stream_new(sd_event_source *es, int listen_fd, uint32_t revent
}
if (s->n_stdout_streams >= STDOUT_STREAMS_MAX) {
struct ucred u;
struct ucred u = UCRED_INVALID;
r = getpeercred(fd, &u);
(void) getpeercred(fd, &u);
/* By closing fd here we make sure that the client won't wait too long for journald to
* gather all the data it adds to the error message to find out that the connection has
@ -737,7 +739,7 @@ static int stdout_stream_new(sd_event_source *es, int listen_fd, uint32_t revent
*/
fd = safe_close(fd);
server_driver_message(s, r < 0 ? 0 : u.pid, NULL, LOG_MESSAGE("Too many stdout streams, refusing connection."), NULL);
server_driver_message(s, u.pid, NULL, LOG_MESSAGE("Too many stdout streams, refusing connection."), NULL);
return 0;
}

View File

@ -207,7 +207,7 @@ int dhcp_identifier_set_iaid(
if (legacy_unstable_byteorder)
/* for historical reasons (a bug), the bits were swapped and thus
* the result was endianness dependent. Preserve that behavior. */
id32 = __bswap_32(id32);
id32 = bswap_32(id32);
else
/* the fixed behavior returns a stable byte order. Since LE is expected
* to be more common, swap the bytes on LE to give the same as legacy

View File

@ -45,7 +45,7 @@ static void test_request_basic(sd_event *e) {
sd_dhcp_client *client;
if (verbose)
printf("* %s\n", __FUNCTION__);
printf("* %s\n", __func__);
/* Initialize client without Anonymize settings. */
r = sd_dhcp_client_new(&client, false);
@ -105,7 +105,7 @@ static void test_request_anonymize(sd_event *e) {
sd_dhcp_client *client;
if (verbose)
printf("* %s\n", __FUNCTION__);
printf("* %s\n", __func__);
/* Initialize client with Anonymize settings. */
r = sd_dhcp_client_new(&client, true);
@ -137,7 +137,7 @@ static void test_checksum(void) {
};
if (verbose)
printf("* %s\n", __FUNCTION__);
printf("* %s\n", __func__);
assert_se(dhcp_packet_checksum((uint8_t*)&buf, 20) == be16toh(0x78ae));
}
@ -158,7 +158,7 @@ static void test_dhcp_identifier_set_iaid(void) {
#if __BYTE_ORDER == __LITTLE_ENDIAN
assert_se(iaid == iaid_legacy);
#else
assert_se(iaid == __bswap_32(iaid_legacy));
assert_se(iaid == bswap_32(iaid_legacy));
#endif
}
@ -279,7 +279,7 @@ static void test_discover_message(sd_event *e) {
int res, r;
if (verbose)
printf("* %s\n", __FUNCTION__);
printf("* %s\n", __func__);
r = sd_dhcp_client_new(&client, false);
assert_se(r >= 0);
@ -497,7 +497,7 @@ static void test_addr_acq(sd_event *e) {
int res, r;
if (verbose)
printf("* %s\n", __FUNCTION__);
printf("* %s\n", __func__);
r = sd_dhcp_client_new(&client, false);
assert_se(r >= 0);

View File

@ -81,7 +81,7 @@ static void test_public_api_setters(sd_event *e) {
.ether_addr_octet = {'A', 'B', 'C', '1', '2', '3'}};
if (verbose)
printf("* %s\n", __FUNCTION__);
printf("* %s\n", __func__);
assert_se(sd_ipv4ll_new(&ll) == 0);
assert_se(ll);
@ -130,7 +130,7 @@ static void test_basic_request(sd_event *e) {
.ether_addr_octet = {'A', 'B', 'C', '1', '2', '3'}};
if (verbose)
printf("* %s\n", __FUNCTION__);
printf("* %s\n", __func__);
assert_se(sd_ipv4ll_new(&ll) == 0);
assert_se(sd_ipv4ll_start(ll) == -EINVAL);

View File

@ -111,7 +111,7 @@ static int test_rs_hangcheck(sd_event_source *s, uint64_t usec,
static void test_radv_prefix(void) {
sd_radv_prefix *p;
printf("* %s\n", __FUNCTION__);
printf("* %s\n", __func__);
assert_se(sd_radv_prefix_new(&p) >= 0);
@ -153,7 +153,7 @@ static void test_radv_prefix(void) {
static void test_radv(void) {
sd_radv *ra;
printf("* %s\n", __FUNCTION__);
printf("* %s\n", __func__);
assert_se(sd_radv_new(&ra) >= 0);
assert_se(ra);
@ -295,7 +295,7 @@ static void test_ra(void) {
sd_radv *ra;
unsigned i;
printf("* %s\n", __FUNCTION__);
printf("* %s\n", __func__);
assert_se(socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, test_fd) >= 0);

View File

@ -273,7 +273,7 @@ static void test_rs(void) {
sd_ndisc *nd;
if (verbose)
printf("* %s\n", __FUNCTION__);
printf("* %s\n", __func__);
send_ra_function = send_ra;
@ -366,7 +366,7 @@ static void test_timeout(void) {
sd_ndisc *nd;
if (verbose)
printf("* %s\n", __FUNCTION__);
printf("* %s\n", __func__);
send_ra_function = test_timeout_value;

View File

@ -142,6 +142,7 @@ BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map bus_common_errors[] = {
SD_BUS_ERROR_MAP(BUS_ERROR_TOO_MANY_OPERATIONS, ENOBUFS),
SD_BUS_ERROR_MAP(BUS_ERROR_AUTHENTICATION_LIMIT_HIT, ETOOMANYREFS),
SD_BUS_ERROR_MAP(BUS_ERROR_HOME_CANT_AUTHENTICATE, EKEYREVOKED),
SD_BUS_ERROR_MAP(BUS_ERROR_HOME_IN_USE, EADDRINUSE),
SD_BUS_ERROR_MAP_END
};

View File

@ -126,5 +126,6 @@
#define BUS_ERROR_TOO_MANY_OPERATIONS "org.freedesktop.home1.TooManyOperations"
#define BUS_ERROR_AUTHENTICATION_LIMIT_HIT "org.freedesktop.home1.AuthenticationLimitHit"
#define BUS_ERROR_HOME_CANT_AUTHENTICATE "org.freedesktop.home1.HomeCantAuthenticate"
#define BUS_ERROR_HOME_IN_USE "org.freedesktop.home1.HomeInUse"
BUS_ERROR_MAP_ELF_USE(bus_common_errors);

View File

@ -249,6 +249,7 @@ _public_ int sd_bus_new(sd_bus **ret) {
.original_pid = getpid_cached(),
.n_groups = SIZE_MAX,
.close_on_exit = true,
.ucred = UCRED_INVALID,
};
/* We guarantee that wqueue always has space for at least one entry */

View File

@ -136,7 +136,7 @@ _public_ int sd_pid_get_cgroup(pid_t pid, char **cgroup) {
}
_public_ int sd_peer_get_session(int fd, char **session) {
struct ucred ucred = {};
struct ucred ucred = UCRED_INVALID;
int r;
assert_return(fd >= 0, -EBADF);

View File

@ -560,7 +560,7 @@ static void log_xkb(struct xkb_context *ctx, enum xkb_log_level lvl, const char
fmt = strjoina("libxkbcommon: ", format);
DISABLE_WARNING_FORMAT_NONLITERAL;
log_internalv(LOG_DEBUG, 0, __FILE__, __LINE__, __func__, fmt, args);
log_internalv(LOG_DEBUG, 0, PROJECT_FILE, __LINE__, __func__, fmt, args);
REENABLE_WARNING;
}

View File

@ -756,7 +756,7 @@ int ethtool_set_features(int *ethtool_fd, const char *ifname, const int features
static int get_glinksettings(int fd, struct ifreq *ifr, struct ethtool_link_usettings **ret) {
struct ecmd {
struct ethtool_link_settings req;
__u32 link_mode_data[3 * ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32];
uint32_t link_mode_data[3 * ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32];
} ecmd = {
.req.cmd = ETHTOOL_GLINKSETTINGS,
};
@ -857,7 +857,7 @@ static int get_gset(int fd, struct ifreq *ifr, struct ethtool_link_usettings **r
static int set_slinksettings(int fd, struct ifreq *ifr, const struct ethtool_link_usettings *u) {
struct {
struct ethtool_link_settings req;
__u32 link_mode_data[3 * ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32];
uint32_t link_mode_data[3 * ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32];
} ecmd = {};
unsigned offset;

View File

@ -250,6 +250,9 @@ int rm_rf_children(
ret = r;
}
if (FLAGS_SET(flags, REMOVE_SYNCFS) && syncfs(dirfd(d)) < 0 && ret >= 0)
ret = -errno;
return ret;
}

View File

@ -14,6 +14,7 @@ typedef enum RemoveFlags {
REMOVE_MISSING_OK = 1 << 4, /* If the top-level directory is missing, ignore the ENOENT for it */
REMOVE_CHMOD = 1 << 5, /* chmod() for write access if we cannot delete or access something */
REMOVE_CHMOD_RESTORE = 1 << 6, /* Restore the old mode before returning */
REMOVE_SYNCFS = 1 << 7, /* syncfs() the root of the specified directory after removing everything in it */
} RemoveFlags;
int unlinkat_harder(int dfd, const char *filename, int unlink_flags, RemoveFlags remove_flags);

View File

@ -435,6 +435,9 @@ void user_record_show(UserRecord *hr, bool show_full_group_info) {
if (hr->password_change_now >= 0)
printf("Pas. Ch. Now: %s\n", yes_no(hr->password_change_now));
if (hr->drop_caches >= 0 || user_record_drop_caches(hr))
printf(" Drop Caches: %s\n", yes_no(user_record_drop_caches(hr)));
if (!strv_isempty(hr->ssh_authorized_keys))
printf("SSH Pub. Key: %zu\n", strv_length(hr->ssh_authorized_keys));

View File

@ -202,6 +202,7 @@ UserRecord* user_record_new(void) {
.pkcs11_protected_authentication_path_permitted = -1,
.fido2_user_presence_permitted = -1,
.fido2_user_verification_permitted = -1,
.drop_caches = -1,
};
return h;
@ -1284,6 +1285,7 @@ static int dispatch_per_machine(const char *name, JsonVariant *variant, JsonDisp
{ "luksPbkdfTimeCostUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_time_cost_usec), 0 },
{ "luksPbkdfMemoryCost", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_memory_cost), 0 },
{ "luksPbkdfParallelThreads", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_parallel_threads), 0 },
{ "dropCaches", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, drop_caches), 0 },
{ "rateLimitIntervalUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, ratelimit_interval_usec), 0 },
{ "rateLimitBurst", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, ratelimit_burst), 0 },
{ "enforcePasswordPolicy", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, enforce_password_policy), 0 },
@ -1406,11 +1408,11 @@ int user_record_build_image_path(UserStorage storage, const char *user_name_and_
return 0;
}
z = strjoin("/home/", user_name_and_realm, suffix);
z = strjoin(get_home_root(), "/", user_name_and_realm, suffix);
if (!z)
return -ENOMEM;
*ret = z;
*ret = path_simplify(z);
return 1;
}
@ -1435,7 +1437,7 @@ static int user_record_augment(UserRecord *h, JsonDispatchFlags json_flags) {
return 0;
if (!h->home_directory && !h->home_directory_auto) {
h->home_directory_auto = path_join("/home/", h->user_name);
h->home_directory_auto = path_join(get_home_root(), h->user_name);
if (!h->home_directory_auto)
return json_log_oom(h->json, json_flags);
}
@ -1629,6 +1631,7 @@ int user_record_load(UserRecord *h, JsonVariant *v, UserRecordLoadFlags load_fla
{ "luksPbkdfTimeCostUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_time_cost_usec), 0 },
{ "luksPbkdfMemoryCost", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_memory_cost), 0 },
{ "luksPbkdfParallelThreads", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_parallel_threads), 0 },
{ "dropCaches", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, drop_caches), 0 },
{ "service", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, service), JSON_SAFE },
{ "rateLimitIntervalUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, ratelimit_interval_usec), 0 },
{ "rateLimitBurst", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, ratelimit_burst), 0 },
@ -2021,6 +2024,16 @@ bool user_record_can_authenticate(UserRecord *h) {
return !strv_isempty(h->hashed_password);
}
bool user_record_drop_caches(UserRecord *h) {
assert(h);
if (h->drop_caches >= 0)
return h->drop_caches;
/* By default drop caches on fscrypt, not otherwise. */
return user_record_storage(h) == USER_FSCRYPT;
}
uint64_t user_record_ratelimit_next_try(UserRecord *h) {
assert(h);

View File

@ -353,6 +353,7 @@ typedef struct UserRecord {
int removable;
int enforce_password_policy;
int auto_login;
int drop_caches;
uint64_t stop_delay_usec; /* How long to leave systemd --user around on log-out */
int kill_processes; /* Whether to kill user processes forcibly on log-out */
@ -419,6 +420,7 @@ int user_record_removable(UserRecord *h);
usec_t user_record_ratelimit_interval_usec(UserRecord *h);
uint64_t user_record_ratelimit_burst(UserRecord *h);
bool user_record_can_authenticate(UserRecord *h);
bool user_record_drop_caches(UserRecord *h);
int user_record_build_image_path(UserStorage storage, const char *user_name_and_realm, char **ret);

View File

@ -258,8 +258,7 @@ static int varlink_new(Varlink **ret) {
.state = _VARLINK_STATE_INVALID,
.ucred.uid = UID_INVALID,
.ucred.gid = GID_INVALID,
.ucred = UCRED_INVALID,
.timestamp = USEC_INFINITY,
.timeout = VARLINK_DEFAULT_TIMEOUT_USEC
@ -2077,7 +2076,7 @@ static int validate_connection(VarlinkServer *server, const struct ucred *ucred)
return 1;
}
static int count_connection(VarlinkServer *server, struct ucred *ucred) {
static int count_connection(VarlinkServer *server, const struct ucred *ucred) {
unsigned c;
int r;
@ -2106,8 +2105,8 @@ static int count_connection(VarlinkServer *server, struct ucred *ucred) {
int varlink_server_add_connection(VarlinkServer *server, int fd, Varlink **ret) {
_cleanup_(varlink_unrefp) Varlink *v = NULL;
struct ucred ucred = UCRED_INVALID;
bool ucred_acquired;
struct ucred ucred;
int r;
assert_return(server, -EINVAL);

View File

@ -120,6 +120,7 @@ static int recurse_dir_callback(
int main(int argc, char *argv[]) {
_cleanup_strv_free_ char **list_recurse_dir = NULL;
const char *p;
usec_t t1, t2, t3, t4;
int r;
log_show_color(true);
@ -131,14 +132,20 @@ int main(int argc, char *argv[]) {
p = "/usr/share/man"; /* something hopefully reasonably stable while we run (and limited in size) */
/* Enumerate the specified dirs in full, once via nftw(), and once via recurse_dir(), and ensure the results are identical */
t1 = now(CLOCK_MONOTONIC);
r = recurse_dir_at(AT_FDCWD, p, 0, UINT_MAX, RECURSE_DIR_SORT|RECURSE_DIR_ENSURE_TYPE|RECURSE_DIR_SAME_MOUNT, recurse_dir_callback, &list_recurse_dir);
t2 = now(CLOCK_MONOTONIC);
if (r == -ENOENT) {
log_warning_errno(r, "Couldn't open directory %s, ignoring: %m", p);
return EXIT_TEST_SKIP;
}
assert_se(r >= 0);
t3 = now(CLOCK_MONOTONIC);
assert_se(nftw(p, nftw_cb, 64, FTW_PHYS|FTW_MOUNT) >= 0);
t4 = now(CLOCK_MONOTONIC);
log_info("recurse_dir(): %s nftw(): %s", FORMAT_TIMESPAN(t2 - t1, 1), FORMAT_TIMESPAN(t4 - t3, 1));
strv_sort(list_recurse_dir);
strv_sort(list_nftw);

View File

@ -237,7 +237,7 @@ static int scsi_dump(struct scsi_id_device *dev_scsi, struct sg_io_hdr *io) {
*/
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
"%s: called with no error",
__FUNCTION__);
__func__);
log_debug("%s: sg_io failed status 0x%x 0x%x 0x%x 0x%x",
dev_scsi->kernel, io->driver_status, io->host_status, io->msg_status, io->status);
@ -255,7 +255,7 @@ static int scsi_dump_v4(struct scsi_id_device *dev_scsi, struct sg_io_v4 *io) {
*/
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
"%s: called with no error",
__FUNCTION__);
__func__);
log_debug("%s: sg_io failed status 0x%x 0x%x 0x%x",
dev_scsi->kernel, io->driver_status, io->transport_status, io->device_status);