blob: 3b641995944a37d106ee89b09cee4db8f9028aa3 [file] [log] [blame]
/*****************************************************************************\
* crontab.c
*****************************************************************************
* 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 <ctype.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "src/common/cron.h"
#include "src/common/log.h"
#include "src/common/pack.h"
#include "src/common/read_config.h"
#include "src/common/slurm_time.h"
#include "src/common/uid.h"
#include "src/common/xmalloc.h"
#include "src/common/xstring.h"
#include "src/slurmctld/slurmctld.h"
typedef struct {
char *alloc_node;
uid_t uid;
gid_t gid;
char **err_msg;
identity_t *id;
char **job_submit_user_msg;
char **failed_lines;
list_t *new_jobs;
uint16_t protocol_version;
int return_code;
} foreach_cron_job_args_t;
static int _handle_job(void *x, void *y)
{
job_desc_msg_t *job = x;
foreach_cron_job_args_t *args = y;
job_record_t *job_ptr = NULL;
char *err_msg = NULL;
dump_job_desc(job);
if (!job->crontab_entry || !valid_cron_entry(job->crontab_entry)) {
error("crontab submission failed due to missing or invalid cron_entry_t");
args->return_code = SLURM_ERROR;
return -1;
}
/*
* The trick to scrontab: use the begin time to gate when the job can
* next run. On requeue, the job will need to recalculate this to
* determine the next valid interval.
*/
job->begin_time = calc_next_cron_start(job->crontab_entry, 0);
/*
* always use the authenticated values from crontab_update_request_msg_t
*/
job->user_id = args->uid;
job->group_id = args->gid;
xfree(job->alloc_node);
job->alloc_node = xstrdup(args->alloc_node);
FREE_NULL_IDENTITY(job->id);
job->id = copy_identity(args->id);
/* enforce this flag so the job submit plugin can differentiate */
job->bitflags |= CRON_JOB;
/* always enable requeue to allow scontrol requeue to work */
job->requeue = 1;
/* give job_submit a chance to play with it first */
args->return_code = validate_job_create_req(job, args->uid, &err_msg);
if (err_msg) {
xstrfmtcat(*args->job_submit_user_msg, "%s\n", err_msg);
xfree(err_msg);
}
if (args->return_code) {
xstrfmtcat(*args->failed_lines, "%u-%u",
((cron_entry_t *) job->crontab_entry)->line_start,
((cron_entry_t *) job->crontab_entry)->line_end);
return -1;
}
args->return_code = job_allocate(job, 0, false, NULL, 0, args->uid,
true, &job_ptr, args->err_msg,
args->protocol_version);
/*
* job_allocate() will return non-terminal error codes.
* job rejection is designated by the job being set to JOB_FAILED
*/
if (job_ptr)
list_push(args->new_jobs, job_ptr);
if (job_ptr && job_ptr->job_state != JOB_FAILED)
args->return_code = SLURM_SUCCESS;
if (args->return_code) {
xstrfmtcat(*args->failed_lines, "%u-%u",
((cron_entry_t *) job->crontab_entry)->line_start,
((cron_entry_t *) job->crontab_entry)->line_end);
return -1;
} else {
xassert(job_ptr->details);
job_ptr->details->crontab_entry = job->crontab_entry;
job->crontab_entry = NULL;
/*
* Ignore user-provided value since this is not guaranteed
* to be in sync with the bitstring data. Reconstruct,
* even though the reconstructed version will be uglier.
*/
xfree(job_ptr->details->crontab_entry->cronspec);
job_ptr->details->crontab_entry->cronspec =
cronspec_from_cron_entry(
job_ptr->details->crontab_entry);
info("Added %pJ from crontab entry from uid=%u, next start is %lu",
job_ptr, job->user_id, job->begin_time);
}
return 0;
}
static int _purge_job(void *x, void *ignored)
{
job_record_t *job_ptr = (job_record_t *) x;
purge_job_record(job_ptr->job_id);
return 0;
}
/*
* Clear the CRON_JOB flag for all jobs by a given user.
*/
static int _clear_requeue_cron(void *x, void *y)
{
job_record_t *job_ptr = x;
uid_t *uid = y;
if ((job_ptr->user_id == *uid) &&
(job_ptr->bit_flags & CRON_JOB)) {
job_ptr->bit_flags &= ~CRON_JOB;
job_state_unset_flag(job_ptr, JOB_REQUEUE);
if (!IS_JOB_RUNNING(job_ptr)) {
time_t now = time(NULL);
job_state_set(job_ptr, JOB_CANCELLED);
job_ptr->start_time = now;
job_ptr->end_time = now;
job_ptr->exit_code = 1;
job_completion_logger(job_ptr, false);
}
}
return 0;
}
static int _set_requeue_cron(void *x, void *y)
{
job_record_t *job_ptr = x;
bool *set = y;
if (*set)
job_ptr->bit_flags |= CRON_JOB;
else
job_ptr->bit_flags &= ~CRON_JOB;
return 0;
}
static int _copy_jobids(void *x, void *y)
{
job_record_t *job_ptr = x;
crontab_update_response_msg_t *response = y;
response->jobids[response->jobids_count++] = job_ptr->job_id;
return 0;
}
extern void crontab_submit(crontab_update_request_msg_t *request,
crontab_update_response_msg_t *response,
char *alloc_node, identity_t *id,
uint16_t protocol_version)
{
foreach_cron_job_args_t args;
char *dir = NULL, *file = NULL;
memset(&args, 0, sizeof(args));
xstrfmtcat(dir, "%s/crontab", slurm_conf.state_save_location);
xstrfmtcat(file, "%s/crontab.%u", dir, request->uid);
(void) mkdir(dir, 0700);
xfree(dir);
memset(response, 0, sizeof(*response));
debug("%s: updating crontab for uid=%u", __func__, request->uid);
if (!request->crontab) {
debug("%s: removing crontab for uid=%u",
__func__, request->uid);
(void) unlink(file);
xfree(file);
} else if (!request->jobs) {
debug("%s: no jobs submitted alongside crontab for uid=%u",
__func__, request->uid);
} else {
/*
* Already authenticated upstream.
*/
args.alloc_node = alloc_node;
args.uid = request->uid;
args.gid = request->gid;
args.err_msg = &response->err_msg;
args.id = id;
args.job_submit_user_msg = &response->job_submit_user_msg;
args.failed_lines = &response->failed_lines;
args.new_jobs = list_create(NULL);
args.protocol_version = protocol_version;
args.return_code = SLURM_SUCCESS;
list_for_each(request->jobs, _handle_job, &args);
response->return_code = args.return_code;
}
/* on submission failure kill all newly created jobs */
if (response->return_code) {
int purged = list_for_each(args.new_jobs, _purge_job, NULL);
debug("%s: failed crontab submission, purged %d records",
__func__, purged);
} else {
bool off = false, on = true;
/*
* Flip the CRON_JOB flag off temporarily to avoid cancelling
* these new jobs.
*/
if (args.new_jobs)
list_for_each(args.new_jobs, _set_requeue_cron, &off);
/* on success, kill/modify old jobs */
list_for_each(job_list, _clear_requeue_cron, &request->uid);
/*
* Flip the flag on now that the old ones have been removed.
*/
if (args.new_jobs) {
list_for_each(args.new_jobs, _set_requeue_cron, &on);
response->jobids = xcalloc(list_count(args.new_jobs),
sizeof(uint32_t));
list_for_each(args.new_jobs, _copy_jobids, response);
}
/*
* save the new file (if defined)
*/
if (request->crontab &&
write_data_to_file(file, request->crontab)) {
error("%s: failed to save file", __func__);
response->return_code = ESLURM_WRITING_TO_FILE;
}
}
xfree(file);
FREE_NULL_LIST(args.new_jobs);
}
extern void crontab_add_disabled_lines(uid_t uid, int line_start, int line_end)
{
char *file = NULL, *lines = NULL;
int fd;
xstrfmtcat(file, "%s/crontab/crontab.%u",
slurm_conf.state_save_location, uid);
xstrfmtcat(lines, "%d-%d,", line_start, line_end);
if ((fd = open(file, O_WRONLY | O_CLOEXEC | O_APPEND)) < 0) {
error("%s: failed to open file `%s`", __func__, file);
xfree(file);
return;
}
safe_write(fd, lines, strlen(lines));
close(fd);
xfree(file);
xfree(lines);
return;
rwfail:
error("%s: failed to append failed lines %d-%d to file `%s`",
__func__, line_start, line_end, file);
close(fd);
xfree(file);
xfree(lines);
}