mirror of
https://github.com/systemd/systemd
synced 2026-04-10 09:04:50 +02:00
Compare commits
32 Commits
e80c5eb6e6
...
d62ab43fd0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d62ab43fd0 | ||
|
|
ed5b77fe2e | ||
|
|
b1681f981b | ||
|
|
ac0a248ee6 | ||
|
|
af0ae29714 | ||
|
|
9aa6c30bbd | ||
|
|
64d5bb4d53 | ||
|
|
3331d99b49 | ||
|
|
e39cde9585 | ||
|
|
b38dc56bab | ||
|
|
aea2f059a3 | ||
|
|
8e4f26a23b | ||
|
|
0bb0316f5e | ||
|
|
882dfbde1c | ||
|
|
25dbf586b2 | ||
|
|
1be8caa6be | ||
|
|
d9c10bf1d2 | ||
|
|
0960b905bc | ||
|
|
f7c72e8725 | ||
|
|
b76a76ac15 | ||
|
|
570366b6e1 | ||
|
|
69332065e2 | ||
|
|
0a9e2e45f0 | ||
|
|
bcafa70974 | ||
|
|
f8e7566cb2 | ||
|
|
4fcd5f8b4b | ||
|
|
4bae593612 | ||
|
|
ab2e98b43c | ||
|
|
640b7c38f8 | ||
|
|
fa30374e15 | ||
|
|
4131e3a816 | ||
|
|
2568548560 |
@ -459,6 +459,73 @@
|
|||||||
<xi:include href="version-info.xml" xpointer="v256"/></listitem>
|
<xi:include href="version-info.xml" xpointer="v256"/></listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term><option>--bind-user=</option></term>
|
||||||
|
|
||||||
|
<listitem><para>Binds the home directory of the specified user on the host into the virtual
|
||||||
|
machine. Takes the name of an existing user on the host as argument. May be used multiple times to
|
||||||
|
bind multiple users into the virtual machine. This does two things:</para>
|
||||||
|
|
||||||
|
<orderedlist>
|
||||||
|
<listitem><para>The user's home directory is made available from the host into
|
||||||
|
<filename>/run/vmhost/home/</filename> using virtiofs. virtiofsd id translation to map the host
|
||||||
|
user's UID/GID to its assigned UID/GID in the virtual machine.</para></listitem>
|
||||||
|
|
||||||
|
<listitem><para>JSON user and group records are generated in that describes the mapped user which
|
||||||
|
are passed into the virtual machine using <literal>userdb.transient.*</literal> credentials.
|
||||||
|
They contain a minimized representation of the host's user record, adjusted to the UID/GID and
|
||||||
|
home directory path assigned to the user in the virtual machine. The
|
||||||
|
<citerefentry><refentrytitle>nss-systemd</refentrytitle><manvolnum>8</manvolnum></citerefentry>
|
||||||
|
glibc NSS module will pick up these records from there and make them available in the virtual
|
||||||
|
machine's user/group databases.</para></listitem>
|
||||||
|
</orderedlist>
|
||||||
|
|
||||||
|
<para>The combination of the two operations above ensures that it is possible to log into the
|
||||||
|
virtual machine using the same account information as on the host. The user is only mapped
|
||||||
|
transiently, while the virtual machine is running, and the mapping itself does not result in
|
||||||
|
persistent changes to the virtual machine (except maybe for log messages generated at login time,
|
||||||
|
and similar). Note that in particular the UID/GID assignment in the virtual machine is not made
|
||||||
|
persistently. If the user is mapped transiently, it is best to not allow the user to make
|
||||||
|
persistent changes to the virtual machine. If the user leaves files or directories owned by the
|
||||||
|
user, and those UIDs/GIDs are reused during later virtual machine invocations (possibly with a
|
||||||
|
different <option>--bind-user=</option> mapping), those files and directories will be accessible to
|
||||||
|
the "new" user.</para>
|
||||||
|
|
||||||
|
<para>The user/group record mapping only works if the virtual machine contains systemd 258 or
|
||||||
|
newer, with <command>nss-systemd</command> properly configured in
|
||||||
|
<filename>nsswitch.conf</filename>. See
|
||||||
|
<citerefentry><refentrytitle>nss-systemd</refentrytitle><manvolnum>8</manvolnum></citerefentry> for
|
||||||
|
details.</para>
|
||||||
|
|
||||||
|
<para>Note that the user record propagated from the host into the virtual machine will contain the
|
||||||
|
UNIX password hash of the user, so that seamless logins in the virtual machine are possible. If the
|
||||||
|
virtual machine is less trusted than the host it is hence important to use a strong UNIX password
|
||||||
|
hash function (e.g. yescrypt or similar, with the <literal>$y$</literal> hash prefix).</para>
|
||||||
|
|
||||||
|
<xi:include href="version-info.xml" xpointer="v259"/></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term><option>--bind-user-shell=</option></term>
|
||||||
|
|
||||||
|
<listitem><para>When used with <option>--bind-user=</option>, includes the specified shell in the
|
||||||
|
user records of users bound into the virtual machine. Takes either a boolean or an absolute path.</para>
|
||||||
|
|
||||||
|
<itemizedlist>
|
||||||
|
<listitem><para>If false (the default), no shell is passed in the user records for users bound into
|
||||||
|
the virtual machine. This causes bound users to the use the virtual machine's default shell.</para></listitem>
|
||||||
|
|
||||||
|
<listitem><para>If true, the shells specified by the host user records are included in the user records of all users bound into the virtual machine.</para></listitem>
|
||||||
|
|
||||||
|
<listitem><para>If passed an absolute path, sets that path as the shell for user records of all users bound into the virtual machine.</para></listitem>
|
||||||
|
</itemizedlist>
|
||||||
|
|
||||||
|
<para>Note: This will not check whether the specified shells exist in the virtual machine.</para>
|
||||||
|
|
||||||
|
<para>This operation is only supported in combination with <option>--bind-user=</option>.</para>
|
||||||
|
|
||||||
|
<xi:include href="version-info.xml" xpointer="v259"/></listitem>
|
||||||
|
</varlistentry>
|
||||||
</variablelist>
|
</variablelist>
|
||||||
</refsect2>
|
</refsect2>
|
||||||
|
|
||||||
|
|||||||
@ -5,5 +5,5 @@ Environment=
|
|||||||
GIT_URL=https://salsa.debian.org/systemd-team/systemd.git
|
GIT_URL=https://salsa.debian.org/systemd-team/systemd.git
|
||||||
GIT_SUBDIR=debian
|
GIT_SUBDIR=debian
|
||||||
GIT_BRANCH=debian/master
|
GIT_BRANCH=debian/master
|
||||||
GIT_COMMIT=e50fce1d4b2a9f1bb990027de8e86603f3b42301
|
GIT_COMMIT=5650452e6b0b430f44d3d48b7322c2b3c8b9477f
|
||||||
PKG_SUBDIR=debian
|
PKG_SUBDIR=debian
|
||||||
|
|||||||
@ -4,40 +4,13 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
#include "assert-util.h"
|
#include "assert-util.h"
|
||||||
#include "env-util.h"
|
|
||||||
#include "errno-util.h"
|
#include "errno-util.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
|
||||||
static bool assert_return_is_critical = BUILD_MODE_DEVELOPER;
|
|
||||||
|
|
||||||
/* Akin to glibc's __abort_msg; which is private and we hence cannot
|
/* Akin to glibc's __abort_msg; which is private and we hence cannot
|
||||||
* use here. */
|
* use here. */
|
||||||
static char *log_abort_msg = NULL;
|
static char *log_abort_msg = NULL;
|
||||||
|
|
||||||
void log_set_assert_return_is_critical(bool b) {
|
|
||||||
assert_return_is_critical = b;
|
|
||||||
}
|
|
||||||
|
|
||||||
void log_set_assert_return_is_critical_from_env(void) {
|
|
||||||
static int cached = INT_MIN;
|
|
||||||
int r;
|
|
||||||
|
|
||||||
if (cached == INT_MIN) {
|
|
||||||
r = secure_getenv_bool("SYSTEMD_ASSERT_RETURN_IS_CRITICAL");
|
|
||||||
if (r < 0 && r != -ENXIO)
|
|
||||||
log_debug_errno(r, "Failed to parse $SYSTEMD_ASSERT_RETURN_IS_CRITICAL, ignoring: %m");
|
|
||||||
|
|
||||||
cached = r;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cached >= 0)
|
|
||||||
log_set_assert_return_is_critical(cached);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool log_get_assert_return_is_critical(void) {
|
|
||||||
return assert_return_is_critical;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void log_assert(
|
static void log_assert(
|
||||||
int level,
|
int level,
|
||||||
const char *text,
|
const char *text,
|
||||||
@ -73,8 +46,8 @@ _noreturn_ void log_assert_failed_unreachable(const char *file, int line, const
|
|||||||
}
|
}
|
||||||
|
|
||||||
void log_assert_failed_return(const char *text, const char *file, int line, const char *func) {
|
void log_assert_failed_return(const char *text, const char *file, int line, const char *func) {
|
||||||
|
/* log_get_assert_return_is_critical is a weak symbol. It may be NULL. */
|
||||||
if (assert_return_is_critical)
|
if (log_get_assert_return_is_critical && log_get_assert_return_is_critical())
|
||||||
log_assert_failed(text, file, line, func);
|
log_assert_failed(text, file, line, func);
|
||||||
|
|
||||||
PROTECT_ERRNO;
|
PROTECT_ERRNO;
|
||||||
|
|||||||
@ -5,9 +5,7 @@
|
|||||||
|
|
||||||
/* Logging for various assertions */
|
/* Logging for various assertions */
|
||||||
|
|
||||||
void log_set_assert_return_is_critical(bool b);
|
bool log_get_assert_return_is_critical(void) _weak_ _pure_;
|
||||||
void log_set_assert_return_is_critical_from_env(void);
|
|
||||||
bool log_get_assert_return_is_critical(void) _pure_;
|
|
||||||
|
|
||||||
void log_assert_failed_return(const char *text, const char *file, int line, const char *func);
|
void log_assert_failed_return(const char *text, const char *file, int line, const char *func);
|
||||||
|
|
||||||
|
|||||||
@ -22,6 +22,6 @@ static struct mempool pool_name = { \
|
|||||||
.at_least = alloc_at_least, \
|
.at_least = alloc_at_least, \
|
||||||
}
|
}
|
||||||
|
|
||||||
__attribute__((weak)) bool mempool_enabled(void);
|
bool mempool_enabled(void) _weak_ _pure_;
|
||||||
|
|
||||||
void mempool_trim(struct mempool *mp);
|
void mempool_trim(struct mempool *mp);
|
||||||
|
|||||||
@ -366,6 +366,18 @@
|
|||||||
send_interface="org.freedesktop.systemd1.Manager"
|
send_interface="org.freedesktop.systemd1.Manager"
|
||||||
send_member="SetShowStatus"/>
|
send_member="SetShowStatus"/>
|
||||||
|
|
||||||
|
<allow send_destination="org.freedesktop.systemd1"
|
||||||
|
send_interface="org.freedesktop.systemd1.Manager"
|
||||||
|
send_member="SetEnvironment"/>
|
||||||
|
|
||||||
|
<allow send_destination="org.freedesktop.systemd1"
|
||||||
|
send_interface="org.freedesktop.systemd1.Manager"
|
||||||
|
send_member="UnsetEnvironment"/>
|
||||||
|
|
||||||
|
<allow send_destination="org.freedesktop.systemd1"
|
||||||
|
send_interface="org.freedesktop.systemd1.Manager"
|
||||||
|
send_member="UnsetAndSetEnvironment"/>
|
||||||
|
|
||||||
<!-- Managed via polkit or other criteria: org.freedesktop.systemd1.Job interface -->
|
<!-- Managed via polkit or other criteria: org.freedesktop.systemd1.Job interface -->
|
||||||
|
|
||||||
<allow send_destination="org.freedesktop.systemd1"
|
<allow send_destination="org.freedesktop.systemd1"
|
||||||
|
|||||||
73
src/coredump/coredump-backtrace.c
Normal file
73
src/coredump/coredump-backtrace.c
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||||
|
|
||||||
|
#include "sd-journal.h"
|
||||||
|
#include "sd-messages.h"
|
||||||
|
|
||||||
|
#include "coredump-backtrace.h"
|
||||||
|
#include "coredump-context.h"
|
||||||
|
#include "iovec-util.h"
|
||||||
|
#include "journal-importer.h"
|
||||||
|
#include "log.h"
|
||||||
|
#include "string-util.h"
|
||||||
|
|
||||||
|
int coredump_backtrace(int argc, char *argv[]) {
|
||||||
|
_cleanup_(journal_importer_cleanup) JournalImporter importer = JOURNAL_IMPORTER_INIT(STDIN_FILENO);
|
||||||
|
_cleanup_(iovw_free_freep) struct iovec_wrapper *iovw = NULL;
|
||||||
|
_cleanup_(context_done) Context context = CONTEXT_NULL;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(argc >= 2);
|
||||||
|
|
||||||
|
log_debug("Processing backtrace on stdin...");
|
||||||
|
|
||||||
|
iovw = iovw_new();
|
||||||
|
if (!iovw)
|
||||||
|
return log_oom();
|
||||||
|
|
||||||
|
(void) iovw_put_string_field(iovw, "MESSAGE_ID=", SD_MESSAGE_BACKTRACE_STR);
|
||||||
|
(void) iovw_put_string_field(iovw, "PRIORITY=", STRINGIFY(LOG_CRIT));
|
||||||
|
|
||||||
|
/* Collect all process metadata from argv[] by making sure to skip the '--backtrace' option. */
|
||||||
|
r = gather_pid_metadata_from_argv(iovw, &context, argc - 2, argv + 2);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
/* Collect the rest of the process metadata retrieved from the runtime */
|
||||||
|
r = gather_pid_metadata_from_procfs(iovw, &context);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
r = journal_importer_process_data(&importer);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to parse journal entry on stdin: %m");
|
||||||
|
if (r == 1 || /* complete entry */
|
||||||
|
journal_importer_eof(&importer)) /* end of data */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (journal_importer_eof(&importer)) {
|
||||||
|
log_warning("Did not receive a full journal entry on stdin, ignoring message sent by reporter.");
|
||||||
|
|
||||||
|
const char *message = strjoina("Process ", context.meta[META_ARGV_PID],
|
||||||
|
" (", context.meta[META_COMM], ")"
|
||||||
|
" of user ", context.meta[META_ARGV_UID],
|
||||||
|
" failed with ", context.meta[META_ARGV_SIGNAL]);
|
||||||
|
|
||||||
|
r = iovw_put_string_field(iovw, "MESSAGE=", message);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
} else {
|
||||||
|
/* The imported iovecs are not supposed to be freed by us so let's copy and merge them at the
|
||||||
|
* end of the array. */
|
||||||
|
r = iovw_append(iovw, &importer.iovw);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
r = sd_journal_sendv(iovw->iovec, iovw->count);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to log backtrace: %m");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
4
src/coredump/coredump-backtrace.h
Normal file
4
src/coredump/coredump-backtrace.h
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
int coredump_backtrace(int argc, char *argv[]);
|
||||||
83
src/coredump/coredump-config.c
Normal file
83
src/coredump/coredump-config.c
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||||
|
|
||||||
|
#include "conf-parser.h"
|
||||||
|
#include "coredump-config.h"
|
||||||
|
#include "format-util.h"
|
||||||
|
#include "journal-importer.h"
|
||||||
|
#include "log.h"
|
||||||
|
#include "string-table.h"
|
||||||
|
#include "string-util.h"
|
||||||
|
|
||||||
|
/* Make sure to not make this larger than the maximum journal entry
|
||||||
|
* size. See DATA_SIZE_MAX in journal-importer.h. */
|
||||||
|
assert_cc(JOURNAL_SIZE_MAX <= DATA_SIZE_MAX);
|
||||||
|
|
||||||
|
CoredumpStorage arg_storage = COREDUMP_STORAGE_EXTERNAL;
|
||||||
|
bool arg_compress = true;
|
||||||
|
uint64_t arg_process_size_max = PROCESS_SIZE_MAX;
|
||||||
|
uint64_t arg_external_size_max = EXTERNAL_SIZE_MAX;
|
||||||
|
uint64_t arg_journal_size_max = JOURNAL_SIZE_MAX;
|
||||||
|
uint64_t arg_keep_free = UINT64_MAX;
|
||||||
|
uint64_t arg_max_use = UINT64_MAX;
|
||||||
|
bool arg_enter_namespace = false;
|
||||||
|
|
||||||
|
static const char* const coredump_storage_table[_COREDUMP_STORAGE_MAX] = {
|
||||||
|
[COREDUMP_STORAGE_NONE] = "none",
|
||||||
|
[COREDUMP_STORAGE_EXTERNAL] = "external",
|
||||||
|
[COREDUMP_STORAGE_JOURNAL] = "journal",
|
||||||
|
};
|
||||||
|
|
||||||
|
DEFINE_PRIVATE_STRING_TABLE_LOOKUP(coredump_storage, CoredumpStorage);
|
||||||
|
static DEFINE_CONFIG_PARSE_ENUM(config_parse_coredump_storage, coredump_storage, CoredumpStorage);
|
||||||
|
|
||||||
|
int coredump_parse_config(void) {
|
||||||
|
static const ConfigTableItem items[] = {
|
||||||
|
{ "Coredump", "Storage", config_parse_coredump_storage, 0, &arg_storage },
|
||||||
|
{ "Coredump", "Compress", config_parse_bool, 0, &arg_compress },
|
||||||
|
{ "Coredump", "ProcessSizeMax", config_parse_iec_uint64, 0, &arg_process_size_max },
|
||||||
|
{ "Coredump", "ExternalSizeMax", config_parse_iec_uint64_infinity, 0, &arg_external_size_max },
|
||||||
|
{ "Coredump", "JournalSizeMax", config_parse_iec_size, 0, &arg_journal_size_max },
|
||||||
|
{ "Coredump", "KeepFree", config_parse_iec_uint64, 0, &arg_keep_free },
|
||||||
|
{ "Coredump", "MaxUse", config_parse_iec_uint64, 0, &arg_max_use },
|
||||||
|
#if HAVE_DWFL_SET_SYSROOT
|
||||||
|
{ "Coredump", "EnterNamespace", config_parse_bool, 0, &arg_enter_namespace },
|
||||||
|
#else
|
||||||
|
{ "Coredump", "EnterNamespace", config_parse_warn_compat, DISABLED_CONFIGURATION, NULL },
|
||||||
|
#endif
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
int r;
|
||||||
|
|
||||||
|
r = config_parse_standard_file_with_dropins(
|
||||||
|
"systemd/coredump.conf",
|
||||||
|
"Coredump\0",
|
||||||
|
config_item_table_lookup,
|
||||||
|
items,
|
||||||
|
CONFIG_PARSE_WARN,
|
||||||
|
/* userdata= */ NULL);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
/* Let's make sure we fix up the maximum size we send to the journal here on the client side, for
|
||||||
|
* efficiency reasons. journald wouldn't accept anything larger anyway. */
|
||||||
|
if (arg_journal_size_max > JOURNAL_SIZE_MAX) {
|
||||||
|
log_warning("JournalSizeMax= set to larger value (%s) than journald would accept (%s), lowering automatically.",
|
||||||
|
FORMAT_BYTES(arg_journal_size_max), FORMAT_BYTES(JOURNAL_SIZE_MAX));
|
||||||
|
arg_journal_size_max = JOURNAL_SIZE_MAX;
|
||||||
|
}
|
||||||
|
|
||||||
|
log_debug("Selected storage '%s'.", coredump_storage_to_string(arg_storage));
|
||||||
|
log_debug("Selected compression %s.", yes_no(arg_compress));
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t coredump_storage_size_max(void) {
|
||||||
|
if (arg_storage == COREDUMP_STORAGE_EXTERNAL)
|
||||||
|
return arg_external_size_max;
|
||||||
|
if (arg_storage == COREDUMP_STORAGE_JOURNAL)
|
||||||
|
return arg_journal_size_max;
|
||||||
|
assert(arg_storage == COREDUMP_STORAGE_NONE);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
44
src/coredump/coredump-config.h
Normal file
44
src/coredump/coredump-config.h
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "basic-forward.h"
|
||||||
|
|
||||||
|
/* The maximum size up to which we process coredumps. We use 1G on 32-bit systems, and 32G on 64-bit systems */
|
||||||
|
#if __SIZEOF_POINTER__ == 4
|
||||||
|
#define PROCESS_SIZE_MAX ((uint64_t) (1LLU*1024LLU*1024LLU*1024LLU))
|
||||||
|
#elif __SIZEOF_POINTER__ == 8
|
||||||
|
#define PROCESS_SIZE_MAX ((uint64_t) (32LLU*1024LLU*1024LLU*1024LLU))
|
||||||
|
#else
|
||||||
|
#error "Unexpected pointer size"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* The maximum size up to which we leave the coredump around on disk */
|
||||||
|
#define EXTERNAL_SIZE_MAX PROCESS_SIZE_MAX
|
||||||
|
|
||||||
|
/* The maximum size up to which we store the coredump in the journal */
|
||||||
|
#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
|
||||||
|
#define JOURNAL_SIZE_MAX ((size_t) (767LU*1024LU*1024LU))
|
||||||
|
#else
|
||||||
|
/* oss-fuzz limits memory usage. */
|
||||||
|
#define JOURNAL_SIZE_MAX ((size_t) (10LU*1024LU*1024LU))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef enum CoredumpStorage {
|
||||||
|
COREDUMP_STORAGE_NONE,
|
||||||
|
COREDUMP_STORAGE_EXTERNAL,
|
||||||
|
COREDUMP_STORAGE_JOURNAL,
|
||||||
|
_COREDUMP_STORAGE_MAX,
|
||||||
|
_COREDUMP_STORAGE_INVALID = -EINVAL,
|
||||||
|
} CoredumpStorage;
|
||||||
|
|
||||||
|
extern CoredumpStorage arg_storage;
|
||||||
|
extern bool arg_compress;
|
||||||
|
extern uint64_t arg_process_size_max;
|
||||||
|
extern uint64_t arg_external_size_max;
|
||||||
|
extern uint64_t arg_journal_size_max;
|
||||||
|
extern uint64_t arg_keep_free;
|
||||||
|
extern uint64_t arg_max_use;
|
||||||
|
extern bool arg_enter_namespace;
|
||||||
|
|
||||||
|
int coredump_parse_config(void);
|
||||||
|
uint64_t coredump_storage_size_max(void);
|
||||||
454
src/coredump/coredump-context.c
Normal file
454
src/coredump/coredump-context.c
Normal file
@ -0,0 +1,454 @@
|
|||||||
|
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||||
|
|
||||||
|
#include "sd-login.h"
|
||||||
|
|
||||||
|
#include "coredump-config.h"
|
||||||
|
#include "coredump-context.h"
|
||||||
|
#include "coredump-util.h"
|
||||||
|
#include "dirent-util.h"
|
||||||
|
#include "fd-util.h"
|
||||||
|
#include "fs-util.h"
|
||||||
|
#include "iovec-wrapper.h"
|
||||||
|
#include "log.h"
|
||||||
|
#include "memstream-util.h"
|
||||||
|
#include "namespace-util.h"
|
||||||
|
#include "parse-util.h"
|
||||||
|
#include "process-util.h"
|
||||||
|
#include "signal-util.h"
|
||||||
|
#include "special.h"
|
||||||
|
#include "string-util.h"
|
||||||
|
#include "user-util.h"
|
||||||
|
|
||||||
|
const char * const meta_field_names[_META_MAX] = {
|
||||||
|
[META_ARGV_PID] = "COREDUMP_PID=",
|
||||||
|
[META_ARGV_UID] = "COREDUMP_UID=",
|
||||||
|
[META_ARGV_GID] = "COREDUMP_GID=",
|
||||||
|
[META_ARGV_SIGNAL] = "COREDUMP_SIGNAL=",
|
||||||
|
[META_ARGV_TIMESTAMP] = "COREDUMP_TIMESTAMP=",
|
||||||
|
[META_ARGV_RLIMIT] = "COREDUMP_RLIMIT=",
|
||||||
|
[META_ARGV_HOSTNAME] = "COREDUMP_HOSTNAME=",
|
||||||
|
[META_ARGV_DUMPABLE] = "COREDUMP_DUMPABLE=",
|
||||||
|
[META_ARGV_PIDFD] = "COREDUMP_BY_PIDFD=",
|
||||||
|
[META_COMM] = "COREDUMP_COMM=",
|
||||||
|
[META_EXE] = "COREDUMP_EXE=",
|
||||||
|
[META_UNIT] = "COREDUMP_UNIT=",
|
||||||
|
[META_PROC_AUXV] = "COREDUMP_PROC_AUXV=",
|
||||||
|
};
|
||||||
|
|
||||||
|
void context_done(Context *c) {
|
||||||
|
assert(c);
|
||||||
|
|
||||||
|
pidref_done(&c->pidref);
|
||||||
|
c->mount_tree_fd = safe_close(c->mount_tree_fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Joins /proc/[pid]/fd/ and /proc/[pid]/fdinfo/ into the following lines:
|
||||||
|
* 0:/dev/pts/23
|
||||||
|
* pos: 0
|
||||||
|
* flags: 0100002
|
||||||
|
*
|
||||||
|
* 1:/dev/pts/23
|
||||||
|
* pos: 0
|
||||||
|
* flags: 0100002
|
||||||
|
*
|
||||||
|
* 2:/dev/pts/23
|
||||||
|
* pos: 0
|
||||||
|
* flags: 0100002
|
||||||
|
* EOF
|
||||||
|
*/
|
||||||
|
static int compose_open_fds(pid_t pid, char **ret) {
|
||||||
|
_cleanup_(memstream_done) MemStream m = {};
|
||||||
|
_cleanup_closedir_ DIR *proc_fd_dir = NULL;
|
||||||
|
_cleanup_close_ int proc_fdinfo_fd = -EBADF;
|
||||||
|
const char *fddelim = "", *path;
|
||||||
|
FILE *stream;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(pid >= 0);
|
||||||
|
assert(ret);
|
||||||
|
|
||||||
|
path = procfs_file_alloca(pid, "fd");
|
||||||
|
proc_fd_dir = opendir(path);
|
||||||
|
if (!proc_fd_dir)
|
||||||
|
return -errno;
|
||||||
|
|
||||||
|
proc_fdinfo_fd = openat(dirfd(proc_fd_dir), "../fdinfo", O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC|O_PATH);
|
||||||
|
if (proc_fdinfo_fd < 0)
|
||||||
|
return -errno;
|
||||||
|
|
||||||
|
stream = memstream_init(&m);
|
||||||
|
if (!stream)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
FOREACH_DIRENT(de, proc_fd_dir, return -errno) {
|
||||||
|
_cleanup_fclose_ FILE *fdinfo = NULL;
|
||||||
|
_cleanup_free_ char *fdname = NULL;
|
||||||
|
_cleanup_close_ int fd = -EBADF;
|
||||||
|
|
||||||
|
r = readlinkat_malloc(dirfd(proc_fd_dir), de->d_name, &fdname);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
fprintf(stream, "%s%s:%s\n", fddelim, de->d_name, fdname);
|
||||||
|
fddelim = "\n";
|
||||||
|
|
||||||
|
/* Use the directory entry from /proc/[pid]/fd with /proc/[pid]/fdinfo */
|
||||||
|
fd = openat(proc_fdinfo_fd, de->d_name, O_NOFOLLOW|O_CLOEXEC|O_RDONLY);
|
||||||
|
if (fd < 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
fdinfo = take_fdopen(&fd, "r");
|
||||||
|
if (!fdinfo)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
_cleanup_free_ char *line = NULL;
|
||||||
|
|
||||||
|
r = read_line(fdinfo, LONG_LINE_MAX, &line);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
if (r == 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
fputs(line, stream);
|
||||||
|
fputc('\n', stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return memstream_finalize(&m, ret, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Returns 1 if the parent was found.
|
||||||
|
* Returns 0 if there is not a process we can call the pid's
|
||||||
|
* container parent (the pid's process isn't 'containerized').
|
||||||
|
* Returns a negative number on errors.
|
||||||
|
*/
|
||||||
|
static int get_process_container_parent_cmdline(PidRef *pid, char** ret_cmdline) {
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(pidref_is_set(pid));
|
||||||
|
assert(!pidref_is_remote(pid));
|
||||||
|
|
||||||
|
r = pidref_from_same_root_fs(pid, &PIDREF_MAKE_FROM_PID(1));
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
if (r > 0) {
|
||||||
|
/* The process uses system root. */
|
||||||
|
*ret_cmdline = NULL;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
_cleanup_(pidref_done) PidRef container_pid = PIDREF_NULL;
|
||||||
|
r = namespace_get_leader(pid, NAMESPACE_MOUNT, &container_pid);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
r = pidref_get_cmdline(&container_pid, SIZE_MAX, PROCESS_CMDLINE_QUOTE_POSIX, ret_cmdline);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int gather_pid_metadata_from_procfs(struct iovec_wrapper *iovw, Context *context) {
|
||||||
|
char *t;
|
||||||
|
size_t size;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(iovw);
|
||||||
|
assert(context);
|
||||||
|
|
||||||
|
/* Note that if we fail on oom later on, we do not roll-back changes to the iovec
|
||||||
|
* structure. (It remains valid, with the first iovec fields initialized.) */
|
||||||
|
|
||||||
|
pid_t pid = context->pidref.pid;
|
||||||
|
|
||||||
|
/* The following is mandatory */
|
||||||
|
r = pidref_get_comm(&context->pidref, &t);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to get COMM: %m");
|
||||||
|
|
||||||
|
r = iovw_put_string_field_free(iovw, "COREDUMP_COMM=", t);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
/* The following are optional, but we use them if present. */
|
||||||
|
r = get_process_exe(pid, &t);
|
||||||
|
if (r >= 0)
|
||||||
|
r = iovw_put_string_field_free(iovw, "COREDUMP_EXE=", t);
|
||||||
|
if (r < 0)
|
||||||
|
log_warning_errno(r, "Failed to get EXE, ignoring: %m");
|
||||||
|
|
||||||
|
if (cg_pidref_get_unit(&context->pidref, &t) >= 0)
|
||||||
|
(void) iovw_put_string_field_free(iovw, "COREDUMP_UNIT=", t);
|
||||||
|
|
||||||
|
if (cg_pidref_get_user_unit(&context->pidref, &t) >= 0)
|
||||||
|
(void) iovw_put_string_field_free(iovw, "COREDUMP_USER_UNIT=", t);
|
||||||
|
|
||||||
|
if (cg_pidref_get_session(&context->pidref, &t) >= 0)
|
||||||
|
(void) iovw_put_string_field_free(iovw, "COREDUMP_SESSION=", t);
|
||||||
|
|
||||||
|
uid_t owner_uid;
|
||||||
|
if (cg_pidref_get_owner_uid(&context->pidref, &owner_uid) >= 0) {
|
||||||
|
r = asprintf(&t, UID_FMT, owner_uid);
|
||||||
|
if (r > 0)
|
||||||
|
(void) iovw_put_string_field_free(iovw, "COREDUMP_OWNER_UID=", t);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sd_pid_get_slice(pid, &t) >= 0)
|
||||||
|
(void) iovw_put_string_field_free(iovw, "COREDUMP_SLICE=", t);
|
||||||
|
|
||||||
|
if (pidref_get_cmdline(&context->pidref, SIZE_MAX, PROCESS_CMDLINE_QUOTE_POSIX, &t) >= 0)
|
||||||
|
(void) iovw_put_string_field_free(iovw, "COREDUMP_CMDLINE=", t);
|
||||||
|
|
||||||
|
if (cg_pid_get_path_shifted(pid, NULL, &t) >= 0)
|
||||||
|
(void) iovw_put_string_field_free(iovw, "COREDUMP_CGROUP=", t);
|
||||||
|
|
||||||
|
if (compose_open_fds(pid, &t) >= 0)
|
||||||
|
(void) iovw_put_string_field_free(iovw, "COREDUMP_OPEN_FDS=", t);
|
||||||
|
|
||||||
|
if (read_full_file(procfs_file_alloca(pid, "status"), &t, /* ret_size= */ NULL) >= 0)
|
||||||
|
(void) iovw_put_string_field_free(iovw, "COREDUMP_PROC_STATUS=", t);
|
||||||
|
|
||||||
|
if (read_full_file(procfs_file_alloca(pid, "maps"), &t, /* ret_size= */ NULL) >= 0)
|
||||||
|
(void) iovw_put_string_field_free(iovw, "COREDUMP_PROC_MAPS=", t);
|
||||||
|
|
||||||
|
if (read_full_file(procfs_file_alloca(pid, "limits"), &t, /* ret_size= */ NULL) >= 0)
|
||||||
|
(void) iovw_put_string_field_free(iovw, "COREDUMP_PROC_LIMITS=", t);
|
||||||
|
|
||||||
|
if (read_full_file(procfs_file_alloca(pid, "cgroup"), &t, /* ret_size= */ NULL) >= 0)
|
||||||
|
(void) iovw_put_string_field_free(iovw, "COREDUMP_PROC_CGROUP=", t);
|
||||||
|
|
||||||
|
if (read_full_file(procfs_file_alloca(pid, "mountinfo"), &t, /* ret_size= */ NULL) >= 0)
|
||||||
|
(void) iovw_put_string_field_free(iovw, "COREDUMP_PROC_MOUNTINFO=", t);
|
||||||
|
|
||||||
|
/* We attach /proc/auxv here. ELF coredumps also contain a note for this (NT_AUXV), see elf(5). */
|
||||||
|
if (read_full_file(procfs_file_alloca(pid, "auxv"), &t, &size) >= 0) {
|
||||||
|
char *buf = malloc(strlen("COREDUMP_PROC_AUXV=") + size + 1);
|
||||||
|
if (buf) {
|
||||||
|
/* Add a dummy terminator to make context_parse_iovw() happy. */
|
||||||
|
*mempcpy_typesafe(stpcpy(buf, "COREDUMP_PROC_AUXV="), t, size) = '\0';
|
||||||
|
(void) iovw_consume(iovw, buf, size + strlen("COREDUMP_PROC_AUXV="));
|
||||||
|
}
|
||||||
|
|
||||||
|
free(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (get_process_cwd(pid, &t) >= 0)
|
||||||
|
(void) iovw_put_string_field_free(iovw, "COREDUMP_CWD=", t);
|
||||||
|
|
||||||
|
if (get_process_root(pid, &t) >= 0) {
|
||||||
|
bool proc_self_root_is_slash;
|
||||||
|
|
||||||
|
proc_self_root_is_slash = strcmp(t, "/") == 0;
|
||||||
|
|
||||||
|
(void) iovw_put_string_field_free(iovw, "COREDUMP_ROOT=", t);
|
||||||
|
|
||||||
|
/* If the process' root is "/", then there is a chance it has
|
||||||
|
* mounted own root and hence being containerized. */
|
||||||
|
if (proc_self_root_is_slash && get_process_container_parent_cmdline(&context->pidref, &t) > 0)
|
||||||
|
(void) iovw_put_string_field_free(iovw, "COREDUMP_CONTAINER_CMDLINE=", t);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (get_process_environ(pid, &t) >= 0)
|
||||||
|
(void) iovw_put_string_field_free(iovw, "COREDUMP_ENVIRON=", t);
|
||||||
|
|
||||||
|
/* Now that we have parsed info from /proc/ ensure the pidfd is still valid before continuing. */
|
||||||
|
r = pidref_verify(&context->pidref);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "PIDFD validation failed: %m");
|
||||||
|
|
||||||
|
/* We successfully acquired all metadata. */
|
||||||
|
return context_parse_iovw(context, iovw);
|
||||||
|
}
|
||||||
|
|
||||||
|
int context_parse_iovw(Context *context, struct iovec_wrapper *iovw) {
|
||||||
|
const char *unit;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(context);
|
||||||
|
assert(iovw);
|
||||||
|
|
||||||
|
/* Converts the data in the iovec array iovw into separate fields. Fills in context->meta[] (for
|
||||||
|
* which no memory is allocated, it just contains direct pointers into the iovec array memory). */
|
||||||
|
|
||||||
|
bool have_signal_name = false;
|
||||||
|
FOREACH_ARRAY(iovec, iovw->iovec, iovw->count) {
|
||||||
|
/* Note that these strings are NUL-terminated, because we made sure that a trailing NUL byte
|
||||||
|
* is in the buffer, though not included in the iov_len count. See coredump_receive() and
|
||||||
|
* gather_pid_metadata_*(). */
|
||||||
|
assert(((char*) iovec->iov_base)[iovec->iov_len] == 0);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < ELEMENTSOF(meta_field_names); i++) {
|
||||||
|
const char *p = memory_startswith(iovec->iov_base, iovec->iov_len, meta_field_names[i]);
|
||||||
|
if (p) {
|
||||||
|
context->meta[i] = p;
|
||||||
|
context->meta_size[i] = iovec->iov_len - strlen(meta_field_names[i]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
have_signal_name = have_signal_name ||
|
||||||
|
memory_startswith(iovec->iov_base, iovec->iov_len, "COREDUMP_SIGNAL_NAME=");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The basic fields from argv[] should always be there, refuse early if not. */
|
||||||
|
for (int i = 0; i < _META_ARGV_REQUIRED; i++)
|
||||||
|
if (!context->meta[i])
|
||||||
|
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||||
|
"A required (%s) has not been sent, aborting.", meta_field_names[i]);
|
||||||
|
|
||||||
|
pid_t parsed_pid;
|
||||||
|
r = parse_pid(context->meta[META_ARGV_PID], &parsed_pid);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to parse PID \"%s\": %m", context->meta[META_ARGV_PID]);
|
||||||
|
if (pidref_is_set(&context->pidref)) {
|
||||||
|
if (context->pidref.pid != parsed_pid)
|
||||||
|
return log_error_errno(r, "Passed PID " PID_FMT " does not match passed " PID_FMT ": %m",
|
||||||
|
parsed_pid, context->pidref.pid);
|
||||||
|
} else {
|
||||||
|
r = pidref_set_pid(&context->pidref, parsed_pid);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to initialize pidref from pid " PID_FMT ": %m", parsed_pid);
|
||||||
|
}
|
||||||
|
|
||||||
|
r = parse_uid(context->meta[META_ARGV_UID], &context->uid);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to parse UID \"%s\": %m", context->meta[META_ARGV_UID]);
|
||||||
|
|
||||||
|
r = parse_gid(context->meta[META_ARGV_GID], &context->gid);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to parse GID \"%s\": %m", context->meta[META_ARGV_GID]);
|
||||||
|
|
||||||
|
r = parse_signo(context->meta[META_ARGV_SIGNAL], &context->signo);
|
||||||
|
if (r < 0)
|
||||||
|
log_warning_errno(r, "Failed to parse signal number \"%s\", ignoring: %m", context->meta[META_ARGV_SIGNAL]);
|
||||||
|
|
||||||
|
r = safe_atou64(context->meta[META_ARGV_RLIMIT], &context->rlimit);
|
||||||
|
if (r < 0)
|
||||||
|
log_warning_errno(r, "Failed to parse resource limit \"%s\", ignoring: %m", context->meta[META_ARGV_RLIMIT]);
|
||||||
|
|
||||||
|
/* The value is set to contents of /proc/sys/fs/suid_dumpable, which we set to SUID_DUMP_SAFE (2),
|
||||||
|
* if the process is marked as not dumpable, see PR_SET_DUMPABLE(2const). */
|
||||||
|
if (context->meta[META_ARGV_DUMPABLE]) {
|
||||||
|
r = safe_atou(context->meta[META_ARGV_DUMPABLE], &context->dumpable);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to parse dumpable field \"%s\": %m", context->meta[META_ARGV_DUMPABLE]);
|
||||||
|
if (context->dumpable > SUID_DUMP_SAFE)
|
||||||
|
log_notice("Got unexpected %%d/dumpable value %u.", context->dumpable);
|
||||||
|
}
|
||||||
|
|
||||||
|
unit = context->meta[META_UNIT];
|
||||||
|
context->is_pid1 = streq(context->meta[META_ARGV_PID], "1") || streq_ptr(unit, SPECIAL_INIT_SCOPE);
|
||||||
|
context->is_journald = streq_ptr(unit, SPECIAL_JOURNALD_SERVICE);
|
||||||
|
|
||||||
|
/* After parsing everything, let's also synthesize a new iovw field for the textual signal name if it
|
||||||
|
* isn't already set. */
|
||||||
|
if (SIGNAL_VALID(context->signo) && !have_signal_name)
|
||||||
|
(void) iovw_put_string_field(iovw, "COREDUMP_SIGNAL_NAME=SIG", signal_to_string(context->signo));
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int gather_pid_metadata_from_argv(
|
||||||
|
struct iovec_wrapper *iovw,
|
||||||
|
Context *context,
|
||||||
|
int argc, char **argv) {
|
||||||
|
|
||||||
|
_cleanup_(pidref_done) PidRef local_pidref = PIDREF_NULL;
|
||||||
|
int r, kernel_fd = -EBADF;
|
||||||
|
|
||||||
|
assert(iovw);
|
||||||
|
assert(context);
|
||||||
|
|
||||||
|
/* We gather all metadata that were passed via argv[] into an array of iovecs that
|
||||||
|
* we'll forward to the socket unit.
|
||||||
|
*
|
||||||
|
* We require at least _META_ARGV_REQUIRED args, but will accept more.
|
||||||
|
* We know how to parse _META_ARGV_MAX args. The rest will be ignored. */
|
||||||
|
|
||||||
|
if (argc < _META_ARGV_REQUIRED)
|
||||||
|
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||||
|
"Not enough arguments passed by the kernel (%i, expected between %i and %i).",
|
||||||
|
argc, _META_ARGV_REQUIRED, _META_ARGV_MAX);
|
||||||
|
|
||||||
|
for (int i = 0; i < MIN(argc, _META_ARGV_MAX); i++) {
|
||||||
|
_cleanup_free_ char *buf = NULL;
|
||||||
|
const char *t = argv[i];
|
||||||
|
|
||||||
|
if (i == META_ARGV_TIMESTAMP) {
|
||||||
|
/* The journal fields contain the timestamp padded with six
|
||||||
|
* zeroes, so that the kernel-supplied 1s granularity timestamps
|
||||||
|
* becomes 1μs granularity, i.e. the granularity systemd usually
|
||||||
|
* operates in. */
|
||||||
|
buf = strjoin(argv[i], "000000");
|
||||||
|
if (!buf)
|
||||||
|
return log_oom();
|
||||||
|
|
||||||
|
t = buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i == META_ARGV_PID) {
|
||||||
|
/* Store this so that we can check whether the core will be forwarded to a container
|
||||||
|
* even when the kernel doesn't provide a pidfd. Can be dropped once baseline is
|
||||||
|
* >= v6.16. */
|
||||||
|
r = pidref_set_pidstr(&local_pidref, t);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to initialize pidref from pid %s: %m", t);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i == META_ARGV_PIDFD) {
|
||||||
|
/* If the current kernel doesn't support the %F specifier (which resolves to a
|
||||||
|
* pidfd), but we included it in the core_pattern expression, we'll receive an empty
|
||||||
|
* string here. Deal with that gracefully. */
|
||||||
|
if (isempty(t))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
assert(!pidref_is_set(&context->pidref));
|
||||||
|
assert(kernel_fd < 0);
|
||||||
|
|
||||||
|
kernel_fd = parse_fd(t);
|
||||||
|
if (kernel_fd < 0)
|
||||||
|
return log_error_errno(kernel_fd, "Failed to parse pidfd \"%s\": %m", t);
|
||||||
|
|
||||||
|
r = pidref_set_pidfd(&context->pidref, kernel_fd);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to initialize pidref from pidfd %d: %m", kernel_fd);
|
||||||
|
|
||||||
|
context->got_pidfd = 1;
|
||||||
|
|
||||||
|
/* If there are containers involved with different versions of the code they might
|
||||||
|
* not be using pidfds, so it would be wrong to set the metadata, skip it. */
|
||||||
|
r = pidref_in_same_namespace(/* pid1 = */ NULL, &context->pidref, NAMESPACE_PID);
|
||||||
|
if (r < 0)
|
||||||
|
log_debug_errno(r, "Failed to check pidns of crashing process, ignoring: %m");
|
||||||
|
if (r <= 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/* We don't print the fd number in the journal as it's meaningless, but we still
|
||||||
|
* record that the parsing was done with a kernel-provided fd as it means it's safe
|
||||||
|
* from races, which is valuable information to provide in the journal record. */
|
||||||
|
t = "1";
|
||||||
|
}
|
||||||
|
|
||||||
|
r = iovw_put_string_field(iovw, meta_field_names[i], t);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Cache some of the process metadata we collected so far and that we'll need to
|
||||||
|
* access soon. */
|
||||||
|
r = context_parse_iovw(context, iovw);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
/* If the kernel didn't give us a PIDFD, then use the one derived from the
|
||||||
|
* PID immediately, given we have it. */
|
||||||
|
if (!pidref_is_set(&context->pidref))
|
||||||
|
context->pidref = TAKE_PIDREF(local_pidref);
|
||||||
|
|
||||||
|
/* Close the kernel-provided FD as the last thing after everything else succeeded. */
|
||||||
|
kernel_fd = safe_close(kernel_fd);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
75
src/coredump/coredump-context.h
Normal file
75
src/coredump/coredump-context.h
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "coredump-forward.h"
|
||||||
|
#include "pidref.h"
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
/* We use these as array indexes for our process metadata cache.
|
||||||
|
*
|
||||||
|
* The first indices of the cache stores the same metadata as the ones passed by the kernel via
|
||||||
|
* argv[], i.e. the strings specified in our pattern defined in /proc/sys/kernel/core_pattern,
|
||||||
|
* see core(5). */
|
||||||
|
|
||||||
|
META_ARGV_PID, /* %P: as seen in the initial pid namespace */
|
||||||
|
META_ARGV_UID, /* %u: as seen in the initial user namespace */
|
||||||
|
META_ARGV_GID, /* %g: as seen in the initial user namespace */
|
||||||
|
META_ARGV_SIGNAL, /* %s: number of signal causing dump */
|
||||||
|
META_ARGV_TIMESTAMP, /* %t: time of dump, expressed as seconds since the Epoch (we expand this to μs granularity) */
|
||||||
|
META_ARGV_RLIMIT, /* %c: core file size soft resource limit */
|
||||||
|
_META_ARGV_REQUIRED,
|
||||||
|
/* The fields below were added to kernel/core_pattern at later points, so they might be missing. */
|
||||||
|
META_ARGV_HOSTNAME = _META_ARGV_REQUIRED, /* %h: hostname */
|
||||||
|
META_ARGV_DUMPABLE, /* %d: as set by the kernel */
|
||||||
|
META_ARGV_PIDFD, /* %F: pidfd of the process, since v6.16 */
|
||||||
|
/* If new fields are added, they should be added here, to maintain compatibility
|
||||||
|
* with callers which don't know about the new fields. */
|
||||||
|
_META_ARGV_MAX,
|
||||||
|
|
||||||
|
/* The following indexes are cached for a couple of special fields we use (and
|
||||||
|
* thereby need to be retrieved quickly) for naming coredump files, and attaching
|
||||||
|
* xattrs. Unlike the previous ones they are retrieved from the runtime
|
||||||
|
* environment. */
|
||||||
|
|
||||||
|
META_COMM = _META_ARGV_MAX,
|
||||||
|
|
||||||
|
/* The rest are similar to the previous ones except that we won't fail if one of
|
||||||
|
* them is missing in a message sent over the socket. */
|
||||||
|
|
||||||
|
META_EXE,
|
||||||
|
META_UNIT,
|
||||||
|
META_PROC_AUXV,
|
||||||
|
_META_MAX
|
||||||
|
} meta_argv_t;
|
||||||
|
|
||||||
|
extern const char * const meta_field_names[_META_MAX];
|
||||||
|
|
||||||
|
struct Context {
|
||||||
|
PidRef pidref;
|
||||||
|
uid_t uid;
|
||||||
|
gid_t gid;
|
||||||
|
unsigned dumpable;
|
||||||
|
int signo;
|
||||||
|
uint64_t rlimit;
|
||||||
|
bool is_pid1;
|
||||||
|
bool is_journald;
|
||||||
|
bool got_pidfd;
|
||||||
|
int mount_tree_fd;
|
||||||
|
|
||||||
|
/* These point into external memory, are not owned by this object */
|
||||||
|
const char *meta[_META_MAX];
|
||||||
|
size_t meta_size[_META_MAX];
|
||||||
|
};
|
||||||
|
|
||||||
|
#define CONTEXT_NULL \
|
||||||
|
(Context) { \
|
||||||
|
.pidref = PIDREF_NULL, \
|
||||||
|
.uid = UID_INVALID, \
|
||||||
|
.gid = GID_INVALID, \
|
||||||
|
.mount_tree_fd = -EBADF, \
|
||||||
|
}
|
||||||
|
|
||||||
|
void context_done(Context *c);
|
||||||
|
int context_parse_iovw(Context *context, struct iovec_wrapper *iovw);
|
||||||
|
int gather_pid_metadata_from_argv(struct iovec_wrapper *iovw, Context *context, int argc, char **argv);
|
||||||
|
int gather_pid_metadata_from_procfs(struct iovec_wrapper *iovw, Context *context);
|
||||||
6
src/coredump/coredump-forward.h
Normal file
6
src/coredump/coredump-forward.h
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "basic-forward.h"
|
||||||
|
|
||||||
|
typedef struct Context Context;
|
||||||
87
src/coredump/coredump-kernel-helper.c
Normal file
87
src/coredump/coredump-kernel-helper.c
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||||
|
|
||||||
|
#include "sd-messages.h"
|
||||||
|
|
||||||
|
#include "coredump-context.h"
|
||||||
|
#include "coredump-kernel-helper.h"
|
||||||
|
#include "coredump-send.h"
|
||||||
|
#include "coredump-submit.h"
|
||||||
|
#include "coredump-util.h"
|
||||||
|
#include "fd-util.h"
|
||||||
|
#include "iovec-wrapper.h"
|
||||||
|
#include "log.h"
|
||||||
|
#include "namespace-util.h"
|
||||||
|
#include "signal-util.h"
|
||||||
|
|
||||||
|
int coredump_kernel_helper(int argc, char *argv[]) {
|
||||||
|
_cleanup_(iovw_free_freep) struct iovec_wrapper *iovw = NULL;
|
||||||
|
_cleanup_(context_done) Context context = CONTEXT_NULL;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
/* When we're invoked by the kernel, stdout/stderr are closed which is dangerous because the fds
|
||||||
|
* could get reallocated. To avoid hard to debug issues, let's instead bind stdout/stderr to
|
||||||
|
* /dev/null. */
|
||||||
|
r = rearrange_stdio(STDIN_FILENO, -EBADF, -EBADF);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to connect stdout/stderr to /dev/null: %m");
|
||||||
|
|
||||||
|
log_debug("Processing coredump received from the kernel...");
|
||||||
|
|
||||||
|
iovw = iovw_new();
|
||||||
|
if (!iovw)
|
||||||
|
return log_oom();
|
||||||
|
|
||||||
|
/* Collect all process metadata passed by the kernel through argv[] */
|
||||||
|
r = gather_pid_metadata_from_argv(iovw, &context, argc - 1, argv + 1);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
/* Collect the rest of the process metadata retrieved from the runtime */
|
||||||
|
r = gather_pid_metadata_from_procfs(iovw, &context);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
if (!context.is_journald)
|
||||||
|
/* OK, now we know it's not the journal, hence we can make use of it now. */
|
||||||
|
log_set_target_and_open(LOG_TARGET_JOURNAL_OR_KMSG);
|
||||||
|
|
||||||
|
/* Log minimal metadata now, so it is not lost if the system is about to shut down. */
|
||||||
|
log_info("Process %s (%s) of user %s terminated abnormally with signal %s/%s, processing...",
|
||||||
|
context.meta[META_ARGV_PID], context.meta[META_COMM],
|
||||||
|
context.meta[META_ARGV_UID], context.meta[META_ARGV_SIGNAL],
|
||||||
|
signal_to_string(context.signo));
|
||||||
|
|
||||||
|
r = pidref_in_same_namespace(/* pid1 = */ NULL, &context.pidref, NAMESPACE_PID);
|
||||||
|
if (r < 0)
|
||||||
|
log_debug_errno(r, "Failed to check pidns of crashing process, ignoring: %m");
|
||||||
|
if (r == 0) {
|
||||||
|
/* If this fails, fallback to the old behavior so that
|
||||||
|
* there is still some record of the crash. */
|
||||||
|
r = coredump_send_to_container(&context);
|
||||||
|
if (r >= 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
r = acquire_pid_mount_tree_fd(&context, &context.mount_tree_fd);
|
||||||
|
if (r < 0)
|
||||||
|
log_warning_errno(r, "Failed to access the mount tree of a container, ignoring: %m");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If this is PID 1, disable coredump collection, we'll unlikely be able to process
|
||||||
|
* it later on.
|
||||||
|
*
|
||||||
|
* FIXME: maybe we should disable coredumps generation from the beginning and
|
||||||
|
* re-enable it only when we know it's either safe (i.e. we're not running OOM) or
|
||||||
|
* it's not PID 1 ? */
|
||||||
|
if (context.is_pid1) {
|
||||||
|
log_notice("Due to PID 1 having crashed coredump collection will now be turned off.");
|
||||||
|
disable_coredumps();
|
||||||
|
}
|
||||||
|
|
||||||
|
(void) iovw_put_string_field(iovw, "MESSAGE_ID=", SD_MESSAGE_COREDUMP_STR);
|
||||||
|
(void) iovw_put_string_field(iovw, "PRIORITY=", STRINGIFY(LOG_CRIT));
|
||||||
|
|
||||||
|
if (context.is_journald || context.is_pid1)
|
||||||
|
return coredump_submit(&context, iovw, STDIN_FILENO);
|
||||||
|
|
||||||
|
return coredump_send(iovw, STDIN_FILENO, &context.pidref, context.mount_tree_fd);
|
||||||
|
}
|
||||||
4
src/coredump/coredump-kernel-helper.h
Normal file
4
src/coredump/coredump-kernel-helper.h
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
int coredump_kernel_helper(int argc, char *argv[]);
|
||||||
162
src/coredump/coredump-receive.c
Normal file
162
src/coredump/coredump-receive.c
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include "coredump-context.h"
|
||||||
|
#include "coredump-receive.h"
|
||||||
|
#include "coredump-submit.h"
|
||||||
|
#include "iovec-util.h"
|
||||||
|
#include "iovec-wrapper.h"
|
||||||
|
#include "fd-util.h"
|
||||||
|
#include "log.h"
|
||||||
|
#include "socket-util.h"
|
||||||
|
|
||||||
|
int coredump_receive(int fd) {
|
||||||
|
_cleanup_(iovw_done_free) struct iovec_wrapper iovw = {};
|
||||||
|
_cleanup_(context_done) Context context = CONTEXT_NULL;
|
||||||
|
_cleanup_close_ int input_fd = -EBADF;
|
||||||
|
enum {
|
||||||
|
STATE_PAYLOAD,
|
||||||
|
STATE_INPUT_FD_DONE,
|
||||||
|
STATE_PID_FD_DONE,
|
||||||
|
} state = STATE_PAYLOAD;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(fd >= 0);
|
||||||
|
|
||||||
|
log_setup();
|
||||||
|
|
||||||
|
log_debug("Processing coredump received via socket...");
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(int))) control;
|
||||||
|
struct msghdr mh = {
|
||||||
|
.msg_control = &control,
|
||||||
|
.msg_controllen = sizeof(control),
|
||||||
|
.msg_iovlen = 1,
|
||||||
|
};
|
||||||
|
ssize_t n, l;
|
||||||
|
|
||||||
|
l = next_datagram_size_fd(fd);
|
||||||
|
if (l < 0)
|
||||||
|
return log_error_errno(l, "Failed to determine datagram size to read: %m");
|
||||||
|
|
||||||
|
_cleanup_(iovec_done) struct iovec iovec = {
|
||||||
|
.iov_len = l,
|
||||||
|
.iov_base = malloc(l + 1),
|
||||||
|
};
|
||||||
|
if (!iovec.iov_base)
|
||||||
|
return log_oom();
|
||||||
|
|
||||||
|
mh.msg_iov = &iovec;
|
||||||
|
|
||||||
|
n = recvmsg_safe(fd, &mh, MSG_CMSG_CLOEXEC);
|
||||||
|
if (n < 0)
|
||||||
|
return log_error_errno(n, "Failed to receive datagram: %m");
|
||||||
|
|
||||||
|
/* The final zero-length datagrams ("sentinels") carry file descriptors and tell us that
|
||||||
|
* we're done. There are three sentinels: one with just the coredump fd, followed by one with
|
||||||
|
* the pidfd, and finally one with the mount tree fd. The latter two or the last one may be
|
||||||
|
* omitted (which is supported for compatibility with older systemd version, in particular to
|
||||||
|
* facilitate cross-container coredumping). */
|
||||||
|
if (n == 0) {
|
||||||
|
struct cmsghdr *found;
|
||||||
|
|
||||||
|
found = cmsg_find(&mh, SOL_SOCKET, SCM_RIGHTS, CMSG_LEN(sizeof(int)));
|
||||||
|
if (!found) {
|
||||||
|
/* This is zero length message but it either doesn't carry a single
|
||||||
|
* descriptor, or it has more than one. This is a protocol violation so let's
|
||||||
|
* bail out.
|
||||||
|
*
|
||||||
|
* Well, not quite! In practice there's one more complication: EOF on
|
||||||
|
* SOCK_SEQPACKET is not distinguishable from a zero length datagram. Hence
|
||||||
|
* if we get a zero length datagram without fds we consider it EOF, and
|
||||||
|
* that's permissible for the final two fds. Hence let's be strict on the
|
||||||
|
* first fd, but lenient on the other two. */
|
||||||
|
|
||||||
|
if (!cmsg_find(&mh, SOL_SOCKET, SCM_RIGHTS, (socklen_t) -1) && state != STATE_PAYLOAD)
|
||||||
|
/* No fds, and already got the first fd → we are done. */
|
||||||
|
break;
|
||||||
|
|
||||||
|
cmsg_close_all(&mh);
|
||||||
|
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG),
|
||||||
|
"Received zero length message with zero or more than one file descriptor(s), expected one.");
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (state) {
|
||||||
|
|
||||||
|
case STATE_PAYLOAD:
|
||||||
|
assert(input_fd < 0);
|
||||||
|
input_fd = *CMSG_TYPED_DATA(found, int);
|
||||||
|
state = STATE_INPUT_FD_DONE;
|
||||||
|
continue;
|
||||||
|
|
||||||
|
case STATE_INPUT_FD_DONE:
|
||||||
|
assert(!pidref_is_set(&context.pidref));
|
||||||
|
|
||||||
|
r = pidref_set_pidfd_consume(&context.pidref, *CMSG_TYPED_DATA(found, int));
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to initialize pidref: %m");
|
||||||
|
|
||||||
|
state = STATE_PID_FD_DONE;
|
||||||
|
continue;
|
||||||
|
|
||||||
|
case STATE_PID_FD_DONE:
|
||||||
|
assert(context.mount_tree_fd < 0);
|
||||||
|
context.mount_tree_fd = *CMSG_TYPED_DATA(found, int);
|
||||||
|
/* We have all FDs we need so we are done. */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
cmsg_close_all(&mh);
|
||||||
|
|
||||||
|
/* Only zero length messages are allowed after the first message that carried a file descriptor. */
|
||||||
|
if (state != STATE_PAYLOAD)
|
||||||
|
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Received unexpected message with non-zero length.");
|
||||||
|
|
||||||
|
/* Payload messages should not carry fds */
|
||||||
|
if (cmsg_find(&mh, SOL_SOCKET, SCM_RIGHTS, (socklen_t) -1))
|
||||||
|
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG),
|
||||||
|
"Received payload message with file descriptor(s), expected none.");
|
||||||
|
|
||||||
|
/* Add trailing NUL byte, in case these are strings */
|
||||||
|
((char*) iovec.iov_base)[n] = 0;
|
||||||
|
iovec.iov_len = (size_t) n;
|
||||||
|
|
||||||
|
if (iovw_put(&iovw, iovec.iov_base, iovec.iov_len) < 0)
|
||||||
|
return log_oom();
|
||||||
|
|
||||||
|
TAKE_STRUCT(iovec);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make sure we got all data we really need */
|
||||||
|
assert(input_fd >= 0);
|
||||||
|
|
||||||
|
r = context_parse_iovw(&context, &iovw);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
/* Make sure we received all the expected fields. We support being called by an *older*
|
||||||
|
* systemd-coredump from the outside, so we require only the basic set of fields that
|
||||||
|
* was being sent when the support for sending to containers over a socket was added
|
||||||
|
* in a108c43e36d3ceb6e34efe37c014fc2cda856000. */
|
||||||
|
meta_argv_t i;
|
||||||
|
FOREACH_ARGUMENT(i,
|
||||||
|
META_ARGV_PID,
|
||||||
|
META_ARGV_UID,
|
||||||
|
META_ARGV_GID,
|
||||||
|
META_ARGV_SIGNAL,
|
||||||
|
META_ARGV_TIMESTAMP,
|
||||||
|
META_ARGV_RLIMIT,
|
||||||
|
META_ARGV_HOSTNAME,
|
||||||
|
META_COMM)
|
||||||
|
if (!context.meta[i])
|
||||||
|
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||||
|
"Mandatory argument %s not received on socket, aborting.",
|
||||||
|
meta_field_names[i]);
|
||||||
|
|
||||||
|
return coredump_submit(&context, &iovw, input_fd);
|
||||||
|
}
|
||||||
4
src/coredump/coredump-receive.h
Normal file
4
src/coredump/coredump-receive.h
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
int coredump_receive(int fd);
|
||||||
334
src/coredump/coredump-send.c
Normal file
334
src/coredump/coredump-send.c
Normal file
@ -0,0 +1,334 @@
|
|||||||
|
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "sd-messages.h"
|
||||||
|
|
||||||
|
#include "coredump-context.h"
|
||||||
|
#include "coredump-send.h"
|
||||||
|
#include "coredump-util.h"
|
||||||
|
#include "errno-util.h"
|
||||||
|
#include "fd-util.h"
|
||||||
|
#include "fs-util.h"
|
||||||
|
#include "iovec-util.h"
|
||||||
|
#include "iovec-wrapper.h"
|
||||||
|
#include "log.h"
|
||||||
|
#include "namespace-util.h"
|
||||||
|
#include "path-util.h"
|
||||||
|
#include "pidref.h"
|
||||||
|
#include "process-util.h"
|
||||||
|
#include "socket-util.h"
|
||||||
|
|
||||||
|
int coredump_send(const struct iovec_wrapper *iovw, int input_fd, PidRef *pidref, int mount_tree_fd) {
|
||||||
|
_cleanup_close_ int fd = -EBADF;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(iovw);
|
||||||
|
assert(input_fd >= 0);
|
||||||
|
|
||||||
|
fd = socket(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0);
|
||||||
|
if (fd < 0)
|
||||||
|
return log_error_errno(errno, "Failed to create coredump socket: %m");
|
||||||
|
|
||||||
|
r = connect_unix_path(fd, AT_FDCWD, "/run/systemd/coredump");
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to connect to coredump service: %m");
|
||||||
|
|
||||||
|
for (size_t i = 0; i < iovw->count; i++) {
|
||||||
|
struct msghdr mh = {
|
||||||
|
.msg_iov = iovw->iovec + i,
|
||||||
|
.msg_iovlen = 1,
|
||||||
|
};
|
||||||
|
struct iovec copy[2];
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
if (sendmsg(fd, &mh, MSG_NOSIGNAL) >= 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (errno == EMSGSIZE && mh.msg_iov[0].iov_len > 0) {
|
||||||
|
/* This field didn't fit? That's a pity. Given that this is
|
||||||
|
* just metadata, let's truncate the field at half, and try
|
||||||
|
* again. We append three dots, in order to show that this is
|
||||||
|
* truncated. */
|
||||||
|
|
||||||
|
if (mh.msg_iov != copy) {
|
||||||
|
/* We don't want to modify the caller's iovec, hence
|
||||||
|
* let's create our own array, consisting of two new
|
||||||
|
* iovecs, where the first is a (truncated) copy of
|
||||||
|
* what we want to send, and the second one contains
|
||||||
|
* the trailing dots. */
|
||||||
|
copy[0] = iovw->iovec[i];
|
||||||
|
copy[1] = IOVEC_MAKE(((const char[]){'.', '.', '.'}), 3);
|
||||||
|
|
||||||
|
mh.msg_iov = copy;
|
||||||
|
mh.msg_iovlen = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
copy[0].iov_len /= 2; /* halve it, and try again */
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return log_error_errno(errno, "Failed to send coredump datagram: %m");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* First sentinel: the coredump fd */
|
||||||
|
r = send_one_fd(fd, input_fd, 0);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to send coredump fd: %m");
|
||||||
|
|
||||||
|
/* The optional second sentinel: the pidfd */
|
||||||
|
if (!pidref_is_set(pidref) || pidref->fd < 0) /* If we have no pidfd, stop now */
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
r = send_one_fd(fd, pidref->fd, 0);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to send pidfd: %m");
|
||||||
|
|
||||||
|
/* The optional third sentinel: the mount tree fd */
|
||||||
|
if (mount_tree_fd < 0) /* If we have no mount tree, stop now */
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
r = send_one_fd(fd, mount_tree_fd, 0);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to send mount tree fd: %m");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int can_forward_coredump(Context *context, const PidRef *pid) {
|
||||||
|
_cleanup_free_ char *cgroup = NULL, *path = NULL, *unit = NULL;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(context);
|
||||||
|
assert(pidref_is_set(pid));
|
||||||
|
assert(!pidref_is_remote(pid));
|
||||||
|
|
||||||
|
/* We need to avoid a situation where the attacker crashes a SUID process or a root daemon and
|
||||||
|
* quickly replaces it with a namespaced process and we forward the coredump to the attacker, into
|
||||||
|
* the namespace. With %F/pidfd we can reliably check the namespace of the original process, hence we
|
||||||
|
* can allow forwarding. */
|
||||||
|
if (!context->got_pidfd && context->dumpable != SUID_DUMP_USER)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
r = cg_pidref_get_path(SYSTEMD_CGROUP_CONTROLLER, pid, &cgroup);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
r = path_extract_directory(cgroup, &path);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
r = cg_path_get_unit_path(path, &unit);
|
||||||
|
if (r == -ENOMEM)
|
||||||
|
return log_oom();
|
||||||
|
if (r == -ENXIO)
|
||||||
|
/* No valid units in this path. */
|
||||||
|
return false;
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
/* We require that this process belongs to a delegated cgroup
|
||||||
|
* (i.e. Delegate=yes), with CoredumpReceive=yes also. */
|
||||||
|
r = cg_is_delegated(unit);
|
||||||
|
if (r <= 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
return cg_has_coredump_receive(unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int send_ucred(int transport_fd, const struct ucred *ucred) {
|
||||||
|
CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(struct ucred))) control = {};
|
||||||
|
struct msghdr mh = {
|
||||||
|
.msg_control = &control,
|
||||||
|
.msg_controllen = sizeof(control),
|
||||||
|
};
|
||||||
|
struct cmsghdr *cmsg;
|
||||||
|
|
||||||
|
assert(transport_fd >= 0);
|
||||||
|
assert(ucred);
|
||||||
|
|
||||||
|
cmsg = CMSG_FIRSTHDR(&mh);
|
||||||
|
*cmsg = (struct cmsghdr) {
|
||||||
|
.cmsg_level = SOL_SOCKET,
|
||||||
|
.cmsg_type = SCM_CREDENTIALS,
|
||||||
|
.cmsg_len = CMSG_LEN(sizeof(struct ucred)),
|
||||||
|
};
|
||||||
|
memcpy(CMSG_DATA(cmsg), ucred, sizeof(struct ucred));
|
||||||
|
|
||||||
|
return RET_NERRNO(sendmsg(transport_fd, &mh, MSG_NOSIGNAL));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int receive_ucred(int transport_fd, struct ucred *ret_ucred) {
|
||||||
|
CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(struct ucred))) control = {};
|
||||||
|
struct msghdr mh = {
|
||||||
|
.msg_control = &control,
|
||||||
|
.msg_controllen = sizeof(control),
|
||||||
|
};
|
||||||
|
struct cmsghdr *cmsg = NULL;
|
||||||
|
struct ucred *ucred = NULL;
|
||||||
|
ssize_t n;
|
||||||
|
|
||||||
|
assert(transport_fd >= 0);
|
||||||
|
assert(ret_ucred);
|
||||||
|
|
||||||
|
n = recvmsg_safe(transport_fd, &mh, 0);
|
||||||
|
if (n < 0)
|
||||||
|
return n;
|
||||||
|
|
||||||
|
CMSG_FOREACH(cmsg, &mh)
|
||||||
|
if (cmsg->cmsg_level == SOL_SOCKET &&
|
||||||
|
cmsg->cmsg_type == SCM_CREDENTIALS &&
|
||||||
|
cmsg->cmsg_len == CMSG_LEN(sizeof(struct ucred))) {
|
||||||
|
|
||||||
|
assert(!ucred);
|
||||||
|
ucred = CMSG_TYPED_DATA(cmsg, struct ucred);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ucred)
|
||||||
|
return -EIO;
|
||||||
|
|
||||||
|
*ret_ucred = *ucred;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int coredump_send_to_container(Context *context) {
|
||||||
|
_cleanup_close_ int pidnsfd = -EBADF, mntnsfd = -EBADF, netnsfd = -EBADF, usernsfd = -EBADF, rootfd = -EBADF;
|
||||||
|
_cleanup_close_pair_ int pair[2] = EBADF_PAIR;
|
||||||
|
pid_t child;
|
||||||
|
struct ucred ucred = {
|
||||||
|
.pid = context->pidref.pid,
|
||||||
|
.uid = context->uid,
|
||||||
|
.gid = context->gid,
|
||||||
|
};
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(context);
|
||||||
|
|
||||||
|
_cleanup_(pidref_done) PidRef leader_pid = PIDREF_NULL;
|
||||||
|
r = namespace_get_leader(&context->pidref, NAMESPACE_PID, &leader_pid);
|
||||||
|
if (r < 0)
|
||||||
|
return log_debug_errno(r, "Failed to get namespace leader: %m");
|
||||||
|
|
||||||
|
r = can_forward_coredump(context, &leader_pid);
|
||||||
|
if (r < 0)
|
||||||
|
return log_debug_errno(r, "Failed to check if coredump can be forwarded: %m");
|
||||||
|
if (r == 0)
|
||||||
|
return log_debug_errno(SYNTHETIC_ERRNO(ENOENT),
|
||||||
|
"Coredump will not be forwarded because no target cgroup was found.");
|
||||||
|
|
||||||
|
r = RET_NERRNO(socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, pair));
|
||||||
|
if (r < 0)
|
||||||
|
return log_debug_errno(r, "Failed to create socket pair: %m");
|
||||||
|
|
||||||
|
r = setsockopt_int(pair[1], SOL_SOCKET, SO_PASSCRED, true);
|
||||||
|
if (r < 0)
|
||||||
|
return log_debug_errno(r, "Failed to set SO_PASSCRED: %m");
|
||||||
|
|
||||||
|
r = pidref_namespace_open(&leader_pid, &pidnsfd, &mntnsfd, &netnsfd, &usernsfd, &rootfd);
|
||||||
|
if (r < 0)
|
||||||
|
return log_debug_errno(r, "Failed to open namespaces of PID " PID_FMT ": %m", leader_pid.pid);
|
||||||
|
|
||||||
|
r = namespace_fork("(sd-coredumpns)", "(sd-coredump)", NULL, 0,
|
||||||
|
FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM,
|
||||||
|
pidnsfd, mntnsfd, netnsfd, usernsfd, rootfd, &child);
|
||||||
|
if (r < 0)
|
||||||
|
return log_debug_errno(r, "Failed to fork into namespaces of PID " PID_FMT ": %m", leader_pid.pid);
|
||||||
|
if (r == 0) {
|
||||||
|
pair[0] = safe_close(pair[0]);
|
||||||
|
|
||||||
|
r = access_nofollow("/run/systemd/coredump", W_OK);
|
||||||
|
if (r < 0) {
|
||||||
|
log_debug_errno(r, "Cannot find coredump socket, exiting: %m");
|
||||||
|
_exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
r = receive_ucred(pair[1], &ucred);
|
||||||
|
if (r < 0) {
|
||||||
|
log_debug_errno(r, "Failed to receive ucred and fd: %m");
|
||||||
|
_exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
_cleanup_(iovw_free_freep) struct iovec_wrapper *iovw = iovw_new();
|
||||||
|
if (!iovw) {
|
||||||
|
log_oom();
|
||||||
|
_exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
(void) iovw_put_string_field(iovw, "MESSAGE_ID=", SD_MESSAGE_COREDUMP_STR);
|
||||||
|
(void) iovw_put_string_field(iovw, "PRIORITY=", STRINGIFY(LOG_CRIT));
|
||||||
|
(void) iovw_put_string_field(iovw, "COREDUMP_FORWARDED=", "1");
|
||||||
|
|
||||||
|
for (int i = 0; i < _META_ARGV_MAX; i++) {
|
||||||
|
char buf[DECIMAL_STR_MAX(pid_t)];
|
||||||
|
const char *t = context->meta[i];
|
||||||
|
|
||||||
|
/* Patch some of the fields with the translated ucred data */
|
||||||
|
switch (i) {
|
||||||
|
|
||||||
|
case META_ARGV_PID:
|
||||||
|
xsprintf(buf, PID_FMT, ucred.pid);
|
||||||
|
t = buf;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case META_ARGV_UID:
|
||||||
|
xsprintf(buf, UID_FMT, ucred.uid);
|
||||||
|
t = buf;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case META_ARGV_GID:
|
||||||
|
xsprintf(buf, GID_FMT, ucred.gid);
|
||||||
|
t = buf;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
r = iovw_put_string_field(iovw, meta_field_names[i], t);
|
||||||
|
if (r < 0) {
|
||||||
|
log_debug_errno(r, "Failed to construct iovec: %m");
|
||||||
|
_exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_cleanup_(context_done) Context child_context = CONTEXT_NULL;
|
||||||
|
r = context_parse_iovw(&child_context, iovw);
|
||||||
|
if (r < 0) {
|
||||||
|
log_debug_errno(r, "Failed to save context: %m");
|
||||||
|
_exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
r = gather_pid_metadata_from_procfs(iovw, &child_context);
|
||||||
|
if (r < 0) {
|
||||||
|
log_debug_errno(r, "Failed to gather metadata from procfs: %m");
|
||||||
|
_exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
r = coredump_send(iovw, STDIN_FILENO, &context->pidref, /* mount_tree_fd= */ -EBADF);
|
||||||
|
if (r < 0) {
|
||||||
|
log_debug_errno(r, "Failed to send iovec to coredump socket: %m");
|
||||||
|
_exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
_exit(EXIT_SUCCESS);
|
||||||
|
}
|
||||||
|
|
||||||
|
pair[1] = safe_close(pair[1]);
|
||||||
|
|
||||||
|
/* We need to translate the PID, UID, and GID of the crashing process
|
||||||
|
* to the container's namespaces. Do this by sending an SCM_CREDENTIALS
|
||||||
|
* message on a socket pair, and read the result when we join the
|
||||||
|
* container. The kernel will perform the translation for us. */
|
||||||
|
r = send_ucred(pair[0], &ucred);
|
||||||
|
if (r < 0)
|
||||||
|
return log_debug_errno(r, "Failed to send metadata to container: %m");
|
||||||
|
|
||||||
|
r = wait_for_terminate_and_check("(sd-coredumpns)", child, 0);
|
||||||
|
if (r < 0)
|
||||||
|
return log_debug_errno(r, "Failed to wait for child to terminate: %m");
|
||||||
|
if (r != EXIT_SUCCESS)
|
||||||
|
return log_debug_errno(SYNTHETIC_ERRNO(EPROTO), "Failed to process coredump in container.");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
7
src/coredump/coredump-send.h
Normal file
7
src/coredump/coredump-send.h
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "coredump-forward.h"
|
||||||
|
|
||||||
|
int coredump_send(const struct iovec_wrapper *iovw, int input_fd, PidRef *pidref, int mount_tree_fd);
|
||||||
|
int coredump_send_to_container(Context *context);
|
||||||
798
src/coredump/coredump-submit.c
Normal file
798
src/coredump/coredump-submit.c
Normal file
@ -0,0 +1,798 @@
|
|||||||
|
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||||
|
|
||||||
|
#include <elf.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <sys/mount.h>
|
||||||
|
#include <sys/statvfs.h>
|
||||||
|
#include <sys/xattr.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "sd-bus.h"
|
||||||
|
#include "sd-id128.h"
|
||||||
|
#include "sd-journal.h"
|
||||||
|
#include "sd-messages.h"
|
||||||
|
|
||||||
|
#include "acl-util.h"
|
||||||
|
#include "bus-error.h"
|
||||||
|
#include "capability-util.h"
|
||||||
|
#include "compress.h"
|
||||||
|
#include "copy.h"
|
||||||
|
#include "coredump-config.h"
|
||||||
|
#include "coredump-context.h"
|
||||||
|
#include "coredump-submit.h"
|
||||||
|
#include "coredump-util.h"
|
||||||
|
#include "coredump-vacuum.h"
|
||||||
|
#include "elf-util.h"
|
||||||
|
#include "errno-util.h"
|
||||||
|
#include "escape.h"
|
||||||
|
#include "fd-util.h"
|
||||||
|
#include "format-util.h"
|
||||||
|
#include "fs-util.h"
|
||||||
|
#include "io-util.h"
|
||||||
|
#include "iovec-wrapper.h"
|
||||||
|
#include "journal-send.h"
|
||||||
|
#include "json-util.h"
|
||||||
|
#include "log.h"
|
||||||
|
#include "mkdir-label.h"
|
||||||
|
#include "namespace-util.h"
|
||||||
|
#include "path-util.h"
|
||||||
|
#include "process-util.h"
|
||||||
|
#include "socket-util.h"
|
||||||
|
#include "stat-util.h"
|
||||||
|
#include "string-util.h"
|
||||||
|
#include "tmpfile-util.h"
|
||||||
|
#include "uid-classification.h"
|
||||||
|
#include "user-util.h"
|
||||||
|
|
||||||
|
/* When checking for available memory and setting lower limits, don't
|
||||||
|
* go below 4MB for writing core files to storage. */
|
||||||
|
#define PROCESS_SIZE_MIN (4U*1024U*1024U)
|
||||||
|
|
||||||
|
#define MOUNT_TREE_ROOT "/run/systemd/mount-rootfs"
|
||||||
|
|
||||||
|
#define filename_escape(s) xescape((s), "./ ")
|
||||||
|
|
||||||
|
static const char* coredump_tmpfile_name(const char *s) {
|
||||||
|
return s ?: "(unnamed temporary file)";
|
||||||
|
}
|
||||||
|
|
||||||
|
static int make_filename(const Context *context, char **ret) {
|
||||||
|
_cleanup_free_ char *c = NULL, *u = NULL, *p = NULL, *t = NULL;
|
||||||
|
sd_id128_t boot = {};
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(context);
|
||||||
|
|
||||||
|
c = filename_escape(context->meta[META_COMM]);
|
||||||
|
if (!c)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
u = filename_escape(context->meta[META_ARGV_UID]);
|
||||||
|
if (!u)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
r = sd_id128_get_boot(&boot);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
p = filename_escape(context->meta[META_ARGV_PID]);
|
||||||
|
if (!p)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
t = filename_escape(context->meta[META_ARGV_TIMESTAMP]);
|
||||||
|
if (!t)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
if (asprintf(ret,
|
||||||
|
"/var/lib/systemd/coredump/core.%s.%s." SD_ID128_FORMAT_STR ".%s.%s",
|
||||||
|
c,
|
||||||
|
u,
|
||||||
|
SD_ID128_FORMAT_VAL(boot),
|
||||||
|
p,
|
||||||
|
t) < 0)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int grant_user_access(int core_fd, const Context *context) {
|
||||||
|
int at_secure = -1;
|
||||||
|
uid_t uid = UID_INVALID, euid = UID_INVALID;
|
||||||
|
uid_t gid = GID_INVALID, egid = GID_INVALID;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(core_fd >= 0);
|
||||||
|
assert(context);
|
||||||
|
|
||||||
|
if (!context->meta[META_PROC_AUXV])
|
||||||
|
return log_warning_errno(SYNTHETIC_ERRNO(ENODATA), "No auxv data, not adjusting permissions.");
|
||||||
|
|
||||||
|
uint8_t elf[EI_NIDENT];
|
||||||
|
errno = 0;
|
||||||
|
if (pread(core_fd, &elf, sizeof(elf), 0) != sizeof(elf))
|
||||||
|
return log_warning_errno(errno_or_else(EIO),
|
||||||
|
"Failed to pread from coredump fd: %s", STRERROR_OR_EOF(errno));
|
||||||
|
|
||||||
|
if (elf[EI_MAG0] != ELFMAG0 ||
|
||||||
|
elf[EI_MAG1] != ELFMAG1 ||
|
||||||
|
elf[EI_MAG2] != ELFMAG2 ||
|
||||||
|
elf[EI_MAG3] != ELFMAG3 ||
|
||||||
|
elf[EI_VERSION] != EV_CURRENT)
|
||||||
|
return log_info_errno(SYNTHETIC_ERRNO(EUCLEAN),
|
||||||
|
"Core file does not have ELF header, not adjusting permissions.");
|
||||||
|
if (!IN_SET(elf[EI_CLASS], ELFCLASS32, ELFCLASS64) ||
|
||||||
|
!IN_SET(elf[EI_DATA], ELFDATA2LSB, ELFDATA2MSB))
|
||||||
|
return log_info_errno(SYNTHETIC_ERRNO(EUCLEAN),
|
||||||
|
"Core file has strange ELF class, not adjusting permissions.");
|
||||||
|
|
||||||
|
if ((elf[EI_DATA] == ELFDATA2LSB) != (__BYTE_ORDER == __LITTLE_ENDIAN))
|
||||||
|
return log_info_errno(SYNTHETIC_ERRNO(EUCLEAN),
|
||||||
|
"Core file has non-native endianness, not adjusting permissions.");
|
||||||
|
|
||||||
|
r = parse_auxv(LOG_WARNING,
|
||||||
|
/* elf_class= */ elf[EI_CLASS],
|
||||||
|
context->meta[META_PROC_AUXV],
|
||||||
|
context->meta_size[META_PROC_AUXV],
|
||||||
|
&at_secure, &uid, &euid, &gid, &egid);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
/* We allow access if %d/dumpable on the command line was exactly 1, we got all the data,
|
||||||
|
* at_secure is not set, and the uid/gid match euid/egid. */
|
||||||
|
bool ret =
|
||||||
|
context->dumpable == SUID_DUMP_USER &&
|
||||||
|
at_secure == 0 &&
|
||||||
|
uid != UID_INVALID && euid != UID_INVALID && uid == euid &&
|
||||||
|
gid != GID_INVALID && egid != GID_INVALID && gid == egid;
|
||||||
|
log_debug("Will %s access (dumpable=%u uid="UID_FMT " euid="UID_FMT " gid="GID_FMT " egid="GID_FMT " at_secure=%s)",
|
||||||
|
ret ? "permit" : "restrict",
|
||||||
|
context->dumpable,
|
||||||
|
uid, euid, gid, egid, yes_no(at_secure));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int fix_acl(int fd, uid_t uid, bool allow_user) {
|
||||||
|
assert(fd >= 0);
|
||||||
|
assert(uid_is_valid(uid));
|
||||||
|
|
||||||
|
#if HAVE_ACL
|
||||||
|
int r;
|
||||||
|
|
||||||
|
/* We don't allow users to read coredumps if the uid or capabilities were changed. */
|
||||||
|
if (!allow_user)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (uid_is_system(uid) || uid_is_dynamic(uid) || uid_is_greeter(uid) || uid == UID_NOBODY)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/* Make sure normal users can read (but not write or delete) their own coredumps */
|
||||||
|
r = fd_add_uid_acl_permission(fd, uid, ACL_READ);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to adjust ACL of the coredump: %m");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int fix_xattr(int fd, const Context *context) {
|
||||||
|
static const char * const xattrs[_META_MAX] = {
|
||||||
|
[META_ARGV_PID] = "user.coredump.pid",
|
||||||
|
[META_ARGV_UID] = "user.coredump.uid",
|
||||||
|
[META_ARGV_GID] = "user.coredump.gid",
|
||||||
|
[META_ARGV_SIGNAL] = "user.coredump.signal",
|
||||||
|
[META_ARGV_TIMESTAMP] = "user.coredump.timestamp",
|
||||||
|
[META_ARGV_RLIMIT] = "user.coredump.rlimit",
|
||||||
|
[META_ARGV_HOSTNAME] = "user.coredump.hostname",
|
||||||
|
[META_COMM] = "user.coredump.comm",
|
||||||
|
[META_EXE] = "user.coredump.exe",
|
||||||
|
};
|
||||||
|
|
||||||
|
int r = 0;
|
||||||
|
|
||||||
|
assert(fd >= 0);
|
||||||
|
|
||||||
|
/* Attach some metadata to coredumps via extended attributes. Just because we can. */
|
||||||
|
|
||||||
|
for (unsigned i = 0; i < _META_MAX; i++) {
|
||||||
|
int k;
|
||||||
|
|
||||||
|
if (isempty(context->meta[i]) || !xattrs[i])
|
||||||
|
continue;
|
||||||
|
|
||||||
|
k = RET_NERRNO(fsetxattr(fd, xattrs[i], context->meta[i], strlen(context->meta[i]), XATTR_CREATE));
|
||||||
|
RET_GATHER(r, k);
|
||||||
|
}
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int fix_permissions_and_link(
|
||||||
|
int fd,
|
||||||
|
const char *filename,
|
||||||
|
const char *target,
|
||||||
|
const Context *context,
|
||||||
|
bool allow_user) {
|
||||||
|
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(fd >= 0);
|
||||||
|
assert(target);
|
||||||
|
assert(context);
|
||||||
|
|
||||||
|
/* Ignore errors on these */
|
||||||
|
(void) fchmod(fd, 0640);
|
||||||
|
(void) fix_acl(fd, context->uid, allow_user);
|
||||||
|
(void) fix_xattr(fd, context);
|
||||||
|
|
||||||
|
r = link_tmpfile(fd, filename, target, LINK_TMPFILE_SYNC);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to move coredump %s into place: %m", target);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int save_external_coredump(
|
||||||
|
const Context *context,
|
||||||
|
int input_fd,
|
||||||
|
char **ret_filename,
|
||||||
|
int *ret_node_fd,
|
||||||
|
int *ret_data_fd,
|
||||||
|
uint64_t *ret_size,
|
||||||
|
uint64_t *ret_compressed_size,
|
||||||
|
bool *ret_truncated) {
|
||||||
|
|
||||||
|
_cleanup_(unlink_and_freep) char *tmp = NULL;
|
||||||
|
_cleanup_free_ char *fn = NULL;
|
||||||
|
_cleanup_close_ int fd = -EBADF;
|
||||||
|
uint64_t process_limit, max_size;
|
||||||
|
bool truncated, storage_on_tmpfs;
|
||||||
|
struct stat st;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(context);
|
||||||
|
assert(ret_filename);
|
||||||
|
assert(ret_node_fd);
|
||||||
|
assert(ret_data_fd);
|
||||||
|
assert(ret_size);
|
||||||
|
assert(ret_compressed_size);
|
||||||
|
assert(ret_truncated);
|
||||||
|
|
||||||
|
if (context->rlimit < page_size())
|
||||||
|
/* Is coredumping disabled? Then don't bother saving/processing the
|
||||||
|
* coredump. Anything below PAGE_SIZE cannot give a readable coredump
|
||||||
|
* (the kernel uses ELF_EXEC_PAGESIZE which is not easily accessible, but
|
||||||
|
* is usually the same as PAGE_SIZE. */
|
||||||
|
return log_info_errno(SYNTHETIC_ERRNO(EBADSLT),
|
||||||
|
"Resource limits disable core dumping for process %s (%s).",
|
||||||
|
context->meta[META_ARGV_PID], context->meta[META_COMM]);
|
||||||
|
|
||||||
|
process_limit = MAX(arg_process_size_max, coredump_storage_size_max());
|
||||||
|
if (process_limit == 0)
|
||||||
|
return log_debug_errno(SYNTHETIC_ERRNO(EBADSLT),
|
||||||
|
"Limits for coredump processing and storage are both 0, not dumping core.");
|
||||||
|
|
||||||
|
/* Never store more than the process configured, or than we actually shall keep or process */
|
||||||
|
max_size = MIN(context->rlimit, process_limit);
|
||||||
|
|
||||||
|
r = make_filename(context, &fn);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to determine coredump file name: %m");
|
||||||
|
|
||||||
|
(void) mkdir_parents_label(fn, 0755);
|
||||||
|
|
||||||
|
fd = open_tmpfile_linkable(fn, O_RDWR|O_CLOEXEC, &tmp);
|
||||||
|
if (fd < 0)
|
||||||
|
return log_error_errno(fd, "Failed to create temporary file for coredump %s: %m", fn);
|
||||||
|
|
||||||
|
/* If storage is on tmpfs, the kernel oomd might kill us if there's MemoryMax set on
|
||||||
|
* the service or the slice it belongs to. This is common on low-resources systems,
|
||||||
|
* to avoid crashing processes to take away too many system resources.
|
||||||
|
* Check the cgroup settings, and set max_size to a bit less than half of the
|
||||||
|
* available memory left to the process.
|
||||||
|
* Then, attempt to write the core file uncompressed first - if the write gets
|
||||||
|
* interrupted, we know we won't be able to write it all, so instead compress what
|
||||||
|
* was written so far, delete the uncompressed truncated core, and then continue
|
||||||
|
* compressing from STDIN. Given the compressed core cannot be larger than the
|
||||||
|
* uncompressed one, and 1KB for metadata is accounted for in the calculation, we
|
||||||
|
* should be able to at least store the full compressed core file. */
|
||||||
|
|
||||||
|
storage_on_tmpfs = fd_is_temporary_fs(fd) > 0;
|
||||||
|
if (storage_on_tmpfs && arg_compress) {
|
||||||
|
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
|
||||||
|
uint64_t cgroup_limit = UINT64_MAX;
|
||||||
|
struct statvfs sv;
|
||||||
|
|
||||||
|
/* If we can't get the cgroup limit, just ignore it, but don't fail,
|
||||||
|
* try anyway with the config settings. */
|
||||||
|
r = sd_bus_default_system(&bus);
|
||||||
|
if (r < 0)
|
||||||
|
log_info_errno(r, "Failed to connect to system bus, skipping MemoryAvailable check: %m");
|
||||||
|
else {
|
||||||
|
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
|
||||||
|
|
||||||
|
r = sd_bus_get_property_trivial(
|
||||||
|
bus,
|
||||||
|
"org.freedesktop.systemd1",
|
||||||
|
"/org/freedesktop/systemd1/unit/self",
|
||||||
|
"org.freedesktop.systemd1.Service",
|
||||||
|
"MemoryAvailable",
|
||||||
|
&error,
|
||||||
|
't', &cgroup_limit);
|
||||||
|
if (r < 0)
|
||||||
|
log_warning_errno(r,
|
||||||
|
"Failed to query MemoryAvailable for current unit, "
|
||||||
|
"falling back to static config settings: %s",
|
||||||
|
bus_error_message(&error, r));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* First, ensure we are not going to go over the cgroup limit */
|
||||||
|
max_size = MIN(cgroup_limit, max_size);
|
||||||
|
/* tmpfs might get full quickly, so check the available space too. But don't worry about
|
||||||
|
* errors here, failing to access the storage location will be better logged when writing to
|
||||||
|
* it. */
|
||||||
|
if (fstatvfs(fd, &sv) >= 0)
|
||||||
|
max_size = MIN((uint64_t)sv.f_frsize * (uint64_t)sv.f_bfree, max_size);
|
||||||
|
/* Impose a lower minimum, otherwise we will miss the basic headers. */
|
||||||
|
max_size = MAX(PROCESS_SIZE_MIN, max_size);
|
||||||
|
/* Ensure we can always switch to compressing on the fly in case we are running out of space
|
||||||
|
* by keeping half of the space/memory available, plus 1KB metadata overhead from the
|
||||||
|
* compression algorithm. */
|
||||||
|
max_size = LESS_BY(max_size, 1024U) / 2;
|
||||||
|
|
||||||
|
log_debug("Limiting core file size to %" PRIu64 " bytes due to cgroup and/or filesystem limits.", max_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
r = copy_bytes(input_fd, fd, max_size, 0);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Cannot store coredump of %s (%s): %m",
|
||||||
|
context->meta[META_ARGV_PID], context->meta[META_COMM]);
|
||||||
|
truncated = r == 1;
|
||||||
|
|
||||||
|
bool allow_user = grant_user_access(fd, context) > 0;
|
||||||
|
|
||||||
|
#if HAVE_COMPRESSION
|
||||||
|
if (arg_compress) {
|
||||||
|
_cleanup_(unlink_and_freep) char *tmp_compressed = NULL;
|
||||||
|
_cleanup_free_ char *fn_compressed = NULL;
|
||||||
|
_cleanup_close_ int fd_compressed = -EBADF;
|
||||||
|
uint64_t uncompressed_size = 0;
|
||||||
|
|
||||||
|
if (lseek(fd, 0, SEEK_SET) < 0)
|
||||||
|
return log_error_errno(errno, "Failed to seek on coredump %s: %m", fn);
|
||||||
|
|
||||||
|
fn_compressed = strjoin(fn, default_compression_extension());
|
||||||
|
if (!fn_compressed)
|
||||||
|
return log_oom();
|
||||||
|
|
||||||
|
fd_compressed = open_tmpfile_linkable(fn_compressed, O_RDWR|O_CLOEXEC, &tmp_compressed);
|
||||||
|
if (fd_compressed < 0)
|
||||||
|
return log_error_errno(fd_compressed, "Failed to create temporary file for coredump %s: %m", fn_compressed);
|
||||||
|
|
||||||
|
r = compress_stream(fd, fd_compressed, max_size, &uncompressed_size);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to compress %s: %m", coredump_tmpfile_name(tmp_compressed));
|
||||||
|
|
||||||
|
if (truncated && storage_on_tmpfs) {
|
||||||
|
uint64_t partial_uncompressed_size = 0;
|
||||||
|
|
||||||
|
/* Uncompressed write was truncated and we are writing to tmpfs: delete
|
||||||
|
* the uncompressed core, and compress the remaining part from STDIN. */
|
||||||
|
|
||||||
|
tmp = unlink_and_free(tmp);
|
||||||
|
fd = safe_close(fd);
|
||||||
|
|
||||||
|
r = compress_stream(input_fd, fd_compressed, max_size, &partial_uncompressed_size);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to compress %s: %m", coredump_tmpfile_name(tmp_compressed));
|
||||||
|
uncompressed_size += partial_uncompressed_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
r = fix_permissions_and_link(fd_compressed, tmp_compressed, fn_compressed, context, allow_user);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
if (fstat(fd_compressed, &st) < 0)
|
||||||
|
return log_error_errno(errno,
|
||||||
|
"Failed to fstat core file %s: %m",
|
||||||
|
coredump_tmpfile_name(tmp_compressed));
|
||||||
|
|
||||||
|
*ret_filename = TAKE_PTR(fn_compressed); /* compressed */
|
||||||
|
*ret_node_fd = TAKE_FD(fd_compressed); /* compressed */
|
||||||
|
*ret_data_fd = TAKE_FD(fd);
|
||||||
|
*ret_size = uncompressed_size;
|
||||||
|
*ret_compressed_size = (uint64_t) st.st_size; /* compressed */
|
||||||
|
*ret_truncated = truncated;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (truncated)
|
||||||
|
log_struct(LOG_INFO,
|
||||||
|
LOG_MESSAGE("Core file was truncated to %"PRIu64" bytes.", max_size),
|
||||||
|
LOG_ITEM("SIZE_LIMIT=%"PRIu64, max_size),
|
||||||
|
LOG_MESSAGE_ID(SD_MESSAGE_TRUNCATED_CORE_STR));
|
||||||
|
|
||||||
|
r = fix_permissions_and_link(fd, tmp, fn, context, allow_user);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to fix permissions and finalize coredump %s into %s: %m", coredump_tmpfile_name(tmp), fn);
|
||||||
|
|
||||||
|
if (fstat(fd, &st) < 0)
|
||||||
|
return log_error_errno(errno, "Failed to fstat core file %s: %m", coredump_tmpfile_name(tmp));
|
||||||
|
|
||||||
|
if (lseek(fd, 0, SEEK_SET) < 0)
|
||||||
|
return log_error_errno(errno, "Failed to seek on coredump %s: %m", fn);
|
||||||
|
|
||||||
|
*ret_filename = TAKE_PTR(fn);
|
||||||
|
*ret_node_fd = -EBADF;
|
||||||
|
*ret_data_fd = TAKE_FD(fd);
|
||||||
|
*ret_size = (uint64_t) st.st_size;
|
||||||
|
*ret_compressed_size = UINT64_MAX;
|
||||||
|
*ret_truncated = truncated;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int maybe_remove_external_coredump(
|
||||||
|
const Context *c,
|
||||||
|
const char *filename,
|
||||||
|
uint64_t size) {
|
||||||
|
|
||||||
|
assert(c);
|
||||||
|
|
||||||
|
/* Returns true if might remove, false if will not remove, < 0 on error. */
|
||||||
|
|
||||||
|
/* Always keep around in case of journald/pid1, since we cannot rely on the journal to accept them. */
|
||||||
|
if (arg_storage != COREDUMP_STORAGE_NONE && (c->is_pid1 || c->is_journald))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (arg_storage == COREDUMP_STORAGE_EXTERNAL &&
|
||||||
|
size <= arg_external_size_max)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!filename)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (unlink(filename) < 0 && errno != ENOENT)
|
||||||
|
return log_error_errno(errno, "Failed to unlink %s: %m", filename);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int acquire_pid_mount_tree_fd(const Context *context, int *ret_fd) {
|
||||||
|
/* Don't bother preparing environment if we can't pass it to libdwfl. */
|
||||||
|
#if !HAVE_DWFL_SET_SYSROOT
|
||||||
|
*ret_fd = -EOPNOTSUPP;
|
||||||
|
log_debug("dwfl_set_sysroot() is not supported.");
|
||||||
|
#else
|
||||||
|
_cleanup_close_ int mntns_fd = -EBADF, root_fd = -EBADF, fd = -EBADF;
|
||||||
|
_cleanup_close_pair_ int pair[2] = EBADF_PAIR;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(context);
|
||||||
|
assert(ret_fd);
|
||||||
|
|
||||||
|
if (!arg_enter_namespace) {
|
||||||
|
*ret_fd = -EHOSTDOWN;
|
||||||
|
log_debug("EnterNamespace=no so we won't use mount tree of the crashed process for generating backtrace.");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, pair) < 0)
|
||||||
|
return log_error_errno(errno, "Failed to create socket pair: %m");
|
||||||
|
|
||||||
|
r = pidref_namespace_open(
|
||||||
|
&context->pidref,
|
||||||
|
/* ret_pidns_fd= */ NULL,
|
||||||
|
&mntns_fd,
|
||||||
|
/* ret_netns_fd= */ NULL,
|
||||||
|
/* ret_userns_fd= */ NULL,
|
||||||
|
&root_fd);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to open mount namespace of crashing process: %m");
|
||||||
|
|
||||||
|
r = namespace_fork("(sd-mount-tree-ns)",
|
||||||
|
"(sd-mount-tree)",
|
||||||
|
/* except_fds= */ NULL,
|
||||||
|
/* n_except_fds= */ 0,
|
||||||
|
FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL|FORK_LOG|FORK_WAIT,
|
||||||
|
/* pidns_fd= */ -EBADF,
|
||||||
|
mntns_fd,
|
||||||
|
/* netns_fd= */ -EBADF,
|
||||||
|
/* userns_fd= */ -EBADF,
|
||||||
|
root_fd,
|
||||||
|
NULL);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
if (r == 0) {
|
||||||
|
pair[0] = safe_close(pair[0]);
|
||||||
|
|
||||||
|
fd = open_tree(-EBADF, "/", AT_NO_AUTOMOUNT | AT_RECURSIVE | AT_SYMLINK_NOFOLLOW | OPEN_TREE_CLOEXEC | OPEN_TREE_CLONE);
|
||||||
|
if (fd < 0) {
|
||||||
|
log_error_errno(errno, "Failed to clone mount tree: %m");
|
||||||
|
_exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
r = send_one_fd(pair[1], fd, 0);
|
||||||
|
if (r < 0) {
|
||||||
|
log_error_errno(r, "Failed to send mount tree to parent: %m");
|
||||||
|
_exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
_exit(EXIT_SUCCESS);
|
||||||
|
}
|
||||||
|
|
||||||
|
pair[1] = safe_close(pair[1]);
|
||||||
|
|
||||||
|
fd = receive_one_fd(pair[0], MSG_DONTWAIT);
|
||||||
|
if (fd < 0)
|
||||||
|
return log_error_errno(fd, "Failed to receive mount tree: %m");
|
||||||
|
|
||||||
|
*ret_fd = TAKE_FD(fd);
|
||||||
|
#endif
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int attach_mount_tree(int mount_tree_fd) {
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(mount_tree_fd >= 0);
|
||||||
|
|
||||||
|
r = detach_mount_namespace();
|
||||||
|
if (r < 0)
|
||||||
|
return log_warning_errno(r, "Failed to detach mount namespace: %m");
|
||||||
|
|
||||||
|
r = mkdir_p_label(MOUNT_TREE_ROOT, 0555);
|
||||||
|
if (r < 0)
|
||||||
|
return log_warning_errno(r, "Failed to create directory: %m");
|
||||||
|
|
||||||
|
r = mount_setattr(mount_tree_fd, "", AT_EMPTY_PATH,
|
||||||
|
&(struct mount_attr) {
|
||||||
|
/* MOUNT_ATTR_NOSYMFOLLOW is left out on purpose to allow libdwfl to resolve symlinks.
|
||||||
|
* libdwfl will use openat2() with RESOLVE_IN_ROOT so there is no risk of symlink escape.
|
||||||
|
* https://sourceware.org/git/?p=elfutils.git;a=patch;h=06f0520f9a78b07c11c343181d552791dd630346 */
|
||||||
|
.attr_set = MOUNT_ATTR_RDONLY|MOUNT_ATTR_NOSUID|MOUNT_ATTR_NODEV|MOUNT_ATTR_NOEXEC,
|
||||||
|
.propagation = MS_SLAVE,
|
||||||
|
}, sizeof(struct mount_attr));
|
||||||
|
if (r < 0)
|
||||||
|
return log_warning_errno(errno, "Failed to change properties of mount tree: %m");
|
||||||
|
|
||||||
|
r = move_mount(mount_tree_fd, "", -EBADF, MOUNT_TREE_ROOT, MOVE_MOUNT_F_EMPTY_PATH);
|
||||||
|
if (r < 0)
|
||||||
|
return log_warning_errno(errno, "Failed to attach mount tree: %m");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int change_uid_gid(const Context *context) {
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(context);
|
||||||
|
|
||||||
|
uid_t uid = context->uid;
|
||||||
|
gid_t gid = context->gid;
|
||||||
|
|
||||||
|
if (uid_is_system(uid)) {
|
||||||
|
const char *user = "systemd-coredump";
|
||||||
|
|
||||||
|
r = get_user_creds(&user, &uid, &gid, NULL, NULL, 0);
|
||||||
|
if (r < 0) {
|
||||||
|
log_warning_errno(r, "Cannot resolve %s user. Proceeding to dump core as root: %m", user);
|
||||||
|
uid = gid = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return drop_privileges(uid, gid, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int allocate_journal_field(int fd, size_t size, char **ret, size_t *ret_size) {
|
||||||
|
_cleanup_free_ char *field = NULL;
|
||||||
|
ssize_t n;
|
||||||
|
|
||||||
|
assert(fd >= 0);
|
||||||
|
assert(ret);
|
||||||
|
assert(ret_size);
|
||||||
|
|
||||||
|
if (lseek(fd, 0, SEEK_SET) < 0)
|
||||||
|
return log_warning_errno(errno, "Failed to seek: %m");
|
||||||
|
|
||||||
|
field = malloc(9 + size);
|
||||||
|
if (!field)
|
||||||
|
return log_warning_errno(SYNTHETIC_ERRNO(ENOMEM),
|
||||||
|
"Failed to allocate memory for coredump, coredump will not be stored.");
|
||||||
|
|
||||||
|
memcpy(field, "COREDUMP=", 9);
|
||||||
|
|
||||||
|
/* NB: simple read() would fail for overly large coredumps, since read() on Linux can only deal with
|
||||||
|
* 0x7ffff000 bytes max. Hence call things in a loop. */
|
||||||
|
n = loop_read(fd, field + 9, size, /* do_poll= */ false);
|
||||||
|
if (n < 0)
|
||||||
|
return log_error_errno((int) n, "Failed to read core data: %m");
|
||||||
|
if ((size_t) n < size)
|
||||||
|
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Core data too short.");
|
||||||
|
|
||||||
|
*ret = TAKE_PTR(field);
|
||||||
|
*ret_size = size + 9;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int coredump_submit(
|
||||||
|
const Context *context,
|
||||||
|
struct iovec_wrapper *iovw,
|
||||||
|
int input_fd) {
|
||||||
|
|
||||||
|
_cleanup_(sd_json_variant_unrefp) sd_json_variant *json_metadata = NULL;
|
||||||
|
_cleanup_close_ int coredump_fd = -EBADF, coredump_node_fd = -EBADF;
|
||||||
|
_cleanup_free_ char *filename = NULL, *coredump_data = NULL, *stacktrace = NULL;
|
||||||
|
const char *module_name, *root = NULL;
|
||||||
|
uint64_t coredump_size = UINT64_MAX, coredump_compressed_size = UINT64_MAX;
|
||||||
|
bool truncated = false, written = false;
|
||||||
|
sd_json_variant *module_json;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(context);
|
||||||
|
assert(iovw);
|
||||||
|
assert(input_fd >= 0);
|
||||||
|
|
||||||
|
/* Vacuum before we write anything again */
|
||||||
|
(void) coredump_vacuum(-1, arg_keep_free, arg_max_use);
|
||||||
|
|
||||||
|
/* Always stream the coredump to disk, if that's possible */
|
||||||
|
written = save_external_coredump(
|
||||||
|
context, input_fd,
|
||||||
|
&filename, &coredump_node_fd, &coredump_fd,
|
||||||
|
&coredump_size, &coredump_compressed_size, &truncated) >= 0;
|
||||||
|
if (written) {
|
||||||
|
/* If we could write it to disk we can now process it. */
|
||||||
|
/* If we don't want to keep the coredump on disk, remove it now, as later on we
|
||||||
|
* will lack the privileges for it. However, we keep the fd to it, so that we can
|
||||||
|
* still process it and log it. */
|
||||||
|
r = maybe_remove_external_coredump(
|
||||||
|
context,
|
||||||
|
filename,
|
||||||
|
coredump_node_fd >= 0 ? coredump_compressed_size : coredump_size);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
if (r == 0)
|
||||||
|
(void) iovw_put_string_field(iovw, "COREDUMP_FILENAME=", filename);
|
||||||
|
else if (arg_storage == COREDUMP_STORAGE_EXTERNAL)
|
||||||
|
log_info("The core will not be stored: size %"PRIu64" is greater than %"PRIu64" (the configured maximum)",
|
||||||
|
coredump_node_fd >= 0 ? coredump_compressed_size : coredump_size, arg_external_size_max);
|
||||||
|
|
||||||
|
/* Vacuum again, but exclude the coredump we just created */
|
||||||
|
(void) coredump_vacuum(coredump_node_fd >= 0 ? coredump_node_fd : coredump_fd, arg_keep_free, arg_max_use);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context->mount_tree_fd >= 0 && attach_mount_tree(context->mount_tree_fd) >= 0)
|
||||||
|
root = MOUNT_TREE_ROOT;
|
||||||
|
|
||||||
|
/* Now, let's drop privileges to become the user who owns the segfaulted process and allocate the
|
||||||
|
* coredump memory under the user's uid. This also ensures that the credentials journald will see are
|
||||||
|
* the ones of the coredumping user, thus making sure the user gets access to the core dump. Let's
|
||||||
|
* also get rid of all capabilities, if we run as root, we won't need them anymore. */
|
||||||
|
r = change_uid_gid(context);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to drop privileges: %m");
|
||||||
|
|
||||||
|
if (written) {
|
||||||
|
/* Try to get a stack trace if we can */
|
||||||
|
if (coredump_size > arg_process_size_max)
|
||||||
|
log_debug("Not generating stack trace: core size %"PRIu64" is greater "
|
||||||
|
"than %"PRIu64" (the configured maximum)",
|
||||||
|
coredump_size, arg_process_size_max);
|
||||||
|
else if (coredump_fd >= 0) {
|
||||||
|
bool skip = startswith(context->meta[META_COMM], "systemd-coredum"); /* COMM is 16 bytes usually */
|
||||||
|
|
||||||
|
(void) parse_elf_object(coredump_fd,
|
||||||
|
context->meta[META_EXE],
|
||||||
|
root,
|
||||||
|
/* fork_disable_dump= */ skip, /* avoid loops */
|
||||||
|
&stacktrace,
|
||||||
|
&json_metadata);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_cleanup_free_ char *core_message = NULL;
|
||||||
|
core_message = strjoin(
|
||||||
|
"Process ", context->meta[META_ARGV_PID],
|
||||||
|
" (", context->meta[META_COMM],
|
||||||
|
") of user ", context->meta[META_ARGV_UID],
|
||||||
|
written ? " dumped core." : " terminated abnormally without generating a coredump.");
|
||||||
|
if (!core_message)
|
||||||
|
return log_oom();
|
||||||
|
|
||||||
|
if (context->is_journald && filename)
|
||||||
|
if (!strextend(&core_message, "\nCoredump diverted to ", filename))
|
||||||
|
return log_oom();
|
||||||
|
|
||||||
|
if (stacktrace)
|
||||||
|
if (!strextend(&core_message, "\n\n", stacktrace))
|
||||||
|
return log_oom();
|
||||||
|
|
||||||
|
if (context->is_journald)
|
||||||
|
/* We might not be able to log to the journal, so let's always print the message to another
|
||||||
|
* log target. The target was set previously to something safe. */
|
||||||
|
log_dispatch(LOG_ERR, 0, core_message);
|
||||||
|
|
||||||
|
(void) iovw_put_string_field(iovw, "MESSAGE=", core_message);
|
||||||
|
|
||||||
|
if (truncated)
|
||||||
|
(void) iovw_put_string_field(iovw, "COREDUMP_TRUNCATED=", "1");
|
||||||
|
|
||||||
|
/* If we managed to parse any ELF metadata (build-id, ELF package meta),
|
||||||
|
* attach it as journal metadata. */
|
||||||
|
if (json_metadata) {
|
||||||
|
_cleanup_free_ char *formatted_json = NULL;
|
||||||
|
|
||||||
|
r = sd_json_variant_format(json_metadata, 0, &formatted_json);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to format JSON package metadata: %m");
|
||||||
|
|
||||||
|
(void) iovw_put_string_field(iovw, "COREDUMP_PACKAGE_JSON=", formatted_json);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* In the unlikely scenario that context->meta[META_EXE] is not available,
|
||||||
|
* let's avoid guessing the module name and skip the loop. */
|
||||||
|
if (context->meta[META_EXE])
|
||||||
|
JSON_VARIANT_OBJECT_FOREACH(module_name, module_json, json_metadata) {
|
||||||
|
sd_json_variant *t;
|
||||||
|
|
||||||
|
/* We only add structured fields for the 'main' ELF module, and only if we can identify it. */
|
||||||
|
if (!path_equal_filename(module_name, context->meta[META_EXE]))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
t = sd_json_variant_by_key(module_json, "name");
|
||||||
|
if (t)
|
||||||
|
(void) iovw_put_string_field(iovw, "COREDUMP_PACKAGE_NAME=", sd_json_variant_string(t));
|
||||||
|
|
||||||
|
t = sd_json_variant_by_key(module_json, "version");
|
||||||
|
if (t)
|
||||||
|
(void) iovw_put_string_field(iovw, "COREDUMP_PACKAGE_VERSION=", sd_json_variant_string(t));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Optionally store the entire coredump in the journal */
|
||||||
|
if (arg_storage == COREDUMP_STORAGE_JOURNAL && coredump_fd >= 0) {
|
||||||
|
if (coredump_size <= arg_journal_size_max) {
|
||||||
|
size_t sz = 0;
|
||||||
|
|
||||||
|
/* Store the coredump itself in the journal */
|
||||||
|
|
||||||
|
r = allocate_journal_field(coredump_fd, (size_t) coredump_size, &coredump_data, &sz);
|
||||||
|
if (r >= 0) {
|
||||||
|
if (iovw_put(iovw, coredump_data, sz) >= 0)
|
||||||
|
TAKE_PTR(coredump_data);
|
||||||
|
} else
|
||||||
|
log_warning_errno(r, "Failed to attach the core to the journal entry: %m");
|
||||||
|
} else
|
||||||
|
log_info("The core will not be stored: size %"PRIu64" is greater than %"PRIu64" (the configured maximum)",
|
||||||
|
coredump_size, arg_journal_size_max);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If journald is coredumping, we have to be careful that we don't deadlock when trying to write the
|
||||||
|
* coredump to the journal, so we put the journal socket in nonblocking mode before trying to write
|
||||||
|
* the coredump to the socket. */
|
||||||
|
|
||||||
|
if (context->is_journald) {
|
||||||
|
r = journal_fd_nonblock(true);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to make journal socket non-blocking: %m");
|
||||||
|
}
|
||||||
|
|
||||||
|
r = sd_journal_sendv(iovw->iovec, iovw->count);
|
||||||
|
|
||||||
|
if (context->is_journald) {
|
||||||
|
int k;
|
||||||
|
|
||||||
|
k = journal_fd_nonblock(false);
|
||||||
|
if (k < 0)
|
||||||
|
return log_error_errno(k, "Failed to make journal socket blocking: %m");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (r == -EAGAIN && context->is_journald)
|
||||||
|
log_warning_errno(r, "Failed to log journal coredump, ignoring: %m");
|
||||||
|
else if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to log coredump: %m");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
10
src/coredump/coredump-submit.h
Normal file
10
src/coredump/coredump-submit.h
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "coredump-forward.h"
|
||||||
|
|
||||||
|
int acquire_pid_mount_tree_fd(const Context *context, int *ret_fd);
|
||||||
|
int coredump_submit(
|
||||||
|
const Context *context,
|
||||||
|
struct iovec_wrapper *iovw,
|
||||||
|
int input_fd);
|
||||||
File diff suppressed because it is too large
Load Diff
@ -6,6 +6,13 @@ endif
|
|||||||
|
|
||||||
systemd_coredump_sources = files(
|
systemd_coredump_sources = files(
|
||||||
'coredump.c',
|
'coredump.c',
|
||||||
|
'coredump-backtrace.c',
|
||||||
|
'coredump-config.c',
|
||||||
|
'coredump-context.c',
|
||||||
|
'coredump-kernel-helper.c',
|
||||||
|
'coredump-receive.c',
|
||||||
|
'coredump-send.c',
|
||||||
|
'coredump-submit.c',
|
||||||
)
|
)
|
||||||
systemd_coredump_extract_sources = files(
|
systemd_coredump_extract_sources = files(
|
||||||
'coredump-vacuum.c',
|
'coredump-vacuum.c',
|
||||||
|
|||||||
@ -2302,7 +2302,7 @@ static int run(int argc, char *argv[]) {
|
|||||||
|
|
||||||
/* Don't run things in private userns, if the mount shall be attached to the host */
|
/* Don't run things in private userns, if the mount shall be attached to the host */
|
||||||
if (!IN_SET(arg_action, ACTION_MOUNT, ACTION_WITH)) {
|
if (!IN_SET(arg_action, ACTION_MOUNT, ACTION_WITH)) {
|
||||||
userns_fd = nsresource_allocate_userns(/* name= */ NULL, UINT64_C(0x10000)); /* allocate 64K users by default */
|
userns_fd = nsresource_allocate_userns(/* name= */ NULL, NSRESOURCE_UIDS_64K); /* allocate 64K users by default */
|
||||||
if (userns_fd < 0)
|
if (userns_fd < 0)
|
||||||
return log_error_errno(userns_fd, "Failed to allocate user namespace with 64K users: %m");
|
return log_error_errno(userns_fd, "Failed to allocate user namespace with 64K users: %m");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
#include "fileio.h"
|
#include "fileio.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
#include "log-assert-critical.h"
|
||||||
|
|
||||||
/* The entry point into the fuzzer */
|
/* The entry point into the fuzzer */
|
||||||
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
|
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
|
||||||
|
|||||||
@ -207,7 +207,9 @@ static int help(int argc, char *argv[], void *userdata) {
|
|||||||
" --version Show package version\n"
|
" --version Show package version\n"
|
||||||
" --format=FORMAT Select format\n"
|
" --format=FORMAT Select format\n"
|
||||||
" --class=CLASS Select image class (machine, sysext, confext,\n"
|
" --class=CLASS Select image class (machine, sysext, confext,\n"
|
||||||
" portable)\n",
|
" portable)\n"
|
||||||
|
" --system Operate in per-system mode\n"
|
||||||
|
" --user Operate in per-user mode\n",
|
||||||
program_invocation_short_name,
|
program_invocation_short_name,
|
||||||
ansi_underline(),
|
ansi_underline(),
|
||||||
ansi_normal(),
|
ansi_normal(),
|
||||||
@ -223,6 +225,8 @@ static int parse_argv(int argc, char *argv[]) {
|
|||||||
ARG_VERSION = 0x100,
|
ARG_VERSION = 0x100,
|
||||||
ARG_FORMAT,
|
ARG_FORMAT,
|
||||||
ARG_CLASS,
|
ARG_CLASS,
|
||||||
|
ARG_SYSTEM,
|
||||||
|
ARG_USER,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct option options[] = {
|
static const struct option options[] = {
|
||||||
@ -230,6 +234,8 @@ static int parse_argv(int argc, char *argv[]) {
|
|||||||
{ "version", no_argument, NULL, ARG_VERSION },
|
{ "version", no_argument, NULL, ARG_VERSION },
|
||||||
{ "format", required_argument, NULL, ARG_FORMAT },
|
{ "format", required_argument, NULL, ARG_FORMAT },
|
||||||
{ "class", required_argument, NULL, ARG_CLASS },
|
{ "class", required_argument, NULL, ARG_CLASS },
|
||||||
|
{ "system", no_argument, NULL, ARG_SYSTEM },
|
||||||
|
{ "user", no_argument, NULL, ARG_USER },
|
||||||
{}
|
{}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -262,6 +268,14 @@ static int parse_argv(int argc, char *argv[]) {
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case ARG_SYSTEM:
|
||||||
|
arg_runtime_scope = RUNTIME_SCOPE_SYSTEM;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ARG_USER:
|
||||||
|
arg_runtime_scope = RUNTIME_SCOPE_USER;
|
||||||
|
break;
|
||||||
|
|
||||||
case '?':
|
case '?':
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
||||||
|
|||||||
@ -6,21 +6,26 @@
|
|||||||
#include "sd-event.h"
|
#include "sd-event.h"
|
||||||
|
|
||||||
#include "capability-util.h"
|
#include "capability-util.h"
|
||||||
|
#include "copy.h"
|
||||||
#include "dirent-util.h"
|
#include "dirent-util.h"
|
||||||
|
#include "dissect-image.h"
|
||||||
#include "fd-util.h"
|
#include "fd-util.h"
|
||||||
#include "fs-util.h"
|
#include "fs-util.h"
|
||||||
#include "import-common.h"
|
#include "import-common.h"
|
||||||
#include "libarchive-util.h"
|
#include "libarchive-util.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
#include "namespace-util.h"
|
||||||
|
#include "nsresource.h"
|
||||||
#include "os-util.h"
|
#include "os-util.h"
|
||||||
#include "pidref.h"
|
#include "pidref.h"
|
||||||
#include "process-util.h"
|
#include "process-util.h"
|
||||||
|
#include "rm-rf.h"
|
||||||
#include "selinux-util.h"
|
#include "selinux-util.h"
|
||||||
#include "stat-util.h"
|
#include "stat-util.h"
|
||||||
#include "tar-util.h"
|
#include "tar-util.h"
|
||||||
#include "tmpfile-util.h"
|
#include "tmpfile-util.h"
|
||||||
|
|
||||||
int import_fork_tar_x(int tree_fd, PidRef *ret_pid) {
|
int import_fork_tar_x(int tree_fd, int userns_fd, PidRef *ret_pid) {
|
||||||
int r;
|
int r;
|
||||||
|
|
||||||
assert(tree_fd >= 0);
|
assert(tree_fd >= 0);
|
||||||
@ -41,7 +46,7 @@ int import_fork_tar_x(int tree_fd, PidRef *ret_pid) {
|
|||||||
r = pidref_safe_fork_full(
|
r = pidref_safe_fork_full(
|
||||||
"tar-x",
|
"tar-x",
|
||||||
/* stdio_fds= */ NULL,
|
/* stdio_fds= */ NULL,
|
||||||
(int[]) { tree_fd, pipefd[0] }, 2,
|
(int[]) { tree_fd, pipefd[0], userns_fd }, userns_fd >= 0 ? 3 : 2,
|
||||||
FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_REOPEN_LOG,
|
FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_REOPEN_LOG,
|
||||||
ret_pid);
|
ret_pid);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
@ -58,12 +63,20 @@ int import_fork_tar_x(int tree_fd, PidRef *ret_pid) {
|
|||||||
|
|
||||||
/* Child */
|
/* Child */
|
||||||
|
|
||||||
|
if (userns_fd >= 0) {
|
||||||
|
r = detach_mount_namespace_userns(userns_fd);
|
||||||
|
if (r < 0) {
|
||||||
|
log_error_errno(r, "Failed to join user namespace: %m");
|
||||||
|
_exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (unshare(CLONE_NEWNET) < 0)
|
if (unshare(CLONE_NEWNET) < 0)
|
||||||
log_warning_errno(errno, "Failed to lock tar into network namespace, ignoring: %m");
|
log_debug_errno(errno, "Failed to lock tar into network namespace, ignoring: %m");
|
||||||
|
|
||||||
r = capability_bounding_set_drop(retain, true);
|
r = capability_bounding_set_drop(retain, true);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
log_warning_errno(r, "Failed to drop capabilities, ignoring: %m");
|
log_debug_errno(r, "Failed to drop capabilities, ignoring: %m");
|
||||||
|
|
||||||
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0)
|
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0)
|
||||||
log_warning_errno(errno, "Failed to enable PR_SET_NO_NEW_PRIVS, ignoring: %m");
|
log_warning_errno(errno, "Failed to enable PR_SET_NO_NEW_PRIVS, ignoring: %m");
|
||||||
@ -291,3 +304,147 @@ int import_allocate_event_with_signals(sd_event **ret) {
|
|||||||
*ret = TAKE_PTR(event);
|
*ret = TAKE_PTR(event);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int import_make_foreign_userns(int *userns_fd) {
|
||||||
|
assert(userns_fd);
|
||||||
|
|
||||||
|
if (*userns_fd >= 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
*userns_fd = nsresource_allocate_userns(/* name= */ NULL, NSRESOURCE_UIDS_64K); /* allocate 64K users */
|
||||||
|
if (*userns_fd < 0)
|
||||||
|
return log_error_errno(*userns_fd, "Failed to allocate transient user namespace: %m");
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int import_copy_foreign(
|
||||||
|
int source_fd,
|
||||||
|
int target_fd,
|
||||||
|
int *userns_fd) {
|
||||||
|
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(source_fd >= 0);
|
||||||
|
assert(target_fd >= 0);
|
||||||
|
assert(userns_fd);
|
||||||
|
|
||||||
|
/* Copies dir referenced by source_fd into dir referenced by source_fd, moves to the specified userns
|
||||||
|
* for that (allocated if needed), which should be foreign UID range */
|
||||||
|
|
||||||
|
r = import_make_foreign_userns(userns_fd);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
r = safe_fork_full(
|
||||||
|
"copy-tree",
|
||||||
|
/* stdio_fds= */ NULL,
|
||||||
|
(int[]) { *userns_fd, source_fd, target_fd }, 3,
|
||||||
|
FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_REOPEN_LOG|FORK_WAIT,
|
||||||
|
/* ret_pid= */ NULL);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
if (r == 0) {
|
||||||
|
r = namespace_enter(
|
||||||
|
/* pidns_fd= */ -EBADF,
|
||||||
|
/* mntns_fd= */ -EBADF,
|
||||||
|
/* netns_fd= */ -EBADF,
|
||||||
|
*userns_fd,
|
||||||
|
/* root_fd= */ -EBADF);
|
||||||
|
if (r < 0) {
|
||||||
|
log_error_errno(r, "Failed to join user namespace: %m");
|
||||||
|
_exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
r = copy_tree_at(
|
||||||
|
source_fd, /* from= */ NULL,
|
||||||
|
target_fd, /* to = */ NULL,
|
||||||
|
/* override_uid= */ UID_INVALID,
|
||||||
|
/* override_gid= */ GID_INVALID,
|
||||||
|
COPY_REFLINK|COPY_HARDLINKS|COPY_MERGE_EMPTY|COPY_MERGE_APPLY_STAT|COPY_SAME_MOUNT|COPY_ALL_XATTRS,
|
||||||
|
/* denylist= */ NULL,
|
||||||
|
/* subvolumes= */ NULL);
|
||||||
|
if (r < 0) {
|
||||||
|
log_error_errno(r, "Failed to copy tree: %m");
|
||||||
|
_exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
_exit(EXIT_SUCCESS);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int import_remove_tree_foreign(const char *path, int *userns_fd) {
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(path);
|
||||||
|
assert(userns_fd);
|
||||||
|
|
||||||
|
r = import_make_foreign_userns(userns_fd);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
_cleanup_close_ int tree_fd = -EBADF;
|
||||||
|
r = mountfsd_mount_directory(
|
||||||
|
path,
|
||||||
|
*userns_fd,
|
||||||
|
DISSECT_IMAGE_FOREIGN_UID,
|
||||||
|
&tree_fd);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
r = safe_fork_full(
|
||||||
|
"rm-tree",
|
||||||
|
/* stdio_fds= */ NULL,
|
||||||
|
(int[]) { *userns_fd, tree_fd }, 2,
|
||||||
|
FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_REOPEN_LOG|FORK_WAIT,
|
||||||
|
/* ret_pid= */ NULL);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
if (r == 0) {
|
||||||
|
/* child */
|
||||||
|
|
||||||
|
r = namespace_enter(
|
||||||
|
/* pidns_fd= */ -EBADF,
|
||||||
|
/* mntns_fd= */ -EBADF,
|
||||||
|
/* netns_fd= */ -EBADF,
|
||||||
|
*userns_fd,
|
||||||
|
/* root_fd= */ -EBADF);
|
||||||
|
if (r < 0) {
|
||||||
|
log_error_errno(r, "Failed to join user namespace: %m");
|
||||||
|
_exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
_cleanup_close_ int dfd = fd_reopen(tree_fd, O_DIRECTORY|O_CLOEXEC);
|
||||||
|
if (dfd < 0) {
|
||||||
|
log_error_errno(r, "Failed to reopen tree fd: %m");
|
||||||
|
_exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
r = rm_rf_children(dfd, REMOVE_PHYSICAL|REMOVE_SUBVOLUME|REMOVE_CHMOD, /* root_dev= */ NULL);
|
||||||
|
if (r < 0)
|
||||||
|
log_warning_errno(r, "Failed to empty '%s' directory in foreign UID mode, ignoring: %m", path);
|
||||||
|
|
||||||
|
_exit(EXIT_SUCCESS);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int import_remove_tree(const char *path, int *userns_fd, ImportFlags flags) {
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(path);
|
||||||
|
assert(userns_fd);
|
||||||
|
|
||||||
|
/* Try the userns dance first, to remove foreign UID range owned trees */
|
||||||
|
if (FLAGS_SET(flags, IMPORT_FOREIGN_UID))
|
||||||
|
(void) import_remove_tree_foreign(path, userns_fd);
|
||||||
|
|
||||||
|
r = rm_rf(path, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME|REMOVE_MISSING_OK|REMOVE_CHMOD);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to remove '%s': %m", path);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|||||||
@ -15,15 +15,16 @@ typedef enum ImportFlags {
|
|||||||
IMPORT_CONVERT_QCOW2 = 1 << 5, /* raw: if we detect a qcow2 image, unpack it */
|
IMPORT_CONVERT_QCOW2 = 1 << 5, /* raw: if we detect a qcow2 image, unpack it */
|
||||||
IMPORT_DIRECT = 1 << 6, /* import without rename games */
|
IMPORT_DIRECT = 1 << 6, /* import without rename games */
|
||||||
IMPORT_SYNC = 1 << 7, /* fsync() right before we are done */
|
IMPORT_SYNC = 1 << 7, /* fsync() right before we are done */
|
||||||
|
IMPORT_FOREIGN_UID = 1 << 8, /* tar: go via nsresourced/mountfsd and make owned by foreign UID */
|
||||||
|
|
||||||
/* When pulling these flags are defined too */
|
/* When pulling these flags are defined too */
|
||||||
IMPORT_PULL_SETTINGS = 1 << 8, /* download .nspawn settings file */
|
IMPORT_PULL_SETTINGS = 1 << 9, /* download .nspawn settings file */
|
||||||
IMPORT_PULL_ROOTHASH = 1 << 9, /* only for raw: download .roothash file for verity */
|
IMPORT_PULL_ROOTHASH = 1 << 10, /* only for raw: download .roothash file for verity */
|
||||||
IMPORT_PULL_ROOTHASH_SIGNATURE = 1 << 10, /* only for raw: download .roothash.p7s file for verity */
|
IMPORT_PULL_ROOTHASH_SIGNATURE = 1 << 11, /* only for raw: download .roothash.p7s file for verity */
|
||||||
IMPORT_PULL_VERITY = 1 << 11, /* only for raw: download .verity file for verity */
|
IMPORT_PULL_VERITY = 1 << 12, /* only for raw: download .verity file for verity */
|
||||||
|
|
||||||
/* The supported flags for the tar and the raw importing */
|
/* The supported flags for the tar and the raw importing */
|
||||||
IMPORT_FLAGS_MASK_TAR = IMPORT_FORCE|IMPORT_READ_ONLY|IMPORT_BTRFS_SUBVOL|IMPORT_BTRFS_QUOTA|IMPORT_DIRECT|IMPORT_SYNC,
|
IMPORT_FLAGS_MASK_TAR = IMPORT_FORCE|IMPORT_READ_ONLY|IMPORT_BTRFS_SUBVOL|IMPORT_BTRFS_QUOTA|IMPORT_DIRECT|IMPORT_SYNC|IMPORT_FOREIGN_UID,
|
||||||
IMPORT_FLAGS_MASK_RAW = IMPORT_FORCE|IMPORT_READ_ONLY|IMPORT_CONVERT_QCOW2|IMPORT_DIRECT|IMPORT_SYNC,
|
IMPORT_FLAGS_MASK_RAW = IMPORT_FORCE|IMPORT_READ_ONLY|IMPORT_CONVERT_QCOW2|IMPORT_DIRECT|IMPORT_SYNC,
|
||||||
|
|
||||||
/* The supported flags for the tar and the raw pulling */
|
/* The supported flags for the tar and the raw pulling */
|
||||||
@ -34,7 +35,7 @@ typedef enum ImportFlags {
|
|||||||
} ImportFlags;
|
} ImportFlags;
|
||||||
|
|
||||||
int import_fork_tar_c(const char *path, PidRef *ret);
|
int import_fork_tar_c(const char *path, PidRef *ret);
|
||||||
int import_fork_tar_x(int tree_fd, PidRef *ret_pid);
|
int import_fork_tar_x(int tree_fd, int userns_fd, PidRef *ret_pid);
|
||||||
|
|
||||||
int import_mangle_os_tree(const char *path);
|
int import_mangle_os_tree(const char *path);
|
||||||
|
|
||||||
@ -42,4 +43,11 @@ bool import_validate_local(const char *name, ImportFlags flags);
|
|||||||
|
|
||||||
int import_allocate_event_with_signals(sd_event **ret);
|
int import_allocate_event_with_signals(sd_event **ret);
|
||||||
|
|
||||||
|
int import_make_foreign_userns(int *userns_fd);
|
||||||
|
|
||||||
|
int import_copy_foreign(int source_fd, int target_fd, int *userns_fd);
|
||||||
|
|
||||||
|
int import_remove_tree_foreign(const char *path, int *userns_fd);
|
||||||
|
int import_remove_tree(const char *path, int *userns_fd, ImportFlags flags);
|
||||||
|
|
||||||
#define IMPORT_BUFFER_SIZE (128U*1024U)
|
#define IMPORT_BUFFER_SIZE (128U*1024U)
|
||||||
|
|||||||
@ -35,10 +35,12 @@ static bool arg_btrfs_subvol = true;
|
|||||||
static bool arg_btrfs_quota = true;
|
static bool arg_btrfs_quota = true;
|
||||||
static bool arg_sync = true;
|
static bool arg_sync = true;
|
||||||
static bool arg_direct = false;
|
static bool arg_direct = false;
|
||||||
static const char *arg_image_root = NULL;
|
static char *arg_image_root = NULL;
|
||||||
static ImageClass arg_class = IMAGE_MACHINE;
|
static ImageClass arg_class = IMAGE_MACHINE;
|
||||||
static RuntimeScope arg_runtime_scope = _RUNTIME_SCOPE_INVALID;
|
static RuntimeScope arg_runtime_scope = _RUNTIME_SCOPE_INVALID;
|
||||||
|
|
||||||
|
STATIC_DESTRUCTOR_REGISTER(arg_image_root, freep);
|
||||||
|
|
||||||
typedef struct ProgressInfo {
|
typedef struct ProgressInfo {
|
||||||
RateLimit limit;
|
RateLimit limit;
|
||||||
char *path;
|
char *path;
|
||||||
@ -280,7 +282,9 @@ static int help(int argc, char *argv[], void *userdata) {
|
|||||||
" subvolume\n"
|
" subvolume\n"
|
||||||
" --sync=BOOL Controls whether to sync() before completing\n"
|
" --sync=BOOL Controls whether to sync() before completing\n"
|
||||||
" --class=CLASS Select image class (machine, sysext, confext,\n"
|
" --class=CLASS Select image class (machine, sysext, confext,\n"
|
||||||
" portable)\n",
|
" portable)\n"
|
||||||
|
" --system Operate in per-system mode\n"
|
||||||
|
" --user Operate in per-user mode\n",
|
||||||
program_invocation_short_name,
|
program_invocation_short_name,
|
||||||
ansi_underline(),
|
ansi_underline(),
|
||||||
ansi_normal(),
|
ansi_normal(),
|
||||||
@ -302,6 +306,8 @@ static int parse_argv(int argc, char *argv[]) {
|
|||||||
ARG_BTRFS_QUOTA,
|
ARG_BTRFS_QUOTA,
|
||||||
ARG_SYNC,
|
ARG_SYNC,
|
||||||
ARG_CLASS,
|
ARG_CLASS,
|
||||||
|
ARG_SYSTEM,
|
||||||
|
ARG_USER,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct option options[] = {
|
static const struct option options[] = {
|
||||||
@ -315,6 +321,8 @@ static int parse_argv(int argc, char *argv[]) {
|
|||||||
{ "btrfs-quota", required_argument, NULL, ARG_BTRFS_QUOTA },
|
{ "btrfs-quota", required_argument, NULL, ARG_BTRFS_QUOTA },
|
||||||
{ "sync", required_argument, NULL, ARG_SYNC },
|
{ "sync", required_argument, NULL, ARG_SYNC },
|
||||||
{ "class", required_argument, NULL, ARG_CLASS },
|
{ "class", required_argument, NULL, ARG_CLASS },
|
||||||
|
{ "system", no_argument, NULL, ARG_SYSTEM },
|
||||||
|
{ "user", no_argument, NULL, ARG_USER },
|
||||||
{}
|
{}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -338,7 +346,10 @@ static int parse_argv(int argc, char *argv[]) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case ARG_IMAGE_ROOT:
|
case ARG_IMAGE_ROOT:
|
||||||
arg_image_root = optarg;
|
r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image_root);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ARG_READ_ONLY:
|
case ARG_READ_ONLY:
|
||||||
@ -377,6 +388,14 @@ static int parse_argv(int argc, char *argv[]) {
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case ARG_SYSTEM:
|
||||||
|
arg_runtime_scope = RUNTIME_SCOPE_SYSTEM;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ARG_USER:
|
||||||
|
arg_runtime_scope = RUNTIME_SCOPE_USER;
|
||||||
|
break;
|
||||||
|
|
||||||
case '?':
|
case '?':
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
||||||
@ -384,8 +403,11 @@ static int parse_argv(int argc, char *argv[]) {
|
|||||||
assert_not_reached();
|
assert_not_reached();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!arg_image_root)
|
if (!arg_image_root) {
|
||||||
arg_image_root = image_root_to_string(arg_class);
|
r = image_root_pick(arg_runtime_scope < 0 ? RUNTIME_SCOPE_SYSTEM : arg_runtime_scope, arg_class, /* runtime= */ false, &arg_image_root);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to pick image root: %m");
|
||||||
|
}
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,6 +17,7 @@
|
|||||||
#include "parse-util.h"
|
#include "parse-util.h"
|
||||||
#include "path-util.h"
|
#include "path-util.h"
|
||||||
#include "proc-cmdline.h"
|
#include "proc-cmdline.h"
|
||||||
|
#include "runtime-scope.h"
|
||||||
#include "specifier.h"
|
#include "specifier.h"
|
||||||
#include "string-util.h"
|
#include "string-util.h"
|
||||||
#include "unit-name.h"
|
#include "unit-name.h"
|
||||||
@ -200,7 +201,10 @@ static int parse_pull_expression(const char *v) {
|
|||||||
if (!GREEDY_REALLOC(arg_transfers, arg_n_transfers + 1))
|
if (!GREEDY_REALLOC(arg_transfers, arg_n_transfers + 1))
|
||||||
return log_oom();
|
return log_oom();
|
||||||
|
|
||||||
const char *image_root = runtime ? image_root_runtime_to_string(class) : image_root_to_string(class);
|
_cleanup_free_ char *image_root = NULL;
|
||||||
|
r = image_root_pick(RUNTIME_SCOPE_SYSTEM, class, runtime, &image_root);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to pick image root: %m");
|
||||||
|
|
||||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL;
|
_cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL;
|
||||||
r = sd_json_buildo(
|
r = sd_json_buildo(
|
||||||
@ -220,7 +224,7 @@ static int parse_pull_expression(const char *v) {
|
|||||||
.type = type,
|
.type = type,
|
||||||
.local = TAKE_PTR(local),
|
.local = TAKE_PTR(local),
|
||||||
.remote = TAKE_PTR(remote),
|
.remote = TAKE_PTR(remote),
|
||||||
.image_root = image_root,
|
.image_root = TAKE_PTR(image_root),
|
||||||
.json = TAKE_PTR(j),
|
.json = TAKE_PTR(j),
|
||||||
.blockdev = blockdev,
|
.blockdev = blockdev,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
#include "alloc-util.h"
|
#include "alloc-util.h"
|
||||||
#include "btrfs-util.h"
|
#include "btrfs-util.h"
|
||||||
|
#include "dissect-image.h"
|
||||||
#include "errno-util.h"
|
#include "errno-util.h"
|
||||||
#include "fd-util.h"
|
#include "fd-util.h"
|
||||||
#include "format-util.h"
|
#include "format-util.h"
|
||||||
@ -46,6 +47,7 @@ typedef struct TarImport {
|
|||||||
int input_fd;
|
int input_fd;
|
||||||
int tar_fd;
|
int tar_fd;
|
||||||
int tree_fd;
|
int tree_fd;
|
||||||
|
int userns_fd;
|
||||||
|
|
||||||
ImportCompress compress;
|
ImportCompress compress;
|
||||||
|
|
||||||
@ -73,7 +75,10 @@ TarImport* tar_import_unref(TarImport *i) {
|
|||||||
|
|
||||||
pidref_done_sigkill_wait(&i->tar_pid);
|
pidref_done_sigkill_wait(&i->tar_pid);
|
||||||
|
|
||||||
rm_rf_subvolume_and_free(i->temp_path);
|
if (i->temp_path) {
|
||||||
|
import_remove_tree(i->temp_path, &i->userns_fd, i->flags);
|
||||||
|
free(i->temp_path);
|
||||||
|
}
|
||||||
|
|
||||||
import_compress_free(&i->compress);
|
import_compress_free(&i->compress);
|
||||||
|
|
||||||
@ -81,6 +86,7 @@ TarImport* tar_import_unref(TarImport *i) {
|
|||||||
|
|
||||||
safe_close(i->tar_fd);
|
safe_close(i->tar_fd);
|
||||||
safe_close(i->tree_fd);
|
safe_close(i->tree_fd);
|
||||||
|
safe_close(i->userns_fd);
|
||||||
|
|
||||||
free(i->final_path);
|
free(i->final_path);
|
||||||
free(i->image_root);
|
free(i->image_root);
|
||||||
@ -114,6 +120,7 @@ int tar_import_new(
|
|||||||
.input_fd = -EBADF,
|
.input_fd = -EBADF,
|
||||||
.tar_fd = -EBADF,
|
.tar_fd = -EBADF,
|
||||||
.tree_fd = -EBADF,
|
.tree_fd = -EBADF,
|
||||||
|
.userns_fd = -EBADF,
|
||||||
.on_finished = on_finished,
|
.on_finished = on_finished,
|
||||||
.userdata = userdata,
|
.userdata = userdata,
|
||||||
.last_percent = UINT_MAX,
|
.last_percent = UINT_MAX,
|
||||||
@ -200,8 +207,8 @@ static int tar_import_finish(TarImport *i) {
|
|||||||
AT_FDCWD, d,
|
AT_FDCWD, d,
|
||||||
AT_FDCWD, i->final_path,
|
AT_FDCWD, i->final_path,
|
||||||
(i->flags & IMPORT_FORCE ? INSTALL_REPLACE : 0) |
|
(i->flags & IMPORT_FORCE ? INSTALL_REPLACE : 0) |
|
||||||
(i->flags & IMPORT_READ_ONLY ? INSTALL_READ_ONLY : 0) |
|
(i->flags & IMPORT_READ_ONLY ? INSTALL_READ_ONLY|INSTALL_GRACEFUL : 0) |
|
||||||
(i->flags & IMPORT_SYNC ? INSTALL_SYNCFS : 0));
|
(i->flags & IMPORT_SYNC ? INSTALL_SYNCFS|INSTALL_GRACEFUL : 0));
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return log_error_errno(r, "Failed to move '%s' into place: %m", i->final_path ?: i->local);
|
return log_error_errno(r, "Failed to move '%s' into place: %m", i->final_path ?: i->local);
|
||||||
|
|
||||||
@ -244,26 +251,41 @@ static int tar_import_fork_tar(TarImport *i) {
|
|||||||
if (FLAGS_SET(i->flags, IMPORT_DIRECT|IMPORT_FORCE))
|
if (FLAGS_SET(i->flags, IMPORT_DIRECT|IMPORT_FORCE))
|
||||||
(void) rm_rf(d, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME);
|
(void) rm_rf(d, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME);
|
||||||
|
|
||||||
if (i->flags & IMPORT_BTRFS_SUBVOL)
|
if (FLAGS_SET(i->flags, IMPORT_FOREIGN_UID)) {
|
||||||
r = btrfs_subvol_make_fallback(AT_FDCWD, d, 0755);
|
r = import_make_foreign_userns(&i->userns_fd);
|
||||||
else
|
if (r < 0)
|
||||||
r = RET_NERRNO(mkdir(d, 0755));
|
return r;
|
||||||
if (r == -EEXIST && (i->flags & IMPORT_DIRECT)) /* EEXIST is OK if in direct mode, but not otherwise,
|
|
||||||
* because in that case our temporary path collided */
|
_cleanup_close_ int directory_fd = -EBADF;
|
||||||
r = 0;
|
r = mountfsd_make_directory(d, /* flags= */ 0, &directory_fd);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return log_error_errno(r, "Failed to create directory/subvolume %s: %m", d);
|
return r;
|
||||||
if (r > 0 && (i->flags & IMPORT_BTRFS_QUOTA)) { /* actually btrfs subvol */
|
|
||||||
if (!(i->flags & IMPORT_DIRECT))
|
r = mountfsd_mount_directory_fd(directory_fd, i->userns_fd, DISSECT_IMAGE_FOREIGN_UID, &i->tree_fd);
|
||||||
(void) import_assign_pool_quota_and_warn(root);
|
if (r < 0)
|
||||||
(void) import_assign_pool_quota_and_warn(d);
|
return r;
|
||||||
|
} else {
|
||||||
|
if (i->flags & IMPORT_BTRFS_SUBVOL)
|
||||||
|
r = btrfs_subvol_make_fallback(AT_FDCWD, d, 0755);
|
||||||
|
else
|
||||||
|
r = RET_NERRNO(mkdir(d, 0755));
|
||||||
|
if (r == -EEXIST && (i->flags & IMPORT_DIRECT)) /* EEXIST is OK if in direct mode, but not otherwise,
|
||||||
|
* because in that case our temporary path collided */
|
||||||
|
r = 0;
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to create directory/subvolume %s: %m", d);
|
||||||
|
if (r > 0 && (i->flags & IMPORT_BTRFS_QUOTA)) { /* actually btrfs subvol */
|
||||||
|
if (!(i->flags & IMPORT_DIRECT))
|
||||||
|
(void) import_assign_pool_quota_and_warn(root);
|
||||||
|
(void) import_assign_pool_quota_and_warn(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
i->tree_fd = open(d, O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW);
|
||||||
|
if (i->tree_fd < 0)
|
||||||
|
return log_error_errno(errno, "Failed to open '%s': %m", d);
|
||||||
}
|
}
|
||||||
|
|
||||||
i->tree_fd = open(d, O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW);
|
i->tar_fd = import_fork_tar_x(i->tree_fd, i->userns_fd, &i->tar_pid);
|
||||||
if (i->tree_fd < 0)
|
|
||||||
return log_error_errno(errno, "Failed to open '%s': %m", d);
|
|
||||||
|
|
||||||
i->tar_fd = import_fork_tar_x(i->tree_fd, &i->tar_pid);
|
|
||||||
if (i->tar_fd < 0)
|
if (i->tar_fd < 0)
|
||||||
return i->tar_fd;
|
return i->tar_fd;
|
||||||
|
|
||||||
|
|||||||
@ -26,12 +26,14 @@
|
|||||||
#include "string-util.h"
|
#include "string-util.h"
|
||||||
#include "verbs.h"
|
#include "verbs.h"
|
||||||
|
|
||||||
static const char *arg_image_root = NULL;
|
static char *arg_image_root = NULL;
|
||||||
static ImportFlags arg_import_flags = IMPORT_BTRFS_SUBVOL | IMPORT_BTRFS_QUOTA | IMPORT_CONVERT_QCOW2 | IMPORT_SYNC;
|
static ImportFlags arg_import_flags = IMPORT_BTRFS_SUBVOL | IMPORT_BTRFS_QUOTA | IMPORT_CONVERT_QCOW2 | IMPORT_SYNC;
|
||||||
static uint64_t arg_offset = UINT64_MAX, arg_size_max = UINT64_MAX;
|
static uint64_t arg_offset = UINT64_MAX, arg_size_max = UINT64_MAX;
|
||||||
static ImageClass arg_class = IMAGE_MACHINE;
|
static ImageClass arg_class = IMAGE_MACHINE;
|
||||||
static RuntimeScope arg_runtime_scope = _RUNTIME_SCOPE_INVALID;
|
static RuntimeScope arg_runtime_scope = _RUNTIME_SCOPE_INVALID;
|
||||||
|
|
||||||
|
STATIC_DESTRUCTOR_REGISTER(arg_image_root, freep);
|
||||||
|
|
||||||
static int normalize_local(const char *local, char **ret) {
|
static int normalize_local(const char *local, char **ret) {
|
||||||
_cleanup_free_ char *ll = NULL;
|
_cleanup_free_ char *ll = NULL;
|
||||||
int r;
|
int r;
|
||||||
@ -290,7 +292,9 @@ static int help(int argc, char *argv[], void *userdata) {
|
|||||||
" --offset=BYTES Offset to seek to in destination\n"
|
" --offset=BYTES Offset to seek to in destination\n"
|
||||||
" --size-max=BYTES Maximum number of bytes to write to destination\n"
|
" --size-max=BYTES Maximum number of bytes to write to destination\n"
|
||||||
" --class=CLASS Select image class (machine, sysext, confext,\n"
|
" --class=CLASS Select image class (machine, sysext, confext,\n"
|
||||||
" portable)\n",
|
" portable)\n"
|
||||||
|
" --system Operate in per-system mode\n"
|
||||||
|
" --user Operate in per-user mode\n",
|
||||||
program_invocation_short_name,
|
program_invocation_short_name,
|
||||||
ansi_underline(),
|
ansi_underline(),
|
||||||
ansi_normal(),
|
ansi_normal(),
|
||||||
@ -315,6 +319,8 @@ static int parse_argv(int argc, char *argv[]) {
|
|||||||
ARG_OFFSET,
|
ARG_OFFSET,
|
||||||
ARG_SIZE_MAX,
|
ARG_SIZE_MAX,
|
||||||
ARG_CLASS,
|
ARG_CLASS,
|
||||||
|
ARG_SYSTEM,
|
||||||
|
ARG_USER,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct option options[] = {
|
static const struct option options[] = {
|
||||||
@ -331,6 +337,8 @@ static int parse_argv(int argc, char *argv[]) {
|
|||||||
{ "offset", required_argument, NULL, ARG_OFFSET },
|
{ "offset", required_argument, NULL, ARG_OFFSET },
|
||||||
{ "size-max", required_argument, NULL, ARG_SIZE_MAX },
|
{ "size-max", required_argument, NULL, ARG_SIZE_MAX },
|
||||||
{ "class", required_argument, NULL, ARG_CLASS },
|
{ "class", required_argument, NULL, ARG_CLASS },
|
||||||
|
{ "system", no_argument, NULL, ARG_SYSTEM },
|
||||||
|
{ "user", no_argument, NULL, ARG_USER },
|
||||||
{}
|
{}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -354,7 +362,10 @@ static int parse_argv(int argc, char *argv[]) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case ARG_IMAGE_ROOT:
|
case ARG_IMAGE_ROOT:
|
||||||
arg_image_root = optarg;
|
r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image_root);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ARG_READ_ONLY:
|
case ARG_READ_ONLY:
|
||||||
@ -430,6 +441,14 @@ static int parse_argv(int argc, char *argv[]) {
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case ARG_SYSTEM:
|
||||||
|
arg_runtime_scope = RUNTIME_SCOPE_SYSTEM;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ARG_USER:
|
||||||
|
arg_runtime_scope = RUNTIME_SCOPE_USER;
|
||||||
|
break;
|
||||||
|
|
||||||
case '?':
|
case '?':
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
||||||
@ -446,8 +465,14 @@ static int parse_argv(int argc, char *argv[]) {
|
|||||||
if (arg_offset != UINT64_MAX && !FLAGS_SET(arg_import_flags, IMPORT_DIRECT))
|
if (arg_offset != UINT64_MAX && !FLAGS_SET(arg_import_flags, IMPORT_DIRECT))
|
||||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "File offset only supported in --direct mode.");
|
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "File offset only supported in --direct mode.");
|
||||||
|
|
||||||
if (!arg_image_root)
|
if (!arg_image_root) {
|
||||||
arg_image_root = image_root_to_string(arg_class);
|
r = image_root_pick(arg_runtime_scope < 0 ? RUNTIME_SCOPE_SYSTEM : arg_runtime_scope, arg_class, /* runtime= */ false, &arg_image_root);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to pick image root: %m");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arg_runtime_scope == RUNTIME_SCOPE_USER)
|
||||||
|
arg_import_flags |= IMPORT_FOREIGN_UID;
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -45,19 +45,28 @@ static ImportVerify arg_verify = IMPORT_VERIFY_SIGNATURE;
|
|||||||
static const char* arg_format = NULL;
|
static const char* arg_format = NULL;
|
||||||
static sd_json_format_flags_t arg_json_format_flags = SD_JSON_FORMAT_OFF;
|
static sd_json_format_flags_t arg_json_format_flags = SD_JSON_FORMAT_OFF;
|
||||||
static ImageClass arg_image_class = _IMAGE_CLASS_INVALID;
|
static ImageClass arg_image_class = _IMAGE_CLASS_INVALID;
|
||||||
|
static RuntimeScope arg_runtime_scope = RUNTIME_SCOPE_SYSTEM;
|
||||||
|
|
||||||
#define PROGRESS_PREFIX "Total:"
|
#define PROGRESS_PREFIX "Total:"
|
||||||
|
|
||||||
static int settle_image_class(void) {
|
static int settle_image_class(void) {
|
||||||
|
int r;
|
||||||
|
|
||||||
if (arg_image_class < 0) {
|
if (arg_image_class < 0) {
|
||||||
_cleanup_free_ char *j = NULL;
|
_cleanup_free_ char *j = NULL;
|
||||||
|
|
||||||
for (ImageClass class = 0; class < _IMAGE_CLASS_MAX; class++)
|
for (ImageClass class = 0; class < _IMAGE_CLASS_MAX; class++) {
|
||||||
|
_cleanup_free_ char *root = NULL;
|
||||||
|
|
||||||
|
r = image_root_pick(arg_runtime_scope, class, /* runtime= */ false, &root);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to pick image root: %m");
|
||||||
|
|
||||||
if (strextendf_with_separator(&j, ", ", "%s (downloads to %s/)",
|
if (strextendf_with_separator(&j, ", ", "%s (downloads to %s/)",
|
||||||
image_class_to_string(class),
|
image_class_to_string(class),
|
||||||
image_root_to_string(class)) < 0)
|
root) < 0)
|
||||||
return log_oom();
|
return log_oom();
|
||||||
|
}
|
||||||
|
|
||||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||||
"No image class specified, retry with --class= set to one of: %s.", j);
|
"No image class specified, retry with --class= set to one of: %s.", j);
|
||||||
@ -1014,6 +1023,8 @@ static int help(int argc, char *argv[], void *userdata) {
|
|||||||
" --no-ask-password Do not ask for system passwords\n"
|
" --no-ask-password Do not ask for system passwords\n"
|
||||||
" -H --host=[USER@]HOST Operate on remote host\n"
|
" -H --host=[USER@]HOST Operate on remote host\n"
|
||||||
" -M --machine=CONTAINER Operate on local container\n"
|
" -M --machine=CONTAINER Operate on local container\n"
|
||||||
|
" --system Connect to system machine manager\n"
|
||||||
|
" --user Connect to user machine manager\n"
|
||||||
" --read-only Create read-only image\n"
|
" --read-only Create read-only image\n"
|
||||||
" -q --quiet Suppress output\n"
|
" -q --quiet Suppress output\n"
|
||||||
" --json=pretty|short|off Generate JSON output\n"
|
" --json=pretty|short|off Generate JSON output\n"
|
||||||
@ -1055,6 +1066,8 @@ static int parse_argv(int argc, char *argv[]) {
|
|||||||
ARG_FORMAT,
|
ARG_FORMAT,
|
||||||
ARG_CLASS,
|
ARG_CLASS,
|
||||||
ARG_KEEP_DOWNLOAD,
|
ARG_KEEP_DOWNLOAD,
|
||||||
|
ARG_SYSTEM,
|
||||||
|
ARG_USER,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct option options[] = {
|
static const struct option options[] = {
|
||||||
@ -1073,6 +1086,8 @@ static int parse_argv(int argc, char *argv[]) {
|
|||||||
{ "format", required_argument, NULL, ARG_FORMAT },
|
{ "format", required_argument, NULL, ARG_FORMAT },
|
||||||
{ "class", required_argument, NULL, ARG_CLASS },
|
{ "class", required_argument, NULL, ARG_CLASS },
|
||||||
{ "keep-download", required_argument, NULL, ARG_KEEP_DOWNLOAD },
|
{ "keep-download", required_argument, NULL, ARG_KEEP_DOWNLOAD },
|
||||||
|
{ "system", no_argument, NULL, ARG_SYSTEM },
|
||||||
|
{ "user", no_argument, NULL, ARG_USER },
|
||||||
{}
|
{}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1198,6 +1213,14 @@ static int parse_argv(int argc, char *argv[]) {
|
|||||||
arg_import_flags_mask |= IMPORT_PULL_KEEP_DOWNLOAD;
|
arg_import_flags_mask |= IMPORT_PULL_KEEP_DOWNLOAD;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case ARG_USER:
|
||||||
|
arg_runtime_scope = RUNTIME_SCOPE_USER;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ARG_SYSTEM:
|
||||||
|
arg_runtime_scope = RUNTIME_SCOPE_SYSTEM;
|
||||||
|
break;
|
||||||
|
|
||||||
case '?':
|
case '?':
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
||||||
@ -1240,9 +1263,9 @@ static int run(int argc, char *argv[]) {
|
|||||||
if (r <= 0)
|
if (r <= 0)
|
||||||
return r;
|
return r;
|
||||||
|
|
||||||
r = bus_connect_transport(arg_transport, arg_host, RUNTIME_SCOPE_SYSTEM, &bus);
|
r = bus_connect_transport(arg_transport, arg_host, arg_runtime_scope, &bus);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return bus_log_connect_error(r, arg_transport, RUNTIME_SCOPE_SYSTEM);
|
return bus_log_connect_error(r, arg_transport, arg_runtime_scope);
|
||||||
|
|
||||||
(void) sd_bus_set_allow_interactive_authorization(bus, arg_ask_password);
|
(void) sd_bus_set_allow_interactive_authorization(bus, arg_ask_password);
|
||||||
|
|
||||||
|
|||||||
@ -32,6 +32,7 @@
|
|||||||
#include "notify-recv.h"
|
#include "notify-recv.h"
|
||||||
#include "os-util.h"
|
#include "os-util.h"
|
||||||
#include "parse-util.h"
|
#include "parse-util.h"
|
||||||
|
#include "path-lookup.h"
|
||||||
#include "percent-util.h"
|
#include "percent-util.h"
|
||||||
#include "pidref.h"
|
#include "pidref.h"
|
||||||
#include "process-util.h"
|
#include "process-util.h"
|
||||||
@ -100,7 +101,8 @@ typedef struct Transfer {
|
|||||||
|
|
||||||
typedef struct Manager {
|
typedef struct Manager {
|
||||||
sd_event *event;
|
sd_event *event;
|
||||||
sd_bus *bus;
|
sd_bus *api_bus;
|
||||||
|
sd_bus *system_bus;
|
||||||
sd_varlink_server *varlink_server;
|
sd_varlink_server *varlink_server;
|
||||||
|
|
||||||
uint32_t current_transfer_id;
|
uint32_t current_transfer_id;
|
||||||
@ -113,7 +115,7 @@ typedef struct Manager {
|
|||||||
bool use_btrfs_subvol;
|
bool use_btrfs_subvol;
|
||||||
bool use_btrfs_quota;
|
bool use_btrfs_quota;
|
||||||
|
|
||||||
RuntimeScope runtime_scope; /* for now: always RUNTIME_SCOPE_SYSTEM */
|
RuntimeScope runtime_scope;
|
||||||
} Manager;
|
} Manager;
|
||||||
|
|
||||||
#define TRANSFERS_MAX 64
|
#define TRANSFERS_MAX 64
|
||||||
@ -223,7 +225,7 @@ static void transfer_send_log_line(Transfer *t, const char *line) {
|
|||||||
log_full(priority, "(transfer%" PRIu32 ") %s", t->id, line);
|
log_full(priority, "(transfer%" PRIu32 ") %s", t->id, line);
|
||||||
|
|
||||||
r = sd_bus_emit_signal(
|
r = sd_bus_emit_signal(
|
||||||
t->manager->bus,
|
t->manager->api_bus,
|
||||||
t->object_path,
|
t->object_path,
|
||||||
"org.freedesktop.import1.Transfer",
|
"org.freedesktop.import1.Transfer",
|
||||||
"LogMessage",
|
"LogMessage",
|
||||||
@ -254,7 +256,7 @@ static void transfer_send_progress_update(Transfer *t) {
|
|||||||
double progress = transfer_percent_as_double(t);
|
double progress = transfer_percent_as_double(t);
|
||||||
|
|
||||||
r = sd_bus_emit_signal(
|
r = sd_bus_emit_signal(
|
||||||
t->manager->bus,
|
t->manager->api_bus,
|
||||||
t->object_path,
|
t->object_path,
|
||||||
"org.freedesktop.import1.Transfer",
|
"org.freedesktop.import1.Transfer",
|
||||||
"ProgressUpdate",
|
"ProgressUpdate",
|
||||||
@ -335,7 +337,7 @@ static int transfer_finalize(Transfer *t, bool success) {
|
|||||||
transfer_send_logs(t, true);
|
transfer_send_logs(t, true);
|
||||||
|
|
||||||
r = sd_bus_emit_signal(
|
r = sd_bus_emit_signal(
|
||||||
t->manager->bus,
|
t->manager->api_bus,
|
||||||
"/org/freedesktop/import1",
|
"/org/freedesktop/import1",
|
||||||
"org.freedesktop.import1.Manager",
|
"org.freedesktop.import1.Manager",
|
||||||
"TransferRemoved",
|
"TransferRemoved",
|
||||||
@ -443,6 +445,7 @@ static int transfer_start(Transfer *t) {
|
|||||||
const char *cmd[] = {
|
const char *cmd[] = {
|
||||||
NULL, /* systemd-import, systemd-import-fs, systemd-export or systemd-pull */
|
NULL, /* systemd-import, systemd-import-fs, systemd-export or systemd-pull */
|
||||||
NULL, /* tar, raw */
|
NULL, /* tar, raw */
|
||||||
|
NULL, /* --system or --user */
|
||||||
NULL, /* --verify= */
|
NULL, /* --verify= */
|
||||||
NULL, /* verify argument */
|
NULL, /* verify argument */
|
||||||
NULL, /* --class= */
|
NULL, /* --class= */
|
||||||
@ -524,6 +527,8 @@ static int transfer_start(Transfer *t) {
|
|||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cmd[k++] = runtime_scope_cmdline_option_to_string(t->manager->runtime_scope);
|
||||||
|
|
||||||
if (t->verify != _IMPORT_VERIFY_INVALID) {
|
if (t->verify != _IMPORT_VERIFY_INVALID) {
|
||||||
cmd[k++] = "--verify";
|
cmd[k++] = "--verify";
|
||||||
cmd[k++] = import_verify_to_string(t->verify);
|
cmd[k++] = import_verify_to_string(t->verify);
|
||||||
@ -602,7 +607,7 @@ static int transfer_start(Transfer *t) {
|
|||||||
return r;
|
return r;
|
||||||
|
|
||||||
r = sd_bus_emit_signal(
|
r = sd_bus_emit_signal(
|
||||||
t->manager->bus,
|
t->manager->api_bus,
|
||||||
"/org/freedesktop/import1",
|
"/org/freedesktop/import1",
|
||||||
"org.freedesktop.import1.Manager",
|
"org.freedesktop.import1.Manager",
|
||||||
"TransferNew",
|
"TransferNew",
|
||||||
@ -630,7 +635,8 @@ static Manager *manager_unref(Manager *m) {
|
|||||||
|
|
||||||
hashmap_free(m->polkit_registry);
|
hashmap_free(m->polkit_registry);
|
||||||
|
|
||||||
m->bus = sd_bus_flush_close_unref(m->bus);
|
m->api_bus = sd_bus_flush_close_unref(m->api_bus);
|
||||||
|
m->system_bus = sd_bus_flush_close_unref(m->system_bus);
|
||||||
m->varlink_server = sd_varlink_server_unref(m->varlink_server);
|
m->varlink_server = sd_varlink_server_unref(m->varlink_server);
|
||||||
|
|
||||||
sd_event_unref(m->event);
|
sd_event_unref(m->event);
|
||||||
@ -681,7 +687,7 @@ static int manager_on_notify(sd_event_source *s, int fd, uint32_t revents, void
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int manager_new(Manager **ret) {
|
static int manager_new(RuntimeScope scope, Manager **ret) {
|
||||||
_cleanup_(manager_unrefp) Manager *m = NULL;
|
_cleanup_(manager_unrefp) Manager *m = NULL;
|
||||||
int r;
|
int r;
|
||||||
|
|
||||||
@ -694,7 +700,7 @@ static int manager_new(Manager **ret) {
|
|||||||
*m = (Manager) {
|
*m = (Manager) {
|
||||||
.use_btrfs_subvol = true,
|
.use_btrfs_subvol = true,
|
||||||
.use_btrfs_quota = true,
|
.use_btrfs_quota = true,
|
||||||
.runtime_scope = RUNTIME_SCOPE_SYSTEM,
|
.runtime_scope = scope,
|
||||||
};
|
};
|
||||||
|
|
||||||
r = sd_event_default(&m->event);
|
r = sd_event_default(&m->event);
|
||||||
@ -757,16 +763,18 @@ static int method_import_tar_or_raw(sd_bus_message *msg, void *userdata, sd_bus_
|
|||||||
|
|
||||||
assert(msg);
|
assert(msg);
|
||||||
|
|
||||||
r = bus_verify_polkit_async(
|
if (m->runtime_scope != RUNTIME_SCOPE_USER) {
|
||||||
msg,
|
r = bus_verify_polkit_async(
|
||||||
"org.freedesktop.import1.import",
|
msg,
|
||||||
/* details= */ NULL,
|
"org.freedesktop.import1.import",
|
||||||
&m->polkit_registry,
|
/* details= */ NULL,
|
||||||
error);
|
&m->polkit_registry,
|
||||||
if (r < 0)
|
error);
|
||||||
return r;
|
if (r < 0)
|
||||||
if (r == 0)
|
return r;
|
||||||
return 1; /* Will call us back */
|
if (r == 0)
|
||||||
|
return 1; /* Will call us back */
|
||||||
|
}
|
||||||
|
|
||||||
if (endswith(sd_bus_message_get_member(msg), "Ex")) {
|
if (endswith(sd_bus_message_get_member(msg), "Ex")) {
|
||||||
const char *sclass;
|
const char *sclass;
|
||||||
@ -858,16 +866,18 @@ static int method_import_fs(sd_bus_message *msg, void *userdata, sd_bus_error *e
|
|||||||
|
|
||||||
assert(msg);
|
assert(msg);
|
||||||
|
|
||||||
r = bus_verify_polkit_async(
|
if (m->runtime_scope != RUNTIME_SCOPE_USER) {
|
||||||
msg,
|
r = bus_verify_polkit_async(
|
||||||
"org.freedesktop.import1.import",
|
msg,
|
||||||
/* details= */ NULL,
|
"org.freedesktop.import1.import",
|
||||||
&m->polkit_registry,
|
/* details= */ NULL,
|
||||||
error);
|
&m->polkit_registry,
|
||||||
if (r < 0)
|
error);
|
||||||
return r;
|
if (r < 0)
|
||||||
if (r == 0)
|
return r;
|
||||||
return 1; /* Will call us back */
|
if (r == 0)
|
||||||
|
return 1; /* Will call us back */
|
||||||
|
}
|
||||||
|
|
||||||
if (endswith(sd_bus_message_get_member(msg), "Ex")) {
|
if (endswith(sd_bus_message_get_member(msg), "Ex")) {
|
||||||
const char *sclass;
|
const char *sclass;
|
||||||
@ -956,16 +966,18 @@ static int method_export_tar_or_raw(sd_bus_message *msg, void *userdata, sd_bus_
|
|||||||
|
|
||||||
assert(msg);
|
assert(msg);
|
||||||
|
|
||||||
r = bus_verify_polkit_async(
|
if (m->runtime_scope != RUNTIME_SCOPE_USER) {
|
||||||
msg,
|
r = bus_verify_polkit_async(
|
||||||
"org.freedesktop.import1.export",
|
msg,
|
||||||
/* details= */ NULL,
|
"org.freedesktop.import1.export",
|
||||||
&m->polkit_registry,
|
/* details= */ NULL,
|
||||||
error);
|
&m->polkit_registry,
|
||||||
if (r < 0)
|
error);
|
||||||
return r;
|
if (r < 0)
|
||||||
if (r == 0)
|
return r;
|
||||||
return 1; /* Will call us back */
|
if (r == 0)
|
||||||
|
return 1; /* Will call us back */
|
||||||
|
}
|
||||||
|
|
||||||
if (endswith(sd_bus_message_get_member(msg), "Ex")) {
|
if (endswith(sd_bus_message_get_member(msg), "Ex")) {
|
||||||
const char *sclass;
|
const char *sclass;
|
||||||
@ -1054,16 +1066,18 @@ static int method_pull_tar_or_raw(sd_bus_message *msg, void *userdata, sd_bus_er
|
|||||||
|
|
||||||
assert(msg);
|
assert(msg);
|
||||||
|
|
||||||
r = bus_verify_polkit_async(
|
if (m->runtime_scope != RUNTIME_SCOPE_USER) {
|
||||||
msg,
|
r = bus_verify_polkit_async(
|
||||||
"org.freedesktop.import1.pull",
|
msg,
|
||||||
/* details= */ NULL,
|
"org.freedesktop.import1.pull",
|
||||||
&m->polkit_registry,
|
/* details= */ NULL,
|
||||||
error);
|
&m->polkit_registry,
|
||||||
if (r < 0)
|
error);
|
||||||
return r;
|
if (r < 0)
|
||||||
if (r == 0)
|
return r;
|
||||||
return 1; /* Will call us back */
|
if (r == 0)
|
||||||
|
return 1; /* Will call us back */
|
||||||
|
}
|
||||||
|
|
||||||
if (endswith(sd_bus_message_get_member(msg), "Ex")) {
|
if (endswith(sd_bus_message_get_member(msg), "Ex")) {
|
||||||
const char *sclass;
|
const char *sclass;
|
||||||
@ -1702,28 +1716,43 @@ static int manager_connect_bus(Manager *m) {
|
|||||||
|
|
||||||
assert(m);
|
assert(m);
|
||||||
assert(m->event);
|
assert(m->event);
|
||||||
assert(!m->bus);
|
assert(!m->system_bus);
|
||||||
|
assert(!m->api_bus);
|
||||||
|
|
||||||
r = bus_open_system_watch_bind(&m->bus);
|
r = bus_open_system_watch_bind(&m->system_bus);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return log_error_errno(r, "Failed to get system bus connection: %m");
|
return log_error_errno(r, "Failed to get system bus connection: %m");
|
||||||
|
|
||||||
r = bus_add_implementation(m->bus, &manager_object, m);
|
r = sd_bus_attach_event(m->system_bus, m->event, 0);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to attach system bus to event loop: %m");
|
||||||
|
|
||||||
|
if (m->runtime_scope == RUNTIME_SCOPE_SYSTEM)
|
||||||
|
m->api_bus = sd_bus_ref(m->system_bus);
|
||||||
|
else {
|
||||||
|
assert(m->runtime_scope == RUNTIME_SCOPE_USER);
|
||||||
|
|
||||||
|
r = sd_bus_default_user(&m->api_bus);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to get user bus connection: %m");
|
||||||
|
|
||||||
|
r = sd_bus_attach_event(m->api_bus, m->event, 0);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to attach user bus to event loop: %m");
|
||||||
|
}
|
||||||
|
|
||||||
|
r = bus_add_implementation(m->api_bus, &manager_object, m);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return r;
|
return r;
|
||||||
|
|
||||||
r = bus_log_control_api_register(m->bus);
|
r = bus_log_control_api_register(m->api_bus);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return r;
|
return r;
|
||||||
|
|
||||||
r = sd_bus_request_name_async(m->bus, NULL, "org.freedesktop.import1", 0, NULL, NULL);
|
r = sd_bus_request_name_async(m->api_bus, NULL, "org.freedesktop.import1", 0, NULL, NULL);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return log_error_errno(r, "Failed to request name: %m");
|
return log_error_errno(r, "Failed to request name: %m");
|
||||||
|
|
||||||
r = sd_bus_attach_event(m->bus, m->event, 0);
|
|
||||||
if (r < 0)
|
|
||||||
return log_error_errno(r, "Failed to attach bus to event loop: %m");
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1867,19 +1896,21 @@ static int vl_method_pull(sd_varlink *link, sd_json_variant *parameters, sd_varl
|
|||||||
if (manager_find(m, tt, p.remote))
|
if (manager_find(m, tt, p.remote))
|
||||||
return sd_varlink_errorbo(link, "io.systemd.Import.AlreadyInProgress", SD_JSON_BUILD_PAIR_STRING("remote", p.remote));
|
return sd_varlink_errorbo(link, "io.systemd.Import.AlreadyInProgress", SD_JSON_BUILD_PAIR_STRING("remote", p.remote));
|
||||||
|
|
||||||
r = varlink_verify_polkit_async(
|
if (m->runtime_scope != RUNTIME_SCOPE_USER) {
|
||||||
link,
|
r = varlink_verify_polkit_async(
|
||||||
m->bus,
|
link,
|
||||||
"org.freedesktop.import1.pull",
|
m->system_bus,
|
||||||
(const char**) STRV_MAKE(
|
"org.freedesktop.import1.pull",
|
||||||
"remote", p.remote,
|
(const char**) STRV_MAKE(
|
||||||
"local", p.local,
|
"remote", p.remote,
|
||||||
"class", image_class_to_string(p.class),
|
"local", p.local,
|
||||||
"type", import_type_to_string(p.type),
|
"class", image_class_to_string(p.class),
|
||||||
"verify", import_verify_to_string(p.verify)),
|
"type", import_type_to_string(p.type),
|
||||||
&m->polkit_registry);
|
"verify", import_verify_to_string(p.verify)),
|
||||||
if (r <= 0)
|
&m->polkit_registry);
|
||||||
return r;
|
if (r <= 0)
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
_cleanup_(transfer_unrefp) Transfer *t = NULL;
|
_cleanup_(transfer_unrefp) Transfer *t = NULL;
|
||||||
|
|
||||||
@ -1938,9 +1969,11 @@ static int manager_connect_varlink(Manager *m) {
|
|||||||
assert(m->event);
|
assert(m->event);
|
||||||
assert(!m->varlink_server);
|
assert(!m->varlink_server);
|
||||||
|
|
||||||
r = varlink_server_new(&m->varlink_server,
|
r = varlink_server_new(
|
||||||
SD_VARLINK_SERVER_ACCOUNT_UID|SD_VARLINK_SERVER_INHERIT_USERDATA,
|
&m->varlink_server,
|
||||||
m);
|
(m->runtime_scope != RUNTIME_SCOPE_USER ? SD_VARLINK_SERVER_ACCOUNT_UID : 0)|
|
||||||
|
SD_VARLINK_SERVER_INHERIT_USERDATA,
|
||||||
|
m);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return log_error_errno(r, "Failed to allocate varlink server object: %m");
|
return log_error_errno(r, "Failed to allocate varlink server object: %m");
|
||||||
|
|
||||||
@ -1969,7 +2002,12 @@ static int manager_connect_varlink(Manager *m) {
|
|||||||
if (r < 0)
|
if (r < 0)
|
||||||
return log_error_errno(r, "Failed to bind to passed Varlink sockets: %m");
|
return log_error_errno(r, "Failed to bind to passed Varlink sockets: %m");
|
||||||
if (r == 0) {
|
if (r == 0) {
|
||||||
r = sd_varlink_server_listen_address(m->varlink_server, "/run/systemd/io.systemd.Import", 0666);
|
_cleanup_free_ char *socket_path = NULL;
|
||||||
|
r = runtime_directory_generic(m->runtime_scope, "systemd/io.systemd.Import", &socket_path);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to determine socket path: %m");
|
||||||
|
|
||||||
|
r = sd_varlink_server_listen_address(m->varlink_server, socket_path, runtime_scope_to_socket_mode(m->runtime_scope) | SD_VARLINK_SERVER_MODE_MKDIR_0755);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return log_error_errno(r, "Failed to bind to Varlink socket: %m");
|
return log_error_errno(r, "Failed to bind to Varlink socket: %m");
|
||||||
}
|
}
|
||||||
@ -2009,6 +2047,7 @@ static void manager_parse_env(Manager *m) {
|
|||||||
|
|
||||||
static int run(int argc, char *argv[]) {
|
static int run(int argc, char *argv[]) {
|
||||||
_cleanup_(manager_unrefp) Manager *m = NULL;
|
_cleanup_(manager_unrefp) Manager *m = NULL;
|
||||||
|
RuntimeScope scope = RUNTIME_SCOPE_SYSTEM;
|
||||||
int r;
|
int r;
|
||||||
|
|
||||||
log_setup();
|
log_setup();
|
||||||
@ -2017,7 +2056,7 @@ static int run(int argc, char *argv[]) {
|
|||||||
"VM and container image import and export service.",
|
"VM and container image import and export service.",
|
||||||
BUS_IMPLEMENTATIONS(&manager_object,
|
BUS_IMPLEMENTATIONS(&manager_object,
|
||||||
&log_control_object),
|
&log_control_object),
|
||||||
/* runtime_scope= */ NULL,
|
&scope,
|
||||||
argc, argv);
|
argc, argv);
|
||||||
if (r <= 0)
|
if (r <= 0)
|
||||||
return r;
|
return r;
|
||||||
@ -2026,7 +2065,7 @@ static int run(int argc, char *argv[]) {
|
|||||||
|
|
||||||
assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD) >= 0);
|
assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD) >= 0);
|
||||||
|
|
||||||
r = manager_new(&m);
|
r = manager_new(scope, &m);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return log_error_errno(r, "Failed to allocate manager object: %m");
|
return log_error_errno(r, "Failed to allocate manager object: %m");
|
||||||
|
|
||||||
@ -2046,7 +2085,7 @@ static int run(int argc, char *argv[]) {
|
|||||||
|
|
||||||
r = bus_event_loop_with_idle(
|
r = bus_event_loop_with_idle(
|
||||||
m->event,
|
m->event,
|
||||||
m->bus,
|
m->api_bus,
|
||||||
"org.freedesktop.import1",
|
"org.freedesktop.import1",
|
||||||
DEFAULT_EXIT_USEC,
|
DEFAULT_EXIT_USEC,
|
||||||
manager_check_idle,
|
manager_check_idle,
|
||||||
|
|||||||
@ -111,6 +111,9 @@ install_data('org.freedesktop.import1.conf',
|
|||||||
install_dir : dbuspolicydir)
|
install_dir : dbuspolicydir)
|
||||||
install_data('org.freedesktop.import1.service',
|
install_data('org.freedesktop.import1.service',
|
||||||
install_dir : dbussystemservicedir)
|
install_dir : dbussystemservicedir)
|
||||||
|
install_data('org.freedesktop.import1.service-for-session',
|
||||||
|
install_dir : dbussessionservicedir,
|
||||||
|
rename : 'org.freedesktop.import1.service')
|
||||||
install_data('org.freedesktop.import1.policy',
|
install_data('org.freedesktop.import1.policy',
|
||||||
install_dir : polkitpolicydir)
|
install_dir : polkitpolicydir)
|
||||||
|
|
||||||
|
|||||||
13
src/import/org.freedesktop.import1.service-for-session
Normal file
13
src/import/org.freedesktop.import1.service-for-session
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||||
|
#
|
||||||
|
# This file is part of systemd.
|
||||||
|
#
|
||||||
|
# systemd is free software; you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU Lesser General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 2.1 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
|
||||||
|
[D-BUS Service]
|
||||||
|
Name=org.freedesktop.import1
|
||||||
|
Exec=/bin/false
|
||||||
|
SystemdService=dbus-org.freedesktop.import1.service
|
||||||
@ -9,11 +9,10 @@
|
|||||||
#include "btrfs-util.h"
|
#include "btrfs-util.h"
|
||||||
#include "copy.h"
|
#include "copy.h"
|
||||||
#include "curl-util.h"
|
#include "curl-util.h"
|
||||||
|
#include "dissect-image.h"
|
||||||
#include "errno-util.h"
|
#include "errno-util.h"
|
||||||
#include "fd-util.h"
|
#include "fd-util.h"
|
||||||
#include "fs-util.h"
|
#include "fs-util.h"
|
||||||
#include "import-common.h"
|
|
||||||
#include "import-util.h"
|
|
||||||
#include "install-file.h"
|
#include "install-file.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "mkdir-label.h"
|
#include "mkdir-label.h"
|
||||||
@ -26,6 +25,7 @@
|
|||||||
#include "rm-rf.h"
|
#include "rm-rf.h"
|
||||||
#include "string-util.h"
|
#include "string-util.h"
|
||||||
#include "tmpfile-util.h"
|
#include "tmpfile-util.h"
|
||||||
|
#include "uid-classification.h"
|
||||||
#include "web-util.h"
|
#include "web-util.h"
|
||||||
|
|
||||||
typedef enum TarProgress {
|
typedef enum TarProgress {
|
||||||
@ -64,6 +64,7 @@ typedef struct TarPull {
|
|||||||
char *checksum;
|
char *checksum;
|
||||||
|
|
||||||
int tree_fd;
|
int tree_fd;
|
||||||
|
int userns_fd;
|
||||||
} TarPull;
|
} TarPull;
|
||||||
|
|
||||||
TarPull* tar_pull_unref(TarPull *i) {
|
TarPull* tar_pull_unref(TarPull *i) {
|
||||||
@ -80,7 +81,10 @@ TarPull* tar_pull_unref(TarPull *i) {
|
|||||||
curl_glue_unref(i->glue);
|
curl_glue_unref(i->glue);
|
||||||
sd_event_unref(i->event);
|
sd_event_unref(i->event);
|
||||||
|
|
||||||
rm_rf_subvolume_and_free(i->temp_path);
|
if (i->temp_path) {
|
||||||
|
import_remove_tree(i->temp_path, &i->userns_fd, i->flags);
|
||||||
|
free(i->temp_path);
|
||||||
|
}
|
||||||
unlink_and_free(i->settings_temp_path);
|
unlink_and_free(i->settings_temp_path);
|
||||||
|
|
||||||
free(i->final_path);
|
free(i->final_path);
|
||||||
@ -90,6 +94,7 @@ TarPull* tar_pull_unref(TarPull *i) {
|
|||||||
free(i->checksum);
|
free(i->checksum);
|
||||||
|
|
||||||
safe_close(i->tree_fd);
|
safe_close(i->tree_fd);
|
||||||
|
safe_close(i->userns_fd);
|
||||||
|
|
||||||
return mfree(i);
|
return mfree(i);
|
||||||
}
|
}
|
||||||
@ -138,6 +143,7 @@ int tar_pull_new(
|
|||||||
.glue = TAKE_PTR(g),
|
.glue = TAKE_PTR(g),
|
||||||
.tar_pid = PIDREF_NULL,
|
.tar_pid = PIDREF_NULL,
|
||||||
.tree_fd = -EBADF,
|
.tree_fd = -EBADF,
|
||||||
|
.userns_fd = -EBADF,
|
||||||
};
|
};
|
||||||
|
|
||||||
i->glue->on_finished = pull_job_curl_on_finished;
|
i->glue->on_finished = pull_job_curl_on_finished;
|
||||||
@ -233,6 +239,9 @@ static int tar_pull_make_local_copy(TarPull *i) {
|
|||||||
if (!i->local)
|
if (!i->local)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
/* Creates a copy/clone of the original downloaded version (which is supposed to remain untouched)
|
||||||
|
* under a local image name (which may then be modified) */
|
||||||
|
|
||||||
assert(i->final_path);
|
assert(i->final_path);
|
||||||
|
|
||||||
p = path_join(i->image_root, i->local);
|
p = path_join(i->image_root, i->local);
|
||||||
@ -244,18 +253,61 @@ static int tar_pull_make_local_copy(TarPull *i) {
|
|||||||
if (r < 0)
|
if (r < 0)
|
||||||
return log_error_errno(r, "Failed to generate temporary filename for %s: %m", p);
|
return log_error_errno(r, "Failed to generate temporary filename for %s: %m", p);
|
||||||
|
|
||||||
if (i->flags & IMPORT_BTRFS_SUBVOL)
|
if (FLAGS_SET(i->flags, IMPORT_FOREIGN_UID)) {
|
||||||
r = btrfs_subvol_snapshot_at(
|
/* Copy in userns */
|
||||||
AT_FDCWD, i->final_path,
|
|
||||||
AT_FDCWD, t,
|
r = import_make_foreign_userns(&i->userns_fd);
|
||||||
(i->flags & IMPORT_BTRFS_QUOTA ? BTRFS_SNAPSHOT_QUOTA : 0)|
|
if (r < 0)
|
||||||
BTRFS_SNAPSHOT_FALLBACK_COPY|
|
return r;
|
||||||
BTRFS_SNAPSHOT_FALLBACK_DIRECTORY|
|
|
||||||
BTRFS_SNAPSHOT_RECURSIVE);
|
/* Usually, tar_pull_job_on_open_disk_tar() would allocate ->tree_fd for us, but if
|
||||||
else
|
* already downloaded the image before, and are just making a copy of the original
|
||||||
r = copy_tree(i->final_path, t, UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_HARDLINKS, NULL, NULL);
|
* download, we need to open ->tree_fd now */
|
||||||
if (r < 0)
|
if (i->tree_fd < 0) {
|
||||||
return log_error_errno(r, "Failed to create local image: %m");
|
_cleanup_close_ int directory_fd = open(i->final_path, O_DIRECTORY|O_CLOEXEC);
|
||||||
|
if (directory_fd < 0)
|
||||||
|
return log_error_errno(errno, "Failed to open '%s': %m", i->final_path);
|
||||||
|
|
||||||
|
struct stat st;
|
||||||
|
if (fstat(directory_fd, &st) < 0)
|
||||||
|
return log_error_errno(errno, "Failed to stat '%s': %m", i->final_path);
|
||||||
|
|
||||||
|
if (uid_is_foreign(st.st_uid)) {
|
||||||
|
r = mountfsd_mount_directory_fd(directory_fd, i->userns_fd, DISSECT_IMAGE_FOREIGN_UID, &i->tree_fd);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
} else
|
||||||
|
i->tree_fd = TAKE_FD(directory_fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
_cleanup_close_ int directory_fd = -EBADF;
|
||||||
|
r = mountfsd_make_directory(t, /* flags= */ 0, &directory_fd);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
_cleanup_close_ int copy_fd = -EBADF;
|
||||||
|
r = mountfsd_mount_directory_fd(directory_fd, i->userns_fd, DISSECT_IMAGE_FOREIGN_UID, ©_fd);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
r = import_copy_foreign(i->tree_fd, copy_fd, &i->userns_fd);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
} else {
|
||||||
|
/* Copy locally */
|
||||||
|
if (i->flags & IMPORT_BTRFS_SUBVOL)
|
||||||
|
r = btrfs_subvol_snapshot_at(
|
||||||
|
AT_FDCWD, i->final_path,
|
||||||
|
AT_FDCWD, t,
|
||||||
|
(i->flags & IMPORT_BTRFS_QUOTA ? BTRFS_SNAPSHOT_QUOTA : 0)|
|
||||||
|
BTRFS_SNAPSHOT_FALLBACK_COPY|
|
||||||
|
BTRFS_SNAPSHOT_FALLBACK_DIRECTORY|
|
||||||
|
BTRFS_SNAPSHOT_RECURSIVE);
|
||||||
|
else
|
||||||
|
r = copy_tree(i->final_path, t, UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_HARDLINKS, NULL, NULL);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to create original download image: %m");
|
||||||
|
}
|
||||||
|
|
||||||
source = t;
|
source = t;
|
||||||
} else
|
} else
|
||||||
@ -264,8 +316,8 @@ static int tar_pull_make_local_copy(TarPull *i) {
|
|||||||
r = install_file(AT_FDCWD, source,
|
r = install_file(AT_FDCWD, source,
|
||||||
AT_FDCWD, p,
|
AT_FDCWD, p,
|
||||||
(i->flags & IMPORT_FORCE ? INSTALL_REPLACE : 0) |
|
(i->flags & IMPORT_FORCE ? INSTALL_REPLACE : 0) |
|
||||||
(i->flags & IMPORT_READ_ONLY ? INSTALL_READ_ONLY : 0) |
|
(i->flags & IMPORT_READ_ONLY ? INSTALL_READ_ONLY|INSTALL_GRACEFUL : 0) |
|
||||||
(i->flags & IMPORT_SYNC ? INSTALL_SYNCFS : 0));
|
(i->flags & IMPORT_SYNC ? INSTALL_SYNCFS|INSTALL_GRACEFUL : 0));
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return log_error_errno(r, "Failed to install local image '%s': %m", p);
|
return log_error_errno(r, "Failed to install local image '%s': %m", p);
|
||||||
|
|
||||||
@ -427,8 +479,8 @@ static void tar_pull_job_on_finished(PullJob *j) {
|
|||||||
r = install_file(
|
r = install_file(
|
||||||
AT_FDCWD, i->local,
|
AT_FDCWD, i->local,
|
||||||
AT_FDCWD, NULL,
|
AT_FDCWD, NULL,
|
||||||
(i->flags & IMPORT_READ_ONLY ? INSTALL_READ_ONLY : 0) |
|
(i->flags & IMPORT_READ_ONLY ? INSTALL_READ_ONLY|INSTALL_GRACEFUL : 0) |
|
||||||
(i->flags & IMPORT_SYNC ? INSTALL_SYNCFS : 0));
|
(i->flags & IMPORT_SYNC ? INSTALL_SYNCFS|INSTALL_GRACEFUL : 0));
|
||||||
if (r < 0) {
|
if (r < 0) {
|
||||||
log_error_errno(r, "Failed to finalize '%s': %m", i->local);
|
log_error_errno(r, "Failed to finalize '%s': %m", i->local);
|
||||||
goto finish;
|
goto finish;
|
||||||
@ -453,8 +505,8 @@ static void tar_pull_job_on_finished(PullJob *j) {
|
|||||||
r = install_file(
|
r = install_file(
|
||||||
AT_FDCWD, i->temp_path,
|
AT_FDCWD, i->temp_path,
|
||||||
AT_FDCWD, i->final_path,
|
AT_FDCWD, i->final_path,
|
||||||
(i->flags & IMPORT_PULL_KEEP_DOWNLOAD ? INSTALL_READ_ONLY : 0) |
|
(i->flags & IMPORT_PULL_KEEP_DOWNLOAD ? INSTALL_READ_ONLY|INSTALL_GRACEFUL : 0) |
|
||||||
(i->flags & IMPORT_SYNC ? INSTALL_SYNCFS : 0));
|
(i->flags & IMPORT_SYNC ? INSTALL_SYNCFS|INSTALL_GRACEFUL : 0));
|
||||||
if (r < 0) {
|
if (r < 0) {
|
||||||
log_error_errno(r, "Failed to rename to final image name to %s: %m", i->final_path);
|
log_error_errno(r, "Failed to rename to final image name to %s: %m", i->final_path);
|
||||||
goto finish;
|
goto finish;
|
||||||
@ -480,7 +532,7 @@ static void tar_pull_job_on_finished(PullJob *j) {
|
|||||||
r = install_file(
|
r = install_file(
|
||||||
AT_FDCWD, i->settings_temp_path,
|
AT_FDCWD, i->settings_temp_path,
|
||||||
AT_FDCWD, i->settings_path,
|
AT_FDCWD, i->settings_path,
|
||||||
INSTALL_READ_ONLY|
|
INSTALL_READ_ONLY|INSTALL_GRACEFUL|
|
||||||
(i->flags & IMPORT_SYNC ? INSTALL_FSYNC_FULL : 0));
|
(i->flags & IMPORT_SYNC ? INSTALL_FSYNC_FULL : 0));
|
||||||
if (r < 0) {
|
if (r < 0) {
|
||||||
log_error_errno(r, "Failed to rename settings file to %s: %m", i->settings_path);
|
log_error_errno(r, "Failed to rename settings file to %s: %m", i->settings_path);
|
||||||
@ -537,26 +589,42 @@ static int tar_pull_job_on_open_disk_tar(PullJob *j) {
|
|||||||
if (FLAGS_SET(i->flags, IMPORT_DIRECT|IMPORT_FORCE))
|
if (FLAGS_SET(i->flags, IMPORT_DIRECT|IMPORT_FORCE))
|
||||||
(void) rm_rf(where, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME);
|
(void) rm_rf(where, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME);
|
||||||
|
|
||||||
if (i->flags & IMPORT_BTRFS_SUBVOL)
|
if (FLAGS_SET(i->flags, IMPORT_FOREIGN_UID)) {
|
||||||
r = btrfs_subvol_make_fallback(AT_FDCWD, where, 0755);
|
r = import_make_foreign_userns(&i->userns_fd);
|
||||||
else
|
if (r < 0)
|
||||||
r = RET_NERRNO(mkdir(where, 0755));
|
return r;
|
||||||
if (r == -EEXIST && (i->flags & IMPORT_DIRECT)) /* EEXIST is OK if in direct mode, but not otherwise,
|
|
||||||
* because in that case our temporary path collided */
|
_cleanup_close_ int directory_fd = -EBADF;
|
||||||
r = 0;
|
r = mountfsd_make_directory(where, /* flags= */ 0, &directory_fd);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return log_error_errno(r, "Failed to create directory/subvolume %s: %m", where);
|
return r;
|
||||||
if (r > 0 && (i->flags & IMPORT_BTRFS_QUOTA)) { /* actually btrfs subvol */
|
|
||||||
if (!(i->flags & IMPORT_DIRECT))
|
r = mountfsd_mount_directory_fd(directory_fd, i->userns_fd, DISSECT_IMAGE_FOREIGN_UID, &i->tree_fd);
|
||||||
(void) import_assign_pool_quota_and_warn(i->image_root);
|
if (r < 0)
|
||||||
(void) import_assign_pool_quota_and_warn(where);
|
return r;
|
||||||
|
} else {
|
||||||
|
if (i->flags & IMPORT_BTRFS_SUBVOL)
|
||||||
|
r = btrfs_subvol_make_fallback(AT_FDCWD, where, 0755);
|
||||||
|
else
|
||||||
|
r = RET_NERRNO(mkdir(where, 0755));
|
||||||
|
if (r == -EEXIST && (i->flags & IMPORT_DIRECT)) /* EEXIST is OK if in direct mode, but not otherwise,
|
||||||
|
* because in that case our temporary path collided */
|
||||||
|
r = 0;
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to create directory/subvolume %s: %m", where);
|
||||||
|
|
||||||
|
if (r > 0 && (i->flags & IMPORT_BTRFS_QUOTA)) { /* actually btrfs subvol */
|
||||||
|
if (!(i->flags & IMPORT_DIRECT))
|
||||||
|
(void) import_assign_pool_quota_and_warn(i->image_root);
|
||||||
|
(void) import_assign_pool_quota_and_warn(where);
|
||||||
|
}
|
||||||
|
|
||||||
|
i->tree_fd = open(where, O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW);
|
||||||
|
if (i->tree_fd < 0)
|
||||||
|
return log_error_errno(errno, "Failed to open '%s': %m", where);
|
||||||
}
|
}
|
||||||
|
|
||||||
i->tree_fd = open(where, O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW);
|
j->disk_fd = import_fork_tar_x(i->tree_fd, i->userns_fd, &i->tar_pid);
|
||||||
if (i->tree_fd < 0)
|
|
||||||
return log_error_errno(errno, "Failed to open '%s': %m", where);
|
|
||||||
|
|
||||||
j->disk_fd = import_fork_tar_x(i->tree_fd, &i->tar_pid);
|
|
||||||
if (j->disk_fd < 0)
|
if (j->disk_fd < 0)
|
||||||
return j->disk_fd;
|
return j->disk_fd;
|
||||||
|
|
||||||
|
|||||||
@ -28,7 +28,7 @@
|
|||||||
#include "verbs.h"
|
#include "verbs.h"
|
||||||
#include "web-util.h"
|
#include "web-util.h"
|
||||||
|
|
||||||
static const char *arg_image_root = NULL;
|
static char *arg_image_root = NULL;
|
||||||
static ImportVerify arg_verify = IMPORT_VERIFY_SIGNATURE;
|
static ImportVerify arg_verify = IMPORT_VERIFY_SIGNATURE;
|
||||||
static ImportFlags arg_import_flags = IMPORT_PULL_SETTINGS | IMPORT_PULL_ROOTHASH | IMPORT_PULL_ROOTHASH_SIGNATURE | IMPORT_PULL_VERITY | IMPORT_BTRFS_SUBVOL | IMPORT_BTRFS_QUOTA | IMPORT_CONVERT_QCOW2 | IMPORT_SYNC;
|
static ImportFlags arg_import_flags = IMPORT_PULL_SETTINGS | IMPORT_PULL_ROOTHASH | IMPORT_PULL_ROOTHASH_SIGNATURE | IMPORT_PULL_VERITY | IMPORT_BTRFS_SUBVOL | IMPORT_BTRFS_QUOTA | IMPORT_CONVERT_QCOW2 | IMPORT_SYNC;
|
||||||
static uint64_t arg_offset = UINT64_MAX, arg_size_max = UINT64_MAX;
|
static uint64_t arg_offset = UINT64_MAX, arg_size_max = UINT64_MAX;
|
||||||
@ -37,6 +37,7 @@ static ImageClass arg_class = IMAGE_MACHINE;
|
|||||||
static RuntimeScope arg_runtime_scope = _RUNTIME_SCOPE_INVALID;
|
static RuntimeScope arg_runtime_scope = _RUNTIME_SCOPE_INVALID;
|
||||||
|
|
||||||
STATIC_DESTRUCTOR_REGISTER(arg_checksum, freep);
|
STATIC_DESTRUCTOR_REGISTER(arg_checksum, freep);
|
||||||
|
STATIC_DESTRUCTOR_REGISTER(arg_image_root, freep);
|
||||||
|
|
||||||
static int normalize_local(const char *local, const char *url, char **ret) {
|
static int normalize_local(const char *local, const char *url, char **ret) {
|
||||||
_cleanup_free_ char *ll = NULL;
|
_cleanup_free_ char *ll = NULL;
|
||||||
@ -275,7 +276,9 @@ static int help(int argc, char *argv[], void *userdata) {
|
|||||||
" --class=CLASS Select image class (machine, sysext, confext,\n"
|
" --class=CLASS Select image class (machine, sysext, confext,\n"
|
||||||
" portable)\n"
|
" portable)\n"
|
||||||
" --keep-download=BOOL Keep a copy pristine copy of the downloaded file\n"
|
" --keep-download=BOOL Keep a copy pristine copy of the downloaded file\n"
|
||||||
" around\n",
|
" around\n"
|
||||||
|
" --system Operate in per-system mode\n"
|
||||||
|
" --user Operate in per-user mode\n",
|
||||||
program_invocation_short_name,
|
program_invocation_short_name,
|
||||||
ansi_underline(),
|
ansi_underline(),
|
||||||
ansi_normal(),
|
ansi_normal(),
|
||||||
@ -306,6 +309,8 @@ static int parse_argv(int argc, char *argv[]) {
|
|||||||
ARG_SIZE_MAX,
|
ARG_SIZE_MAX,
|
||||||
ARG_CLASS,
|
ARG_CLASS,
|
||||||
ARG_KEEP_DOWNLOAD,
|
ARG_KEEP_DOWNLOAD,
|
||||||
|
ARG_SYSTEM,
|
||||||
|
ARG_USER,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct option options[] = {
|
static const struct option options[] = {
|
||||||
@ -328,6 +333,8 @@ static int parse_argv(int argc, char *argv[]) {
|
|||||||
{ "size-max", required_argument, NULL, ARG_SIZE_MAX },
|
{ "size-max", required_argument, NULL, ARG_SIZE_MAX },
|
||||||
{ "class", required_argument, NULL, ARG_CLASS },
|
{ "class", required_argument, NULL, ARG_CLASS },
|
||||||
{ "keep-download", required_argument, NULL, ARG_KEEP_DOWNLOAD },
|
{ "keep-download", required_argument, NULL, ARG_KEEP_DOWNLOAD },
|
||||||
|
{ "system", no_argument, NULL, ARG_SYSTEM },
|
||||||
|
{ "user", no_argument, NULL, ARG_USER },
|
||||||
{}
|
{}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -352,7 +359,10 @@ static int parse_argv(int argc, char *argv[]) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case ARG_IMAGE_ROOT:
|
case ARG_IMAGE_ROOT:
|
||||||
arg_image_root = optarg;
|
r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image_root);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ARG_VERIFY: {
|
case ARG_VERIFY: {
|
||||||
@ -508,6 +518,14 @@ static int parse_argv(int argc, char *argv[]) {
|
|||||||
auto_keep_download = false;
|
auto_keep_download = false;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case ARG_SYSTEM:
|
||||||
|
arg_runtime_scope = RUNTIME_SCOPE_SYSTEM;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ARG_USER:
|
||||||
|
arg_runtime_scope = RUNTIME_SCOPE_USER;
|
||||||
|
break;
|
||||||
|
|
||||||
case '?':
|
case '?':
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
||||||
@ -527,8 +545,11 @@ static int parse_argv(int argc, char *argv[]) {
|
|||||||
if (arg_checksum && (arg_import_flags & (IMPORT_PULL_SETTINGS|IMPORT_PULL_ROOTHASH|IMPORT_PULL_ROOTHASH_SIGNATURE|IMPORT_PULL_VERITY)) != 0)
|
if (arg_checksum && (arg_import_flags & (IMPORT_PULL_SETTINGS|IMPORT_PULL_ROOTHASH|IMPORT_PULL_ROOTHASH_SIGNATURE|IMPORT_PULL_VERITY)) != 0)
|
||||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Literal checksum verification only supported if no associated files are downloaded.");
|
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Literal checksum verification only supported if no associated files are downloaded.");
|
||||||
|
|
||||||
if (!arg_image_root)
|
if (!arg_image_root) {
|
||||||
arg_image_root = image_root_to_string(arg_class);
|
r = image_root_pick(arg_runtime_scope < 0 ? RUNTIME_SCOPE_SYSTEM : arg_runtime_scope, arg_class, /* runtime= */ false, &arg_image_root);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to pick image root: %m");
|
||||||
|
}
|
||||||
|
|
||||||
/* .nspawn settings files only really make sense for machine images, not for sysext/confext/portable */
|
/* .nspawn settings files only really make sense for machine images, not for sysext/confext/portable */
|
||||||
if (auto_settings && arg_class != IMAGE_MACHINE)
|
if (auto_settings && arg_class != IMAGE_MACHINE)
|
||||||
@ -539,6 +560,9 @@ static int parse_argv(int argc, char *argv[]) {
|
|||||||
if (auto_keep_download)
|
if (auto_keep_download)
|
||||||
SET_FLAG(arg_import_flags, IMPORT_PULL_KEEP_DOWNLOAD, arg_class == IMAGE_MACHINE);
|
SET_FLAG(arg_import_flags, IMPORT_PULL_KEEP_DOWNLOAD, arg_class == IMAGE_MACHINE);
|
||||||
|
|
||||||
|
if (arg_runtime_scope == RUNTIME_SCOPE_USER)
|
||||||
|
arg_import_flags |= IMPORT_FOREIGN_UID;
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -9,6 +9,7 @@
|
|||||||
#include "sd-varlink.h"
|
#include "sd-varlink.h"
|
||||||
|
|
||||||
#include "alloc-util.h"
|
#include "alloc-util.h"
|
||||||
|
#include "env-util.h"
|
||||||
#include "errno-list.h"
|
#include "errno-list.h"
|
||||||
#include "errno-util.h"
|
#include "errno-util.h"
|
||||||
#include "escape.h"
|
#include "escape.h"
|
||||||
@ -269,18 +270,15 @@ _public_ int sd_varlink_connect_exec(sd_varlink **ret, const char *_command, cha
|
|||||||
|
|
||||||
uint64_t pidfdid;
|
uint64_t pidfdid;
|
||||||
if (pidfd_get_inode_id_self_cached(&pidfdid) >= 0) {
|
if (pidfd_get_inode_id_self_cached(&pidfdid) >= 0) {
|
||||||
char spidfdid[DECIMAL_STR_MAX(uint64_t)+1];
|
r = setenvf("LISTEN_PIDFDID", /* overwrite= */ true, "%" PRIu64, pidfdid);
|
||||||
xsprintf(spidfdid, "%" PRIu64, pidfdid);
|
if (r < 0) {
|
||||||
|
log_debug_errno(r, "Failed to set environment variable 'LISTEN_PIDFDID': %m");
|
||||||
if (setenv("LISTEN_PIDFDID", spidfdid, /* override= */ true) < 0) {
|
|
||||||
log_debug_errno(errno,
|
|
||||||
"Failed to set environment variable 'LISTEN_PIDFDID': %m");
|
|
||||||
_exit(EXIT_FAILURE);
|
_exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
STRV_FOREACH_PAIR(a, b, setenv_list) {
|
STRV_FOREACH_PAIR(a, b, setenv_list) {
|
||||||
if (setenv(*a, *b, /* override= */ true) < 0) {
|
if (setenv(*a, *b, /* overwrite= */ true) < 0) {
|
||||||
log_debug_errno(errno, "Failed to set environment variable '%s': %m", *a);
|
log_debug_errno(errno, "Failed to set environment variable '%s': %m", *a);
|
||||||
_exit(EXIT_FAILURE);
|
_exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -142,7 +142,6 @@ static int start_one_worker(Manager *m) {
|
|||||||
if (r < 0)
|
if (r < 0)
|
||||||
return log_error_errno(r, "Failed to fork new worker child: %m");
|
return log_error_errno(r, "Failed to fork new worker child: %m");
|
||||||
if (r == 0) {
|
if (r == 0) {
|
||||||
char pids[DECIMAL_STR_MAX(pid_t)];
|
|
||||||
/* Child */
|
/* Child */
|
||||||
|
|
||||||
if (m->listen_fd == 3) {
|
if (m->listen_fd == 3) {
|
||||||
@ -160,19 +159,17 @@ static int start_one_worker(Manager *m) {
|
|||||||
safe_close(m->listen_fd);
|
safe_close(m->listen_fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
xsprintf(pids, PID_FMT, pid);
|
r = setenvf("LISTEN_PID", /* overwrite= */ true, PID_FMT, pid);
|
||||||
if (setenv("LISTEN_PID", pids, 1) < 0) {
|
if (r < 0) {
|
||||||
log_error_errno(errno, "Failed to set $LISTEN_PID: %m");
|
log_error_errno(r, "Failed to set $LISTEN_PID: %m");
|
||||||
_exit(EXIT_FAILURE);
|
_exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t pidfdid;
|
uint64_t pidfdid;
|
||||||
if (pidfd_get_inode_id_self_cached(&pidfdid) >= 0) {
|
if (pidfd_get_inode_id_self_cached(&pidfdid) >= 0) {
|
||||||
char pidfdids[DECIMAL_STR_MAX(uint64_t)];
|
r = setenvf("LISTEN_PIDFDID", /* overwrite= */ true, "%" PRIu64, pidfdid);
|
||||||
|
if (r < 0) {
|
||||||
xsprintf(pidfdids, "%" PRIu64, pidfdid);
|
log_error_errno(r, "Failed to set $LISTEN_PIDFDID: %m");
|
||||||
if (setenv("LISTEN_PIDFDID", pidfdids, 1) < 0) {
|
|
||||||
log_error_errno(errno, "Failed to set $LISTEN_PIDFDID: %m");
|
|
||||||
_exit(EXIT_FAILURE);
|
_exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -427,6 +427,8 @@ static int help(void) {
|
|||||||
" --overlay-ro=PATH[:PATH...]:PATH\n"
|
" --overlay-ro=PATH[:PATH...]:PATH\n"
|
||||||
" Similar, but creates a read-only overlay mount\n"
|
" Similar, but creates a read-only overlay mount\n"
|
||||||
" --bind-user=NAME Bind user from host to container\n"
|
" --bind-user=NAME Bind user from host to container\n"
|
||||||
|
" --bind-user-shell=BOOL|PATH\n"
|
||||||
|
" Configure the shell to use for --bind-user= users\n"
|
||||||
"\n%3$sInput/Output:%4$s\n"
|
"\n%3$sInput/Output:%4$s\n"
|
||||||
" --console=MODE Select how stdin/stdout/stderr and /dev/console are\n"
|
" --console=MODE Select how stdin/stdout/stderr and /dev/console are\n"
|
||||||
" set up for the container.\n"
|
" set up for the container.\n"
|
||||||
@ -4017,6 +4019,7 @@ static int outer_child(
|
|||||||
arg_bind_user,
|
arg_bind_user,
|
||||||
arg_bind_user_shell,
|
arg_bind_user_shell,
|
||||||
arg_bind_user_shell_copy,
|
arg_bind_user_shell_copy,
|
||||||
|
"/run/host/home",
|
||||||
&bind_user_context);
|
&bind_user_context);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return r;
|
return r;
|
||||||
@ -6007,7 +6010,7 @@ static int run(int argc, char *argv[]) {
|
|||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
userns_fd = nsresource_allocate_userns(userns_name, UINT64_C(0x10000));
|
userns_fd = nsresource_allocate_userns(userns_name, NSRESOURCE_UIDS_64K); /* allocate 64K UIDs */
|
||||||
if (userns_fd < 0) {
|
if (userns_fd < 0) {
|
||||||
r = log_error_errno(userns_fd, "Failed to allocate user namespace with 64K users: %m");
|
r = log_error_errno(userns_fd, "Failed to allocate user namespace with 64K users: %m");
|
||||||
goto finish;
|
goto finish;
|
||||||
@ -6019,7 +6022,7 @@ static int run(int argc, char *argv[]) {
|
|||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
arg_uid_range = UINT32_C(0x10000);
|
arg_uid_range = NSRESOURCE_UIDS_64K;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (arg_directory) {
|
if (arg_directory) {
|
||||||
|
|||||||
@ -178,7 +178,6 @@ static int start_one_worker(Manager *m) {
|
|||||||
if (r < 0)
|
if (r < 0)
|
||||||
return log_error_errno(r, "Failed to fork new worker child: %m");
|
return log_error_errno(r, "Failed to fork new worker child: %m");
|
||||||
if (r == 0) {
|
if (r == 0) {
|
||||||
char pids[DECIMAL_STR_MAX(pid_t)];
|
|
||||||
/* Child */
|
/* Child */
|
||||||
|
|
||||||
if (m->listen_fd == 3) {
|
if (m->listen_fd == 3) {
|
||||||
@ -196,19 +195,17 @@ static int start_one_worker(Manager *m) {
|
|||||||
safe_close(m->listen_fd);
|
safe_close(m->listen_fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
xsprintf(pids, PID_FMT, pid);
|
r = setenvf("LISTEN_PID", /* overwrite= */ true, PID_FMT, pid);
|
||||||
if (setenv("LISTEN_PID", pids, 1) < 0) {
|
if (r < 0) {
|
||||||
log_error_errno(errno, "Failed to set $LISTEN_PID: %m");
|
log_error_errno(r, "Failed to set $LISTEN_PID: %m");
|
||||||
_exit(EXIT_FAILURE);
|
_exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t pidfdid;
|
uint64_t pidfdid;
|
||||||
if (pidfd_get_inode_id_self_cached(&pidfdid) >= 0) {
|
if (pidfd_get_inode_id_self_cached(&pidfdid) >= 0) {
|
||||||
char pidfdids[DECIMAL_STR_MAX(uint64_t)];
|
r = setenvf("LISTEN_PIDFDID", /* overwrite= */ true, "%" PRIu64, pidfdid);
|
||||||
|
if (r < 0) {
|
||||||
xsprintf(pidfdids, "%" PRIu64, pidfdid);
|
log_error_errno(r, "Failed to set $LISTEN_PIDFDID: %m");
|
||||||
if (setenv("LISTEN_PIDFDID", pidfdids, 1) < 0) {
|
|
||||||
log_error_errno(errno, "Failed to set $LISTEN_PIDFDID: %m");
|
|
||||||
_exit(EXIT_FAILURE);
|
_exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -657,7 +657,10 @@ static int hardlink_context_setup(
|
|||||||
return -errno;
|
return -errno;
|
||||||
}
|
}
|
||||||
|
|
||||||
r = tempfn_random_child(to, "hardlink", &c->subdir);
|
if (to)
|
||||||
|
r = tempfn_random_child(to, "hardlink", &c->subdir);
|
||||||
|
else
|
||||||
|
r = tempfn_random("hardlink", /* extra= */ NULL, &c->subdir);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return r;
|
return r;
|
||||||
|
|
||||||
@ -1127,7 +1130,6 @@ static int fd_copy_directory(
|
|||||||
int r;
|
int r;
|
||||||
|
|
||||||
assert(st);
|
assert(st);
|
||||||
assert(to);
|
|
||||||
|
|
||||||
if (depth_left == 0)
|
if (depth_left == 0)
|
||||||
return -ENAMETOOLONG;
|
return -ENAMETOOLONG;
|
||||||
@ -1257,7 +1259,7 @@ static int fd_copy_directory(
|
|||||||
}
|
}
|
||||||
|
|
||||||
finish:
|
finish:
|
||||||
if (!exists) {
|
if (FLAGS_SET(copy_flags, COPY_MERGE_APPLY_STAT) || !exists) {
|
||||||
if (fchown(fdt,
|
if (fchown(fdt,
|
||||||
uid_is_valid(override_uid) ? override_uid : st->st_uid,
|
uid_is_valid(override_uid) ? override_uid : st->st_uid,
|
||||||
gid_is_valid(override_gid) ? override_gid : st->st_gid) < 0)
|
gid_is_valid(override_gid) ? override_gid : st->st_gid) < 0)
|
||||||
@ -1345,6 +1347,10 @@ static int fd_copy_tree_generic(
|
|||||||
override_gid, copy_flags, denylist, subvolumes, hardlink_context,
|
override_gid, copy_flags, denylist, subvolumes, hardlink_context,
|
||||||
display_path, progress_path, progress_bytes, userdata);
|
display_path, progress_path, progress_bytes, userdata);
|
||||||
|
|
||||||
|
/* Only if we are copying a directory we are fine if the target dir is referenced by fd only */
|
||||||
|
if (!to)
|
||||||
|
return -ENOTDIR;
|
||||||
|
|
||||||
DenyType t = PTR_TO_INT(hashmap_get(denylist, st));
|
DenyType t = PTR_TO_INT(hashmap_get(denylist, st));
|
||||||
if (t == DENY_INODE) {
|
if (t == DENY_INODE) {
|
||||||
log_debug("%s is in the denylist, ignoring", from ?: "file to copy");
|
log_debug("%s is in the denylist, ignoring", from ?: "file to copy");
|
||||||
@ -1382,7 +1388,6 @@ int copy_tree_at_full(
|
|||||||
struct stat st;
|
struct stat st;
|
||||||
int r;
|
int r;
|
||||||
|
|
||||||
assert(to);
|
|
||||||
assert(!FLAGS_SET(copy_flags, COPY_LOCK_BSD));
|
assert(!FLAGS_SET(copy_flags, COPY_LOCK_BSD));
|
||||||
|
|
||||||
if (fstatat(fdf, strempty(from), &st, AT_SYMLINK_NOFOLLOW | (isempty(from) ? AT_EMPTY_PATH : 0)) < 0)
|
if (fstatat(fdf, strempty(from), &st, AT_SYMLINK_NOFOLLOW | (isempty(from) ? AT_EMPTY_PATH : 0)) < 0)
|
||||||
|
|||||||
@ -33,6 +33,7 @@ typedef enum CopyFlags {
|
|||||||
*/
|
*/
|
||||||
COPY_NOCOW_AFTER = 1 << 20,
|
COPY_NOCOW_AFTER = 1 << 20,
|
||||||
COPY_PRESERVE_FS_VERITY = 1 << 21, /* Preserve fs-verity when copying. */
|
COPY_PRESERVE_FS_VERITY = 1 << 21, /* Preserve fs-verity when copying. */
|
||||||
|
COPY_MERGE_APPLY_STAT = 1 << 22, /* When we reuse an existing directory inode, apply source ownership/mode/xattrs/timestamps */
|
||||||
} CopyFlags;
|
} CopyFlags;
|
||||||
|
|
||||||
typedef enum DenyType {
|
typedef enum DenyType {
|
||||||
|
|||||||
@ -110,7 +110,7 @@ static const char *const image_root_table[_IMAGE_CLASS_MAX] = {
|
|||||||
[IMAGE_CONFEXT] = "/var/lib/confexts",
|
[IMAGE_CONFEXT] = "/var/lib/confexts",
|
||||||
};
|
};
|
||||||
|
|
||||||
DEFINE_STRING_TABLE_LOOKUP_TO_STRING(image_root, ImageClass);
|
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(image_root, ImageClass);
|
||||||
|
|
||||||
static const char *const image_root_runtime_table[_IMAGE_CLASS_MAX] = {
|
static const char *const image_root_runtime_table[_IMAGE_CLASS_MAX] = {
|
||||||
[IMAGE_MACHINE] = "/run/machines",
|
[IMAGE_MACHINE] = "/run/machines",
|
||||||
@ -119,7 +119,7 @@ static const char *const image_root_runtime_table[_IMAGE_CLASS_MAX] = {
|
|||||||
[IMAGE_CONFEXT] = "/run/confexts",
|
[IMAGE_CONFEXT] = "/run/confexts",
|
||||||
};
|
};
|
||||||
|
|
||||||
DEFINE_STRING_TABLE_LOOKUP_TO_STRING(image_root_runtime, ImageClass);
|
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(image_root_runtime, ImageClass);
|
||||||
|
|
||||||
static const char *const image_dirname_table[_IMAGE_CLASS_MAX] = {
|
static const char *const image_dirname_table[_IMAGE_CLASS_MAX] = {
|
||||||
[IMAGE_MACHINE] = "machines",
|
[IMAGE_MACHINE] = "machines",
|
||||||
@ -2022,3 +2022,45 @@ static const char* const image_type_table[_IMAGE_TYPE_MAX] = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
DEFINE_STRING_TABLE_LOOKUP(image_type, ImageType);
|
DEFINE_STRING_TABLE_LOOKUP(image_type, ImageType);
|
||||||
|
|
||||||
|
int image_root_pick(
|
||||||
|
RuntimeScope scope,
|
||||||
|
ImageClass c,
|
||||||
|
bool runtime,
|
||||||
|
char **ret) {
|
||||||
|
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(scope >= 0);
|
||||||
|
assert(scope < _RUNTIME_SCOPE_MAX);
|
||||||
|
assert(c >= 0);
|
||||||
|
assert(c < _IMAGE_CLASS_MAX);
|
||||||
|
assert(ret);
|
||||||
|
|
||||||
|
/* Picks the primary target directory for downloads, depending on invocation contexts */
|
||||||
|
|
||||||
|
_cleanup_free_ char *s = NULL;
|
||||||
|
switch (scope) {
|
||||||
|
|
||||||
|
case RUNTIME_SCOPE_SYSTEM: {
|
||||||
|
s = strdup(runtime ? image_root_runtime_to_string(c) : image_root_to_string(c));
|
||||||
|
if (!s)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case RUNTIME_SCOPE_USER:
|
||||||
|
r = sd_path_lookup(runtime ? SD_PATH_USER_RUNTIME : SD_PATH_USER_STATE_PRIVATE, "machines", &s);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
|
|
||||||
|
*ret = TAKE_PTR(s);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|||||||
@ -95,8 +95,7 @@ bool image_is_host(const struct Image *i);
|
|||||||
|
|
||||||
int image_to_json(const struct Image *i, sd_json_variant **ret);
|
int image_to_json(const struct Image *i, sd_json_variant **ret);
|
||||||
|
|
||||||
const char* image_root_to_string(ImageClass c) _const_;
|
int image_root_pick(RuntimeScope scope, ImageClass c, bool runtime, char **ret);
|
||||||
const char* image_root_runtime_to_string(ImageClass c) _const_;
|
|
||||||
|
|
||||||
extern const struct hash_ops image_hash_ops;
|
extern const struct hash_ops image_hash_ops;
|
||||||
|
|
||||||
|
|||||||
@ -4848,14 +4848,17 @@ int mountfsd_mount_image(
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
int mountfsd_mount_directory(
|
int mountfsd_mount_directory_fd(
|
||||||
const char *path,
|
int directory_fd,
|
||||||
int userns_fd,
|
int userns_fd,
|
||||||
DissectImageFlags flags,
|
DissectImageFlags flags,
|
||||||
int *ret_mount_fd) {
|
int *ret_mount_fd) {
|
||||||
|
|
||||||
int r;
|
int r;
|
||||||
|
|
||||||
|
assert(directory_fd >= 0);
|
||||||
|
assert(ret_mount_fd);
|
||||||
|
|
||||||
/* Pick one identity, not both, that makes no sense. */
|
/* Pick one identity, not both, that makes no sense. */
|
||||||
assert(!FLAGS_SET(flags, DISSECT_IMAGE_FOREIGN_UID|DISSECT_IMAGE_IDENTITY_UID));
|
assert(!FLAGS_SET(flags, DISSECT_IMAGE_FOREIGN_UID|DISSECT_IMAGE_IDENTITY_UID));
|
||||||
|
|
||||||
@ -4872,10 +4875,6 @@ int mountfsd_mount_directory(
|
|||||||
if (r < 0)
|
if (r < 0)
|
||||||
return log_error_errno(r, "Failed to enable varlink fd passing for write: %m");
|
return log_error_errno(r, "Failed to enable varlink fd passing for write: %m");
|
||||||
|
|
||||||
_cleanup_close_ int directory_fd = open(path, O_DIRECTORY|O_RDONLY|O_CLOEXEC|O_PATH);
|
|
||||||
if (directory_fd < 0)
|
|
||||||
return log_error_errno(errno, "Failed to open '%s': %m", path);
|
|
||||||
|
|
||||||
r = sd_varlink_push_dup_fd(vl, directory_fd);
|
r = sd_varlink_push_dup_fd(vl, directory_fd);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return log_error_errno(r, "Failed to push directory fd into varlink connection: %m");
|
return log_error_errno(r, "Failed to push directory fd into varlink connection: %m");
|
||||||
@ -4919,3 +4918,103 @@ int mountfsd_mount_directory(
|
|||||||
*ret_mount_fd = TAKE_FD(fsmount_fd);
|
*ret_mount_fd = TAKE_FD(fsmount_fd);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int mountfsd_mount_directory(
|
||||||
|
const char *path,
|
||||||
|
int userns_fd,
|
||||||
|
DissectImageFlags flags,
|
||||||
|
int *ret_mount_fd) {
|
||||||
|
|
||||||
|
assert(path);
|
||||||
|
assert(ret_mount_fd);
|
||||||
|
|
||||||
|
_cleanup_close_ int directory_fd = open(path, O_DIRECTORY|O_RDONLY|O_CLOEXEC|O_PATH);
|
||||||
|
if (directory_fd < 0)
|
||||||
|
return log_error_errno(errno, "Failed to open '%s': %m", path);
|
||||||
|
|
||||||
|
return mountfsd_mount_directory_fd(directory_fd, userns_fd, flags, ret_mount_fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
int mountfsd_make_directory_fd(
|
||||||
|
int parent_fd,
|
||||||
|
const char *name,
|
||||||
|
DissectImageFlags flags,
|
||||||
|
int *ret_directory_fd) {
|
||||||
|
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(parent_fd >= 0);
|
||||||
|
assert(name);
|
||||||
|
assert(ret_directory_fd);
|
||||||
|
|
||||||
|
_cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL;
|
||||||
|
r = sd_varlink_connect_address(&vl, "/run/systemd/io.systemd.MountFileSystem");
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to connect to mountfsd: %m");
|
||||||
|
|
||||||
|
r = sd_varlink_set_allow_fd_passing_input(vl, true);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to enable varlink fd passing for read: %m");
|
||||||
|
|
||||||
|
r = sd_varlink_set_allow_fd_passing_output(vl, true);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to enable varlink fd passing for write: %m");
|
||||||
|
|
||||||
|
r = sd_varlink_push_dup_fd(vl, parent_fd);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to push parent fd into varlink connection: %m");
|
||||||
|
|
||||||
|
sd_json_variant *reply = NULL;
|
||||||
|
const char *error_id = NULL;
|
||||||
|
r = varlink_callbo_and_log(
|
||||||
|
vl,
|
||||||
|
"io.systemd.MountFileSystem.MakeDirectory",
|
||||||
|
&reply,
|
||||||
|
&error_id,
|
||||||
|
SD_JSON_BUILD_PAIR_UNSIGNED("parentFileDescriptor", 0),
|
||||||
|
SD_JSON_BUILD_PAIR_STRING("name", name),
|
||||||
|
SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", FLAGS_SET(flags, DISSECT_IMAGE_ALLOW_INTERACTIVE_AUTH)));
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
static const sd_json_dispatch_field dispatch_table[] = {
|
||||||
|
{ "directoryFileDescriptor", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint, 0, SD_JSON_MANDATORY },
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
unsigned directory_fd_idx = UINT_MAX;
|
||||||
|
r = sd_json_dispatch(reply, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &directory_fd_idx);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to parse MountImage() reply: %m");
|
||||||
|
|
||||||
|
_cleanup_close_ int directory_fd = sd_varlink_take_fd(vl, directory_fd_idx);
|
||||||
|
if (directory_fd < 0)
|
||||||
|
return log_error_errno(directory_fd, "Failed to take directory fd from Varlink connection: %m");
|
||||||
|
|
||||||
|
*ret_directory_fd = TAKE_FD(directory_fd);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int mountfsd_make_directory(
|
||||||
|
const char *path,
|
||||||
|
DissectImageFlags flags,
|
||||||
|
int *ret_directory_fd) {
|
||||||
|
|
||||||
|
int r;
|
||||||
|
|
||||||
|
_cleanup_free_ char *parent = NULL;
|
||||||
|
r = path_extract_directory(path, &parent);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to extract parent directory from '%s': %m", path);
|
||||||
|
|
||||||
|
_cleanup_free_ char *dirname = NULL;
|
||||||
|
r = path_extract_filename(path, &dirname);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to extract directory name from '%s': %m", path);
|
||||||
|
|
||||||
|
_cleanup_close_ int fd = open(parent, O_DIRECTORY|O_CLOEXEC);
|
||||||
|
if (fd < 0)
|
||||||
|
return log_error_errno(r, "Failed to open '%s': %m", parent);
|
||||||
|
|
||||||
|
return mountfsd_make_directory_fd(fd, dirname, flags, ret_directory_fd);
|
||||||
|
}
|
||||||
|
|||||||
@ -258,4 +258,8 @@ static inline const char* dissected_partition_fstype(const DissectedPartition *m
|
|||||||
int get_common_dissect_directory(char **ret);
|
int get_common_dissect_directory(char **ret);
|
||||||
|
|
||||||
int mountfsd_mount_image(const char *path, int userns_fd, const ImagePolicy *image_policy, const VeritySettings *verity, DissectImageFlags flags, DissectedImage **ret);
|
int mountfsd_mount_image(const char *path, int userns_fd, const ImagePolicy *image_policy, const VeritySettings *verity, DissectImageFlags flags, DissectedImage **ret);
|
||||||
|
int mountfsd_mount_directory_fd(int directory_fd, int userns_fd, DissectImageFlags flags, int *ret_mount_fd);
|
||||||
int mountfsd_mount_directory(const char *path, int userns_fd, DissectImageFlags flags, int *ret_mount_fd);
|
int mountfsd_mount_directory(const char *path, int userns_fd, DissectImageFlags flags, int *ret_mount_fd);
|
||||||
|
|
||||||
|
int mountfsd_make_directory_fd(int parent_fd, const char *name, DissectImageFlags flags, int *ret_directory_fd);
|
||||||
|
int mountfsd_make_directory(const char *path, DissectImageFlags flags, int *ret_directory_fd);
|
||||||
|
|||||||
@ -95,6 +95,11 @@ static int unlinkat_maybe_dir(int dirfd, const char *pathname) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool need_opath(InstallFileFlags flags) {
|
||||||
|
/* Returns true when we need to pin the source file via an O_PATH fd */
|
||||||
|
return (flags & (INSTALL_FSYNC|INSTALL_FSYNC_FULL|INSTALL_SYNCFS|INSTALL_READ_ONLY)) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
int install_file(int source_atfd, const char *source_name,
|
int install_file(int source_atfd, const char *source_name,
|
||||||
int target_atfd, const char *target_name,
|
int target_atfd, const char *target_name,
|
||||||
InstallFileFlags flags) {
|
InstallFileFlags flags) {
|
||||||
@ -118,7 +123,7 @@ int install_file(int source_atfd, const char *source_name,
|
|||||||
* already in place, and only the syncing/read-only marking shall be applied. Note that with
|
* already in place, and only the syncing/read-only marking shall be applied. Note that with
|
||||||
* target_name=NULL and flags=0 this call is a NOP */
|
* target_name=NULL and flags=0 this call is a NOP */
|
||||||
|
|
||||||
if ((flags & (INSTALL_FSYNC|INSTALL_FSYNC_FULL|INSTALL_SYNCFS|INSTALL_READ_ONLY)) != 0) {
|
if (need_opath(flags)) {
|
||||||
_cleanup_close_ int pfd = -EBADF;
|
_cleanup_close_ int pfd = -EBADF;
|
||||||
struct stat st;
|
struct stat st;
|
||||||
|
|
||||||
@ -137,24 +142,32 @@ int install_file(int source_atfd, const char *source_name,
|
|||||||
_cleanup_close_ int regfd = -EBADF;
|
_cleanup_close_ int regfd = -EBADF;
|
||||||
|
|
||||||
regfd = fd_reopen(pfd, O_RDONLY|O_CLOEXEC);
|
regfd = fd_reopen(pfd, O_RDONLY|O_CLOEXEC);
|
||||||
if (regfd < 0)
|
if (regfd < 0) {
|
||||||
return regfd;
|
if (!FLAGS_SET(flags, INSTALL_GRACEFUL))
|
||||||
|
return log_debug_errno(regfd, "Failed to open referenced inode: %m");
|
||||||
|
|
||||||
if ((flags & (INSTALL_FSYNC_FULL|INSTALL_SYNCFS)) != 0) {
|
log_debug_errno(regfd, "Failed to open referenced inode, ignoring: %m");
|
||||||
/* If this is just a regular file (as oppose to a fully populated directory)
|
} else {
|
||||||
* let's downgrade INSTALL_SYNCFS to INSTALL_FSYNC_FULL, after all this is
|
if ((flags & (INSTALL_FSYNC_FULL|INSTALL_SYNCFS)) != 0)
|
||||||
* going to be a single inode we install */
|
/* If this is just a regular file (as opposed to a fully populated
|
||||||
r = fsync_full(regfd);
|
* directory) let's downgrade INSTALL_SYNCFS to INSTALL_FSYNC_FULL,
|
||||||
if (r < 0)
|
* after all this is going to be a single inode we install */
|
||||||
return r;
|
r = fsync_full(regfd);
|
||||||
} else if (flags & INSTALL_FSYNC) {
|
else if (flags & INSTALL_FSYNC)
|
||||||
if (fsync(regfd) < 0)
|
r = RET_NERRNO(fsync(regfd));
|
||||||
return -errno;
|
else
|
||||||
|
r = 0;
|
||||||
|
if (r < 0) {
|
||||||
|
if (!FLAGS_SET(flags, INSTALL_GRACEFUL))
|
||||||
|
return log_debug_errno(r, "Failed to sync source inode: %m");
|
||||||
|
|
||||||
|
log_debug_errno(r, "Failed to sync source inode, ignoring: %m");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flags & INSTALL_READ_ONLY)
|
||||||
|
rofd = TAKE_FD(regfd);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (flags & INSTALL_READ_ONLY)
|
|
||||||
rofd = TAKE_FD(regfd);
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,24 +175,31 @@ int install_file(int source_atfd, const char *source_name,
|
|||||||
_cleanup_close_ int dfd = -EBADF;
|
_cleanup_close_ int dfd = -EBADF;
|
||||||
|
|
||||||
dfd = fd_reopen(pfd, O_RDONLY|O_DIRECTORY|O_CLOEXEC);
|
dfd = fd_reopen(pfd, O_RDONLY|O_DIRECTORY|O_CLOEXEC);
|
||||||
if (dfd < 0)
|
if (dfd < 0) {
|
||||||
return dfd;
|
if (!FLAGS_SET(flags, INSTALL_GRACEFUL))
|
||||||
|
return log_debug_errno(dfd, "Failed to open referenced inode: %m");
|
||||||
|
|
||||||
if (flags & INSTALL_SYNCFS) {
|
log_debug_errno(dfd, "Failed to open referenced inode, ignoring: %m");
|
||||||
if (syncfs(dfd) < 0)
|
} else {
|
||||||
return -errno;
|
if (flags & INSTALL_SYNCFS)
|
||||||
} else if (flags & INSTALL_FSYNC_FULL) {
|
r = RET_NERRNO(syncfs(dfd));
|
||||||
r = fsync_full(dfd);
|
else if (flags & INSTALL_FSYNC_FULL)
|
||||||
if (r < 0)
|
r = fsync_full(dfd);
|
||||||
return r;
|
else if (flags & INSTALL_FSYNC)
|
||||||
} else if (flags & INSTALL_FSYNC) {
|
r = RET_NERRNO(fsync(dfd));
|
||||||
if (fsync(dfd) < 0)
|
else
|
||||||
return -errno;
|
r = 0;
|
||||||
|
if (r < 0) {
|
||||||
|
if (!FLAGS_SET(flags, INSTALL_GRACEFUL))
|
||||||
|
return log_debug_errno(r, "Failed to sync source inode: %m");
|
||||||
|
|
||||||
|
log_debug_errno(r, "Failed to sync source inode, ignoring: %m");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flags & INSTALL_READ_ONLY)
|
||||||
|
rofd = TAKE_FD(dfd);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (flags & INSTALL_READ_ONLY)
|
|
||||||
rofd = TAKE_FD(dfd);
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,8 +210,12 @@ int install_file(int source_atfd, const char *source_name,
|
|||||||
|
|
||||||
if (target_name && (flags & (INSTALL_FSYNC_FULL|INSTALL_SYNCFS)) != 0) {
|
if (target_name && (flags & (INSTALL_FSYNC_FULL|INSTALL_SYNCFS)) != 0) {
|
||||||
r = fsync_directory_of_file(pfd);
|
r = fsync_directory_of_file(pfd);
|
||||||
if (r < 0)
|
if (r < 0) {
|
||||||
return r;
|
if (!FLAGS_SET(flags, INSTALL_GRACEFUL))
|
||||||
|
return log_debug_errno(r, "Failed to sync source inode: %m");
|
||||||
|
|
||||||
|
log_debug_errno(r, "Failed to sync source inode, ignoring: %m");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@ -254,8 +278,12 @@ int install_file(int source_atfd, const char *source_name,
|
|||||||
|
|
||||||
if (rofd >= 0) {
|
if (rofd >= 0) {
|
||||||
r = fs_make_very_read_only(rofd);
|
r = fs_make_very_read_only(rofd);
|
||||||
if (r < 0)
|
if (r < 0) {
|
||||||
return r;
|
if (!FLAGS_SET(flags, INSTALL_GRACEFUL))
|
||||||
|
return log_debug_errno(r, "Failed to make inode read-only: %m");
|
||||||
|
|
||||||
|
log_debug_errno(r, "Failed to make inode read-only, ignoring: %m");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((flags & (INSTALL_FSYNC_FULL|INSTALL_SYNCFS)) != 0) {
|
if ((flags & (INSTALL_FSYNC_FULL|INSTALL_SYNCFS)) != 0) {
|
||||||
@ -263,8 +291,12 @@ int install_file(int source_atfd, const char *source_name,
|
|||||||
r = fsync_parent_at(target_atfd, target_name);
|
r = fsync_parent_at(target_atfd, target_name);
|
||||||
else
|
else
|
||||||
r = fsync_parent_at(source_atfd, source_name);
|
r = fsync_parent_at(source_atfd, source_name);
|
||||||
if (r < 0)
|
if (r < 0) {
|
||||||
return r;
|
if (!FLAGS_SET(flags, INSTALL_GRACEFUL))
|
||||||
|
return log_debug_errno(r, "Failed to sync inode: %m");
|
||||||
|
|
||||||
|
log_debug_errno(r, "Failed to sync inode, ignoring: %m");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@ -9,6 +9,7 @@ typedef enum InstallFileFlags {
|
|||||||
INSTALL_FSYNC = 1 << 2, /* fsync() file contents before moving file in */
|
INSTALL_FSYNC = 1 << 2, /* fsync() file contents before moving file in */
|
||||||
INSTALL_FSYNC_FULL = 1 << 3, /* like INSTALL_FSYNC, but also fsync() parent dir before+after moving file in */
|
INSTALL_FSYNC_FULL = 1 << 3, /* like INSTALL_FSYNC, but also fsync() parent dir before+after moving file in */
|
||||||
INSTALL_SYNCFS = 1 << 4, /* syncfs() before moving file in, fsync() parent dir after moving file in */
|
INSTALL_SYNCFS = 1 << 4, /* syncfs() before moving file in, fsync() parent dir after moving file in */
|
||||||
|
INSTALL_GRACEFUL = 1 << 5, /* don't fail if we cannot sync or mark read-only */
|
||||||
} InstallFileFlags;
|
} InstallFileFlags;
|
||||||
|
|
||||||
int install_file(int source_atfd, const char *source_name, int target_atfd, const char *target_name, InstallFileFlags flags);
|
int install_file(int source_atfd, const char *source_name, int target_atfd, const char *target_name, InstallFileFlags flags);
|
||||||
|
|||||||
31
src/shared/log-assert-critical.c
Normal file
31
src/shared/log-assert-critical.c
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||||
|
|
||||||
|
#include "env-util.h"
|
||||||
|
#include "log.h"
|
||||||
|
#include "log-assert-critical.h"
|
||||||
|
|
||||||
|
static bool assert_return_is_critical = BUILD_MODE_DEVELOPER;
|
||||||
|
|
||||||
|
void log_set_assert_return_is_critical(bool b) {
|
||||||
|
assert_return_is_critical = b;
|
||||||
|
}
|
||||||
|
|
||||||
|
void log_set_assert_return_is_critical_from_env(void) {
|
||||||
|
static int cached = INT_MIN;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
if (cached == INT_MIN) {
|
||||||
|
r = secure_getenv_bool("SYSTEMD_ASSERT_RETURN_IS_CRITICAL");
|
||||||
|
if (r < 0 && r != -ENXIO)
|
||||||
|
log_debug_errno(r, "Failed to parse $SYSTEMD_ASSERT_RETURN_IS_CRITICAL, ignoring: %m");
|
||||||
|
|
||||||
|
cached = r;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cached >= 0)
|
||||||
|
log_set_assert_return_is_critical(cached);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool log_get_assert_return_is_critical(void) {
|
||||||
|
return assert_return_is_critical;
|
||||||
|
}
|
||||||
7
src/shared/log-assert-critical.h
Normal file
7
src/shared/log-assert-critical.h
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "basic-forward.h"
|
||||||
|
|
||||||
|
void log_set_assert_return_is_critical(bool b);
|
||||||
|
void log_set_assert_return_is_critical_from_env(void);
|
||||||
@ -33,14 +33,14 @@ static int check_etc_passwd_collisions(
|
|||||||
if (r == -ENOENT)
|
if (r == -ENOENT)
|
||||||
return 0; /* no user database? then no user, hence no collision */
|
return 0; /* no user database? then no user, hence no collision */
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return log_error_errno(r, "Failed to open /etc/passwd of container: %m");
|
return log_error_errno(r, "Failed to open /etc/passwd of machine: %m");
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
struct passwd *pw;
|
struct passwd *pw;
|
||||||
|
|
||||||
r = fgetpwent_sane(f, &pw);
|
r = fgetpwent_sane(f, &pw);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return log_error_errno(r, "Failed to iterate through /etc/passwd of container: %m");
|
return log_error_errno(r, "Failed to iterate through /etc/passwd of machine: %m");
|
||||||
if (r == 0) /* EOF */
|
if (r == 0) /* EOF */
|
||||||
return 0; /* no collision */
|
return 0; /* no collision */
|
||||||
|
|
||||||
@ -68,14 +68,14 @@ static int check_etc_group_collisions(
|
|||||||
if (r == -ENOENT)
|
if (r == -ENOENT)
|
||||||
return 0; /* no group database? then no group, hence no collision */
|
return 0; /* no group database? then no group, hence no collision */
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return log_error_errno(r, "Failed to open /etc/group of container: %m");
|
return log_error_errno(r, "Failed to open /etc/group of machine: %m");
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
struct group *gr;
|
struct group *gr;
|
||||||
|
|
||||||
r = fgetgrent_sane(f, &gr);
|
r = fgetgrent_sane(f, &gr);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return log_error_errno(r, "Failed to iterate through /etc/group of container: %m");
|
return log_error_errno(r, "Failed to iterate through /etc/group of machine: %m");
|
||||||
if (r == 0)
|
if (r == 0)
|
||||||
return 0; /* no collision */
|
return 0; /* no collision */
|
||||||
|
|
||||||
@ -93,6 +93,7 @@ static int convert_user(
|
|||||||
uid_t allocate_uid,
|
uid_t allocate_uid,
|
||||||
const char *shell,
|
const char *shell,
|
||||||
bool shell_copy,
|
bool shell_copy,
|
||||||
|
const char *home_mount_directory,
|
||||||
UserRecord **ret_converted_user,
|
UserRecord **ret_converted_user,
|
||||||
GroupRecord **ret_converted_group) {
|
GroupRecord **ret_converted_group) {
|
||||||
|
|
||||||
@ -114,16 +115,16 @@ static int convert_user(
|
|||||||
return r;
|
return r;
|
||||||
if (r > 0)
|
if (r > 0)
|
||||||
return log_error_errno(SYNTHETIC_ERRNO(EBUSY),
|
return log_error_errno(SYNTHETIC_ERRNO(EBUSY),
|
||||||
"Sorry, the user '%s' already exists in the container.", u->user_name);
|
"Sorry, the user '%s' already exists in the machine.", u->user_name);
|
||||||
|
|
||||||
r = check_etc_group_collisions(directory, g->group_name, GID_INVALID);
|
r = check_etc_group_collisions(directory, g->group_name, GID_INVALID);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return r;
|
return r;
|
||||||
if (r > 0)
|
if (r > 0)
|
||||||
return log_error_errno(SYNTHETIC_ERRNO(EBUSY),
|
return log_error_errno(SYNTHETIC_ERRNO(EBUSY),
|
||||||
"Sorry, the group '%s' already exists in the container.", g->group_name);
|
"Sorry, the group '%s' already exists in the machine.", g->group_name);
|
||||||
|
|
||||||
h = path_join("/run/host/home/", u->user_name);
|
h = path_join(home_mount_directory, u->user_name);
|
||||||
if (!h)
|
if (!h)
|
||||||
return log_oom();
|
return log_oom();
|
||||||
|
|
||||||
@ -148,7 +149,7 @@ static int convert_user(
|
|||||||
SD_JSON_BUILD_PAIR_CONDITION(!strv_isempty(u->hashed_password), "hashedPassword", SD_JSON_BUILD_VARIANT(hp)),
|
SD_JSON_BUILD_PAIR_CONDITION(!strv_isempty(u->hashed_password), "hashedPassword", SD_JSON_BUILD_VARIANT(hp)),
|
||||||
SD_JSON_BUILD_PAIR_CONDITION(!!ssh, "sshAuthorizedKeys", SD_JSON_BUILD_VARIANT(ssh))))));
|
SD_JSON_BUILD_PAIR_CONDITION(!!ssh, "sshAuthorizedKeys", SD_JSON_BUILD_VARIANT(ssh))))));
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return log_error_errno(r, "Failed to build container user record: %m");
|
return log_error_errno(r, "Failed to build machine user record: %m");
|
||||||
|
|
||||||
r = group_record_build(
|
r = group_record_build(
|
||||||
&converted_group,
|
&converted_group,
|
||||||
@ -158,7 +159,7 @@ static int convert_user(
|
|||||||
SD_JSON_BUILD_PAIR_CONDITION(g->disposition >= 0, "disposition", SD_JSON_BUILD_STRING(user_disposition_to_string(g->disposition))),
|
SD_JSON_BUILD_PAIR_CONDITION(g->disposition >= 0, "disposition", SD_JSON_BUILD_STRING(user_disposition_to_string(g->disposition))),
|
||||||
SD_JSON_BUILD_PAIR("service", JSON_BUILD_CONST_STRING("io.systemd.NSpawn"))));
|
SD_JSON_BUILD_PAIR("service", JSON_BUILD_CONST_STRING("io.systemd.NSpawn"))));
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return log_error_errno(r, "Failed to build container group record: %m");
|
return log_error_errno(r, "Failed to build machine group record: %m");
|
||||||
|
|
||||||
*ret_converted_user = TAKE_PTR(converted_user);
|
*ret_converted_user = TAKE_PTR(converted_user);
|
||||||
*ret_converted_group = TAKE_PTR(converted_group);
|
*ret_converted_group = TAKE_PTR(converted_group);
|
||||||
@ -175,7 +176,7 @@ static int find_free_uid(const char *directory, uid_t *current_uid) {
|
|||||||
if (*current_uid > MAP_UID_MAX)
|
if (*current_uid > MAP_UID_MAX)
|
||||||
return log_error_errno(
|
return log_error_errno(
|
||||||
SYNTHETIC_ERRNO(EBUSY),
|
SYNTHETIC_ERRNO(EBUSY),
|
||||||
"No suitable available UID in range " UID_FMT "…" UID_FMT " in container detected, can't map user.",
|
"No suitable available UID in range " UID_FMT "…" UID_FMT " in machine detected, can't map user.",
|
||||||
MAP_UID_MIN, MAP_UID_MAX);
|
MAP_UID_MIN, MAP_UID_MAX);
|
||||||
|
|
||||||
r = check_etc_passwd_collisions(directory, NULL, *current_uid);
|
r = check_etc_passwd_collisions(directory, NULL, *current_uid);
|
||||||
@ -210,6 +211,7 @@ int machine_bind_user_prepare(
|
|||||||
char **bind_user,
|
char **bind_user,
|
||||||
const char *bind_user_shell,
|
const char *bind_user_shell,
|
||||||
bool bind_user_shell_copy,
|
bool bind_user_shell_copy,
|
||||||
|
const char *bind_user_home_mount_directory,
|
||||||
MachineBindUserContext **ret) {
|
MachineBindUserContext **ret) {
|
||||||
|
|
||||||
_cleanup_(machine_bind_user_context_freep) MachineBindUserContext *c = NULL;
|
_cleanup_(machine_bind_user_context_freep) MachineBindUserContext *c = NULL;
|
||||||
@ -219,7 +221,7 @@ int machine_bind_user_prepare(
|
|||||||
assert(ret);
|
assert(ret);
|
||||||
|
|
||||||
/* This resolves the users specified in 'bind_user', generates a minimalized JSON user + group record
|
/* This resolves the users specified in 'bind_user', generates a minimalized JSON user + group record
|
||||||
* for it to stick in the container, allocates a UID/GID for it, and updates the custom mount table,
|
* for it to stick in the machine, allocates a UID/GID for it, and updates the custom mount table,
|
||||||
* to include an appropriate bind mount mapping.
|
* to include an appropriate bind mount mapping.
|
||||||
*
|
*
|
||||||
* This extends the passed custom_mounts/n_custom_mounts with the home directories, and allocates a
|
* This extends the passed custom_mounts/n_custom_mounts with the home directories, and allocates a
|
||||||
@ -264,13 +266,13 @@ int machine_bind_user_prepare(
|
|||||||
if (r < 0)
|
if (r < 0)
|
||||||
return log_error_errno(r, "Failed to resolve group of user '%s': %m", u->user_name);
|
return log_error_errno(r, "Failed to resolve group of user '%s': %m", u->user_name);
|
||||||
|
|
||||||
/* We want to synthesize exactly one user + group from the host into the container. This only
|
/* We want to synthesize exactly one user + group from the host into the machine. This only
|
||||||
* makes sense if the user on the host has its own private group. We can't reasonably check
|
* makes sense if the user on the host has its own private group. We can't reasonably check
|
||||||
* this, so we just check of the name of user and group match.
|
* this, so we just check of the name of user and group match.
|
||||||
*
|
*
|
||||||
* One of these days we might want to support users in a shared/common group too, but it's
|
* One of these days we might want to support users in a shared/common group too, but it's
|
||||||
* not clear to me how this would have to be mapped, precisely given that the common group
|
* not clear to me how this would have to be mapped, precisely given that the common group
|
||||||
* probably already exists in the container. */
|
* probably already exists in the machine. */
|
||||||
if (!streq(u->user_name, g->group_name))
|
if (!streq(u->user_name, g->group_name))
|
||||||
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
|
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
|
||||||
"Sorry, mapping users without private groups is currently not supported.");
|
"Sorry, mapping users without private groups is currently not supported.");
|
||||||
@ -279,7 +281,14 @@ int machine_bind_user_prepare(
|
|||||||
if (r < 0)
|
if (r < 0)
|
||||||
return r;
|
return r;
|
||||||
|
|
||||||
r = convert_user(directory, u, g, current_uid, bind_user_shell, bind_user_shell_copy, &cu, &cg);
|
r = convert_user(
|
||||||
|
directory,
|
||||||
|
u, g,
|
||||||
|
current_uid,
|
||||||
|
bind_user_shell,
|
||||||
|
bind_user_shell_copy,
|
||||||
|
bind_user_home_mount_directory,
|
||||||
|
&cu, &cg);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return r;
|
return r;
|
||||||
|
|
||||||
|
|||||||
@ -27,4 +27,5 @@ int machine_bind_user_prepare(
|
|||||||
char **bind_user,
|
char **bind_user,
|
||||||
const char *bind_user_shell,
|
const char *bind_user_shell,
|
||||||
bool bind_user_shell_copy,
|
bool bind_user_shell_copy,
|
||||||
|
const char *bind_user_home_mount_directory,
|
||||||
MachineBindUserContext **ret);
|
MachineBindUserContext **ret);
|
||||||
|
|||||||
@ -119,6 +119,7 @@ shared_sources = files(
|
|||||||
'libmount-util.c',
|
'libmount-util.c',
|
||||||
'local-addresses.c',
|
'local-addresses.c',
|
||||||
'locale-setup.c',
|
'locale-setup.c',
|
||||||
|
'log-assert-critical.c',
|
||||||
'logs-show.c',
|
'logs-show.c',
|
||||||
'loop-util.c',
|
'loop-util.c',
|
||||||
'loopback-setup.c',
|
'loopback-setup.c',
|
||||||
|
|||||||
@ -3,6 +3,10 @@
|
|||||||
|
|
||||||
#include "shared-forward.h"
|
#include "shared-forward.h"
|
||||||
|
|
||||||
|
/* Helpful constants for the only numbers of UIDs that can currently be allocated */
|
||||||
|
#define NSRESOURCE_UIDS_64K 0x10000U
|
||||||
|
#define NSRESOURCE_UIDS_1 1U
|
||||||
|
|
||||||
int nsresource_allocate_userns(const char *name, uint64_t size);
|
int nsresource_allocate_userns(const char *name, uint64_t size);
|
||||||
int nsresource_register_userns(const char *name, int userns_fd);
|
int nsresource_register_userns(const char *name, int userns_fd);
|
||||||
int nsresource_add_mount(int userns_fd, int mount_fd);
|
int nsresource_add_mount(int userns_fd, int mount_fd);
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
#include "assert-util.h"
|
#include "assert-util.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
#include "log-assert-critical.h"
|
||||||
#include "nss-util.h"
|
#include "nss-util.h"
|
||||||
|
|
||||||
sd_json_dispatch_flags_t nss_json_dispatch_flags = SD_JSON_ALLOW_EXTENSIONS;
|
sd_json_dispatch_flags_t nss_json_dispatch_flags = SD_JSON_ALLOW_EXTENSIONS;
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
#include "errno-util.h"
|
#include "errno-util.h"
|
||||||
#include "shared-forward.h"
|
#include "shared-forward.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
#include "log-assert-critical.h"
|
||||||
#include "static-destruct.h"
|
#include "static-destruct.h"
|
||||||
#include "signal-util.h"
|
#include "signal-util.h"
|
||||||
#include "stdio-util.h"
|
#include "stdio-util.h"
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
#include "string-util.h"
|
#include "string-util.h"
|
||||||
#include "vmspawn-mount.h"
|
#include "vmspawn-mount.h"
|
||||||
|
|
||||||
static void runtime_mount_done(RuntimeMount *mount) {
|
void runtime_mount_done(RuntimeMount *mount) {
|
||||||
assert(mount);
|
assert(mount);
|
||||||
|
|
||||||
mount->source = mfree(mount->source);
|
mount->source = mfree(mount->source);
|
||||||
@ -24,7 +24,11 @@ void runtime_mount_context_done(RuntimeMountContext *ctx) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int runtime_mount_parse(RuntimeMountContext *ctx, const char *s, bool read_only) {
|
int runtime_mount_parse(RuntimeMountContext *ctx, const char *s, bool read_only) {
|
||||||
_cleanup_(runtime_mount_done) RuntimeMount mount = { .read_only = read_only };
|
_cleanup_(runtime_mount_done) RuntimeMount mount = {
|
||||||
|
.read_only = read_only,
|
||||||
|
.source_uid = UID_INVALID,
|
||||||
|
.target_uid = UID_INVALID,
|
||||||
|
};
|
||||||
_cleanup_free_ char *source_rel = NULL;
|
_cleanup_free_ char *source_rel = NULL;
|
||||||
int r;
|
int r;
|
||||||
|
|
||||||
|
|||||||
@ -6,7 +6,9 @@
|
|||||||
typedef struct RuntimeMount {
|
typedef struct RuntimeMount {
|
||||||
bool read_only;
|
bool read_only;
|
||||||
char *source;
|
char *source;
|
||||||
|
uid_t source_uid;
|
||||||
char *target;
|
char *target;
|
||||||
|
uid_t target_uid;
|
||||||
} RuntimeMount;
|
} RuntimeMount;
|
||||||
|
|
||||||
typedef struct RuntimeMountContext {
|
typedef struct RuntimeMountContext {
|
||||||
@ -14,5 +16,6 @@ typedef struct RuntimeMountContext {
|
|||||||
size_t n_mounts;
|
size_t n_mounts;
|
||||||
} RuntimeMountContext;
|
} RuntimeMountContext;
|
||||||
|
|
||||||
|
void runtime_mount_done(RuntimeMount *mount);
|
||||||
void runtime_mount_context_done(RuntimeMountContext *ctx);
|
void runtime_mount_context_done(RuntimeMountContext *ctx);
|
||||||
int runtime_mount_parse(RuntimeMountContext *ctx, const char *s, bool read_only);
|
int runtime_mount_parse(RuntimeMountContext *ctx, const char *s, bool read_only);
|
||||||
|
|||||||
@ -35,11 +35,13 @@
|
|||||||
#include "format-util.h"
|
#include "format-util.h"
|
||||||
#include "fs-util.h"
|
#include "fs-util.h"
|
||||||
#include "gpt.h"
|
#include "gpt.h"
|
||||||
|
#include "group-record.h"
|
||||||
#include "hexdecoct.h"
|
#include "hexdecoct.h"
|
||||||
#include "hostname-setup.h"
|
#include "hostname-setup.h"
|
||||||
#include "hostname-util.h"
|
#include "hostname-util.h"
|
||||||
#include "id128-util.h"
|
#include "id128-util.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
#include "machine-bind-user.h"
|
||||||
#include "machine-credential.h"
|
#include "machine-credential.h"
|
||||||
#include "main-func.h"
|
#include "main-func.h"
|
||||||
#include "mkdir.h"
|
#include "mkdir.h"
|
||||||
@ -68,6 +70,8 @@
|
|||||||
#include "terminal-util.h"
|
#include "terminal-util.h"
|
||||||
#include "tmpfile-util.h"
|
#include "tmpfile-util.h"
|
||||||
#include "unit-name.h"
|
#include "unit-name.h"
|
||||||
|
#include "user-record.h"
|
||||||
|
#include "user-util.h"
|
||||||
#include "utf8.h"
|
#include "utf8.h"
|
||||||
#include "vmspawn-mount.h"
|
#include "vmspawn-mount.h"
|
||||||
#include "vmspawn-register.h"
|
#include "vmspawn-register.h"
|
||||||
@ -136,6 +140,9 @@ static char *arg_tpm_state_path = NULL;
|
|||||||
static TpmStateMode arg_tpm_state_mode = TPM_STATE_AUTO;
|
static TpmStateMode arg_tpm_state_mode = TPM_STATE_AUTO;
|
||||||
static bool arg_ask_password = true;
|
static bool arg_ask_password = true;
|
||||||
static bool arg_notify_ready = true;
|
static bool arg_notify_ready = true;
|
||||||
|
static char **arg_bind_user = NULL;
|
||||||
|
static char *arg_bind_user_shell = NULL;
|
||||||
|
static bool arg_bind_user_shell_copy = false;
|
||||||
|
|
||||||
STATIC_DESTRUCTOR_REGISTER(arg_directory, freep);
|
STATIC_DESTRUCTOR_REGISTER(arg_directory, freep);
|
||||||
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
|
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
|
||||||
@ -155,6 +162,8 @@ STATIC_DESTRUCTOR_REGISTER(arg_ssh_key_type, freep);
|
|||||||
STATIC_DESTRUCTOR_REGISTER(arg_smbios11, strv_freep);
|
STATIC_DESTRUCTOR_REGISTER(arg_smbios11, strv_freep);
|
||||||
STATIC_DESTRUCTOR_REGISTER(arg_tpm_state_path, freep);
|
STATIC_DESTRUCTOR_REGISTER(arg_tpm_state_path, freep);
|
||||||
STATIC_DESTRUCTOR_REGISTER(arg_property, strv_freep);
|
STATIC_DESTRUCTOR_REGISTER(arg_property, strv_freep);
|
||||||
|
STATIC_DESTRUCTOR_REGISTER(arg_bind_user, strv_freep);
|
||||||
|
STATIC_DESTRUCTOR_REGISTER(arg_bind_user_shell, freep);
|
||||||
|
|
||||||
static int help(void) {
|
static int help(void) {
|
||||||
_cleanup_free_ char *link = NULL;
|
_cleanup_free_ char *link = NULL;
|
||||||
@ -215,6 +224,9 @@ static int help(void) {
|
|||||||
" --bind-ro=SOURCE[:TARGET]\n"
|
" --bind-ro=SOURCE[:TARGET]\n"
|
||||||
" Mount a file or directory, but read-only\n"
|
" Mount a file or directory, but read-only\n"
|
||||||
" --extra-drive=PATH Adds an additional disk to the virtual machine\n"
|
" --extra-drive=PATH Adds an additional disk to the virtual machine\n"
|
||||||
|
" --bind-user=NAME Bind user from host to virtual machine\n"
|
||||||
|
" --bind-user-shell=BOOL|PATH\n"
|
||||||
|
" Configure the shell to use for --bind-user= users\n"
|
||||||
"\n%3$sIntegration:%4$s\n"
|
"\n%3$sIntegration:%4$s\n"
|
||||||
" --forward-journal=FILE|DIR\n"
|
" --forward-journal=FILE|DIR\n"
|
||||||
" Forward the VM's journal to the host\n"
|
" Forward the VM's journal to the host\n"
|
||||||
@ -289,6 +301,8 @@ static int parse_argv(int argc, char *argv[]) {
|
|||||||
ARG_NO_ASK_PASSWORD,
|
ARG_NO_ASK_PASSWORD,
|
||||||
ARG_PROPERTY,
|
ARG_PROPERTY,
|
||||||
ARG_NOTIFY_READY,
|
ARG_NOTIFY_READY,
|
||||||
|
ARG_BIND_USER,
|
||||||
|
ARG_BIND_USER_SHELL,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct option options[] = {
|
static const struct option options[] = {
|
||||||
@ -338,6 +352,8 @@ static int parse_argv(int argc, char *argv[]) {
|
|||||||
{ "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
|
{ "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
|
||||||
{ "property", required_argument, NULL, ARG_PROPERTY },
|
{ "property", required_argument, NULL, ARG_PROPERTY },
|
||||||
{ "notify-ready", required_argument, NULL, ARG_NOTIFY_READY },
|
{ "notify-ready", required_argument, NULL, ARG_NOTIFY_READY },
|
||||||
|
{ "bind-user", required_argument, NULL, ARG_BIND_USER },
|
||||||
|
{ "bind-user-shell", required_argument, NULL, ARG_BIND_USER_SHELL },
|
||||||
{}
|
{}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -675,6 +691,30 @@ static int parse_argv(int argc, char *argv[]) {
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case ARG_BIND_USER:
|
||||||
|
if (!valid_user_group_name(optarg, /* flags= */ 0))
|
||||||
|
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid user name to bind: %s", optarg);
|
||||||
|
|
||||||
|
if (strv_extend(&arg_bind_user, optarg) < 0)
|
||||||
|
return log_oom();
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ARG_BIND_USER_SHELL: {
|
||||||
|
bool copy = false;
|
||||||
|
char *sh = NULL;
|
||||||
|
r = parse_user_shell(optarg, &sh, ©);
|
||||||
|
if (r == -ENOMEM)
|
||||||
|
return log_oom();
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Invalid user shell to bind: %s", optarg);
|
||||||
|
|
||||||
|
free_and_replace(arg_bind_user_shell, sh);
|
||||||
|
arg_bind_user_shell_copy = copy;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case '?':
|
case '?':
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
||||||
@ -682,6 +722,12 @@ static int parse_argv(int argc, char *argv[]) {
|
|||||||
assert_not_reached();
|
assert_not_reached();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Drop duplicate --bind-user= entries */
|
||||||
|
strv_uniq(arg_bind_user);
|
||||||
|
|
||||||
|
if (arg_bind_user_shell && strv_isempty(arg_bind_user))
|
||||||
|
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot use --bind-user-shell= without --bind-user=");
|
||||||
|
|
||||||
if (argc > optind) {
|
if (argc > optind) {
|
||||||
arg_kernel_cmdline_extra = strv_copy(argv + optind);
|
arg_kernel_cmdline_extra = strv_copy(argv + optind);
|
||||||
if (!arg_kernel_cmdline_extra)
|
if (!arg_kernel_cmdline_extra)
|
||||||
@ -1359,7 +1405,9 @@ static int find_virtiofsd(char **ret) {
|
|||||||
static int start_virtiofsd(
|
static int start_virtiofsd(
|
||||||
const char *scope,
|
const char *scope,
|
||||||
const char *directory,
|
const char *directory,
|
||||||
bool uidmap,
|
uid_t source_uid,
|
||||||
|
uid_t target_uid,
|
||||||
|
uid_t uid_range,
|
||||||
const char *runtime_dir,
|
const char *runtime_dir,
|
||||||
const char *sd_socket_activate,
|
const char *sd_socket_activate,
|
||||||
char **ret_listen_address,
|
char **ret_listen_address,
|
||||||
@ -1397,20 +1445,20 @@ static int start_virtiofsd(
|
|||||||
if (!argv)
|
if (!argv)
|
||||||
return log_oom();
|
return log_oom();
|
||||||
|
|
||||||
if (uidmap && arg_uid_shift != UID_INVALID) {
|
if (source_uid != UID_INVALID && target_uid != UID_INVALID && uid_range != UID_INVALID) {
|
||||||
r = strv_extend(&argv, "--uid-map");
|
r = strv_extend(&argv, "--translate-uid");
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return log_oom();
|
return log_oom();
|
||||||
|
|
||||||
r = strv_extendf(&argv, ":0:" UID_FMT ":" UID_FMT ":", arg_uid_shift, arg_uid_range);
|
r = strv_extendf(&argv, "map:" UID_FMT ":" UID_FMT ":" UID_FMT, target_uid, source_uid, uid_range);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return log_oom();
|
return log_oom();
|
||||||
|
|
||||||
r = strv_extend(&argv, "--gid-map");
|
r = strv_extend(&argv, "--translate-gid");
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return log_oom();
|
return log_oom();
|
||||||
|
|
||||||
r = strv_extendf(&argv, ":0:" GID_FMT ":" GID_FMT ":", arg_uid_shift, arg_uid_range);
|
r = strv_extendf(&argv, "map:" GID_FMT ":" GID_FMT ":" GID_FMT, target_uid, source_uid, uid_range);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return log_oom();
|
return log_oom();
|
||||||
}
|
}
|
||||||
@ -1425,6 +1473,65 @@ static int start_virtiofsd(
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int bind_user_setup(
|
||||||
|
const MachineBindUserContext *context,
|
||||||
|
MachineCredentialContext *credentials,
|
||||||
|
RuntimeMountContext *mounts) {
|
||||||
|
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(credentials);
|
||||||
|
assert(mounts);
|
||||||
|
|
||||||
|
if (!context)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
FOREACH_ARRAY(bind_user, context->data, context->n_data) {
|
||||||
|
_cleanup_free_ char *formatted = NULL;
|
||||||
|
r = sd_json_variant_format(bind_user->payload_user->json, SD_JSON_FORMAT_NEWLINE, &formatted);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to format JSON user record: %m");
|
||||||
|
|
||||||
|
_cleanup_free_ char *cred = strjoin("userdb.transient.user.", bind_user->payload_user->user_name);
|
||||||
|
if (!cred)
|
||||||
|
return log_oom();
|
||||||
|
|
||||||
|
r = machine_credential_add(credentials, cred, formatted, SIZE_MAX);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
formatted = mfree(formatted);
|
||||||
|
r = sd_json_variant_format(bind_user->payload_group->json, SD_JSON_FORMAT_NEWLINE, &formatted);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to format JSON group record: %m");
|
||||||
|
|
||||||
|
free(cred);
|
||||||
|
cred = strjoin("userdb.transient.group.", bind_user->payload_group->group_name);
|
||||||
|
if (!cred)
|
||||||
|
return log_oom();
|
||||||
|
|
||||||
|
r = machine_credential_add(credentials, cred, formatted, SIZE_MAX);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
_cleanup_(runtime_mount_done) RuntimeMount mount = {
|
||||||
|
.source = strdup(user_record_home_directory(bind_user->host_user)),
|
||||||
|
.source_uid = bind_user->host_user->uid,
|
||||||
|
.target = strdup(user_record_home_directory(bind_user->payload_user)),
|
||||||
|
.target_uid = bind_user->payload_user->uid,
|
||||||
|
};
|
||||||
|
if (!mount.source || !mount.target)
|
||||||
|
return log_oom();
|
||||||
|
|
||||||
|
if (!GREEDY_REALLOC(mounts->mounts, mounts->n_mounts + 1))
|
||||||
|
return log_oom();
|
||||||
|
|
||||||
|
mounts->mounts[mounts->n_mounts++] = TAKE_STRUCT(mount);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int kernel_cmdline_maybe_append_root(void) {
|
static int kernel_cmdline_maybe_append_root(void) {
|
||||||
int r;
|
int r;
|
||||||
bool cmdline_contains_root = strv_find_startswith(arg_kernel_cmdline_extra, "root=")
|
bool cmdline_contains_root = strv_find_startswith(arg_kernel_cmdline_extra, "root=")
|
||||||
@ -1726,6 +1833,21 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
|
|||||||
if (r < 0)
|
if (r < 0)
|
||||||
return log_error_errno(r, "Failed to find OVMF config: %m");
|
return log_error_errno(r, "Failed to find OVMF config: %m");
|
||||||
|
|
||||||
|
_cleanup_(machine_bind_user_context_freep) MachineBindUserContext *bind_user_context = NULL;
|
||||||
|
r = machine_bind_user_prepare(
|
||||||
|
/* directory= */ NULL,
|
||||||
|
arg_bind_user,
|
||||||
|
arg_bind_user_shell,
|
||||||
|
arg_bind_user_shell_copy,
|
||||||
|
"/run/vmhost/home",
|
||||||
|
&bind_user_context);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
r = bind_user_setup(bind_user_context, &arg_credentials, &arg_runtime_mounts);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
/* only warn if the user hasn't disabled secureboot */
|
/* only warn if the user hasn't disabled secureboot */
|
||||||
if (!ovmf_config->supports_sb && arg_secure_boot)
|
if (!ovmf_config->supports_sb && arg_secure_boot)
|
||||||
log_warning("Couldn't find OVMF firmware blob with Secure Boot support, "
|
log_warning("Couldn't find OVMF firmware blob with Secure Boot support, "
|
||||||
@ -2177,7 +2299,9 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
|
|||||||
r = start_virtiofsd(
|
r = start_virtiofsd(
|
||||||
unit,
|
unit,
|
||||||
arg_directory,
|
arg_directory,
|
||||||
/* uidmap= */ true,
|
/* source_uid= */ arg_uid_shift,
|
||||||
|
/* target_uid= */ 0,
|
||||||
|
/* uid_range= */ arg_uid_range,
|
||||||
runtime_dir,
|
runtime_dir,
|
||||||
sd_socket_activate,
|
sd_socket_activate,
|
||||||
&listen_address,
|
&listen_address,
|
||||||
@ -2267,7 +2391,9 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
|
|||||||
r = start_virtiofsd(
|
r = start_virtiofsd(
|
||||||
unit,
|
unit,
|
||||||
mount->source,
|
mount->source,
|
||||||
/* uidmap= */ false,
|
/* source_uid= */ mount->source_uid,
|
||||||
|
/* target_uid= */ mount->target_uid,
|
||||||
|
/* uid_range= */ 1U,
|
||||||
runtime_dir,
|
runtime_dir,
|
||||||
sd_socket_activate,
|
sd_socket_activate,
|
||||||
&listen_address,
|
&listen_address,
|
||||||
@ -2441,7 +2567,7 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (arg_forward_journal) {
|
if (arg_forward_journal) {
|
||||||
_cleanup_free_ char *listen_address = NULL, *cred = NULL;
|
_cleanup_free_ char *listen_address = NULL;
|
||||||
|
|
||||||
if (!GREEDY_REALLOC(children, n_children + 1))
|
if (!GREEDY_REALLOC(children, n_children + 1))
|
||||||
return log_oom();
|
return log_oom();
|
||||||
@ -2459,11 +2585,7 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
|
|||||||
pidref_done(&child);
|
pidref_done(&child);
|
||||||
children[n_children++] = TAKE_PTR(source);
|
children[n_children++] = TAKE_PTR(source);
|
||||||
|
|
||||||
cred = strjoin("journal.forward_to_socket:", listen_address);
|
r = machine_credential_add(&arg_credentials, "journal.forward_to_socket", listen_address, SIZE_MAX);
|
||||||
if (!cred)
|
|
||||||
return log_oom();
|
|
||||||
|
|
||||||
r = machine_credential_set(&arg_credentials, cred);
|
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
@ -2509,13 +2631,14 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
|
|||||||
|
|
||||||
/* on distros that provide their own sshd@.service file we need to provide a dropin which
|
/* on distros that provide their own sshd@.service file we need to provide a dropin which
|
||||||
* picks up our public key credential */
|
* picks up our public key credential */
|
||||||
r = machine_credential_set(
|
r = machine_credential_add(
|
||||||
&arg_credentials,
|
&arg_credentials,
|
||||||
"systemd.unit-dropin.sshd-vsock@.service:"
|
"systemd.unit-dropin.sshd-vsock@.service",
|
||||||
"[Service]\n"
|
"[Service]\n"
|
||||||
"ExecStart=\n"
|
"ExecStart=\n"
|
||||||
"ExecStart=-sshd -i -o 'AuthorizedKeysFile=%d/ssh.ephemeral-authorized_keys-all .ssh/authorized_keys'\n"
|
"ExecStart=-sshd -i -o 'AuthorizedKeysFile=%d/ssh.ephemeral-authorized_keys-all .ssh/authorized_keys'\n"
|
||||||
"ImportCredential=ssh.ephemeral-authorized_keys-all\n");
|
"ImportCredential=ssh.ephemeral-authorized_keys-all\n",
|
||||||
|
SIZE_MAX);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return log_error_errno(r, "Failed to set credential systemd.unit-dropin.sshd-vsock@.service: %m");
|
return log_error_errno(r, "Failed to set credential systemd.unit-dropin.sshd-vsock@.service: %m");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,6 +28,16 @@ units = [
|
|||||||
'conditions' : ['ENABLE_MACHINED'],
|
'conditions' : ['ENABLE_MACHINED'],
|
||||||
'symlinks' : ['sockets.target.wants/'],
|
'symlinks' : ['sockets.target.wants/'],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'file' : 'systemd-importd.service.in',
|
||||||
|
'conditions' : ['ENABLE_IMPORTD'],
|
||||||
|
'symlinks' : ['dbus-org.freedesktop.import1.service'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'file' : 'systemd-importd.socket',
|
||||||
|
'conditions' : ['ENABLE_IMPORTD'],
|
||||||
|
'symlinks' : ['sockets.target.wants/'],
|
||||||
|
},
|
||||||
{ 'file' : 'paths.target' },
|
{ 'file' : 'paths.target' },
|
||||||
{ 'file' : 'printer.target' },
|
{ 'file' : 'printer.target' },
|
||||||
{ 'file' : 'session.slice' },
|
{ 'file' : 'session.slice' },
|
||||||
|
|||||||
28
units/user/systemd-importd.service.in
Normal file
28
units/user/systemd-importd.service.in
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||||
|
#
|
||||||
|
# This file is part of systemd.
|
||||||
|
#
|
||||||
|
# systemd is free software; you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU Lesser General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 2.1 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
|
||||||
|
[Unit]
|
||||||
|
Description=Disk Image Download Service
|
||||||
|
Documentation=man:systemd-importd.service(8)
|
||||||
|
Documentation=man:org.freedesktop.import1(5)
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=notify
|
||||||
|
ExecStart={{LIBEXECDIR}}/systemd-importd --user
|
||||||
|
BusName=org.freedesktop.import1
|
||||||
|
KillMode=mixed
|
||||||
|
NoNewPrivileges=yes
|
||||||
|
MemoryDenyWriteExecute=yes
|
||||||
|
RestrictRealtime=yes
|
||||||
|
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
|
||||||
|
SystemCallFilter=@system-service @mount
|
||||||
|
SystemCallErrorNumber=EPERM
|
||||||
|
SystemCallArchitectures=native
|
||||||
|
LockPersonality=yes
|
||||||
|
{{SERVICE_WATCHDOG}}
|
||||||
18
units/user/systemd-importd.socket
Normal file
18
units/user/systemd-importd.socket
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||||
|
#
|
||||||
|
# This file is part of systemd.
|
||||||
|
#
|
||||||
|
# systemd is free software; you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU Lesser General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 2.1 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
|
||||||
|
[Unit]
|
||||||
|
Description=Disk Image Download Service Socket
|
||||||
|
Documentation=man:systemd-importd.service(8)
|
||||||
|
Documentation=man:org.freedesktop.import1(5)
|
||||||
|
|
||||||
|
[Socket]
|
||||||
|
ListenStream=%t/systemd/io.systemd.Import
|
||||||
|
FileDescriptorName=varlink
|
||||||
|
SocketMode=0600
|
||||||
Loading…
x
Reference in New Issue
Block a user