creds: Add ImportCredential=
ImportCredential= takes a credential name and searches for a matching credential in all the credential stores we know about it. It supports globs which are expanded so that all matching credentials are loaded.
This commit is contained in:
@@ -44,8 +44,8 @@ static int help(void) {
|
||||
" --id=ID Query identifier (e.g. \"cryptsetup:/dev/sda5\")\n"
|
||||
" --keyname=NAME Kernel key name for caching passwords (e.g. \"cryptsetup\")\n"
|
||||
" --credential=NAME\n"
|
||||
" Credential name for LoadCredential=/SetCredential=\n"
|
||||
" credentials\n"
|
||||
" Credential name for ImportCredential=, LoadCredential= or\n"
|
||||
" SetCredential= credentials\n"
|
||||
" --timeout=SEC Timeout in seconds\n"
|
||||
" --echo=yes|no|masked\n"
|
||||
" Control whether to show password while typing (echo)\n"
|
||||
|
||||
+77
-32
@@ -26,6 +26,7 @@
|
||||
#include "io-util.h"
|
||||
#include "ioprio-util.h"
|
||||
#include "journal-file.h"
|
||||
#include "load-fragment.h"
|
||||
#include "memstream-util.h"
|
||||
#include "missing_ioprio.h"
|
||||
#include "mountpoint-util.h"
|
||||
@@ -928,6 +929,36 @@ static int property_get_load_credential(
|
||||
return sd_bus_message_close_container(reply);
|
||||
}
|
||||
|
||||
static int property_get_import_credential(
|
||||
sd_bus *bus,
|
||||
const char *path,
|
||||
const char *interface,
|
||||
const char *property,
|
||||
sd_bus_message *reply,
|
||||
void *userdata,
|
||||
sd_bus_error *error) {
|
||||
|
||||
ExecContext *c = ASSERT_PTR(userdata);
|
||||
const char *s;
|
||||
int r;
|
||||
|
||||
assert(bus);
|
||||
assert(property);
|
||||
assert(reply);
|
||||
|
||||
r = sd_bus_message_open_container(reply, 'a', "s");
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
SET_FOREACH(s, c->import_credentials) {
|
||||
r = sd_bus_message_append(reply, "s", s);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
return sd_bus_message_close_container(reply);
|
||||
}
|
||||
|
||||
static int property_get_root_hash(
|
||||
sd_bus *bus,
|
||||
const char *path,
|
||||
@@ -1281,6 +1312,7 @@ const sd_bus_vtable bus_exec_vtable[] = {
|
||||
SD_BUS_PROPERTY("SetCredentialEncrypted", "a(say)", property_get_set_credential, 0, SD_BUS_VTABLE_PROPERTY_CONST),
|
||||
SD_BUS_PROPERTY("LoadCredential", "a(ss)", property_get_load_credential, 0, SD_BUS_VTABLE_PROPERTY_CONST),
|
||||
SD_BUS_PROPERTY("LoadCredentialEncrypted", "a(ss)", property_get_load_credential, 0, SD_BUS_VTABLE_PROPERTY_CONST),
|
||||
SD_BUS_PROPERTY("ImportCredential", "as", property_get_import_credential, 0, SD_BUS_VTABLE_PROPERTY_CONST),
|
||||
SD_BUS_PROPERTY("SupplementaryGroups", "as", NULL, offsetof(ExecContext, supplementary_groups), SD_BUS_VTABLE_PROPERTY_CONST),
|
||||
SD_BUS_PROPERTY("PAMName", "s", NULL, offsetof(ExecContext, pam_name), SD_BUS_VTABLE_PROPERTY_CONST),
|
||||
SD_BUS_PROPERTY("ReadWritePaths", "as", NULL, offsetof(ExecContext, read_write_paths), SD_BUS_VTABLE_PROPERTY_CONST),
|
||||
@@ -2311,39 +2343,11 @@ int bus_exec_context_set_transient_property(
|
||||
isempty = false;
|
||||
|
||||
if (!UNIT_WRITE_FLAGS_NOOP(flags)) {
|
||||
_cleanup_free_ char *copy = NULL;
|
||||
ExecLoadCredential *old;
|
||||
bool encrypted = streq(name, "LoadCredentialEncrypted");
|
||||
|
||||
copy = strdup(source);
|
||||
if (!copy)
|
||||
return -ENOMEM;
|
||||
|
||||
old = hashmap_get(c->load_credentials, id);
|
||||
if (old) {
|
||||
free_and_replace(old->path, copy);
|
||||
old->encrypted = streq(name, "LoadCredentialEncrypted");
|
||||
} else {
|
||||
_cleanup_(exec_load_credential_freep) ExecLoadCredential *lc = NULL;
|
||||
|
||||
lc = new(ExecLoadCredential, 1);
|
||||
if (!lc)
|
||||
return -ENOMEM;
|
||||
|
||||
*lc = (ExecLoadCredential) {
|
||||
.id = strdup(id),
|
||||
.path = TAKE_PTR(copy),
|
||||
.encrypted = streq(name, "LoadCredentialEncrypted"),
|
||||
};
|
||||
|
||||
if (!lc->id)
|
||||
return -ENOMEM;
|
||||
|
||||
r = hashmap_ensure_put(&c->load_credentials, &exec_load_credential_hash_ops, lc->id, lc);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
TAKE_PTR(lc);
|
||||
}
|
||||
r = hashmap_put_credential(&c->load_credentials, id, source, encrypted);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
(void) unit_write_settingf(u, flags|UNIT_ESCAPE_SPECIFIERS, name, "%s=%s:%s", name, id, source);
|
||||
}
|
||||
@@ -2360,6 +2364,47 @@ int bus_exec_context_set_transient_property(
|
||||
|
||||
return 1;
|
||||
|
||||
} else if (streq(name, "ImportCredential")) {
|
||||
bool isempty = true;
|
||||
|
||||
r = sd_bus_message_enter_container(message, 'a', "s");
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
for (;;) {
|
||||
const char *s;
|
||||
|
||||
r = sd_bus_message_read(message, "s", &s);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0)
|
||||
break;
|
||||
|
||||
if (!filename_is_valid(s))
|
||||
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Credential name is invalid: %s", s);
|
||||
|
||||
isempty = false;
|
||||
|
||||
if (!UNIT_WRITE_FLAGS_NOOP(flags)) {
|
||||
r = set_put_strdup(&c->import_credentials, s);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
(void) unit_write_settingf(u, flags|UNIT_ESCAPE_SPECIFIERS, name, "%s=%s", name, s);
|
||||
}
|
||||
}
|
||||
|
||||
r = sd_bus_message_exit_container(message);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (!UNIT_WRITE_FLAGS_NOOP(flags) && isempty) {
|
||||
c->import_credentials = set_free(c->import_credentials);
|
||||
(void) unit_write_settingf(u, flags, name, "%s=", name);
|
||||
}
|
||||
|
||||
return 1;
|
||||
|
||||
} else if (streq(name, "SyslogLevel")) {
|
||||
int32_t level;
|
||||
|
||||
|
||||
+152
-26
@@ -1544,7 +1544,8 @@ bool exec_context_has_credentials(const ExecContext *context) {
|
||||
assert(context);
|
||||
|
||||
return !hashmap_isempty(context->set_credentials) ||
|
||||
!hashmap_isempty(context->load_credentials);
|
||||
!hashmap_isempty(context->load_credentials) ||
|
||||
!set_isempty(context->import_credentials);
|
||||
}
|
||||
|
||||
#if HAVE_SECCOMP
|
||||
@@ -2802,6 +2803,111 @@ static char **credential_search_path(const ExecParameters *params, CredentialSea
|
||||
return TAKE_PTR(l);
|
||||
}
|
||||
|
||||
static int maybe_decrypt_and_write_credential(
|
||||
int dir_fd,
|
||||
const char *id,
|
||||
bool encrypted,
|
||||
uid_t uid,
|
||||
bool ownership_ok,
|
||||
const char *data,
|
||||
size_t size,
|
||||
uint64_t *left) {
|
||||
|
||||
_cleanup_free_ void *plaintext = NULL;
|
||||
size_t add;
|
||||
int r;
|
||||
|
||||
if (encrypted) {
|
||||
size_t plaintext_size = 0;
|
||||
|
||||
r = decrypt_credential_and_warn(id, now(CLOCK_REALTIME), NULL, NULL, data, size,
|
||||
&plaintext, &plaintext_size);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
data = plaintext;
|
||||
size = plaintext_size;
|
||||
}
|
||||
|
||||
add = strlen(id) + size;
|
||||
if (add > *left)
|
||||
return -E2BIG;
|
||||
|
||||
r = write_credential(dir_fd, id, data, size, uid, ownership_ok);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to write credential '%s': %m", id);
|
||||
|
||||
*left -= add;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int load_credential_glob(
|
||||
const char *path,
|
||||
bool encrypted,
|
||||
char **search_path,
|
||||
ReadFullFileFlags flags,
|
||||
int write_dfd,
|
||||
uid_t uid,
|
||||
bool ownership_ok,
|
||||
uint64_t *left) {
|
||||
|
||||
int r;
|
||||
|
||||
STRV_FOREACH(d, search_path) {
|
||||
_cleanup_globfree_ glob_t pglob = {};
|
||||
_cleanup_free_ char *j = NULL;
|
||||
|
||||
j = path_join(*d, path);
|
||||
if (!j)
|
||||
return -ENOMEM;
|
||||
|
||||
r = safe_glob(j, 0, &pglob);
|
||||
if (r == -ENOENT)
|
||||
continue;
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
for (unsigned n = 0; n < pglob.gl_pathc; n++) {
|
||||
_cleanup_free_ char *fn = NULL;
|
||||
_cleanup_(erase_and_freep) char *data = NULL;
|
||||
size_t size;
|
||||
|
||||
/* path is absolute, hence pass AT_FDCWD as nop dir fd here */
|
||||
r = read_full_file_full(
|
||||
AT_FDCWD,
|
||||
pglob.gl_pathv[n],
|
||||
UINT64_MAX,
|
||||
encrypted ? CREDENTIAL_ENCRYPTED_SIZE_MAX : CREDENTIAL_SIZE_MAX,
|
||||
flags,
|
||||
NULL,
|
||||
&data, &size);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to read credential '%s': %m",
|
||||
pglob.gl_pathv[n]);
|
||||
|
||||
r = path_extract_filename(pglob.gl_pathv[n], &fn);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to extract filename from '%s': %m",
|
||||
pglob.gl_pathv[n]);
|
||||
|
||||
r = maybe_decrypt_and_write_credential(
|
||||
write_dfd,
|
||||
fn,
|
||||
encrypted,
|
||||
uid,
|
||||
ownership_ok,
|
||||
data, size,
|
||||
left);
|
||||
if (r == -EEXIST)
|
||||
continue;
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int load_credential(
|
||||
const ExecContext *context,
|
||||
const ExecParameters *params,
|
||||
@@ -2821,7 +2927,7 @@ static int load_credential(
|
||||
_cleanup_free_ char *bindname = NULL;
|
||||
const char *source = NULL;
|
||||
bool missing_ok = true;
|
||||
size_t size, add, maxsz;
|
||||
size_t size, maxsz;
|
||||
int r;
|
||||
|
||||
assert(context);
|
||||
@@ -2923,28 +3029,7 @@ static int load_credential(
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to read credential '%s': %m", path);
|
||||
|
||||
if (encrypted) {
|
||||
_cleanup_free_ void *plaintext = NULL;
|
||||
size_t plaintext_size = 0;
|
||||
|
||||
r = decrypt_credential_and_warn(id, now(CLOCK_REALTIME), NULL, NULL, data, size, &plaintext, &plaintext_size);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
free_and_replace(data, plaintext);
|
||||
size = plaintext_size;
|
||||
}
|
||||
|
||||
add = strlen(id) + size;
|
||||
if (add > *left)
|
||||
return -E2BIG;
|
||||
|
||||
r = write_credential(write_dfd, id, data, size, uid, ownership_ok);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to write credential '%s': %m", id);
|
||||
|
||||
*left -= add;
|
||||
return 0;
|
||||
return maybe_decrypt_and_write_credential(write_dfd, id, encrypted, uid, ownership_ok, data, size, left);
|
||||
}
|
||||
|
||||
struct load_cred_args {
|
||||
@@ -3019,6 +3104,7 @@ static int acquire_credentials(
|
||||
|
||||
uint64_t left = CREDENTIALS_TOTAL_SIZE_MAX;
|
||||
_cleanup_close_ int dfd = -EBADF;
|
||||
const char *ic;
|
||||
ExecLoadCredential *lc;
|
||||
ExecSetCredential *sc;
|
||||
int r;
|
||||
@@ -3084,8 +3170,47 @@ static int acquire_credentials(
|
||||
return r;
|
||||
}
|
||||
|
||||
/* Second, we add in literally specified credentials. If the credentials already exist, we'll not add
|
||||
* them, so that they can act as a "default" if the same credential is specified multiple times. */
|
||||
/* Next, look for system credentials and credentials in the credentials store. Note that these do not
|
||||
* override any credentials found earlier. */
|
||||
SET_FOREACH(ic, context->import_credentials) {
|
||||
_cleanup_free_ char **search_path = NULL;
|
||||
|
||||
search_path = credential_search_path(params, CREDENTIAL_SEARCH_PATH_TRUSTED);
|
||||
if (!search_path)
|
||||
return -ENOMEM;
|
||||
|
||||
r = load_credential_glob(
|
||||
ic,
|
||||
/* encrypted = */ false,
|
||||
search_path,
|
||||
READ_FULL_FILE_SECURE|READ_FULL_FILE_FAIL_WHEN_LARGER,
|
||||
dfd,
|
||||
uid,
|
||||
ownership_ok,
|
||||
&left);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
search_path = strv_free(search_path);
|
||||
search_path = credential_search_path(params, CREDENTIAL_SEARCH_PATH_ENCRYPTED);
|
||||
if (!search_path)
|
||||
return -ENOMEM;
|
||||
|
||||
r = load_credential_glob(
|
||||
ic,
|
||||
/* encrypted = */ true,
|
||||
search_path,
|
||||
READ_FULL_FILE_SECURE|READ_FULL_FILE_FAIL_WHEN_LARGER|READ_FULL_FILE_UNBASE64,
|
||||
dfd,
|
||||
uid,
|
||||
ownership_ok,
|
||||
&left);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
/* Finally, we add in literally specified credentials. If the credentials already exist, we'll not
|
||||
* add them, so that they can act as a "default" if the same credential is specified multiple times. */
|
||||
HASHMAP_FOREACH(sc, context->set_credentials) {
|
||||
_cleanup_(erase_and_freep) void *plaintext = NULL;
|
||||
const char *data;
|
||||
@@ -5883,6 +6008,7 @@ void exec_context_done(ExecContext *c) {
|
||||
|
||||
c->load_credentials = hashmap_free(c->load_credentials);
|
||||
c->set_credentials = hashmap_free(c->set_credentials);
|
||||
c->import_credentials = set_free(c->import_credentials);
|
||||
|
||||
c->root_image_policy = image_policy_free(c->root_image_policy);
|
||||
c->mount_image_policy = image_policy_free(c->mount_image_policy);
|
||||
|
||||
@@ -361,6 +361,7 @@ struct ExecContext {
|
||||
|
||||
Hashmap *set_credentials; /* output id → ExecSetCredential */
|
||||
Hashmap *load_credentials; /* output id → ExecLoadCredential */
|
||||
Set *import_credentials;
|
||||
|
||||
ImagePolicy *root_image_policy, *mount_image_policy, *extension_image_policy;
|
||||
};
|
||||
|
||||
@@ -151,6 +151,7 @@
|
||||
{{type}}.SetCredentialEncrypted, config_parse_set_credential, 1, offsetof({{type}}, exec_context)
|
||||
{{type}}.LoadCredential, config_parse_load_credential, 0, offsetof({{type}}, exec_context)
|
||||
{{type}}.LoadCredentialEncrypted, config_parse_load_credential, 1, offsetof({{type}}, exec_context)
|
||||
{{type}}.ImportCredential, config_parse_import_credential, 0, offsetof({{type}}, exec_context.import_credentials)
|
||||
{{type}}.TimeoutCleanSec, config_parse_sec, 0, offsetof({{type}}, exec_context.timeout_clean_usec)
|
||||
{% if HAVE_PAM %}
|
||||
{{type}}.PAMName, config_parse_unit_string_printf, 0, offsetof({{type}}, exec_context.pam_name)
|
||||
|
||||
+82
-24
@@ -4840,6 +4840,46 @@ int config_parse_set_credential(
|
||||
return 0;
|
||||
}
|
||||
|
||||
int hashmap_put_credential(Hashmap **h, const char *id, const char *path, bool encrypted) {
|
||||
ExecLoadCredential *old;
|
||||
int r;
|
||||
|
||||
assert(h);
|
||||
assert(id);
|
||||
assert(path);
|
||||
|
||||
old = hashmap_get(*h, id);
|
||||
if (old) {
|
||||
r = free_and_strdup(&old->path, path);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
old->encrypted = encrypted;
|
||||
} else {
|
||||
_cleanup_(exec_load_credential_freep) ExecLoadCredential *lc = NULL;
|
||||
|
||||
lc = new(ExecLoadCredential, 1);
|
||||
if (!lc)
|
||||
return log_oom();
|
||||
|
||||
*lc = (ExecLoadCredential) {
|
||||
.id = strdup(id),
|
||||
.path = strdup(path),
|
||||
.encrypted = encrypted,
|
||||
};
|
||||
if (!lc->id || !lc->path)
|
||||
return -ENOMEM;
|
||||
|
||||
r = hashmap_ensure_put(h, &exec_load_credential_hash_ops, lc->id, lc);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
TAKE_PTR(lc);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int config_parse_load_credential(
|
||||
const char *unit,
|
||||
const char *filename,
|
||||
@@ -4854,7 +4894,6 @@ int config_parse_load_credential(
|
||||
|
||||
_cleanup_free_ char *word = NULL, *k = NULL, *q = NULL;
|
||||
ExecContext *context = ASSERT_PTR(data);
|
||||
ExecLoadCredential *old;
|
||||
bool encrypted = ltype;
|
||||
Unit *u = userdata;
|
||||
const char *p;
|
||||
@@ -4907,35 +4946,54 @@ int config_parse_load_credential(
|
||||
}
|
||||
}
|
||||
|
||||
old = hashmap_get(context->load_credentials, k);
|
||||
if (old) {
|
||||
free_and_replace(old->path, q);
|
||||
old->encrypted = encrypted;
|
||||
} else {
|
||||
_cleanup_(exec_load_credential_freep) ExecLoadCredential *lc = NULL;
|
||||
r = hashmap_put_credential(&context->load_credentials, k, q, encrypted);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to store load credential '%s': %m", rvalue);
|
||||
|
||||
lc = new(ExecLoadCredential, 1);
|
||||
if (!lc)
|
||||
return log_oom();
|
||||
return 0;
|
||||
}
|
||||
|
||||
*lc = (ExecLoadCredential) {
|
||||
.id = TAKE_PTR(k),
|
||||
.path = TAKE_PTR(q),
|
||||
.encrypted = encrypted,
|
||||
};
|
||||
int config_parse_import_credential(
|
||||
const char *unit,
|
||||
const char *filename,
|
||||
unsigned line,
|
||||
const char *section,
|
||||
unsigned section_line,
|
||||
const char *lvalue,
|
||||
int ltype,
|
||||
const char *rvalue,
|
||||
void *data,
|
||||
void *userdata) {
|
||||
|
||||
r = hashmap_ensure_put(&context->load_credentials, &exec_load_credential_hash_ops, lc->id, lc);
|
||||
if (r == -ENOMEM)
|
||||
return log_oom();
|
||||
if (r < 0) {
|
||||
log_syntax(unit, LOG_WARNING, filename, line, r,
|
||||
"Duplicated credential value '%s', ignoring assignment: %s", lc->id, rvalue);
|
||||
return 0;
|
||||
}
|
||||
_cleanup_free_ char *s = NULL;
|
||||
Set** import_credentials = ASSERT_PTR(data);
|
||||
Unit *u = userdata;
|
||||
int r;
|
||||
|
||||
TAKE_PTR(lc);
|
||||
assert(filename);
|
||||
assert(lvalue);
|
||||
assert(rvalue);
|
||||
|
||||
if (isempty(rvalue)) {
|
||||
/* Empty assignment resets the list */
|
||||
*import_credentials = set_free(*import_credentials);
|
||||
return 0;
|
||||
}
|
||||
|
||||
r = unit_cred_printf(u, rvalue, &s);
|
||||
if (r < 0) {
|
||||
log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to resolve unit specifiers in \"%s\", ignoring: %m", s);
|
||||
return 0;
|
||||
}
|
||||
if (!filename_is_valid(s)) {
|
||||
log_syntax(unit, LOG_WARNING, filename, line, 0, "Credential name \"%s\" not valid, ignoring.", s);
|
||||
return 0;
|
||||
}
|
||||
|
||||
r = set_put_strdup(import_credentials, s);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to store credential name '%s': %m", rvalue);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@ int unit_is_likely_recursive_template_dependency(Unit *u, const char *name, cons
|
||||
int parse_crash_chvt(const char *value, int *data);
|
||||
int parse_confirm_spawn(const char *value, char **console);
|
||||
|
||||
int hashmap_put_credential(Hashmap **h, const char *id, const char *path, bool encrypted);
|
||||
|
||||
/* Read service data from .desktop file style configuration fragments */
|
||||
|
||||
int unit_load_fragment(Unit *u);
|
||||
@@ -105,6 +107,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_exec_preserve_mode);
|
||||
CONFIG_PARSER_PROTOTYPE(config_parse_exec_directories);
|
||||
CONFIG_PARSER_PROTOTYPE(config_parse_set_credential);
|
||||
CONFIG_PARSER_PROTOTYPE(config_parse_load_credential);
|
||||
CONFIG_PARSER_PROTOTYPE(config_parse_import_credential);
|
||||
CONFIG_PARSER_PROTOTYPE(config_parse_set_status);
|
||||
CONFIG_PARSER_PROTOTYPE(config_parse_namespace_path_strv);
|
||||
CONFIG_PARSER_PROTOTYPE(config_parse_temporary_filesystems);
|
||||
|
||||
@@ -1209,6 +1209,17 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (streq(field, "ImportCredential")) {
|
||||
if (isempty(eq))
|
||||
r = sd_bus_message_append(m, "(sv)", field, "as", 0);
|
||||
else
|
||||
r = sd_bus_message_append(m, "(sv)", field, "as", 1, eq);
|
||||
if (r < 0)
|
||||
return bus_log_create_error(r);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (streq(field, "LogExtraFields")) {
|
||||
r = sd_bus_message_open_container(m, 'r', "sv");
|
||||
if (r < 0)
|
||||
|
||||
Reference in New Issue
Block a user