blob: a580f4f8cd4f4f05a076c7eef0f9da6edccccc18 [file] [log] [blame]
/*****************************************************************************\
* cgroup_dbus.c - dbus utility functions for cgroup/v2.
*****************************************************************************
* Copyright (C) SchedMD LLC.
*
* This file is part of Slurm, a resource management program.
* For details, see <https://slurm.schedmd.com/>.
* Please also read the included file: DISCLAIMER.
*
* Slurm is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* In addition, as a special exception, the copyright holders give permission
* to link the code of portions of this program with the OpenSSL library under
* certain conditions as described in each individual source file, and
* distribute linked combinations including the two. You must obey the GNU
* General Public License in all respects for all of the code used other than
* OpenSSL. If you modify file(s) with this exception, you may extend this
* exception to your version of the file(s), but you are not obligated to do
* so. If you do not wish to do so, delete this exception statement from your
* version. If you delete this exception statement from all source files in
* the program, then also delete it here.
*
* Slurm is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along
* with Slurm; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
\*****************************************************************************/
#include "src/plugins/cgroup/v2/cgroup_dbus.h"
/*
* This is how systemd code understand you're asking for the max. of some
* cgroup interface, e.g. pids.max, memory.[low|high|max], etc.
*/
#define SYSTEMD_CGROUP_LIMIT_MAX ((uint64_t) -1)
static int _process_and_close_reply_msg(DBusMessage *msg)
{
DBusMessageIter itr;
int type, rc = SLURM_SUCCESS;
char *tmp_str;
dbus_message_iter_init(msg, &itr);
do {
type = dbus_message_iter_get_arg_type(&itr);
switch (type) {
case DBUS_TYPE_OBJECT_PATH:
dbus_message_iter_get_basic(&itr, &tmp_str);
log_flag(CGROUP, "Possibly created new scope: %s",
tmp_str);
break;
case DBUS_TYPE_STRING:
case DBUS_TYPE_SIGNATURE:
rc = SLURM_ERROR;
dbus_message_iter_get_basic(&itr, &tmp_str);
log_flag(CGROUP, "The unit may already exist or we got an error: %s",
tmp_str);
break;
default:
rc = SLURM_ERROR;
error("%s: Invalid response type %c not supported by Slurm",
__func__, type);
break;
}
} while (dbus_message_iter_next(&itr));
dbus_message_unref(msg);
return rc;
}
static bool _set_scope_properties(DBusMessageIter *main_itr, pid_t *p,
int npids, bool delegate)
{
DBusMessageIter it[4] = { DBUS_MESSAGE_ITER_INIT_CLOSED };
const char *pid_prop_name = "PIDs";
const char *dlg_prop_name = "Delegate";
const char *tasksmax_prop_name = "TasksMax";
const char pid_prop_sig[] = { DBUS_TYPE_ARRAY, DBUS_TYPE_UINT32, '\0' };
const char dlg_prop_sig [] = { DBUS_TYPE_BOOLEAN, '\0' };
const char tasksmax_prop_sig [] = { DBUS_TYPE_UINT64, '\0' };
char sig[5];
int dlg = delegate ? 1 : 0;
uint64_t tasksmax_val = SYSTEMD_CGROUP_LIMIT_MAX;
/* Signature for the container - (sv) part */
sig[0] = DBUS_STRUCT_BEGIN_CHAR;
sig[1] = DBUS_TYPE_STRING;
sig[2] = DBUS_TYPE_VARIANT;
sig[3] = DBUS_STRUCT_END_CHAR;
sig[4] = '\0';
/* Open array - a(sv) */
if (!dbus_message_iter_open_container(main_itr, DBUS_TYPE_ARRAY, sig,
&it[0]))
goto oom;
/*
* Add PIDs property - PIDs <pid1, pid2, ...>
*/
/* Open struct */
if (!dbus_message_iter_open_container(&it[0], DBUS_TYPE_STRUCT, NULL,
&it[1]))
goto abandon;
/* Insert string */
if (!dbus_message_iter_append_basic(&it[1], DBUS_TYPE_STRING,
&pid_prop_name))
goto abandon;
/* Open variant */
if (!dbus_message_iter_open_container(&it[1], DBUS_TYPE_VARIANT,
pid_prop_sig, &it[2]))
goto abandon;
/* Open array of uint32 */
if (!dbus_message_iter_open_container(&it[2], *(pid_prop_sig),
(pid_prop_sig + 1), &it[3]))
goto abandon;
/* Insert elements */
if (!dbus_message_iter_append_fixed_array(&it[3], *(pid_prop_sig + 1),
&p, npids))
goto abandon;
/*
* At this point we have the array of pids inserted, let's close this
* block: Close array, Close variant, Close struct.
*/
if (!dbus_message_iter_close_container(&it[2], &it[3])
|| !dbus_message_iter_close_container(&it[1], &it[2])
|| !dbus_message_iter_close_container(&it[0], &it[1]))
goto abandon;
/*
* Add the property of Delegate = yes. We are into the array (it1) and
* we need to open a new struct (it2) to put the string and the boolean.
*/
if (!dbus_message_iter_open_container(&it[0], DBUS_TYPE_STRUCT, NULL,
&it[1]))
goto abandon;
if (!dbus_message_iter_append_basic(&it[1], DBUS_TYPE_STRING,
&dlg_prop_name))
goto abandon;
if (!dbus_message_iter_open_container(&it[1], DBUS_TYPE_VARIANT,
dlg_prop_sig, &it[2]))
goto abandon;
if (!dbus_message_iter_append_basic(&it[2], *(dlg_prop_sig), &dlg))
goto abandon;
/*
* At this point we have the Delegate=yes inserted, let's close this
* block: Close variant, Close struct.
*/
if (!dbus_message_iter_close_container(&it[1], &it[2])
|| !dbus_message_iter_close_container(&it[0], &it[1]))
goto abandon;
/*
* Add the property of TasksMax = infinity. We are into the array (it1)
* and we need to open a new struct (it2) to put the string and the
* variant which is a uint64_t.
*/
if (!dbus_message_iter_open_container(&it[0], DBUS_TYPE_STRUCT, NULL,
&it[1]))
goto abandon;
if (!dbus_message_iter_append_basic(&it[1], DBUS_TYPE_STRING,
&tasksmax_prop_name))
goto abandon;
if (!dbus_message_iter_open_container(&it[1], DBUS_TYPE_VARIANT,
tasksmax_prop_sig, &it[2]))
goto abandon;
if (!dbus_message_iter_append_basic(&it[2], *(tasksmax_prop_sig),
&tasksmax_val))
goto abandon;
/*
* At this point we have the TasksMax=infinity inserted, let's close all
* block: Close variant, Close struct, Close array.
*/
if (!dbus_message_iter_close_container(&it[1], &it[2])
|| !dbus_message_iter_close_container(&it[0], &it[1])
|| !dbus_message_iter_close_container(main_itr, &it[0]))
goto abandon;
return true;
abandon:
dbus_message_iter_abandon_container_if_open(&it[2], &it[3]);
dbus_message_iter_abandon_container_if_open(&it[1], &it[2]);
dbus_message_iter_abandon_container_if_open(&it[0], &it[1]);
dbus_message_iter_abandon_container_if_open(main_itr, &it[0]);
oom:
error("%s: not enough memory setting dbus msg.", __func__);
return false;
}
static bool _set_scope_aux(DBusMessageIter *main_itr)
{
char sig[9];
DBusMessageIter it1 = DBUS_MESSAGE_ITER_INIT_CLOSED;
/*
* Systemd's StartTransientUnit method requires to setup this signature
* but at the same time requires it to be NULL. This is the last part:
* 'a(sa(sv))'
*/
sig[0] = DBUS_STRUCT_BEGIN_CHAR;
sig[1] = DBUS_TYPE_STRING;
sig[2] = DBUS_TYPE_ARRAY;
sig[3] = DBUS_STRUCT_BEGIN_CHAR;
sig[4] = DBUS_TYPE_STRING;
sig[5] = DBUS_TYPE_VARIANT;
sig[6] = DBUS_STRUCT_END_CHAR;
sig[7] = DBUS_STRUCT_END_CHAR;
sig[8] = '\0';
/* The array, which will contain the signature but have 0 elements. */
if (!dbus_message_iter_open_container(main_itr, DBUS_TYPE_ARRAY,
sig, &it1))
goto oom;
/* Close the array. */
if (!dbus_message_iter_close_container(main_itr, &it1)) {
dbus_message_iter_abandon_container_if_open(main_itr, &it1);
goto oom;
}
return true;
oom:
error("%s: not enough memory setting dbus msg.", __func__);
return false;
}
/*
* Slurm function to attach stepd to a systemd scope, using dbus.
*/
extern int cgroup_dbus_attach_to_scope(pid_t stepd_pid, char *full_path)
{
const char *mode = "fail";
char *scope_name = xbasename(full_path);
DBusMessage *msg;
DBusMessageIter args_itr = DBUS_MESSAGE_ITER_INIT_CLOSED;
DBusConnection *conn = NULL;
DBusPendingCall *pending;
DBusError err;
pid_t pids[] = { stepd_pid };
int npids = 1;
log_flag(CGROUP, "Creating Slurm scope %s into system slice and adding pid %d.",
scope_name, stepd_pid);
dbus_error_init(&err);
/*
* Connect to the system bus daemon and register our connection.
* This function may block until auth. and bus registration are
* complete.
*/
conn = dbus_bus_get(DBUS_BUS_SYSTEM, &err);
if (dbus_error_is_set(&err)) {
error("%s: cannot connect to dbus system daemon: %s",
__func__, err.message);
dbus_error_free(&err);
}
if (!conn)
return SLURM_ERROR;
/* Create the new method call. */
msg = dbus_message_new_method_call("org.freedesktop.systemd1",
"/org/freedesktop/systemd1",
"org.freedesktop.systemd1.Manager",
"StartTransientUnit");
if (!msg) {
error("%s: not enough memory setting dbus msg.", __func__);
return SLURM_ERROR;
}
/*
* Initialize the iterator for appending arguments to the end of the
* message.
*/
dbus_message_iter_init_append(msg, &args_itr);
/* Append our scope name to the arguments. */
if (!dbus_message_iter_append_basic(&args_itr, DBUS_TYPE_STRING,
&scope_name)) {
error("%s: memory couldn't be allocated while appending argument.",
__func__);
return SLURM_ERROR;
}
/*
* Append the scope mode. Normally it is 'fail' or 'replace'.
* Check systemd docs fore more info.
*/
if (!dbus_message_iter_append_basic(&args_itr, DBUS_TYPE_STRING,
&mode)) {
error("%s: memory couldn't be allocated while appending argument.",
__func__);
return SLURM_ERROR;
}
/*
* Start adding specific 'properties' as arguments to our message.
* Properties in this context are systemd unit properties. We're
* interested in adding Delegate=yes, and the PIDs list (stepd's pid)
* which will be moved to this scope container at startup.
*/
if (!_set_scope_properties(&args_itr, pids, npids, true)) {
error("%s: cannot set scope properties, scope not started.",
__func__);
return SLURM_ERROR;
}
/*
* 'Auxiliary units'
* Systemd's StartTransientUnit method signature requires to set this
* and to be null. These are useless parameters but need to be defined.
*/
if (!_set_scope_aux(&args_itr)) {
error("%s: cannot set scope auxiliary units, scope not started.",
__func__);
return SLURM_ERROR;
}
log_flag(CGROUP,"dbus StartTransientUnit msg signature: %s",
dbus_message_get_signature(msg));
/*
* Queue the msg to send and get a handle for the reply.
* -1 is infinite timeout.
*/
if (!dbus_connection_send_with_reply(conn, msg, &pending, -1)) {
error("%s: failed to send dbus message.", __func__);
return SLURM_ERROR;
}
if (!pending) {
error("%s: could not get a handle for dbus reply.", __func__);
return SLURM_ERROR;
}
/* Block until the outgoing message queue is empty. */
dbus_connection_flush(conn);
/* Decrement the ref. count of msg and free it if the cnt is 0. */
dbus_message_unref(msg);
/* Wait for the reply. */
dbus_pending_call_block(pending);
if (!(msg = dbus_pending_call_steal_reply(pending))) {
dbus_connection_unref(conn);
error("%s: cannot start scope, dbus reply msg is null.",
__func__);
return SLURM_ERROR;
}
dbus_pending_call_unref(pending);
dbus_connection_unref(conn);
if (_process_and_close_reply_msg(msg) != SLURM_SUCCESS)
return SLURM_ERROR;
return SLURM_SUCCESS;
}