mirror of
https://github.com/systemd/systemd
synced 2026-04-10 17:15:03 +02:00
Compare commits
No commits in common. "d62ab43fd0ddb077514f8294ac5345d78e1a7ef3" and "e80c5eb6e63c394fd90c2d8cf454ee649e59ae21" have entirely different histories.
d62ab43fd0
...
e80c5eb6e6
@ -459,73 +459,6 @@
|
|||||||
<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=5650452e6b0b430f44d3d48b7322c2b3c8b9477f
|
GIT_COMMIT=e50fce1d4b2a9f1bb990027de8e86603f3b42301
|
||||||
PKG_SUBDIR=debian
|
PKG_SUBDIR=debian
|
||||||
|
|||||||
@ -4,13 +4,40 @@
|
|||||||
#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,
|
||||||
@ -46,8 +73,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 (log_get_assert_return_is_critical && log_get_assert_return_is_critical())
|
if (assert_return_is_critical)
|
||||||
log_assert_failed(text, file, line, func);
|
log_assert_failed(text, file, line, func);
|
||||||
|
|
||||||
PROTECT_ERRNO;
|
PROTECT_ERRNO;
|
||||||
|
|||||||
@ -5,7 +5,9 @@
|
|||||||
|
|
||||||
/* Logging for various assertions */
|
/* Logging for various assertions */
|
||||||
|
|
||||||
bool log_get_assert_return_is_critical(void) _weak_ _pure_;
|
void log_set_assert_return_is_critical(bool b);
|
||||||
|
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, \
|
||||||
}
|
}
|
||||||
|
|
||||||
bool mempool_enabled(void) _weak_ _pure_;
|
__attribute__((weak)) bool mempool_enabled(void);
|
||||||
|
|
||||||
void mempool_trim(struct mempool *mp);
|
void mempool_trim(struct mempool *mp);
|
||||||
|
|||||||
@ -366,18 +366,6 @@
|
|||||||
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"
|
||||||
|
|||||||
@ -1,73 +0,0 @@
|
|||||||
/* 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;
|
|
||||||
}
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
int coredump_backtrace(int argc, char *argv[]);
|
|
||||||
@ -1,83 +0,0 @@
|
|||||||
/* 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;
|
|
||||||
}
|
|
||||||
@ -1,44 +0,0 @@
|
|||||||
/* 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);
|
|
||||||
@ -1,454 +0,0 @@
|
|||||||
/* 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;
|
|
||||||
}
|
|
||||||
@ -1,75 +0,0 @@
|
|||||||
/* 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);
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "basic-forward.h"
|
|
||||||
|
|
||||||
typedef struct Context Context;
|
|
||||||
@ -1,87 +0,0 @@
|
|||||||
/* 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);
|
|
||||||
}
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
int coredump_kernel_helper(int argc, char *argv[]);
|
|
||||||
@ -1,162 +0,0 @@
|
|||||||
/* 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);
|
|
||||||
}
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
int coredump_receive(int fd);
|
|
||||||
@ -1,334 +0,0 @@
|
|||||||
/* 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;
|
|
||||||
}
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
/* 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);
|
|
||||||
@ -1,798 +0,0 @@
|
|||||||
/* 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;
|
|
||||||
}
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
/* 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,13 +6,6 @@ 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, NSRESOURCE_UIDS_64K); /* allocate 64K users by default */
|
userns_fd = nsresource_allocate_userns(/* name= */ NULL, UINT64_C(0x10000)); /* 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,7 +7,6 @@
|
|||||||
|
|
||||||
#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,9 +207,7 @@ 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(),
|
||||||
@ -225,8 +223,6 @@ 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[] = {
|
||||||
@ -234,8 +230,6 @@ 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 },
|
|
||||||
{}
|
{}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -268,14 +262,6 @@ 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,26 +6,21 @@
|
|||||||
#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, int userns_fd, PidRef *ret_pid) {
|
int import_fork_tar_x(int tree_fd, PidRef *ret_pid) {
|
||||||
int r;
|
int r;
|
||||||
|
|
||||||
assert(tree_fd >= 0);
|
assert(tree_fd >= 0);
|
||||||
@ -46,7 +41,7 @@ int import_fork_tar_x(int tree_fd, int userns_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], userns_fd }, userns_fd >= 0 ? 3 : 2,
|
(int[]) { tree_fd, pipefd[0] }, 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)
|
||||||
@ -63,20 +58,12 @@ int import_fork_tar_x(int tree_fd, int userns_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_debug_errno(errno, "Failed to lock tar into network namespace, ignoring: %m");
|
log_warning_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_debug_errno(r, "Failed to drop capabilities, ignoring: %m");
|
log_warning_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");
|
||||||
@ -304,147 +291,3 @@ 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,16 +15,15 @@ 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 << 9, /* download .nspawn settings file */
|
IMPORT_PULL_SETTINGS = 1 << 8, /* download .nspawn settings file */
|
||||||
IMPORT_PULL_ROOTHASH = 1 << 10, /* only for raw: download .roothash file for verity */
|
IMPORT_PULL_ROOTHASH = 1 << 9, /* only for raw: download .roothash file for verity */
|
||||||
IMPORT_PULL_ROOTHASH_SIGNATURE = 1 << 11, /* only for raw: download .roothash.p7s file for verity */
|
IMPORT_PULL_ROOTHASH_SIGNATURE = 1 << 10, /* only for raw: download .roothash.p7s file for verity */
|
||||||
IMPORT_PULL_VERITY = 1 << 12, /* only for raw: download .verity file for verity */
|
IMPORT_PULL_VERITY = 1 << 11, /* 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_FOREIGN_UID,
|
IMPORT_FLAGS_MASK_TAR = IMPORT_FORCE|IMPORT_READ_ONLY|IMPORT_BTRFS_SUBVOL|IMPORT_BTRFS_QUOTA|IMPORT_DIRECT|IMPORT_SYNC,
|
||||||
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 */
|
||||||
@ -35,7 +34,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, int userns_fd, PidRef *ret_pid);
|
int import_fork_tar_x(int tree_fd, PidRef *ret_pid);
|
||||||
|
|
||||||
int import_mangle_os_tree(const char *path);
|
int import_mangle_os_tree(const char *path);
|
||||||
|
|
||||||
@ -43,11 +42,4 @@ 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,12 +35,10 @@ 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 char *arg_image_root = NULL;
|
static const 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;
|
||||||
@ -282,9 +280,7 @@ 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(),
|
||||||
@ -306,8 +302,6 @@ 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[] = {
|
||||||
@ -321,8 +315,6 @@ 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 },
|
|
||||||
{}
|
{}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -346,10 +338,7 @@ static int parse_argv(int argc, char *argv[]) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case ARG_IMAGE_ROOT:
|
case ARG_IMAGE_ROOT:
|
||||||
r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image_root);
|
arg_image_root = optarg;
|
||||||
if (r < 0)
|
|
||||||
return r;
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ARG_READ_ONLY:
|
case ARG_READ_ONLY:
|
||||||
@ -388,14 +377,6 @@ 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;
|
||||||
|
|
||||||
@ -403,11 +384,8 @@ static int parse_argv(int argc, char *argv[]) {
|
|||||||
assert_not_reached();
|
assert_not_reached();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!arg_image_root) {
|
if (!arg_image_root)
|
||||||
r = image_root_pick(arg_runtime_scope < 0 ? RUNTIME_SCOPE_SYSTEM : arg_runtime_scope, arg_class, /* runtime= */ false, &arg_image_root);
|
arg_image_root = image_root_to_string(arg_class);
|
||||||
if (r < 0)
|
|
||||||
return log_error_errno(r, "Failed to pick image root: %m");
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,7 +17,6 @@
|
|||||||
#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"
|
||||||
@ -201,10 +200,7 @@ 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();
|
||||||
|
|
||||||
_cleanup_free_ char *image_root = NULL;
|
const char *image_root = runtime ? image_root_runtime_to_string(class) : image_root_to_string(class);
|
||||||
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(
|
||||||
@ -224,7 +220,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 = TAKE_PTR(image_root),
|
.image_root = image_root,
|
||||||
.json = TAKE_PTR(j),
|
.json = TAKE_PTR(j),
|
||||||
.blockdev = blockdev,
|
.blockdev = blockdev,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -7,7 +7,6 @@
|
|||||||
|
|
||||||
#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"
|
||||||
@ -47,7 +46,6 @@ 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;
|
||||||
|
|
||||||
@ -75,10 +73,7 @@ TarImport* tar_import_unref(TarImport *i) {
|
|||||||
|
|
||||||
pidref_done_sigkill_wait(&i->tar_pid);
|
pidref_done_sigkill_wait(&i->tar_pid);
|
||||||
|
|
||||||
if (i->temp_path) {
|
rm_rf_subvolume_and_free(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);
|
||||||
|
|
||||||
@ -86,7 +81,6 @@ 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);
|
||||||
@ -120,7 +114,6 @@ 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,
|
||||||
@ -207,8 +200,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|INSTALL_GRACEFUL : 0) |
|
(i->flags & IMPORT_READ_ONLY ? INSTALL_READ_ONLY : 0) |
|
||||||
(i->flags & IMPORT_SYNC ? INSTALL_SYNCFS|INSTALL_GRACEFUL : 0));
|
(i->flags & IMPORT_SYNC ? INSTALL_SYNCFS : 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);
|
||||||
|
|
||||||
@ -251,20 +244,6 @@ 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 (FLAGS_SET(i->flags, IMPORT_FOREIGN_UID)) {
|
|
||||||
r = import_make_foreign_userns(&i->userns_fd);
|
|
||||||
if (r < 0)
|
|
||||||
return r;
|
|
||||||
|
|
||||||
_cleanup_close_ int directory_fd = -EBADF;
|
|
||||||
r = mountfsd_make_directory(d, /* flags= */ 0, &directory_fd);
|
|
||||||
if (r < 0)
|
|
||||||
return r;
|
|
||||||
|
|
||||||
r = mountfsd_mount_directory_fd(directory_fd, i->userns_fd, DISSECT_IMAGE_FOREIGN_UID, &i->tree_fd);
|
|
||||||
if (r < 0)
|
|
||||||
return r;
|
|
||||||
} else {
|
|
||||||
if (i->flags & IMPORT_BTRFS_SUBVOL)
|
if (i->flags & IMPORT_BTRFS_SUBVOL)
|
||||||
r = btrfs_subvol_make_fallback(AT_FDCWD, d, 0755);
|
r = btrfs_subvol_make_fallback(AT_FDCWD, d, 0755);
|
||||||
else
|
else
|
||||||
@ -283,9 +262,8 @@ static int tar_import_fork_tar(TarImport *i) {
|
|||||||
i->tree_fd = open(d, O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW);
|
i->tree_fd = open(d, O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW);
|
||||||
if (i->tree_fd < 0)
|
if (i->tree_fd < 0)
|
||||||
return log_error_errno(errno, "Failed to open '%s': %m", d);
|
return log_error_errno(errno, "Failed to open '%s': %m", d);
|
||||||
}
|
|
||||||
|
|
||||||
i->tar_fd = import_fork_tar_x(i->tree_fd, i->userns_fd, &i->tar_pid);
|
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,14 +26,12 @@
|
|||||||
#include "string-util.h"
|
#include "string-util.h"
|
||||||
#include "verbs.h"
|
#include "verbs.h"
|
||||||
|
|
||||||
static char *arg_image_root = NULL;
|
static const 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;
|
||||||
@ -292,9 +290,7 @@ 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(),
|
||||||
@ -319,8 +315,6 @@ 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[] = {
|
||||||
@ -337,8 +331,6 @@ 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 },
|
|
||||||
{}
|
{}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -362,10 +354,7 @@ static int parse_argv(int argc, char *argv[]) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case ARG_IMAGE_ROOT:
|
case ARG_IMAGE_ROOT:
|
||||||
r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image_root);
|
arg_image_root = optarg;
|
||||||
if (r < 0)
|
|
||||||
return r;
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ARG_READ_ONLY:
|
case ARG_READ_ONLY:
|
||||||
@ -441,14 +430,6 @@ 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;
|
||||||
|
|
||||||
@ -465,14 +446,8 @@ 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)
|
||||||
r = image_root_pick(arg_runtime_scope < 0 ? RUNTIME_SCOPE_SYSTEM : arg_runtime_scope, arg_class, /* runtime= */ false, &arg_image_root);
|
arg_image_root = image_root_to_string(arg_class);
|
||||||
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,28 +45,19 @@ 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),
|
||||||
root) < 0)
|
image_root_to_string(class)) < 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);
|
||||||
@ -1023,8 +1014,6 @@ 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"
|
||||||
@ -1066,8 +1055,6 @@ 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[] = {
|
||||||
@ -1086,8 +1073,6 @@ 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 },
|
|
||||||
{}
|
{}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1213,14 +1198,6 @@ 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;
|
||||||
|
|
||||||
@ -1263,9 +1240,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, arg_runtime_scope, &bus);
|
r = bus_connect_transport(arg_transport, arg_host, RUNTIME_SCOPE_SYSTEM, &bus);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return bus_log_connect_error(r, arg_transport, arg_runtime_scope);
|
return bus_log_connect_error(r, arg_transport, RUNTIME_SCOPE_SYSTEM);
|
||||||
|
|
||||||
(void) sd_bus_set_allow_interactive_authorization(bus, arg_ask_password);
|
(void) sd_bus_set_allow_interactive_authorization(bus, arg_ask_password);
|
||||||
|
|
||||||
|
|||||||
@ -32,7 +32,6 @@
|
|||||||
#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"
|
||||||
@ -101,8 +100,7 @@ typedef struct Transfer {
|
|||||||
|
|
||||||
typedef struct Manager {
|
typedef struct Manager {
|
||||||
sd_event *event;
|
sd_event *event;
|
||||||
sd_bus *api_bus;
|
sd_bus *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;
|
||||||
@ -115,7 +113,7 @@ typedef struct Manager {
|
|||||||
bool use_btrfs_subvol;
|
bool use_btrfs_subvol;
|
||||||
bool use_btrfs_quota;
|
bool use_btrfs_quota;
|
||||||
|
|
||||||
RuntimeScope runtime_scope;
|
RuntimeScope runtime_scope; /* for now: always RUNTIME_SCOPE_SYSTEM */
|
||||||
} Manager;
|
} Manager;
|
||||||
|
|
||||||
#define TRANSFERS_MAX 64
|
#define TRANSFERS_MAX 64
|
||||||
@ -225,7 +223,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->api_bus,
|
t->manager->bus,
|
||||||
t->object_path,
|
t->object_path,
|
||||||
"org.freedesktop.import1.Transfer",
|
"org.freedesktop.import1.Transfer",
|
||||||
"LogMessage",
|
"LogMessage",
|
||||||
@ -256,7 +254,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->api_bus,
|
t->manager->bus,
|
||||||
t->object_path,
|
t->object_path,
|
||||||
"org.freedesktop.import1.Transfer",
|
"org.freedesktop.import1.Transfer",
|
||||||
"ProgressUpdate",
|
"ProgressUpdate",
|
||||||
@ -337,7 +335,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->api_bus,
|
t->manager->bus,
|
||||||
"/org/freedesktop/import1",
|
"/org/freedesktop/import1",
|
||||||
"org.freedesktop.import1.Manager",
|
"org.freedesktop.import1.Manager",
|
||||||
"TransferRemoved",
|
"TransferRemoved",
|
||||||
@ -445,7 +443,6 @@ 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= */
|
||||||
@ -527,8 +524,6 @@ 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);
|
||||||
@ -607,7 +602,7 @@ static int transfer_start(Transfer *t) {
|
|||||||
return r;
|
return r;
|
||||||
|
|
||||||
r = sd_bus_emit_signal(
|
r = sd_bus_emit_signal(
|
||||||
t->manager->api_bus,
|
t->manager->bus,
|
||||||
"/org/freedesktop/import1",
|
"/org/freedesktop/import1",
|
||||||
"org.freedesktop.import1.Manager",
|
"org.freedesktop.import1.Manager",
|
||||||
"TransferNew",
|
"TransferNew",
|
||||||
@ -635,8 +630,7 @@ static Manager *manager_unref(Manager *m) {
|
|||||||
|
|
||||||
hashmap_free(m->polkit_registry);
|
hashmap_free(m->polkit_registry);
|
||||||
|
|
||||||
m->api_bus = sd_bus_flush_close_unref(m->api_bus);
|
m->bus = sd_bus_flush_close_unref(m->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);
|
||||||
@ -687,7 +681,7 @@ static int manager_on_notify(sd_event_source *s, int fd, uint32_t revents, void
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int manager_new(RuntimeScope scope, Manager **ret) {
|
static int manager_new(Manager **ret) {
|
||||||
_cleanup_(manager_unrefp) Manager *m = NULL;
|
_cleanup_(manager_unrefp) Manager *m = NULL;
|
||||||
int r;
|
int r;
|
||||||
|
|
||||||
@ -700,7 +694,7 @@ static int manager_new(RuntimeScope scope, Manager **ret) {
|
|||||||
*m = (Manager) {
|
*m = (Manager) {
|
||||||
.use_btrfs_subvol = true,
|
.use_btrfs_subvol = true,
|
||||||
.use_btrfs_quota = true,
|
.use_btrfs_quota = true,
|
||||||
.runtime_scope = scope,
|
.runtime_scope = RUNTIME_SCOPE_SYSTEM,
|
||||||
};
|
};
|
||||||
|
|
||||||
r = sd_event_default(&m->event);
|
r = sd_event_default(&m->event);
|
||||||
@ -763,7 +757,6 @@ static int method_import_tar_or_raw(sd_bus_message *msg, void *userdata, sd_bus_
|
|||||||
|
|
||||||
assert(msg);
|
assert(msg);
|
||||||
|
|
||||||
if (m->runtime_scope != RUNTIME_SCOPE_USER) {
|
|
||||||
r = bus_verify_polkit_async(
|
r = bus_verify_polkit_async(
|
||||||
msg,
|
msg,
|
||||||
"org.freedesktop.import1.import",
|
"org.freedesktop.import1.import",
|
||||||
@ -774,7 +767,6 @@ static int method_import_tar_or_raw(sd_bus_message *msg, void *userdata, sd_bus_
|
|||||||
return r;
|
return r;
|
||||||
if (r == 0)
|
if (r == 0)
|
||||||
return 1; /* Will call us back */
|
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;
|
||||||
@ -866,7 +858,6 @@ static int method_import_fs(sd_bus_message *msg, void *userdata, sd_bus_error *e
|
|||||||
|
|
||||||
assert(msg);
|
assert(msg);
|
||||||
|
|
||||||
if (m->runtime_scope != RUNTIME_SCOPE_USER) {
|
|
||||||
r = bus_verify_polkit_async(
|
r = bus_verify_polkit_async(
|
||||||
msg,
|
msg,
|
||||||
"org.freedesktop.import1.import",
|
"org.freedesktop.import1.import",
|
||||||
@ -877,7 +868,6 @@ static int method_import_fs(sd_bus_message *msg, void *userdata, sd_bus_error *e
|
|||||||
return r;
|
return r;
|
||||||
if (r == 0)
|
if (r == 0)
|
||||||
return 1; /* Will call us back */
|
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;
|
||||||
@ -966,7 +956,6 @@ static int method_export_tar_or_raw(sd_bus_message *msg, void *userdata, sd_bus_
|
|||||||
|
|
||||||
assert(msg);
|
assert(msg);
|
||||||
|
|
||||||
if (m->runtime_scope != RUNTIME_SCOPE_USER) {
|
|
||||||
r = bus_verify_polkit_async(
|
r = bus_verify_polkit_async(
|
||||||
msg,
|
msg,
|
||||||
"org.freedesktop.import1.export",
|
"org.freedesktop.import1.export",
|
||||||
@ -977,7 +966,6 @@ static int method_export_tar_or_raw(sd_bus_message *msg, void *userdata, sd_bus_
|
|||||||
return r;
|
return r;
|
||||||
if (r == 0)
|
if (r == 0)
|
||||||
return 1; /* Will call us back */
|
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;
|
||||||
@ -1066,7 +1054,6 @@ static int method_pull_tar_or_raw(sd_bus_message *msg, void *userdata, sd_bus_er
|
|||||||
|
|
||||||
assert(msg);
|
assert(msg);
|
||||||
|
|
||||||
if (m->runtime_scope != RUNTIME_SCOPE_USER) {
|
|
||||||
r = bus_verify_polkit_async(
|
r = bus_verify_polkit_async(
|
||||||
msg,
|
msg,
|
||||||
"org.freedesktop.import1.pull",
|
"org.freedesktop.import1.pull",
|
||||||
@ -1077,7 +1064,6 @@ static int method_pull_tar_or_raw(sd_bus_message *msg, void *userdata, sd_bus_er
|
|||||||
return r;
|
return r;
|
||||||
if (r == 0)
|
if (r == 0)
|
||||||
return 1; /* Will call us back */
|
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;
|
||||||
@ -1716,43 +1702,28 @@ static int manager_connect_bus(Manager *m) {
|
|||||||
|
|
||||||
assert(m);
|
assert(m);
|
||||||
assert(m->event);
|
assert(m->event);
|
||||||
assert(!m->system_bus);
|
assert(!m->bus);
|
||||||
assert(!m->api_bus);
|
|
||||||
|
|
||||||
r = bus_open_system_watch_bind(&m->system_bus);
|
r = bus_open_system_watch_bind(&m->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 = sd_bus_attach_event(m->system_bus, m->event, 0);
|
r = bus_add_implementation(m->bus, &manager_object, m);
|
||||||
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->api_bus);
|
r = bus_log_control_api_register(m->bus);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return r;
|
return r;
|
||||||
|
|
||||||
r = sd_bus_request_name_async(m->api_bus, NULL, "org.freedesktop.import1", 0, NULL, NULL);
|
r = sd_bus_request_name_async(m->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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1896,10 +1867,9 @@ 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));
|
||||||
|
|
||||||
if (m->runtime_scope != RUNTIME_SCOPE_USER) {
|
|
||||||
r = varlink_verify_polkit_async(
|
r = varlink_verify_polkit_async(
|
||||||
link,
|
link,
|
||||||
m->system_bus,
|
m->bus,
|
||||||
"org.freedesktop.import1.pull",
|
"org.freedesktop.import1.pull",
|
||||||
(const char**) STRV_MAKE(
|
(const char**) STRV_MAKE(
|
||||||
"remote", p.remote,
|
"remote", p.remote,
|
||||||
@ -1910,7 +1880,6 @@ static int vl_method_pull(sd_varlink *link, sd_json_variant *parameters, sd_varl
|
|||||||
&m->polkit_registry);
|
&m->polkit_registry);
|
||||||
if (r <= 0)
|
if (r <= 0)
|
||||||
return r;
|
return r;
|
||||||
}
|
|
||||||
|
|
||||||
_cleanup_(transfer_unrefp) Transfer *t = NULL;
|
_cleanup_(transfer_unrefp) Transfer *t = NULL;
|
||||||
|
|
||||||
@ -1969,10 +1938,8 @@ 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(
|
r = varlink_server_new(&m->varlink_server,
|
||||||
&m->varlink_server,
|
SD_VARLINK_SERVER_ACCOUNT_UID|SD_VARLINK_SERVER_INHERIT_USERDATA,
|
||||||
(m->runtime_scope != RUNTIME_SCOPE_USER ? SD_VARLINK_SERVER_ACCOUNT_UID : 0)|
|
|
||||||
SD_VARLINK_SERVER_INHERIT_USERDATA,
|
|
||||||
m);
|
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");
|
||||||
@ -2002,12 +1969,7 @@ 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) {
|
||||||
_cleanup_free_ char *socket_path = NULL;
|
r = sd_varlink_server_listen_address(m->varlink_server, "/run/systemd/io.systemd.Import", 0666);
|
||||||
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");
|
||||||
}
|
}
|
||||||
@ -2047,7 +2009,6 @@ 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();
|
||||||
@ -2056,7 +2017,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),
|
||||||
&scope,
|
/* runtime_scope= */ NULL,
|
||||||
argc, argv);
|
argc, argv);
|
||||||
if (r <= 0)
|
if (r <= 0)
|
||||||
return r;
|
return r;
|
||||||
@ -2065,7 +2026,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(scope, &m);
|
r = manager_new(&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");
|
||||||
|
|
||||||
@ -2085,7 +2046,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->api_bus,
|
m->bus,
|
||||||
"org.freedesktop.import1",
|
"org.freedesktop.import1",
|
||||||
DEFAULT_EXIT_USEC,
|
DEFAULT_EXIT_USEC,
|
||||||
manager_check_idle,
|
manager_check_idle,
|
||||||
|
|||||||
@ -111,9 +111,6 @@ 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)
|
||||||
|
|
||||||
|
|||||||
@ -1,13 +0,0 @@
|
|||||||
# 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,10 +9,11 @@
|
|||||||
#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"
|
||||||
@ -25,7 +26,6 @@
|
|||||||
#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,7 +64,6 @@ 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) {
|
||||||
@ -81,10 +80,7 @@ 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);
|
||||||
|
|
||||||
if (i->temp_path) {
|
rm_rf_subvolume_and_free(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);
|
||||||
@ -94,7 +90,6 @@ 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);
|
||||||
}
|
}
|
||||||
@ -143,7 +138,6 @@ 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;
|
||||||
@ -239,9 +233,6 @@ 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);
|
||||||
@ -253,48 +244,6 @@ 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 (FLAGS_SET(i->flags, IMPORT_FOREIGN_UID)) {
|
|
||||||
/* Copy in userns */
|
|
||||||
|
|
||||||
r = import_make_foreign_userns(&i->userns_fd);
|
|
||||||
if (r < 0)
|
|
||||||
return r;
|
|
||||||
|
|
||||||
/* Usually, tar_pull_job_on_open_disk_tar() would allocate ->tree_fd for us, but if
|
|
||||||
* already downloaded the image before, and are just making a copy of the original
|
|
||||||
* download, we need to open ->tree_fd now */
|
|
||||||
if (i->tree_fd < 0) {
|
|
||||||
_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)
|
if (i->flags & IMPORT_BTRFS_SUBVOL)
|
||||||
r = btrfs_subvol_snapshot_at(
|
r = btrfs_subvol_snapshot_at(
|
||||||
AT_FDCWD, i->final_path,
|
AT_FDCWD, i->final_path,
|
||||||
@ -306,8 +255,7 @@ static int tar_pull_make_local_copy(TarPull *i) {
|
|||||||
else
|
else
|
||||||
r = copy_tree(i->final_path, t, UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_HARDLINKS, NULL, NULL);
|
r = copy_tree(i->final_path, t, UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_HARDLINKS, NULL, NULL);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return log_error_errno(r, "Failed to create original download image: %m");
|
return log_error_errno(r, "Failed to create local image: %m");
|
||||||
}
|
|
||||||
|
|
||||||
source = t;
|
source = t;
|
||||||
} else
|
} else
|
||||||
@ -316,8 +264,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|INSTALL_GRACEFUL : 0) |
|
(i->flags & IMPORT_READ_ONLY ? INSTALL_READ_ONLY : 0) |
|
||||||
(i->flags & IMPORT_SYNC ? INSTALL_SYNCFS|INSTALL_GRACEFUL : 0));
|
(i->flags & IMPORT_SYNC ? INSTALL_SYNCFS : 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);
|
||||||
|
|
||||||
@ -479,8 +427,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|INSTALL_GRACEFUL : 0) |
|
(i->flags & IMPORT_READ_ONLY ? INSTALL_READ_ONLY : 0) |
|
||||||
(i->flags & IMPORT_SYNC ? INSTALL_SYNCFS|INSTALL_GRACEFUL : 0));
|
(i->flags & IMPORT_SYNC ? INSTALL_SYNCFS : 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;
|
||||||
@ -505,8 +453,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|INSTALL_GRACEFUL : 0) |
|
(i->flags & IMPORT_PULL_KEEP_DOWNLOAD ? INSTALL_READ_ONLY : 0) |
|
||||||
(i->flags & IMPORT_SYNC ? INSTALL_SYNCFS|INSTALL_GRACEFUL : 0));
|
(i->flags & IMPORT_SYNC ? INSTALL_SYNCFS : 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;
|
||||||
@ -532,7 +480,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_GRACEFUL|
|
INSTALL_READ_ONLY|
|
||||||
(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);
|
||||||
@ -589,20 +537,6 @@ 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 (FLAGS_SET(i->flags, IMPORT_FOREIGN_UID)) {
|
|
||||||
r = import_make_foreign_userns(&i->userns_fd);
|
|
||||||
if (r < 0)
|
|
||||||
return r;
|
|
||||||
|
|
||||||
_cleanup_close_ int directory_fd = -EBADF;
|
|
||||||
r = mountfsd_make_directory(where, /* flags= */ 0, &directory_fd);
|
|
||||||
if (r < 0)
|
|
||||||
return r;
|
|
||||||
|
|
||||||
r = mountfsd_mount_directory_fd(directory_fd, i->userns_fd, DISSECT_IMAGE_FOREIGN_UID, &i->tree_fd);
|
|
||||||
if (r < 0)
|
|
||||||
return r;
|
|
||||||
} else {
|
|
||||||
if (i->flags & IMPORT_BTRFS_SUBVOL)
|
if (i->flags & IMPORT_BTRFS_SUBVOL)
|
||||||
r = btrfs_subvol_make_fallback(AT_FDCWD, where, 0755);
|
r = btrfs_subvol_make_fallback(AT_FDCWD, where, 0755);
|
||||||
else
|
else
|
||||||
@ -612,7 +546,6 @@ static int tar_pull_job_on_open_disk_tar(PullJob *j) {
|
|||||||
r = 0;
|
r = 0;
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return log_error_errno(r, "Failed to create directory/subvolume %s: %m", where);
|
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 (r > 0 && (i->flags & IMPORT_BTRFS_QUOTA)) { /* actually btrfs subvol */
|
||||||
if (!(i->flags & IMPORT_DIRECT))
|
if (!(i->flags & IMPORT_DIRECT))
|
||||||
(void) import_assign_pool_quota_and_warn(i->image_root);
|
(void) import_assign_pool_quota_and_warn(i->image_root);
|
||||||
@ -622,9 +555,8 @@ static int tar_pull_job_on_open_disk_tar(PullJob *j) {
|
|||||||
i->tree_fd = open(where, O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW);
|
i->tree_fd = open(where, O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW);
|
||||||
if (i->tree_fd < 0)
|
if (i->tree_fd < 0)
|
||||||
return log_error_errno(errno, "Failed to open '%s': %m", where);
|
return log_error_errno(errno, "Failed to open '%s': %m", where);
|
||||||
}
|
|
||||||
|
|
||||||
j->disk_fd = import_fork_tar_x(i->tree_fd, i->userns_fd, &i->tar_pid);
|
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 char *arg_image_root = NULL;
|
static const 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,7 +37,6 @@ 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;
|
||||||
@ -276,9 +275,7 @@ 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(),
|
||||||
@ -309,8 +306,6 @@ 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[] = {
|
||||||
@ -333,8 +328,6 @@ 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 },
|
|
||||||
{}
|
{}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -359,10 +352,7 @@ static int parse_argv(int argc, char *argv[]) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case ARG_IMAGE_ROOT:
|
case ARG_IMAGE_ROOT:
|
||||||
r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image_root);
|
arg_image_root = optarg;
|
||||||
if (r < 0)
|
|
||||||
return r;
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ARG_VERIFY: {
|
case ARG_VERIFY: {
|
||||||
@ -518,14 +508,6 @@ 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;
|
||||||
|
|
||||||
@ -545,11 +527,8 @@ 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)
|
||||||
r = image_root_pick(arg_runtime_scope < 0 ? RUNTIME_SCOPE_SYSTEM : arg_runtime_scope, arg_class, /* runtime= */ false, &arg_image_root);
|
arg_image_root = image_root_to_string(arg_class);
|
||||||
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)
|
||||||
@ -560,9 +539,6 @@ 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,7 +9,6 @@
|
|||||||
#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"
|
||||||
@ -270,15 +269,18 @@ _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) {
|
||||||
r = setenvf("LISTEN_PIDFDID", /* overwrite= */ true, "%" PRIu64, pidfdid);
|
char spidfdid[DECIMAL_STR_MAX(uint64_t)+1];
|
||||||
if (r < 0) {
|
xsprintf(spidfdid, "%" PRIu64, pidfdid);
|
||||||
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, /* overwrite= */ true) < 0) {
|
if (setenv(*a, *b, /* override= */ 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,6 +142,7 @@ 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) {
|
||||||
@ -159,17 +160,19 @@ static int start_one_worker(Manager *m) {
|
|||||||
safe_close(m->listen_fd);
|
safe_close(m->listen_fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
r = setenvf("LISTEN_PID", /* overwrite= */ true, PID_FMT, pid);
|
xsprintf(pids, PID_FMT, pid);
|
||||||
if (r < 0) {
|
if (setenv("LISTEN_PID", pids, 1) < 0) {
|
||||||
log_error_errno(r, "Failed to set $LISTEN_PID: %m");
|
log_error_errno(errno, "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) {
|
||||||
r = setenvf("LISTEN_PIDFDID", /* overwrite= */ true, "%" PRIu64, pidfdid);
|
char pidfdids[DECIMAL_STR_MAX(uint64_t)];
|
||||||
if (r < 0) {
|
|
||||||
log_error_errno(r, "Failed to set $LISTEN_PIDFDID: %m");
|
xsprintf(pidfdids, "%" PRIu64, pidfdid);
|
||||||
|
if (setenv("LISTEN_PIDFDID", pidfdids, 1) < 0) {
|
||||||
|
log_error_errno(errno, "Failed to set $LISTEN_PIDFDID: %m");
|
||||||
_exit(EXIT_FAILURE);
|
_exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -427,8 +427,6 @@ 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"
|
||||||
@ -4019,7 +4017,6 @@ 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;
|
||||||
@ -6010,7 +6007,7 @@ static int run(int argc, char *argv[]) {
|
|||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
userns_fd = nsresource_allocate_userns(userns_name, NSRESOURCE_UIDS_64K); /* allocate 64K UIDs */
|
userns_fd = nsresource_allocate_userns(userns_name, UINT64_C(0x10000));
|
||||||
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;
|
||||||
@ -6022,7 +6019,7 @@ static int run(int argc, char *argv[]) {
|
|||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
arg_uid_range = NSRESOURCE_UIDS_64K;
|
arg_uid_range = UINT32_C(0x10000);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (arg_directory) {
|
if (arg_directory) {
|
||||||
|
|||||||
@ -178,6 +178,7 @@ 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) {
|
||||||
@ -195,17 +196,19 @@ static int start_one_worker(Manager *m) {
|
|||||||
safe_close(m->listen_fd);
|
safe_close(m->listen_fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
r = setenvf("LISTEN_PID", /* overwrite= */ true, PID_FMT, pid);
|
xsprintf(pids, PID_FMT, pid);
|
||||||
if (r < 0) {
|
if (setenv("LISTEN_PID", pids, 1) < 0) {
|
||||||
log_error_errno(r, "Failed to set $LISTEN_PID: %m");
|
log_error_errno(errno, "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) {
|
||||||
r = setenvf("LISTEN_PIDFDID", /* overwrite= */ true, "%" PRIu64, pidfdid);
|
char pidfdids[DECIMAL_STR_MAX(uint64_t)];
|
||||||
if (r < 0) {
|
|
||||||
log_error_errno(r, "Failed to set $LISTEN_PIDFDID: %m");
|
xsprintf(pidfdids, "%" PRIu64, pidfdid);
|
||||||
|
if (setenv("LISTEN_PIDFDID", pidfdids, 1) < 0) {
|
||||||
|
log_error_errno(errno, "Failed to set $LISTEN_PIDFDID: %m");
|
||||||
_exit(EXIT_FAILURE);
|
_exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -657,10 +657,7 @@ static int hardlink_context_setup(
|
|||||||
return -errno;
|
return -errno;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (to)
|
|
||||||
r = tempfn_random_child(to, "hardlink", &c->subdir);
|
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;
|
||||||
|
|
||||||
@ -1130,6 +1127,7 @@ 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;
|
||||||
@ -1259,7 +1257,7 @@ static int fd_copy_directory(
|
|||||||
}
|
}
|
||||||
|
|
||||||
finish:
|
finish:
|
||||||
if (FLAGS_SET(copy_flags, COPY_MERGE_APPLY_STAT) || !exists) {
|
if (!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)
|
||||||
@ -1347,10 +1345,6 @@ 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");
|
||||||
@ -1388,6 +1382,7 @@ 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,7 +33,6 @@ 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_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(image_root, ImageClass);
|
DEFINE_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_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(image_root_runtime, ImageClass);
|
DEFINE_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,45 +2022,3 @@ 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,7 +95,8 @@ 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);
|
||||||
|
|
||||||
int image_root_pick(RuntimeScope scope, ImageClass c, bool runtime, char **ret);
|
const char* image_root_to_string(ImageClass c) _const_;
|
||||||
|
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,17 +4848,14 @@ int mountfsd_mount_image(
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
int mountfsd_mount_directory_fd(
|
int mountfsd_mount_directory(
|
||||||
int directory_fd,
|
const char *path,
|
||||||
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));
|
||||||
|
|
||||||
@ -4875,6 +4872,10 @@ int mountfsd_mount_directory_fd(
|
|||||||
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");
|
||||||
@ -4918,103 +4919,3 @@ int mountfsd_mount_directory_fd(
|
|||||||
*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,8 +258,4 @@ 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,11 +95,6 @@ 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) {
|
||||||
@ -123,7 +118,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 (need_opath(flags)) {
|
if ((flags & (INSTALL_FSYNC|INSTALL_FSYNC_FULL|INSTALL_SYNCFS|INSTALL_READ_ONLY)) != 0) {
|
||||||
_cleanup_close_ int pfd = -EBADF;
|
_cleanup_close_ int pfd = -EBADF;
|
||||||
struct stat st;
|
struct stat st;
|
||||||
|
|
||||||
@ -142,31 +137,23 @@ 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)
|
||||||
if (!FLAGS_SET(flags, INSTALL_GRACEFUL))
|
return regfd;
|
||||||
return log_debug_errno(regfd, "Failed to open referenced inode: %m");
|
|
||||||
|
|
||||||
log_debug_errno(regfd, "Failed to open referenced inode, ignoring: %m");
|
if ((flags & (INSTALL_FSYNC_FULL|INSTALL_SYNCFS)) != 0) {
|
||||||
} else {
|
/* If this is just a regular file (as oppose to a fully populated directory)
|
||||||
if ((flags & (INSTALL_FSYNC_FULL|INSTALL_SYNCFS)) != 0)
|
* let's downgrade INSTALL_SYNCFS to INSTALL_FSYNC_FULL, after all this is
|
||||||
/* If this is just a regular file (as opposed to a fully populated
|
* going to be a single inode we install */
|
||||||
* directory) let's downgrade INSTALL_SYNCFS to INSTALL_FSYNC_FULL,
|
|
||||||
* after all this is going to be a single inode we install */
|
|
||||||
r = fsync_full(regfd);
|
r = fsync_full(regfd);
|
||||||
else if (flags & INSTALL_FSYNC)
|
if (r < 0)
|
||||||
r = RET_NERRNO(fsync(regfd));
|
return r;
|
||||||
else
|
} else if (flags & INSTALL_FSYNC) {
|
||||||
r = 0;
|
if (fsync(regfd) < 0)
|
||||||
if (r < 0) {
|
return -errno;
|
||||||
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)
|
if (flags & INSTALL_READ_ONLY)
|
||||||
rofd = TAKE_FD(regfd);
|
rofd = TAKE_FD(regfd);
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -175,30 +162,23 @@ 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)
|
||||||
if (!FLAGS_SET(flags, INSTALL_GRACEFUL))
|
return dfd;
|
||||||
return log_debug_errno(dfd, "Failed to open referenced inode: %m");
|
|
||||||
|
|
||||||
log_debug_errno(dfd, "Failed to open referenced inode, ignoring: %m");
|
if (flags & INSTALL_SYNCFS) {
|
||||||
} else {
|
if (syncfs(dfd) < 0)
|
||||||
if (flags & INSTALL_SYNCFS)
|
return -errno;
|
||||||
r = RET_NERRNO(syncfs(dfd));
|
} else if (flags & INSTALL_FSYNC_FULL) {
|
||||||
else if (flags & INSTALL_FSYNC_FULL)
|
|
||||||
r = fsync_full(dfd);
|
r = fsync_full(dfd);
|
||||||
else if (flags & INSTALL_FSYNC)
|
if (r < 0)
|
||||||
r = RET_NERRNO(fsync(dfd));
|
return r;
|
||||||
else
|
} else if (flags & INSTALL_FSYNC) {
|
||||||
r = 0;
|
if (fsync(dfd) < 0)
|
||||||
if (r < 0) {
|
return -errno;
|
||||||
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)
|
if (flags & INSTALL_READ_ONLY)
|
||||||
rofd = TAKE_FD(dfd);
|
rofd = TAKE_FD(dfd);
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -210,12 +190,8 @@ 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)
|
||||||
if (!FLAGS_SET(flags, INSTALL_GRACEFUL))
|
return r;
|
||||||
return log_debug_errno(r, "Failed to sync source inode: %m");
|
|
||||||
|
|
||||||
log_debug_errno(r, "Failed to sync source inode, ignoring: %m");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@ -278,12 +254,8 @@ 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)
|
||||||
if (!FLAGS_SET(flags, INSTALL_GRACEFUL))
|
return r;
|
||||||
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) {
|
||||||
@ -291,12 +263,8 @@ 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)
|
||||||
if (!FLAGS_SET(flags, INSTALL_GRACEFUL))
|
return r;
|
||||||
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,7 +9,6 @@ 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);
|
||||||
|
|||||||
@ -1,31 +0,0 @@
|
|||||||
/* 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;
|
|
||||||
}
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
/* 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 machine: %m");
|
return log_error_errno(r, "Failed to open /etc/passwd of container: %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 machine: %m");
|
return log_error_errno(r, "Failed to iterate through /etc/passwd of container: %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 machine: %m");
|
return log_error_errno(r, "Failed to open /etc/group of container: %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 machine: %m");
|
return log_error_errno(r, "Failed to iterate through /etc/group of container: %m");
|
||||||
if (r == 0)
|
if (r == 0)
|
||||||
return 0; /* no collision */
|
return 0; /* no collision */
|
||||||
|
|
||||||
@ -93,7 +93,6 @@ 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) {
|
||||||
|
|
||||||
@ -115,16 +114,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 machine.", u->user_name);
|
"Sorry, the user '%s' already exists in the container.", 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 machine.", g->group_name);
|
"Sorry, the group '%s' already exists in the container.", g->group_name);
|
||||||
|
|
||||||
h = path_join(home_mount_directory, u->user_name);
|
h = path_join("/run/host/home/", u->user_name);
|
||||||
if (!h)
|
if (!h)
|
||||||
return log_oom();
|
return log_oom();
|
||||||
|
|
||||||
@ -149,7 +148,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 machine user record: %m");
|
return log_error_errno(r, "Failed to build container user record: %m");
|
||||||
|
|
||||||
r = group_record_build(
|
r = group_record_build(
|
||||||
&converted_group,
|
&converted_group,
|
||||||
@ -159,7 +158,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 machine group record: %m");
|
return log_error_errno(r, "Failed to build container 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);
|
||||||
@ -176,7 +175,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 machine detected, can't map user.",
|
"No suitable available UID in range " UID_FMT "…" UID_FMT " in container 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);
|
||||||
@ -211,7 +210,6 @@ 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;
|
||||||
@ -221,7 +219,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 machine, allocates a UID/GID for it, and updates the custom mount table,
|
* for it to stick in the container, 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
|
||||||
@ -266,13 +264,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 machine. This only
|
/* We want to synthesize exactly one user + group from the host into the container. 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 machine. */
|
* probably already exists in the container. */
|
||||||
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.");
|
||||||
@ -281,14 +279,7 @@ int machine_bind_user_prepare(
|
|||||||
if (r < 0)
|
if (r < 0)
|
||||||
return r;
|
return r;
|
||||||
|
|
||||||
r = convert_user(
|
r = convert_user(directory, u, g, current_uid, bind_user_shell, bind_user_shell_copy, &cu, &cg);
|
||||||
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,5 +27,4 @@ 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,7 +119,6 @@ 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,10 +3,6 @@
|
|||||||
|
|
||||||
#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,7 +6,6 @@
|
|||||||
|
|
||||||
#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,7 +7,6 @@
|
|||||||
#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"
|
||||||
|
|
||||||
void runtime_mount_done(RuntimeMount *mount) {
|
static void runtime_mount_done(RuntimeMount *mount) {
|
||||||
assert(mount);
|
assert(mount);
|
||||||
|
|
||||||
mount->source = mfree(mount->source);
|
mount->source = mfree(mount->source);
|
||||||
@ -24,11 +24,7 @@ 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 = {
|
_cleanup_(runtime_mount_done) RuntimeMount mount = { .read_only = read_only };
|
||||||
.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,9 +6,7 @@
|
|||||||
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 {
|
||||||
@ -16,6 +14,5 @@ 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,13 +35,11 @@
|
|||||||
#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"
|
||||||
@ -70,8 +68,6 @@
|
|||||||
#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"
|
||||||
@ -140,9 +136,6 @@ 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);
|
||||||
@ -162,8 +155,6 @@ 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;
|
||||||
@ -224,9 +215,6 @@ 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"
|
||||||
@ -301,8 +289,6 @@ 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[] = {
|
||||||
@ -352,8 +338,6 @@ 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 },
|
|
||||||
{}
|
{}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -691,30 +675,6 @@ 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;
|
||||||
|
|
||||||
@ -722,12 +682,6 @@ 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)
|
||||||
@ -1405,9 +1359,7 @@ 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,
|
||||||
uid_t source_uid,
|
bool uidmap,
|
||||||
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,
|
||||||
@ -1445,20 +1397,20 @@ static int start_virtiofsd(
|
|||||||
if (!argv)
|
if (!argv)
|
||||||
return log_oom();
|
return log_oom();
|
||||||
|
|
||||||
if (source_uid != UID_INVALID && target_uid != UID_INVALID && uid_range != UID_INVALID) {
|
if (uidmap && arg_uid_shift != UID_INVALID) {
|
||||||
r = strv_extend(&argv, "--translate-uid");
|
r = strv_extend(&argv, "--uid-map");
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return log_oom();
|
return log_oom();
|
||||||
|
|
||||||
r = strv_extendf(&argv, "map:" UID_FMT ":" UID_FMT ":" UID_FMT, target_uid, source_uid, uid_range);
|
r = strv_extendf(&argv, ":0:" UID_FMT ":" UID_FMT ":", arg_uid_shift, arg_uid_range);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return log_oom();
|
return log_oom();
|
||||||
|
|
||||||
r = strv_extend(&argv, "--translate-gid");
|
r = strv_extend(&argv, "--gid-map");
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return log_oom();
|
return log_oom();
|
||||||
|
|
||||||
r = strv_extendf(&argv, "map:" GID_FMT ":" GID_FMT ":" GID_FMT, target_uid, source_uid, uid_range);
|
r = strv_extendf(&argv, ":0:" GID_FMT ":" GID_FMT ":", arg_uid_shift, arg_uid_range);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return log_oom();
|
return log_oom();
|
||||||
}
|
}
|
||||||
@ -1473,65 +1425,6 @@ 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=")
|
||||||
@ -1833,21 +1726,6 @@ 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, "
|
||||||
@ -2299,9 +2177,7 @@ 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,
|
||||||
/* source_uid= */ arg_uid_shift,
|
/* uidmap= */ true,
|
||||||
/* target_uid= */ 0,
|
|
||||||
/* uid_range= */ arg_uid_range,
|
|
||||||
runtime_dir,
|
runtime_dir,
|
||||||
sd_socket_activate,
|
sd_socket_activate,
|
||||||
&listen_address,
|
&listen_address,
|
||||||
@ -2391,9 +2267,7 @@ 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,
|
||||||
/* source_uid= */ mount->source_uid,
|
/* uidmap= */ false,
|
||||||
/* target_uid= */ mount->target_uid,
|
|
||||||
/* uid_range= */ 1U,
|
|
||||||
runtime_dir,
|
runtime_dir,
|
||||||
sd_socket_activate,
|
sd_socket_activate,
|
||||||
&listen_address,
|
&listen_address,
|
||||||
@ -2567,7 +2441,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;
|
_cleanup_free_ char *listen_address = NULL, *cred = NULL;
|
||||||
|
|
||||||
if (!GREEDY_REALLOC(children, n_children + 1))
|
if (!GREEDY_REALLOC(children, n_children + 1))
|
||||||
return log_oom();
|
return log_oom();
|
||||||
@ -2585,7 +2459,11 @@ 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);
|
||||||
|
|
||||||
r = machine_credential_add(&arg_credentials, "journal.forward_to_socket", listen_address, SIZE_MAX);
|
cred = strjoin("journal.forward_to_socket:", listen_address);
|
||||||
|
if (!cred)
|
||||||
|
return log_oom();
|
||||||
|
|
||||||
|
r = machine_credential_set(&arg_credentials, cred);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
@ -2631,14 +2509,13 @@ 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_add(
|
r = machine_credential_set(
|
||||||
&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,16 +28,6 @@ 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' },
|
||||||
|
|||||||
@ -1,28 +0,0 @@
|
|||||||
# 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}}
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
# 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