blob: 4c3fafe704fde11282fa662ad8f1d34843dd60ad [file] [log] [blame] [edit]
/*****************************************************************************\
* xlate.c - translate #BSUB and #PBS options for sbatch
*****************************************************************************
* Copyright (C) 2002-2007 The Regents of the University of California.
* Copyright (C) 2008-2010 Lawrence Livermore National Security.
* Copyright (C) SchedMD LLC.
* Produced at Lawrence Livermore National Laboratory (cf, DISCLAIMER).
* Written by Mark Grondona <grondona1@llnl.gov>, et. al.
* CODE-OCEC-09-009. All rights reserved.
*
* 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 "config.h"
#define _GNU_SOURCE
#include <ctype.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h> /* getenv */
#include <sys/types.h>
#include <unistd.h>
#include "slurm/slurm.h"
#include "src/common/cpu_frequency.h"
#include "src/common/log.h"
#include "src/common/parse_time.h"
#include "src/common/proc_args.h"
#include "src/common/uid.h"
#include "src/common/xmalloc.h"
#include "src/common/xstring.h"
#include "src/common/util-net.h"
#include "src/sbatch/opt.h"
/* Wrapper functions */
static void _set_pbs_options(int argc, char **argv);
static void _set_bsub_options(int argc, char **argv);
static char *_xlate_pbs_mail_type(const char *arg);
static void _parse_pbs_resource_list(char *rl);
/*
* set wrapper (ie. pbs, bsub) options from batch script
*
* Build an argv-style array of options from the script "body",
* then pass the array to _set_*_options for further parsing.
*/
extern bool xlate_batch_script(const char *file, const void *body,
int size, int magic)
{
char *magic_word;
void (*wrp_func) (int,char**) = NULL;
int magic_word_len;
int argc;
char **argv;
void *state = NULL;
char *line;
char *option;
char *ptr;
int skipped = 0;
int lineno = 0;
int non_comments = 0;
int i;
bool found = false;
/* Check what command it is */
switch (magic) {
case WRPR_BSUB:
magic_word = "#BSUB";
wrp_func = _set_bsub_options;
break;
case WRPR_PBS:
magic_word = "#PBS";
wrp_func = _set_pbs_options;
break;
default:
return false;
}
magic_word_len = strlen(magic_word);
/* getopt_long skips over the first argument, so fill it in */
argc = 1;
argv = xmalloc(sizeof(char *));
argv[0] = "sbatch";
while ((line = next_line(body, size, &state)) != NULL) {
lineno++;
if (xstrncmp(line, magic_word, magic_word_len) != 0) {
if (line[0] != '#')
non_comments++;
xfree(line);
if (non_comments > 100)
break;
continue;
}
/* Set found to be true since we found a valid command */
found = true;
/* this line starts with the magic word */
ptr = line + magic_word_len;
while ((option = get_argument(file, lineno, ptr, &skipped))) {
debug2("Found in script, argument \"%s\"", option);
argc += 1;
xrealloc(argv, sizeof(char*) * argc);
/* Only check the even options here (they are
* the - options) */
if (magic == WRPR_BSUB && !(argc%2)) {
/* Since Slurm doesn't allow long
* names with a single '-' we must
* translate before hand.
*/
if (!xstrcmp("-cwd", option)) {
xfree(option);
option = xstrdup("-c");
}
}
argv[argc-1] = option;
ptr += skipped;
}
xfree(line);
}
if ((argc > 0) && (wrp_func != NULL))
wrp_func(argc, argv);
for (i = 1; i < argc; i++)
xfree(argv[i]);
xfree(argv);
return found;
}
static void _set_bsub_options(int argc, char **argv) {
int opt_char, option_index = 0;
char *bsub_opt_string = "+c:e:J:m:M:n:o:q:W:x";
char *char_ptr;
struct option bsub_long_options[] = {
{"cwd", required_argument, 0, 'c'},
{"error_file", required_argument, 0, 'e'},
{"job_name", required_argument, 0, 'J'},
{"hostname", required_argument, 0, 'm'},
{"memory_limit", required_argument, 0, 'M'},
{"output_file", required_argument, 0, 'o'},
{"queue_name", required_argument, 0, 'q'},
{"time", required_argument, 0, 'W'},
{"exclusive", no_argument, 0, 'x'},
{NULL, 0, 0, 0}
};
optind = 0;
while ((opt_char = getopt_long(argc, argv, bsub_opt_string,
bsub_long_options, &option_index))
!= -1) {
int xlate_val = 0;
char *xlate_arg = NULL;
switch (opt_char) {
case 'c':
xlate_val = 'D';
xlate_arg = xstrdup(optarg);
break;
/* These options all have a direct correspondence. */
case 'e':
case 'J':
case 'o':
xlate_val = opt_char;
xlate_arg = xstrdup(optarg);
break;
case 'm':
xlate_val = 'w';
xlate_arg = xstrdup(optarg);
/*
* Since BSUB uses a list of space separated hosts,
* we need to replace the spaces with commas.
*/
while ((char_ptr = strstr(xlate_arg, " ")))
*char_ptr = ',';
break;
case 'M':
xlate_val = LONG_OPT_MEM_PER_CPU;
xlate_arg = xstrdup(optarg);
/* set the default unit to KB to match LSF behavior */
if (isdigit(optarg[strlen(optarg) - 1]))
xstrcat(xlate_arg, "K");
break;
case 'n':
/* Since it is valid in bsub to give a min and
* max task count we will only read the max if
* it exists.
*/
char_ptr = strstr(optarg, ",");
if (char_ptr) {
char_ptr++;
if (!char_ptr[0]) {
error("#BSUB -n format not correct "
"given: '%s'",
optarg);
exit(error_exit);
}
} else
char_ptr = optarg;
xlate_val = 'n';
xlate_arg = xstrdup(char_ptr);
break;
case 'q':
xlate_val = 'p';
xlate_arg = xstrdup(optarg);
break;
case 'W':
xlate_val = 't';
xlate_arg = xstrdup(optarg);
break;
case 'x':
xlate_val = LONG_OPT_EXCLUSIVE;
break;
default:
error("Unrecognized command line parameter %c",
opt_char);
exit(error_exit);
}
if (xlate_val)
slurm_process_option_or_exit(&opt, xlate_val, xlate_arg,
false, false);
xfree(xlate_arg);
}
if (optind < argc) {
error("Invalid argument: %s", argv[optind]);
exit(error_exit);
}
}
static void _set_pbs_options(int argc, char **argv)
{
int opt_char, option_index = 0;
char *pbs_opt_string = "+a:A:c:C:d:e:hIj:J:k:l:m:M:N:o:p:q:r:S:t:u:v:VW:w:z";
struct option pbs_long_options[] = {
{"start_time", required_argument, 0, 'a'},
{"account", required_argument, 0, 'A'},
{"checkpoint", required_argument, 0, 'c'},
{"working_dir", required_argument, 0, 'C'},
{"error", required_argument, 0, 'e'},
{"hold", no_argument, 0, 'h'},
{"interactive", no_argument, 0, 'I'},
{"join", optional_argument, 0, 'j'},
{"job_array", required_argument, 0, 'J'},
{"keep", required_argument, 0, 'k'},
{"resource_list", required_argument, 0, 'l'},
{"mail_options", required_argument, 0, 'm'},
{"mail_user_list", required_argument, 0, 'M'},
{"job_name", required_argument, 0, 'N'},
{"out", required_argument, 0, 'o'},
{"priority", required_argument, 0, 'p'},
{"destination", required_argument, 0, 'q'},
{"rerunable", required_argument, 0, 'r'},
{"script_path", required_argument, 0, 'S'},
{"array", required_argument, 0, 't'},
{"running_user", required_argument, 0, 'u'},
{"variable_list", required_argument, 0, 'v'},
{"all_env", no_argument, 0, 'V'},
{"attributes", required_argument, 0, 'W'},
{"no_std", no_argument, 0, 'z'},
{NULL, 0, 0, 0}
};
optind = 0;
while ((opt_char = getopt_long(argc, argv, pbs_opt_string,
pbs_long_options, &option_index))
!= -1) {
int xlate_val = 0;
char *xlate_arg = NULL;
switch (opt_char) {
case 'a':
xlate_val = 'b';
xlate_arg = xstrdup(optarg);
break;
/* These options all have a direct correspondence. */
case 'A':
case 'e':
case 'o':
xlate_val = opt_char;
xlate_arg = xstrdup(optarg);
break;
case 'c':
break;
case 'C':
break;
case 'w':
case 'd':
xlate_val = 'D';
xlate_arg = xstrdup(optarg);
break;
case 'h':
xlate_val = 'H';
break;
case 'I':
break;
case 'j':
break;
case 'J':
case 't':
/* PBS Pro uses -J. Torque uses -t. */
xlate_val = 'a';
xlate_arg = xstrdup(optarg);
break;
case 'k':
break;
case 'l':
_parse_pbs_resource_list(optarg);
break;
case 'm':
if (!optarg) /* CLANG Fix */
break;
xlate_val = LONG_OPT_MAIL_TYPE;
xlate_arg = _xlate_pbs_mail_type(optarg);
break;
case 'M':
xlate_val = LONG_OPT_MAIL_USER;
xlate_arg = xstrdup(optarg);
break;
case 'N':
xlate_val = 'J';
xlate_arg = xstrdup(optarg);
break;
case 'p':
xlate_val = LONG_OPT_NICE;
xlate_arg = xstrdup(optarg);
break;
case 'q':
xlate_val = 'p';
xlate_arg = xstrdup(optarg);
break;
case 'r':
break;
case 'S':
break;
case 'u':
break;
case 'v':
xlate_val = LONG_OPT_EXPORT;
xlate_arg = xstrdup(opt.export_env);
if (xlate_arg)
xstrcat(xlate_arg, ",");
xstrcat(xlate_arg, optarg);
break;
case 'V':
break;
case 'W':
if (!optarg) /* CLANG Fix */
break;
if (!xstrncasecmp(optarg, "umask=", 6)) {
xlate_val = LONG_OPT_UMASK;
xlate_arg = xstrdup(optarg+6);
} else if (!xstrncasecmp(optarg, "depend=", 7)) {
xlate_val = 'd';
xlate_arg = xstrdup(optarg+7);
} else {
verbose("Ignored PBS attributes: %s", optarg);
}
break;
case 'z':
break;
default:
error("Unrecognized command line parameter %c",
opt_char);
exit(error_exit);
}
if (xlate_val)
slurm_process_option_or_exit(&opt, xlate_val, xlate_arg,
false, false);
xfree(xlate_arg);
}
if (optind < argc) {
error("Invalid argument: %s", argv[optind]);
exit(error_exit);
}
}
static char *_get_pbs_node_name(char *node_options, int *i)
{
int start = (*i);
char *value = NULL;
while (node_options[*i] &&
(node_options[*i] != '+') &&
(node_options[*i] != ':'))
(*i)++;
value = xmalloc((*i)-start+1);
memcpy(value, node_options+start, (*i)-start);
if (node_options[*i])
(*i)++;
return value;
}
static void _get_next_pbs_node_part(char *node_options, int *i)
{
while (node_options[*i] &&
(node_options[*i] != '+') &&
(node_options[*i] != ':'))
(*i)++;
if (node_options[*i])
(*i)++;
}
static void _parse_pbs_nodes_opts(char *node_opts)
{
int i = 0;
char *temp = NULL;
int ppn = 0;
int node_cnt = 0;
hostlist_t *hl = hostlist_create(NULL);
while (node_opts[i]) {
if (!xstrncmp(node_opts+i, "gpus=", 5)) {
i += 5;
temp = _get_pbs_node_name(node_opts, &i);
slurm_process_option_or_exit(&opt,
LONG_OPT_GPUS_PER_NODE,
temp, false, false);
xfree(temp);
} else if (!xstrncmp(node_opts+i, "ppn=", 4)) {
i+=4;
ppn += strtol(node_opts+i, NULL, 10);
_get_next_pbs_node_part(node_opts, &i);
} else if (isdigit(node_opts[i])) {
node_cnt += strtol(node_opts+i, NULL, 10);
_get_next_pbs_node_part(node_opts, &i);
} else if (isalpha(node_opts[i])) {
temp = _get_pbs_node_name(node_opts, &i);
hostlist_push_host(hl, temp);
xfree(temp);
} else
i++;
}
if (!node_cnt)
node_cnt = 1;
else {
char *nodes = xstrdup_printf("%d", node_cnt);
slurm_process_option_or_exit(&opt, 'N', nodes, false, false);
xfree(nodes);
}
if (ppn) {
char *ntasks;
ppn *= node_cnt;
ntasks = xstrdup_printf("%d", ppn);
slurm_process_option_or_exit(&opt, 'n', ntasks, false, false);
}
if (hostlist_count(hl) > 0) {
char *nodelist = hostlist_ranged_string_xmalloc(hl);
slurm_process_option_or_exit(&opt, 'w', nodelist, false, false);
xfree(nodelist);
}
hostlist_destroy(hl);
}
static void _get_next_pbs_option(char *pbs_options, int *i)
{
while (pbs_options[*i] && pbs_options[*i] != ',')
(*i)++;
if (pbs_options[*i])
(*i)++;
}
static char *_get_pbs_option_value(char *pbs_options, int *i, char sep)
{
int start = (*i);
char *value = NULL;
while (pbs_options[*i] && pbs_options[*i] != sep)
(*i)++;
value = xmalloc((*i)-start+1);
memcpy(value, pbs_options+start, (*i)-start);
if (pbs_options[*i])
(*i)++;
return value;
}
static void _parse_pbs_resource_list(char *rl)
{
int i = 0;
int gpus = 0;
char *temp = NULL;
int pbs_pro_flag = 0; /* Bits: select:1 ncpus:2 mpiprocs:4 */
while (rl[i]) {
if (!xstrncasecmp(rl+i, "accelerator=", 12)) {
i += 12;
if (!xstrncasecmp(rl+i, "true", 4) && (gpus < 1))
gpus = 1;
/* Also see "naccelerators=" below */
} else if (!xstrncmp(rl+i, "arch=", 5)) {
i+=5;
_get_next_pbs_option(rl, &i);
} else if (!xstrncmp(rl+i, "cput=", 5)) {
i+=5;
temp = _get_pbs_option_value(rl, &i, ',');
if (!temp) {
error("No value given for cput");
exit(error_exit);
}
slurm_process_option_or_exit(&opt, 't', temp, false,
false);
xfree(temp);
} else if (!xstrncmp(rl+i, "file=", 5)) {
int end = 0;
i+=5;
temp = _get_pbs_option_value(rl, &i, ',');
if (!temp) {
error("No value given for file");
exit(error_exit);
}
end = strlen(temp) - 1;
if (toupper(temp[end]) == 'B') {
/* In Torque they do GB or MB on the
* end of size, we just want G or M so
* we will remove the b on the end
*/
temp[end] = '\0';
}
slurm_process_option_or_exit(&opt, LONG_OPT_TMP, temp,
false, false);
xfree(temp);
} else if (!xstrncmp(rl+i, "host=", 5)) {
i+=5;
_get_next_pbs_option(rl, &i);
} else if (!xstrncmp(rl+i, "mem=", 4)) {
int end = 0;
i+=4;
temp = _get_pbs_option_value(rl, &i, ',');
if (!temp) {
error("No value given for mem");
exit(error_exit);
}
end = strlen(temp) - 1;
if (toupper(temp[end]) == 'B') {
/* In Torque they do GB or MB on the
* end of size, we just want G or M so
* we will remove the b on the end
*/
temp[end] = '\0';
}
slurm_process_option_or_exit(&opt, LONG_OPT_MEM, temp,
false, false);
xfree(temp);
} else if (!xstrncasecmp(rl+i, "mpiprocs=", 9)) {
i += 9;
temp = _get_pbs_option_value(rl, &i, ':');
if (temp) {
pbs_pro_flag |= 4;
slurm_process_option_or_exit(
&opt, LONG_OPT_NTASKSPERNODE, temp,
false, false);
xfree(temp);
}
} else if (!xstrncasecmp(rl+i, "naccelerators=", 14)) {
i += 14;
temp = _get_pbs_option_value(rl, &i, ',');
if (temp) {
gpus = parse_int("naccelerators", temp, true);
xfree(temp);
}
} else if (!xstrncasecmp(rl+i, "ncpus=", 6)) {
i += 6;
temp = _get_pbs_option_value(rl, &i, ':');
if (temp) {
pbs_pro_flag |= 2;
slurm_process_option_or_exit(&opt,
LONG_OPT_MINCPUS,
temp, false,
false);
xfree(temp);
}
} else if (!xstrncmp(rl+i, "nice=", 5)) {
i += 5;
temp = _get_pbs_option_value(rl, &i, ',');
slurm_process_option_or_exit(&opt, LONG_OPT_NICE, temp,
false, false);
xfree(temp);
} else if (!xstrncmp(rl+i, "nodes=", 6)) {
i+=6;
temp = _get_pbs_option_value(rl, &i, ',');
if (!temp) {
error("No value given for nodes");
exit(error_exit);
}
_parse_pbs_nodes_opts(temp);
xfree(temp);
} else if (!xstrncmp(rl+i, "opsys=", 6)) {
i+=6;
_get_next_pbs_option(rl, &i);
} else if (!xstrncmp(rl+i, "other=", 6)) {
i+=6;
_get_next_pbs_option(rl, &i);
} else if (!xstrncmp(rl+i, "pcput=", 6)) {
i+=6;
temp = _get_pbs_option_value(rl, &i, ',');
if (!temp) {
error("No value given for pcput");
exit(error_exit);
}
slurm_process_option_or_exit(&opt, 't', temp, false,
false);
xfree(temp);
} else if (!xstrncmp(rl+i, "pmem=", 5)) {
i+=5;
_get_next_pbs_option(rl, &i);
} else if (!xstrncmp(rl+i, "proc=", 5)) {
i += 5;
temp = _get_pbs_option_value(rl, &i, ',');
if (opt.constraint)
xstrfmtcat(temp, ",%s", opt.constraint);
slurm_process_option_or_exit(&opt, 'C', temp, false,
false);
xfree(temp);
_get_next_pbs_option(rl, &i);
} else if (!xstrncmp(rl+i, "pvmem=", 6)) {
i+=6;
_get_next_pbs_option(rl, &i);
} else if (!xstrncasecmp(rl+i, "select=", 7)) {
i += 7;
temp = _get_pbs_option_value(rl, &i, ':');
if (temp) {
pbs_pro_flag |= 1;
slurm_process_option_or_exit(&opt, 'N', temp,
false, false);
xfree(temp);
}
} else if (!xstrncmp(rl+i, "software=", 9)) {
i+=9;
_get_next_pbs_option(rl, &i);
} else if (!xstrncmp(rl+i, "vmem=", 5)) {
i+=5;
_get_next_pbs_option(rl, &i);
} else if (!xstrncmp(rl+i, "walltime=", 9)) {
i+=9;
temp = _get_pbs_option_value(rl, &i, ',');
if (!temp) {
error("No value given for walltime");
exit(error_exit);
}
slurm_process_option_or_exit(&opt, 't', temp, false,
false);
xfree(temp);
} else
i++;
}
if ((pbs_pro_flag == 7) && (opt.pn_min_cpus > opt.ntasks_per_node)) {
/* This logic will allocate the proper CPU count on each
* node if the CPU count per node is evenly divisible by
* the task count on each node. Slurm can't handle something
* like cpus_per_node=10 and ntasks_per_node=8 */
int cpus_per_task = opt.pn_min_cpus / opt.ntasks_per_node;
temp = xstrdup_printf("%d", cpus_per_task);
slurm_process_option_or_exit(&opt, 'c', temp, false, false);
xfree(temp);
}
if (gpus > 0) {
if (opt.gres)
temp = xstrdup_printf("%s,gpu:%d", opt.gres, gpus);
else
temp = xstrdup_printf("gpu:%d", gpus);
slurm_process_option_or_exit(&opt, LONG_OPT_GRES, temp, false,
false);
xfree(temp);
}
}
static char *_xlate_pbs_mail_type(const char *arg)
{
char *xlated = NULL;
if (strchr(arg, 'b') || strchr(arg, 'B'))
xstrfmtcat(xlated, "%sBEGIN", (xlated ? "," : ""));
if (strchr(arg, 'e') || strchr(arg, 'E'))
xstrfmtcat(xlated, "%sEND", (xlated ? "," : ""));
if (strchr(arg, 'a') || strchr(arg, 'A'))
xstrfmtcat(xlated, "%sFAIL", (xlated ? "," : ""));
if (strchr(arg, 'n') || strchr(arg, 'N')) {
xfree(xlated);
xlated = xstrdup("NONE");
}
return xlated;
}