+496
-2
@@ -6,6 +6,7 @@
|
||||
#include <sys/eventfd.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/personality.h>
|
||||
#include <sys/prctl.h>
|
||||
#include <sys/shm.h>
|
||||
@@ -32,6 +33,7 @@
|
||||
|
||||
#include "sd-messages.h"
|
||||
|
||||
#include "acl-util.h"
|
||||
#include "af-list.h"
|
||||
#include "alloc-util.h"
|
||||
#if HAVE_APPARMOR
|
||||
@@ -41,8 +43,8 @@
|
||||
#include "barrier.h"
|
||||
#include "cap-list.h"
|
||||
#include "capability-util.h"
|
||||
#include "chown-recursive.h"
|
||||
#include "cgroup-setup.h"
|
||||
#include "chown-recursive.h"
|
||||
#include "cpu-set-util.h"
|
||||
#include "def.h"
|
||||
#include "env-file.h"
|
||||
@@ -51,6 +53,7 @@
|
||||
#include "execute.h"
|
||||
#include "exit-status.h"
|
||||
#include "fd-util.h"
|
||||
#include "fileio.h"
|
||||
#include "format-util.h"
|
||||
#include "fs-util.h"
|
||||
#include "glob-util.h"
|
||||
@@ -64,6 +67,7 @@
|
||||
#include "memory-util.h"
|
||||
#include "missing_fs.h"
|
||||
#include "mkdir.h"
|
||||
#include "mountpoint-util.h"
|
||||
#include "namespace.h"
|
||||
#include "parse-util.h"
|
||||
#include "path-util.h"
|
||||
@@ -85,6 +89,7 @@
|
||||
#include "strv.h"
|
||||
#include "syslog-util.h"
|
||||
#include "terminal-util.h"
|
||||
#include "tmpfile-util.h"
|
||||
#include "umask-util.h"
|
||||
#include "unit.h"
|
||||
#include "user-util.h"
|
||||
@@ -1417,6 +1422,14 @@ static bool context_has_no_new_privileges(const ExecContext *c) {
|
||||
c->protect_hostname;
|
||||
}
|
||||
|
||||
static bool exec_context_has_credentials(const ExecContext *context) {
|
||||
|
||||
assert(context);
|
||||
|
||||
return !hashmap_isempty(context->set_credentials) ||
|
||||
context->load_credentials;
|
||||
}
|
||||
|
||||
#if HAVE_SECCOMP
|
||||
|
||||
static bool skip_seccomp_unavailable(const Unit* u, const char* msg) {
|
||||
@@ -1725,7 +1738,7 @@ static int build_environment(
|
||||
assert(p);
|
||||
assert(ret);
|
||||
|
||||
#define N_ENV_VARS 15
|
||||
#define N_ENV_VARS 16
|
||||
our_env = new0(char*, N_ENV_VARS + _EXEC_DIRECTORY_TYPE_MAX);
|
||||
if (!our_env)
|
||||
return -ENOMEM;
|
||||
@@ -1873,6 +1886,14 @@ static int build_environment(
|
||||
our_env[n_env++] = x;
|
||||
}
|
||||
|
||||
if (exec_context_has_credentials(c) && p->prefix[EXEC_DIRECTORY_RUNTIME]) {
|
||||
x = strjoin("CREDENTIALS_DIRECTORY=", p->prefix[EXEC_DIRECTORY_RUNTIME], "/credentials/", u->id);
|
||||
if (!x)
|
||||
return -ENOMEM;
|
||||
|
||||
our_env[n_env++] = x;
|
||||
}
|
||||
|
||||
our_env[n_env++] = NULL;
|
||||
assert(n_env <= N_ENV_VARS + _EXEC_DIRECTORY_TYPE_MAX);
|
||||
#undef N_ENV_VARS
|
||||
@@ -2378,6 +2399,437 @@ fail:
|
||||
return r;
|
||||
}
|
||||
|
||||
static int write_credential(
|
||||
int dfd,
|
||||
const char *id,
|
||||
const void *data,
|
||||
size_t size,
|
||||
uid_t uid,
|
||||
bool ownership_ok) {
|
||||
|
||||
_cleanup_(unlink_and_freep) char *tmp = NULL;
|
||||
_cleanup_close_ int fd = -1;
|
||||
int r;
|
||||
|
||||
r = tempfn_random_child("", "cred", &tmp);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
fd = openat(dfd, tmp, O_CREAT|O_RDWR|O_CLOEXEC|O_EXCL|O_NOFOLLOW|O_NOCTTY, 0600);
|
||||
if (fd < 0) {
|
||||
tmp = mfree(tmp);
|
||||
return -errno;
|
||||
}
|
||||
|
||||
r = loop_write(fd, data, size, /* do_pool = */ false);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (fchmod(fd, 0400) < 0) /* Take away "w" bit */
|
||||
return -errno;
|
||||
|
||||
if (uid_is_valid(uid) && uid != getuid()) {
|
||||
#if HAVE_ACL
|
||||
r = fd_add_uid_acl_permission(fd, uid, /* read = */ true, /* write = */ false, /* execute = */ false);
|
||||
#else
|
||||
r = -EOPNOTSUPP;
|
||||
#endif
|
||||
if (r < 0) {
|
||||
if (!ERRNO_IS_NOT_SUPPORTED(r) && !ERRNO_IS_PRIVILEGE(r))
|
||||
return r;
|
||||
|
||||
if (!ownership_ok) /* Ideally we use ACLs, since we can neatly express what we want
|
||||
* to express: that the user gets read access and nothing
|
||||
* else. But if the backing fs can't support that (e.g. ramfs)
|
||||
* then we can use file ownership instead. But that's only safe if
|
||||
* we can then re-mount the whole thing read-only, so that the
|
||||
* user can no longer chmod() the file to gain write access. */
|
||||
return r;
|
||||
|
||||
if (fchown(fd, uid, (gid_t) -1) < 0)
|
||||
return -errno;
|
||||
}
|
||||
}
|
||||
|
||||
if (renameat(dfd, tmp, dfd, id) < 0)
|
||||
return -errno;
|
||||
|
||||
tmp = mfree(tmp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define CREDENTIALS_BYTES_MAX (1024LU * 1024LU) /* Refuse to pass more than 1M, after all this is unswappable memory */
|
||||
|
||||
static int acquire_credentials(
|
||||
const ExecContext *context,
|
||||
const ExecParameters *params,
|
||||
const char *p,
|
||||
uid_t uid,
|
||||
bool ownership_ok) {
|
||||
|
||||
uint64_t left = CREDENTIALS_BYTES_MAX;
|
||||
_cleanup_close_ int dfd = -1;
|
||||
ExecSetCredential *sc;
|
||||
char **id, **fn;
|
||||
Iterator iterator;
|
||||
int r;
|
||||
|
||||
assert(context);
|
||||
assert(p);
|
||||
|
||||
dfd = open(p, O_DIRECTORY|O_CLOEXEC);
|
||||
if (dfd < 0)
|
||||
return -errno;
|
||||
|
||||
/* First we use the literally specified credentials. Note that they might be overriden again below,
|
||||
* and thus act as a "default" if the same credential is specified multiple times */
|
||||
HASHMAP_FOREACH(sc, context->set_credentials, iterator) {
|
||||
size_t add;
|
||||
|
||||
add = strlen(sc->id) + sc->size;
|
||||
if (add > left)
|
||||
return -E2BIG;
|
||||
|
||||
r = write_credential(dfd, sc->id, sc->data, sc->size, uid, ownership_ok);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
left -= add;
|
||||
}
|
||||
|
||||
/* Then, load credential off disk (or acquire via AF_UNIX socket) */
|
||||
STRV_FOREACH_PAIR(id, fn, context->load_credentials) {
|
||||
ReadFullFileFlags flags = READ_FULL_FILE_SECURE;
|
||||
_cleanup_(erase_and_freep) char *data = NULL;
|
||||
_cleanup_free_ char *j = NULL;
|
||||
const char *source;
|
||||
size_t size, add;
|
||||
|
||||
if (path_is_absolute(*fn)) {
|
||||
/* If this is an absolute path, read the data directly from it, and support AF_UNIX sockets */
|
||||
source = *fn;
|
||||
flags |= READ_FULL_FILE_CONNECT_SOCKET;
|
||||
} else if (params->received_credentials) {
|
||||
/* If this is a relative path, take it relative to the credentials we received
|
||||
* ourselves. We don't support the AF_UNIX stuff in this mode, since we are operating
|
||||
* on a credential store, i.e. this is guaranteed to be regular files. */
|
||||
j = path_join(params->received_credentials, *fn);
|
||||
if (!j)
|
||||
return -ENOMEM;
|
||||
|
||||
source = j;
|
||||
} else
|
||||
source = NULL;
|
||||
|
||||
if (source)
|
||||
r = read_full_file_full(AT_FDCWD, source, flags, &data, &size);
|
||||
else
|
||||
r = -ENOENT;
|
||||
if (r == -ENOENT &&
|
||||
faccessat(dfd, *id, F_OK, AT_SYMLINK_NOFOLLOW) >= 0) /* If the source file doesn't exist, but we already acquired the key otherwise, then don't fail */
|
||||
continue;
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
add = strlen(*id) + size;
|
||||
if (add > left)
|
||||
return -E2BIG;
|
||||
|
||||
r = write_credential(dfd, *id, data, size, uid, ownership_ok);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
left -= add;
|
||||
}
|
||||
|
||||
if (fchmod(dfd, 0500) < 0) /* Now take away the "w" bit */
|
||||
return -errno;
|
||||
|
||||
/* After we created all keys with the right perms, also make sure the credential store as a whole is
|
||||
* accessible */
|
||||
|
||||
if (uid_is_valid(uid) && uid != getuid()) {
|
||||
#if HAVE_ACL
|
||||
r = fd_add_uid_acl_permission(dfd, uid, /* read = */ true, /* write = */ false, /* execute = */ true);
|
||||
#else
|
||||
r = -EOPNOTSUPP;
|
||||
#endif
|
||||
if (r < 0) {
|
||||
if (!ERRNO_IS_NOT_SUPPORTED(r) && !ERRNO_IS_PRIVILEGE(r))
|
||||
return r;
|
||||
|
||||
if (!ownership_ok)
|
||||
return r;
|
||||
|
||||
if (fchown(dfd, uid, (gid_t) -1) < 0)
|
||||
return -errno;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int setup_credentials_internal(
|
||||
const ExecContext *context,
|
||||
const ExecParameters *params,
|
||||
const char *final, /* This is where the credential store shall eventually end up at */
|
||||
const char *workspace, /* This is where we can prepare it before moving it to the final place */
|
||||
bool reuse_workspace, /* Whether to reuse any existing workspace mount if it already is a mount */
|
||||
bool must_mount, /* Whether to require that we mount something, it's not OK to use the plain directory fall back */
|
||||
uid_t uid) {
|
||||
|
||||
int r, workspace_mounted; /* negative if we don't know yet whether we have/can mount something; true
|
||||
* if we mounted something; false if we definitely can't mount anything */
|
||||
bool final_mounted;
|
||||
const char *where;
|
||||
|
||||
assert(context);
|
||||
assert(final);
|
||||
assert(workspace);
|
||||
|
||||
if (reuse_workspace) {
|
||||
r = path_is_mount_point(workspace, NULL, 0);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r > 0)
|
||||
workspace_mounted = true; /* If this is already a mount, and we are supposed to reuse it, let's keep this in mind */
|
||||
else
|
||||
workspace_mounted = -1; /* We need to figure out if we can mount something to the workspace */
|
||||
} else
|
||||
workspace_mounted = -1; /* ditto */
|
||||
|
||||
r = path_is_mount_point(final, NULL, 0);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r > 0) {
|
||||
/* If the final place already has something mounted, we use that. If the workspace also has
|
||||
* something mounted we assume it's actually the same mount (but with MS_RDONLY
|
||||
* different). */
|
||||
final_mounted = true;
|
||||
|
||||
if (workspace_mounted < 0) {
|
||||
/* If the final place is mounted, but the workspace we isn't, then let's bind mount
|
||||
* the final version to the workspace, and make it writable, so that we can make
|
||||
* changes */
|
||||
|
||||
if (mount(final, workspace, NULL, MS_BIND|MS_REC, NULL) < 0)
|
||||
return -errno;
|
||||
|
||||
if (mount(NULL, workspace, NULL, MS_BIND|MS_REMOUNT|MS_NODEV|MS_NOEXEC|MS_NOSUID, NULL) < 0)
|
||||
return -errno;
|
||||
|
||||
workspace_mounted = true;
|
||||
}
|
||||
} else
|
||||
final_mounted = false;
|
||||
|
||||
if (workspace_mounted < 0) {
|
||||
/* Nothing is mounted on the workspace yet, let's try to mount something now */
|
||||
for (int try = 0;; try++) {
|
||||
|
||||
if (try == 0) {
|
||||
/* Try "ramfs" first, since it's not swap backed */
|
||||
if (mount("ramfs", workspace, "ramfs", MS_NODEV|MS_NOEXEC|MS_NOSUID, "mode=0700") >= 0) {
|
||||
workspace_mounted = true;
|
||||
break;
|
||||
}
|
||||
|
||||
} else if (try == 1) {
|
||||
_cleanup_free_ char *opts = NULL;
|
||||
|
||||
if (asprintf(&opts, "mode=0700,nr_inodes=1024,size=%lu", CREDENTIALS_BYTES_MAX) < 0)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Fall back to "tmpfs" otherwise */
|
||||
if (mount("tmpfs", workspace, "tmpfs", MS_NODEV|MS_NOEXEC|MS_NOSUID, opts) >= 0) {
|
||||
workspace_mounted = true;
|
||||
break;
|
||||
}
|
||||
|
||||
} else {
|
||||
/* If that didn't work, try to make a bind mount from the final to the workspace, so that we can make it writable there. */
|
||||
if (mount(final, workspace, NULL, MS_BIND|MS_REC, NULL) < 0) {
|
||||
if (!ERRNO_IS_PRIVILEGE(errno)) /* Propagate anything that isn't a permission problem */
|
||||
return -errno;
|
||||
|
||||
if (must_mount) /* If we it's not OK to use the plain directory
|
||||
* fallback, propagate all errors too */
|
||||
return -errno;
|
||||
|
||||
/* If we lack privileges to bind mount stuff, then let's gracefully
|
||||
* proceed for compat with container envs, and just use the final dir
|
||||
* as is. */
|
||||
|
||||
workspace_mounted = false;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Make the new bind mount writable (i.e. drop MS_RDONLY) */
|
||||
if (mount(NULL, workspace, NULL, MS_BIND|MS_REMOUNT|MS_NODEV|MS_NOEXEC|MS_NOSUID, NULL) < 0)
|
||||
return -errno;
|
||||
|
||||
workspace_mounted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert(!must_mount || workspace_mounted > 0);
|
||||
where = workspace_mounted ? workspace : final;
|
||||
|
||||
r = acquire_credentials(context, params, where, uid, workspace_mounted);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (workspace_mounted) {
|
||||
/* Make workspace read-only now, so that any bind mount we make from it defaults to read-only too */
|
||||
if (mount(NULL, workspace, NULL, MS_BIND|MS_REMOUNT|MS_RDONLY|MS_NODEV|MS_NOEXEC|MS_NOSUID, NULL) < 0)
|
||||
return -errno;
|
||||
|
||||
/* And mount it to the final place, read-only */
|
||||
if (final_mounted) {
|
||||
if (umount2(workspace, MNT_DETACH|UMOUNT_NOFOLLOW) < 0)
|
||||
return -errno;
|
||||
} else {
|
||||
if (mount(workspace, final, NULL, MS_MOVE, NULL) < 0)
|
||||
return -errno;
|
||||
}
|
||||
} else {
|
||||
_cleanup_free_ char *parent = NULL;
|
||||
|
||||
/* If we do not have our own mount put used the plain directory fallback, then we need to
|
||||
* open access to the top-level credential directory and the per-service directory now */
|
||||
|
||||
parent = dirname_malloc(final);
|
||||
if (!parent)
|
||||
return -ENOMEM;
|
||||
if (chmod(parent, 0755) < 0)
|
||||
return -errno;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int setup_credentials(
|
||||
const ExecContext *context,
|
||||
const ExecParameters *params,
|
||||
const char *unit,
|
||||
uid_t uid) {
|
||||
|
||||
_cleanup_free_ char *p = NULL, *q = NULL;
|
||||
const char *i;
|
||||
int r;
|
||||
|
||||
assert(context);
|
||||
assert(params);
|
||||
|
||||
if (!exec_context_has_credentials(context))
|
||||
return 0;
|
||||
|
||||
if (!params->prefix[EXEC_DIRECTORY_RUNTIME])
|
||||
return -EINVAL;
|
||||
|
||||
/* This where we'll place stuff when we are done; this main credentials directory is world-readable,
|
||||
* and the subdir we mount over with a read-only file system readable by the service's user */
|
||||
q = path_join(params->prefix[EXEC_DIRECTORY_RUNTIME], "credentials");
|
||||
if (!q)
|
||||
return -ENOMEM;
|
||||
|
||||
r = mkdir_label(q, 0755); /* top-level dir: world readable/searchable */
|
||||
if (r < 0 && r != -EEXIST)
|
||||
return r;
|
||||
|
||||
p = path_join(q, unit);
|
||||
if (!p)
|
||||
return -ENOMEM;
|
||||
|
||||
r = mkdir_label(p, 0700); /* per-unit dir: private to user */
|
||||
if (r < 0 && r != -EEXIST)
|
||||
return r;
|
||||
|
||||
r = safe_fork("(sd-mkdcreds)", FORK_DEATHSIG|FORK_WAIT|FORK_NEW_MOUNTNS, NULL);
|
||||
if (r < 0) {
|
||||
_cleanup_free_ char *t = NULL, *u = NULL;
|
||||
|
||||
/* If this is not a privilege or support issue then propagate the error */
|
||||
if (!ERRNO_IS_NOT_SUPPORTED(r) && !ERRNO_IS_PRIVILEGE(r))
|
||||
return r;
|
||||
|
||||
/* Temporary workspace, that remains inaccessible all the time. We prepare stuff there before moving
|
||||
* it into place, so that users can't access half-initialized credential stores. */
|
||||
t = path_join(params->prefix[EXEC_DIRECTORY_RUNTIME], "systemd/temporary-credentials");
|
||||
if (!t)
|
||||
return -ENOMEM;
|
||||
|
||||
/* We can't set up a mount namespace. In that case operate on a fixed, inaccessible per-unit
|
||||
* directory outside of /run/credentials/ first, and then move it over to /run/credentials/
|
||||
* after it is fully set up */
|
||||
u = path_join(t, unit);
|
||||
if (!u)
|
||||
return -ENOMEM;
|
||||
|
||||
FOREACH_STRING(i, t, u) {
|
||||
r = mkdir_label(i, 0700);
|
||||
if (r < 0 && r != -EEXIST)
|
||||
return r;
|
||||
}
|
||||
|
||||
r = setup_credentials_internal(
|
||||
context,
|
||||
params,
|
||||
p, /* final mount point */
|
||||
u, /* temporary workspace to overmount */
|
||||
true, /* reuse the workspace if it is already a mount */
|
||||
false, /* it's OK to fall back to a plain directory if we can't mount anything */
|
||||
uid);
|
||||
|
||||
(void) rmdir(u); /* remove the workspace again if we can. */
|
||||
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
} else if (r == 0) {
|
||||
|
||||
/* We managed to set up a mount namespace, and are now in a child. That's great. In this case
|
||||
* we can use the same directory for all cases, after turning off propagation. Question
|
||||
* though is: where do we turn off propagation exactly, and where do we place the workspace
|
||||
* directory? We need some place that is guaranteed to be a mount point in the host, and
|
||||
* which is guaranteed to have a subdir we can mount over. /run/ is not suitable for this,
|
||||
* since we ultimately want to move the resulting file system there, i.e. we need propagation
|
||||
* for /run/ eventually. We could use our own /run/systemd/bind mount on itself, but that
|
||||
* would be visible in the host mount table all the time, which we want to avoid. Hence, what
|
||||
* we do here instead we use /dev/ and /dev/shm/ for our purposes. We know for sure that
|
||||
* /dev/ is a mount point and we now for sure that /dev/shm/ exists. Hence we can turn off
|
||||
* propagation on the former, and then overmount the latter.
|
||||
*
|
||||
* Yes it's nasty playing games with /dev/ and /dev/shm/ like this, since it does not exist
|
||||
* for this purpose, but there are few other candidates that work equally well for us, and
|
||||
* given that the we do this in a privately namespaced short-lived single-threaded process
|
||||
* that noone else sees this should be OK to do.*/
|
||||
|
||||
if (mount(NULL, "/dev", NULL, MS_SLAVE|MS_REC, NULL) < 0) /* Turn off propagation from our namespace to host */
|
||||
goto child_fail;
|
||||
|
||||
r = setup_credentials_internal(
|
||||
context,
|
||||
params,
|
||||
p, /* final mount point */
|
||||
"/dev/shm", /* temporary workspace to overmount */
|
||||
false, /* do not reuse /dev/shm if it is already a mount, under no circumstances */
|
||||
true, /* insist that something is mounted, do not allow fallback to plain directory */
|
||||
uid);
|
||||
if (r < 0)
|
||||
goto child_fail;
|
||||
|
||||
_exit(EXIT_SUCCESS);
|
||||
|
||||
child_fail:
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if ENABLE_SMACK
|
||||
static int setup_smack(
|
||||
const ExecContext *context,
|
||||
@@ -3489,6 +3941,14 @@ static int exec_child(
|
||||
return log_unit_error_errno(unit, r, "Failed to set up special execution directory in %s: %m", params->prefix[dt]);
|
||||
}
|
||||
|
||||
if (FLAGS_SET(params->flags, EXEC_WRITE_CREDENTIALS)) {
|
||||
r = setup_credentials(context, params, unit->id, uid);
|
||||
if (r < 0) {
|
||||
*exit_status = EXIT_CREDENTIALS;
|
||||
return log_unit_error_errno(unit, r, "Failed to set up credentials: %m");
|
||||
}
|
||||
}
|
||||
|
||||
r = build_environment(
|
||||
unit,
|
||||
context,
|
||||
@@ -4276,6 +4736,9 @@ void exec_context_done(ExecContext *c) {
|
||||
c->network_namespace_path = mfree(c->network_namespace_path);
|
||||
|
||||
c->log_namespace = mfree(c->log_namespace);
|
||||
|
||||
c->load_credentials = strv_free(c->load_credentials);
|
||||
c->set_credentials = hashmap_free(c->set_credentials);
|
||||
}
|
||||
|
||||
int exec_context_destroy_runtime_directory(const ExecContext *c, const char *runtime_prefix) {
|
||||
@@ -4304,6 +4767,26 @@ int exec_context_destroy_runtime_directory(const ExecContext *c, const char *run
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_context_destroy_credentials(const ExecContext *c, const char *runtime_prefix, const char *unit) {
|
||||
_cleanup_free_ char *p = NULL;
|
||||
|
||||
assert(c);
|
||||
|
||||
if (!runtime_prefix || !unit)
|
||||
return 0;
|
||||
|
||||
p = path_join(runtime_prefix, "credentials", unit);
|
||||
if (!p)
|
||||
return -ENOMEM;
|
||||
|
||||
/* This is either a tmpfs/ramfs of its own, or a plain directory. Either way, let's first try to
|
||||
* unmount it, and afterwards remove the mount point */
|
||||
(void) umount2(p, MNT_DETACH|UMOUNT_NOFOLLOW);
|
||||
(void) rm_rf(p, REMOVE_ROOT|REMOVE_CHMOD);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void exec_command_done(ExecCommand *c) {
|
||||
assert(c);
|
||||
|
||||
@@ -5812,6 +6295,17 @@ void exec_params_clear(ExecParameters *p) {
|
||||
p->exec_fd = safe_close(p->exec_fd);
|
||||
}
|
||||
|
||||
ExecSetCredential *exec_set_credential_free(ExecSetCredential *sc) {
|
||||
if (!sc)
|
||||
return NULL;
|
||||
|
||||
free(sc->id);
|
||||
free(sc->data);
|
||||
return mfree(sc);
|
||||
}
|
||||
|
||||
DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(exec_set_credential_hash_ops, char, string_hash_func, string_compare_func, ExecSetCredential, exec_set_credential_free);
|
||||
|
||||
static const char* const exec_input_table[_EXEC_INPUT_MAX] = {
|
||||
[EXEC_INPUT_NULL] = "null",
|
||||
[EXEC_INPUT_TTY] = "tty",
|
||||
|
||||
Reference in New Issue
Block a user