| /*****************************************************************************\ |
| * 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); |
| } |