| /*****************************************************************************\ |
| * scrun.c - Slurm OCI container runtime proxy |
| ***************************************************************************** |
| * 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. |
| \*****************************************************************************/ |
| |
| #define _XOPEN_SOURCE 600 /* putenv(), unsetenv() */ |
| #define _GNU_SOURCE /* getopt_long(), get_current_dir_name() */ |
| #include <ctype.h> |
| #include <fcntl.h> |
| #include <getopt.h> |
| #include <libgen.h> |
| #include <signal.h> |
| #include <stdlib.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| |
| #include "src/common/data.h" |
| #include "src/common/log.h" |
| #include "src/common/proc_args.h" |
| #include "src/common/read_config.h" |
| #include "src/common/ref.h" |
| #include "src/common/setproctitle.h" |
| #include "src/common/uid.h" |
| #include "src/common/xassert.h" |
| #include "src/common/xmalloc.h" |
| #include "src/common/xstring.h" |
| |
| #include "src/interfaces/gres.h" |
| #include "src/interfaces/hash.h" |
| #include "src/interfaces/select.h" |
| #include "src/interfaces/serializer.h" |
| |
| #include "src/scrun/scrun.h" |
| |
| decl_static_data(usage_txt); |
| |
| #define ANCHOR_FILE_TPL "scrun-%s-anchor-%s" |
| |
| static char *slurm_conf_filename = NULL; |
| static int command_requested = -1; |
| log_options_t log_opt = LOG_OPTS_STDERR_ONLY; |
| log_facility_t log_fac = SYSLOG_FACILITY_USER; |
| char *log_file = NULL; |
| char *log_format = NULL; |
| oci_conf_t *oci_conf = NULL; |
| |
| #ifndef OCI_VERSION |
| const char *OCI_VERSION = "1.0.0"; |
| #endif |
| |
| extern void update_logging(void) |
| { |
| bool json = false; |
| int rc; |
| |
| if (!log_file) { |
| /* do nothing */ |
| } else if (!log_format) { |
| json = true; |
| log_opt.logfile_fmt = LOG_FILE_FMT_TIMESTAMP; |
| } else if (!xstrcasecmp(log_format, "json")) { |
| log_opt.logfile_fmt = LOG_FILE_FMT_JSON; |
| } else { |
| fatal("%s: unknown log format %s", |
| __func__, log_format); |
| } |
| |
| if (log_file && (log_opt.logfile_level <= LOG_LEVEL_QUIET)) |
| log_opt.logfile_level = LOG_LEVEL_FATAL; |
| |
| if ((rc = log_alter(log_opt, log_fac, log_file))) |
| fatal("Logging failure: %s", slurm_strerror(rc)); |
| |
| if (json) { |
| /* docker requires RFC3339 timestamps */ |
| log_set_timefmt(LOG_FMT_RFC3339); |
| } |
| } |
| |
| /* |
| * docker containerd example calls: |
| * |
| * runc --root /var/run/docker/runtime-runc/moby |
| * --log /run/containerd/io.containerd.runtime.v2.task/moby/$LONG_HEX/log.json |
| * --log-format json |
| * create |
| * --bundle /run/containerd/io.containerd.runtime.v2.task/moby/$LONG_HEX |
| * --pid-file /run/containerd/io.containerd.runtime.v2.task/moby/$LONG_HEX/init.pid |
| * $LONG_HEX |
| */ |
| |
| static void _parse_create(int argc, char **argv) |
| { |
| #define OPT_LONG_BUNDLE 0x100 |
| #define OPT_LONG_CONSOLE_SOCKET 0x101 |
| #define OPT_LONG_NO_PIVOT 0x102 |
| #define OPT_LONG_NO_NEW_KEYRING 0x103 |
| #define OPT_LONG_PRESERVE_FDS 0x104 |
| #define OPT_LONG_PID_FILE 0x105 |
| |
| static const struct option long_options[] = { |
| { "bundle", required_argument, NULL, OPT_LONG_BUNDLE }, |
| { "console-socket", required_argument, NULL, |
| OPT_LONG_CONSOLE_SOCKET }, |
| { "no-pivot", no_argument, NULL, OPT_LONG_NO_PIVOT }, |
| { "no-new-keyring", no_argument, NULL, |
| OPT_LONG_NO_NEW_KEYRING }, |
| { "preserve-fds", no_argument, NULL, OPT_LONG_PRESERVE_FDS }, |
| { "pid-file", required_argument, NULL, OPT_LONG_PID_FILE }, |
| { NULL, 0, NULL, 0 } |
| }; |
| int option_index = 0; |
| int c = 0; |
| |
| if (get_log_level() >= LOG_LEVEL_DEBUG2) { |
| for (int i = 0; i < argc; i++) |
| debug2("create arg[%d]=%s", i, argv[i]); |
| } |
| |
| while ((c = getopt_long(argc, argv, "b:", long_options, |
| &option_index)) != -1) { |
| switch (c) { |
| case OPT_LONG_BUNDLE: |
| case 'b': |
| xfree(state.bundle); |
| state.bundle = xstrdup(optarg); |
| state.orig_bundle = xstrdup(optarg); |
| break; |
| case OPT_LONG_CONSOLE_SOCKET: |
| xfree(state.console_socket); |
| state.console_socket = xstrdup(optarg); |
| break; |
| case OPT_LONG_NO_PIVOT: |
| debug("WARNING: ignoring --no-pivot argument"); |
| break; |
| case OPT_LONG_NO_NEW_KEYRING: |
| debug("WARNING: ignoring --no-new-keyring argument"); |
| break; |
| case OPT_LONG_PRESERVE_FDS: |
| debug("WARNING: ignoring --preserve-fds argument"); |
| break; |
| case OPT_LONG_PID_FILE: |
| xfree(state.pid_file); |
| state.pid_file = xstrdup(optarg); |
| break; |
| default: |
| fatal("unknown argument: %s", argv[optopt]); |
| } |
| } |
| |
| if (optind == (argc - 1)) { |
| state.id = xstrdup(argv[optind]); |
| } else { |
| fatal("container-id not provided"); |
| } |
| |
| if (optind < (argc - 1)) { |
| fatal("unexpected argument %d: %s", optind, argv[optind]); |
| } |
| |
| if (!state.bundle) { |
| char *dir = get_current_dir_name(); |
| state.bundle = xstrdup(dir); |
| free(dir); |
| } |
| } |
| |
| static void _parse_version(int argc, char **argv) |
| { |
| /* does nothing */ |
| } |
| |
| /* |
| * docker containerd example calls: |
| * |
| * runc --root /var/run/docker/runtime-runc/moby |
| * --log /run/containerd/io.containerd.runtime.v2.task/moby/$LONG_HEX/log.json |
| * --log-format json |
| * start $LONG_HEX |
| * |
| */ |
| static void _parse_start(int argc, char **argv) |
| { |
| if (argc != 2) |
| fatal("Unexpected arguments"); |
| |
| state.id = xstrdup(argv[1]); |
| } |
| |
| static void _parse_state(int argc, char **argv) |
| { |
| if (argc != 2) |
| fatal("Unexpected arguments"); |
| |
| state.id = xstrdup(argv[1]); |
| } |
| |
| /* |
| * scrun --root /tmp/docker-exec/runtime-runc/moby |
| * --log /run/containerd/io.containerd.runtime.v2.task/moby/$LONG_HEX/log.json |
| * --log-format json --systemd-cgroup kill --all $LONG_HEX 9 |
| */ |
| static void _parse_kill(int argc, char **argv) |
| { |
| #define OPT_LONG_ALL 0x100 |
| |
| static const struct option long_options[] = { |
| { "all", no_argument, NULL, OPT_LONG_ALL }, |
| { NULL, 0, NULL, 0 } |
| }; |
| int option_index = 0; |
| int c = 0; |
| int signal; |
| |
| if (get_log_level() >= LOG_LEVEL_DEBUG2) { |
| for (int i = 0; i < argc; i++) |
| debug2("kill arg[%d]=%s", i, argv[i]); |
| } |
| |
| while ((c = getopt_long(argc, argv, "", long_options, |
| &option_index)) != -1) { |
| switch (c) { |
| case OPT_LONG_ALL: |
| debug("WARNING: ignoring --all argument"); |
| break; |
| default: |
| fatal("unknown argument: %s", argv[optopt]); |
| } |
| } |
| |
| if (optind < argc) { |
| state.id = xstrdup(argv[optind]); |
| optind++; |
| debug("container-id=%s", state.id); |
| } else { |
| fatal("container-id not provided"); |
| } |
| |
| if (optind >= argc) { |
| debug("defaulting to SIGTERM"); |
| signal = SIGTERM; |
| } else { |
| const char *s = argv[optind]; |
| |
| if (isdigit(s[0])) { |
| signal = atoi(s); |
| } else { |
| signal = sig_name2num(s); |
| } |
| |
| if ((signal < 1) || (signal >= SIGRTMAX)) |
| fatal("Invalid requested signal: %s", s); |
| |
| optind++; |
| } |
| |
| if (optind < argc) { |
| fatal("unexpected argument %d/%d: %s", |
| optind, argc, argv[optind]); |
| } |
| |
| state.requested_signal = signal; |
| } |
| |
| static void _parse_delete(int argc, char **argv) |
| { |
| #define OPT_LONG_FORCE 0x100 |
| static const struct option long_options[] = { |
| { "force", no_argument, NULL, OPT_LONG_FORCE }, |
| { NULL, 0, NULL, 0 } |
| }; |
| int option_index = 0; |
| int c = 0; |
| |
| if (get_log_level() >= LOG_LEVEL_DEBUG2) { |
| for (int i = 0; i < argc; i++) |
| debug2("delete arg[%d]=%s", i, argv[i]); |
| } |
| |
| while ((c = getopt_long(argc, argv, "f", long_options, &option_index)) != |
| -1) { |
| switch (c) { |
| case OPT_LONG_FORCE: |
| case 'f': |
| state.force = true; |
| break; |
| default: |
| error("%s: unknown argument: %s", |
| __func__, argv[optopt]); |
| } |
| } |
| |
| if (optind == (argc - 1)) { |
| state.id = xstrdup(argv[optind]); |
| } else { |
| fatal("container-id not provided"); |
| } |
| |
| if (optind < (argc - 1)) { |
| fatal("unexpected argument %d: %s", optind, argv[optind]); |
| } |
| } |
| |
| /* |
| * _usage - print a message describing the command line arguments of slurmrestd |
| */ |
| static void _usage(void) |
| { |
| char *txt; |
| static_ref_to_cstring(txt, usage_txt); |
| fprintf(stderr, "%s", txt); |
| xfree(txt); |
| } |
| |
| static void _parse_env(void) |
| { |
| char *buffer = NULL; |
| |
| if ((buffer = getenv("SCRUN_DEBUG"))) { |
| log_opt.stderr_level = log_string2num(buffer); |
| log_opt.syslog_level = log_opt.stderr_level; |
| log_opt.logfile_level = log_opt.stderr_level; |
| |
| if (log_opt.stderr_level <= 0) |
| fatal("Invalid env SCRUN_DEBUG=%s", buffer); |
| |
| update_logging(); |
| |
| debug("%s: SCRUN_DEBUG=%s", |
| __func__, log_num2string(log_opt.stderr_level)); |
| } |
| |
| if ((buffer = getenv("SCRUN_STDERR_DEBUG"))) { |
| log_opt.stderr_level = log_string2num(buffer); |
| |
| if (log_opt.stderr_level <= 0) |
| fatal("Invalid env SCRUN_STDERR_DEBUG=%s", buffer); |
| |
| update_logging(); |
| |
| debug("%s: SCRUN_STDERR_DEBUG=%s", |
| __func__, log_num2string(log_opt.stderr_level)); |
| } |
| |
| if ((buffer = getenv("SCRUN_SYSLOG_DEBUG"))) { |
| log_opt.syslog_level = log_string2num(buffer); |
| |
| if (log_opt.syslog_level <= 0) |
| fatal("Invalid env SCRUN_SYSLOG_DEBUG=%s", buffer); |
| |
| update_logging(); |
| |
| debug("%s: SCRUN_SYSLOG_DEBUG=%s", |
| __func__, log_num2string(log_opt.syslog_level)); |
| } |
| |
| if ((buffer = getenv("SCRUN_FILE_DEBUG"))) { |
| log_opt.logfile_level = log_string2num(buffer); |
| |
| if (log_opt.logfile_level <= 0) |
| fatal("Invalid env SCRUN_FILE_DEBUG=%s", buffer); |
| |
| update_logging(); |
| |
| debug("%s: SCRUN_FILE_DEBUG=%s", |
| __func__, log_num2string(log_opt.logfile_level)); |
| } |
| } |
| |
| /* SIGPIPE handler - mostly a no-op */ |
| static void _sigpipe_handler(int signum) |
| { |
| ssize_t wrote; |
| static const char *msg = "scrun: received SIGPIPE"; |
| /* |
| * Can't use normal logging due to possible dead lock: |
| * debug5("%s: received SIGPIPE", __func__); |
| * |
| * try best effort to log to stderr: |
| */ |
| wrote = write(STDERR_FILENO, msg, strlen(msg)); |
| if (wrote < 0) |
| fatal("%s: unable to log SIGPIPE: %m", __func__); |
| } |
| |
| static void _disable_sigpipe(void) |
| { |
| struct sigaction sigpipe_handler = { |
| .sa_handler = _sigpipe_handler, |
| }; |
| |
| if (sigaction(SIGPIPE, &sigpipe_handler, NULL) == -1) |
| fatal("%s: unable to control SIGPIPE: %m", __func__); |
| } |
| |
| /* |
| * Get the path to the assigned unix socket for the container. |
| * populates state.anchor_socket if not already set. |
| */ |
| static void _get_anchor_socket(void) |
| { |
| char *socket, *user; |
| uid_t uid = getuid(); |
| slurm_hash_t hash = { |
| .type = HASH_PLUGIN_K12, |
| }; |
| |
| xassert(!state.anchor_socket); |
| |
| if (!(user = uid_to_string_or_null(uid))) |
| fatal("Unable to lookup user name for uid:%u", uid); |
| |
| socket = xstrdup_printf(ANCHOR_FILE_TPL, user, state.id); |
| |
| /* |
| * Container ids are arbitrarily long but unix sockets have a very fixed |
| * max. so generate a nice unique string and then hash it to have the |
| * anchor socket path. |
| */ |
| hash_g_compute(socket, strlen(socket), NULL, 0, &hash); |
| |
| state.anchor_socket = xstrdup_printf( |
| "%s/%02x%02x%02x%02x%02x%02x%02x%02x%02x", state.root_dir, |
| (int) hash.hash[0], (int) hash.hash[1], (int) hash.hash[2], |
| (int) hash.hash[3], (int) hash.hash[4], (int) hash.hash[5], |
| (int) hash.hash[6], (int) hash.hash[7], (int) hash.hash[8]); |
| |
| debug("%s: anchor socket hash: %s -> %s", |
| __func__, socket, state.anchor_socket); |
| |
| xfree(socket); |
| xfree(user); |
| } |
| |
| static const struct { |
| char *command; |
| void (*parse)(int argc, char **argv); |
| int (*func)(void); |
| bool get_anchor_socket; |
| } commands[] = { |
| { "create", _parse_create, command_create, true }, |
| { "start", _parse_start, command_start, true }, |
| { "state", _parse_state, command_state, true }, |
| { "kill", _parse_kill, command_kill, true }, |
| { "delete", _parse_delete, command_delete, true }, |
| { "version", _parse_version, command_version, false }, |
| }; |
| |
| /* |
| * _parse_commandline - parse and process any command line arguments |
| * IN argc - number of command line arguments |
| * IN argv - the command line arguments |
| * IN/OUT conf_ptr - pointer to current configuration, update as needed |
| */ |
| static int _parse_commandline(int argc, char **argv) |
| { |
| #define OPT_LONG_CGROUP_MANAGER 0x100 |
| #define OPT_LONG_DEBUG 0x101 |
| #define OPT_LONG_LOG_FILE 0x102 |
| #define OPT_LONG_LOG_FORMAT 0x103 |
| #define OPT_LONG_ROOT 0x104 |
| #define OPT_LONG_ROOTLESS 0x105 |
| #define OPT_LONG_SYSTEMD_CGROUP 0x106 |
| #define OPT_LONG_HELP 0x107 |
| #define OPT_LONG_USAGE 0x108 |
| #define OPT_LONG_VERSION 0x109 |
| |
| static const struct option long_options[] = { |
| { "cgroup-manager", required_argument, NULL, |
| OPT_LONG_CGROUP_MANAGER }, |
| { "debug", no_argument, NULL, OPT_LONG_DEBUG }, |
| { "log", required_argument, NULL, OPT_LONG_LOG_FILE }, |
| { "log-format", required_argument, NULL, OPT_LONG_LOG_FORMAT }, |
| { "root", required_argument, NULL, OPT_LONG_ROOT }, |
| { "rootless", required_argument, NULL, OPT_LONG_ROOTLESS }, |
| { "systemd-cgroup", no_argument, NULL, |
| OPT_LONG_SYSTEMD_CGROUP }, |
| { "help", no_argument, NULL, OPT_LONG_HELP }, |
| { "usage", no_argument, NULL, OPT_LONG_USAGE }, |
| { "version", no_argument, NULL, OPT_LONG_VERSION }, |
| { NULL, 0, NULL, 0 } |
| }; |
| int option_index = 0; |
| int c = 0; |
| int index; |
| |
| /* stop processing on first non-arg in getopt_long() */ |
| if (putenv("POSIXLY_CORRECT=1")) |
| fatal("Unable to set POSIXLY_CORRECT in environment: %m"); |
| |
| optind = 0; |
| while ((c = getopt_long(argc, argv, "f:vV?", long_options, |
| &option_index)) != -1) { |
| switch (c) { |
| case OPT_LONG_CGROUP_MANAGER: |
| debug("WARNING: ignoring --cgroup-manager argument"); |
| break; |
| case OPT_LONG_LOG_FILE : |
| xfree(log_file); |
| log_file = xstrdup(optarg); |
| debug("%s: logging to %s", __func__, log_file); |
| break; |
| case OPT_LONG_LOG_FORMAT: |
| xfree(log_format); |
| log_format = xstrdup(optarg); |
| break; |
| case 'f': |
| xfree(slurm_conf_filename); |
| slurm_conf_filename = xstrdup(optarg); |
| break; |
| case OPT_LONG_DEBUG: |
| log_opt.stderr_level = LOG_LEVEL_DEBUG; |
| break; |
| case 'v': |
| log_opt.stderr_level++; |
| break; |
| case OPT_LONG_VERSION: |
| exit(command_version()); |
| case 'V': |
| break; |
| case OPT_LONG_ROOT: |
| xfree(state.root_dir); |
| state.root_dir = xstrdup(optarg); |
| break; |
| case OPT_LONG_ROOTLESS: |
| debug("WARNING: ignoring --rootless argument"); |
| break; |
| case OPT_LONG_SYSTEMD_CGROUP: |
| debug("WARNING: ignoring --systemd-cgroup argument"); |
| break; |
| case '?': |
| case OPT_LONG_HELP: |
| case OPT_LONG_USAGE: |
| _usage(); |
| exit(0); |
| default: |
| _usage(); |
| exit(1); |
| } |
| } |
| |
| if (optind >= argc) |
| fatal("command not provided"); |
| |
| for (int i = 0; i < ARRAY_SIZE(commands); i++) { |
| if (!xstrcasecmp(argv[optind], commands[i].command)) { |
| command_requested = i; |
| break; |
| } |
| } |
| |
| if (command_requested == -1) |
| fatal("unknown command: %s", argv[optind]); |
| |
| /* return next value to start parsing again */ |
| index = optind + 1; |
| |
| /* clear all parsing state for next run */ |
| optarg = NULL; |
| optopt = 0; |
| optind = 0; |
| |
| /* do not unset POSIXLY_CORRECT as later parsing will get corrupted */ |
| |
| return index; |
| } |
| |
| static int _try_tmp_path(const char *path) |
| { |
| int rc; |
| |
| /* |
| * Attempt to verify a given path to act as --root. This check is not |
| * perfect and could easily be considered a TOCTOU violation. That is |
| * not the purpose of this check since this path must be valid for all |
| * calls to scrun and the actually security is checked later when |
| * attempting to either create the spool dir or to access the anchor |
| * socket. |
| */ |
| |
| if ((rc = access(path, W_OK|R_OK))) { |
| debug("%s: access to %s denied: %m", __func__, path); |
| return rc; |
| } |
| |
| debug("%s: access to %s allowed", __func__, path); |
| |
| /* update root path */ |
| xfree(state.root_dir); |
| state.root_dir = xstrdup(path); |
| |
| return rc; |
| } |
| |
| static void _set_root() |
| { |
| const char *epath; |
| char *path; |
| int rc; |
| |
| /* |
| * Attempt to guess best place for root since scrun may be in a user |
| * namespace. Based partially on |
| * https://refspecs.linuxfoundation.org/fhs.shtml |
| */ |
| |
| /* Try systemd provided tmpdir first */ |
| if ((epath = getenv("XDG_RUNTIME_DIR")) && !_try_tmp_path(epath)) |
| return; |
| |
| if (!getuid()) |
| fatal("scrun is being run as root and is likely inside of a username space. Refusing to guess path for --root. It must be explicitly provided."); |
| |
| path = xstrdup_printf("/run/user/%d/", getuid()); |
| rc = _try_tmp_path(path); |
| xfree(path); |
| if (!rc) |
| return; |
| |
| if ((epath = getenv("TMPDIR"))) { |
| /* assume this is not user specific tmpdir */ |
| path = xstrdup_printf("%s/%d/", epath, getuid()); |
| rc = _try_tmp_path(path); |
| xfree(path); |
| if (!rc) |
| return; |
| } |
| |
| fatal("Unable to determine value for --root. It must be explicitly provided."); |
| } |
| |
| extern int main(int argc, char **argv) |
| { |
| int rc = SLURM_SUCCESS; |
| int argv_offset; |
| char **command_argv; |
| int command_argc; |
| |
| if (log_init(xbasename(argv[0]), log_opt, log_fac, log_file)) |
| fatal("Unable to setup logging: %m"); |
| |
| init_setproctitle(argc, argv); |
| _parse_env(); |
| argv_offset = _parse_commandline(argc, argv) - 1; |
| |
| slurm_init(slurm_conf_filename); |
| if ((rc = get_oci_conf(&oci_conf))) |
| fatal("%s: unable to load oci.conf: %s", |
| __func__, slurm_strerror(rc)); |
| |
| init_state(); |
| |
| if (!state.root_dir || !state.root_dir[0]) |
| _set_root(); |
| |
| serializer_required(MIME_TYPE_JSON); |
| |
| if (get_log_level() >= LOG_LEVEL_DEBUG2) { |
| for (int i = 0; i < argc; i++) |
| debug2("%s: %s argv[%d]=%s", |
| __func__, xbasename(argv[0]), i, argv[i]); |
| } |
| |
| /* extract command from arguments */ |
| command_argc = argc - argv_offset; |
| command_argv = xcalloc(command_argc, sizeof(*command_argv)); |
| command_argv[0] = argv[0]; |
| for (int j = 1, i = (argv_offset + 1); i < argc; i++, j++) |
| command_argv[j] = argv[i]; |
| |
| _disable_sigpipe(); |
| xassert(!state.id); |
| |
| commands[command_requested].parse(command_argc, command_argv); |
| |
| if (commands[command_requested].get_anchor_socket) { |
| xassert(state.id && state.id[0]); |
| _get_anchor_socket(); |
| } |
| |
| rc = commands[command_requested].func(); |
| |
| debug("exiting[%d]=%s", rc, slurm_strerror(rc)); |
| |
| #ifdef MEMORY_LEAK_DEBUG |
| destroy_state(); |
| FREE_NULL_OCI_CONF(oci_conf); |
| xfree(slurm_conf_filename); |
| xfree(command_argv); |
| fini_setproctitle(); |
| slurm_fini(); |
| log_fini(); |
| #endif /* MEMORY_LEAK_DEBUG */ |
| |
| return rc; |
| } |