/*****************************************************************************\ * crontab.c ***************************************************************************** * Copyright (C) 2020 SchedMD LLC. * Written by Tim Wickberg * * This file is part of Slurm, a resource management program. * For details, see . * 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 #include #include #include #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; char **failed_lines; List 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 = (job_desc_msg_t *) x; foreach_cron_job_args_t *args = (foreach_cron_job_args_t *) y; job_record_t *job_ptr = 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); /* * 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); /* enforce this flag so the job submit plugin can differentiate */ job->bitflags |= CRON_JOB; /* give job_submit a chance to play with it first */ args->return_code = validate_job_create_req(job, args->uid, args->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 JobId=%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 JOB_REQUEUE_CRON for all jobs by a given user. */ static int _clear_requeue_cron(void *x, void *y) { job_record_t *job_ptr = (job_record_t *) x; uid_t *uid = (uid_t *) y; if ((job_ptr->user_id == *uid) && (job_ptr->bit_flags & CRON_JOB)) { job_ptr->bit_flags &= ~CRON_JOB; job_ptr->job_state &= ~JOB_REQUEUE; if (!IS_JOB_RUNNING(job_ptr)) { time_t now = time(NULL); job_ptr->job_state = 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 = (job_record_t *) x; bool *set = (bool *) 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 = (job_record_t *) x; crontab_update_response_msg_t *response = (crontab_update_response_msg_t *) 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, 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.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); return; }