| /*****************************************************************************\ | |
| * parse.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 <getopt.h> | |
| #include <unistd.h> | |
| #include "src/common/bitstring.h" | |
| #include "src/common/cron.h" | |
| #include "src/common/fetch_config.h" | |
| #include "src/common/log.h" | |
| #include "src/common/read_config.h" | |
| #include "src/common/slurm_time.h" | |
| #include "src/common/xmalloc.h" | |
| #include "src/common/xstring.h" | |
| #include "scrontab.h" | |
| static int _parse_range(bitstr_t *b, char **pos) | |
| { | |
| char *end = *pos; | |
| char save; | |
| while (isdigit((int) *end) || *end == '-' || *end == ',') | |
| end++; | |
| save = *end; | |
| *end = '\0'; | |
| if (bit_unfmt(b, *pos)) { | |
| *end = save; | |
| return SLURM_ERROR; | |
| } | |
| *end = save; | |
| *pos = end; | |
| return SLURM_SUCCESS; | |
| } | |
| /* | |
| * Handle the a step value if present on the line. | |
| * Advance string pointer to after the step value. | |
| */ | |
| static int _handle_step(bitstr_t *b, int start, char **pos) | |
| { | |
| char *endptr; | |
| long stepval; | |
| bitstr_t *mask; | |
| if (**pos != '/') | |
| return SLURM_SUCCESS; | |
| (*pos)++; | |
| stepval = strtol(*pos, &endptr, 10); | |
| if (*pos == endptr || stepval <= 1 || stepval >= bit_size(b)) { | |
| error("bad step value"); | |
| return SLURM_ERROR; | |
| } | |
| *pos = endptr; | |
| mask = bit_alloc(bit_size(b)); | |
| for (int i = start; i < bit_size(mask); i += stepval) | |
| bit_set(mask, i); | |
| bit_and(b, mask); | |
| FREE_NULL_BITMAP(mask); | |
| return SLURM_SUCCESS; | |
| } | |
| extern cron_entry_t *cronspec_to_bitstring(char *pos) | |
| { | |
| /* save initial string position for later */ | |
| char *pos_init = pos; | |
| cron_entry_t *entry = new_cron_entry(); | |
| if (*pos == '@') { | |
| pos++; | |
| if (!strncasecmp(pos, "yearly", 6) || | |
| !strncasecmp(pos, "annually", 8)) { | |
| /* "0 0 1 1 *" */ | |
| bit_set(entry->minute, 0); | |
| bit_set(entry->hour, 0); | |
| bit_set(entry->day_of_month, 1); | |
| bit_set(entry->month, 1); | |
| bit_set_all(entry->day_of_week); | |
| entry->flags |= CRON_WILD_DOW; | |
| if (!strncasecmp(pos, "yearly", 6)) | |
| pos += 6; | |
| else | |
| pos += 8; | |
| } else if (!strncasecmp(pos, "monthly", 7)) { | |
| /* "0 0 1 * *" */ | |
| bit_set(entry->minute, 0); | |
| bit_set(entry->hour, 0); | |
| bit_set(entry->day_of_month, 1); | |
| bit_set_all(entry->month); | |
| entry->flags |= CRON_WILD_MONTH; | |
| bit_set_all(entry->day_of_week); | |
| entry->flags |= CRON_WILD_DOW; | |
| pos += 7; | |
| } else if (!strncasecmp(pos, "weekly", 6)) { | |
| /* "0 0 * * 0" */ | |
| bit_set(entry->minute, 0); | |
| bit_set(entry->hour, 0); | |
| bit_set_all(entry->day_of_month); | |
| entry->flags |= CRON_WILD_DOM; | |
| bit_set_all(entry->month); | |
| entry->flags |= CRON_WILD_MONTH; | |
| bit_set(entry->day_of_week, 0); | |
| pos += 6; | |
| } else if (!strncasecmp(pos, "daily", 5) || | |
| !strncasecmp(pos, "midnight", 8)) { | |
| /* "0 0 * * *" */ | |
| bit_set(entry->minute, 0); | |
| bit_set(entry->hour, 0); | |
| bit_set_all(entry->day_of_month); | |
| entry->flags |= CRON_WILD_DOM; | |
| bit_set_all(entry->month); | |
| entry->flags |= CRON_WILD_MONTH; | |
| bit_set_all(entry->day_of_week); | |
| entry->flags |= CRON_WILD_DOW; | |
| if (!strncasecmp(pos, "daily", 5)) | |
| pos += 5; | |
| else | |
| pos += 8; | |
| } else if (!strncasecmp(pos, "elevenses", 9)) { | |
| /* "0 11 * * *" */ | |
| bit_set(entry->minute, 0); | |
| bit_set(entry->hour, 11); | |
| bit_set_all(entry->day_of_month); | |
| entry->flags |= CRON_WILD_DOM; | |
| bit_set_all(entry->month); | |
| entry->flags |= CRON_WILD_MONTH; | |
| bit_set_all(entry->day_of_week); | |
| entry->flags |= CRON_WILD_DOW; | |
| pos += 9; | |
| } else if (!strncasecmp(pos, "fika", 4)) { | |
| /* "0 15 * * *" */ | |
| bit_set(entry->minute, 0); | |
| bit_set(entry->hour, 15); | |
| bit_set_all(entry->day_of_month); | |
| entry->flags |= CRON_WILD_DOM; | |
| bit_set_all(entry->month); | |
| entry->flags |= CRON_WILD_MONTH; | |
| bit_set_all(entry->day_of_week); | |
| entry->flags |= CRON_WILD_DOW; | |
| pos += 4; | |
| } else if (!strncasecmp(pos, "teatime", 7)) { | |
| /* "0 16 * * *" */ | |
| bit_set(entry->minute, 0); | |
| bit_set(entry->hour, 16); | |
| bit_set_all(entry->day_of_month); | |
| entry->flags |= CRON_WILD_DOM; | |
| bit_set_all(entry->month); | |
| entry->flags |= CRON_WILD_MONTH; | |
| bit_set_all(entry->day_of_week); | |
| entry->flags |= CRON_WILD_DOW; | |
| pos += 7; | |
| } else if (!strncasecmp(pos, "hourly", 6)) { | |
| /* "0 * * * *" */ | |
| bit_set(entry->minute, 0); | |
| bit_set_all(entry->hour); | |
| bit_set_all(entry->day_of_month); | |
| entry->flags |= CRON_WILD_DOM; | |
| bit_set_all(entry->month); | |
| entry->flags |= CRON_WILD_MONTH; | |
| bit_set_all(entry->day_of_week); | |
| entry->flags |= CRON_WILD_DOW; | |
| pos += 6; | |
| } else { | |
| error("invalid @ line"); | |
| goto fail; | |
| } | |
| goto command; | |
| } | |
| /* minute */ | |
| if (*pos == '\0' || *pos == '\n') { | |
| error("%s: unexpected end of line", __func__); | |
| goto fail; | |
| } else if (*pos == '*') { | |
| bit_set_all(entry->minute); | |
| pos++; | |
| if (*pos != '/') | |
| entry->flags |= CRON_WILD_MINUTE; | |
| } else { | |
| if (_parse_range(entry->minute, &pos)) | |
| goto fail; | |
| } | |
| if (_handle_step(entry->minute, 0, &pos)) | |
| goto fail; | |
| if (bit_test(entry->minute, 60)) | |
| bit_set(entry->minute, 0); | |
| bit_clear(entry->minute, 60); | |
| if (*pos != ' ' && *pos != '\t') | |
| goto fail; | |
| while (*pos == ' ' || *pos == '\t') | |
| pos++; | |
| /* hour */ | |
| if (*pos == '\0' || *pos == '\n') { | |
| error("%s: unexpected end of line", __func__); | |
| goto fail; | |
| } else if (*pos == '*') { | |
| bit_set_all(entry->hour); | |
| pos++; | |
| if (*pos != '/') | |
| entry->flags |= CRON_WILD_HOUR; | |
| } else { | |
| if (_parse_range(entry->hour, &pos)) | |
| goto fail; | |
| } | |
| if (_handle_step(entry->hour, 0, &pos)) | |
| goto fail; | |
| if (bit_test(entry->hour, 24)) | |
| bit_set(entry->hour, 0); | |
| bit_clear(entry->hour, 24); | |
| if (*pos != ' ' && *pos != '\t') | |
| goto fail; | |
| while (*pos == ' ' || *pos == '\t') | |
| pos++; | |
| /* day of month */ | |
| if (*pos == '\0' || *pos == '\n') { | |
| error("%s: unexpected end of line", __func__); | |
| goto fail; | |
| } else if (*pos == '*') { | |
| bit_set_all(entry->day_of_month); | |
| pos++; | |
| if (*pos != '/') | |
| entry->flags |= CRON_WILD_DOM; | |
| } else { | |
| if (_parse_range(entry->day_of_month, &pos)) | |
| goto fail; | |
| } | |
| if (_handle_step(entry->day_of_month, 1, &pos)) | |
| goto fail; | |
| if (*pos != ' ' && *pos != '\t') | |
| goto fail; | |
| while (*pos == ' ' || *pos == '\t') | |
| pos++; | |
| /* month */ | |
| if (*pos == '\0' || *pos == '\n') { | |
| error("%s: unexpected end of line", __func__); | |
| goto fail; | |
| } else if (*pos == '*') { | |
| bit_set_all(entry->month); | |
| pos++; | |
| if (*pos != '/') | |
| entry->flags |= CRON_WILD_MONTH; | |
| } else if (isalpha((int) *pos)) { | |
| if (!strncasecmp(pos, "jan", 3)) | |
| bit_set(entry->month, 1); | |
| else if (!strncasecmp(pos, "feb", 3)) | |
| bit_set(entry->month, 2); | |
| else if (!strncasecmp(pos, "mar", 3)) | |
| bit_set(entry->month, 3); | |
| else if (!strncasecmp(pos, "apr", 3)) | |
| bit_set(entry->month, 4); | |
| else if (!strncasecmp(pos, "may", 3)) | |
| bit_set(entry->month, 5); | |
| else if (!strncasecmp(pos, "jun", 3)) | |
| bit_set(entry->month, 6); | |
| else if (!strncasecmp(pos, "jul", 3)) | |
| bit_set(entry->month, 7); | |
| else if (!strncasecmp(pos, "aug", 3)) | |
| bit_set(entry->month, 8); | |
| else if (!strncasecmp(pos, "sep", 3)) | |
| bit_set(entry->month, 9); | |
| else if (!strncasecmp(pos, "oct", 3)) | |
| bit_set(entry->month, 10); | |
| else if (!strncasecmp(pos, "nov", 3)) | |
| bit_set(entry->month, 11); | |
| else if (!strncasecmp(pos, "dec", 3)) | |
| bit_set(entry->month, 12); | |
| else { | |
| error("bad month specification"); | |
| goto fail; | |
| } | |
| pos += 3; | |
| } else { | |
| if (_parse_range(entry->month, &pos)) | |
| goto fail; | |
| } | |
| if (_handle_step(entry->month, 1, &pos)) | |
| goto fail; | |
| if (*pos != ' ' && *pos != '\t') | |
| goto fail; | |
| while (*pos == ' ' || *pos == '\t') | |
| pos++; | |
| /* day of week */ | |
| if (*pos == '\0' || *pos == '\n') { | |
| error("%s: unexpected end of line", __func__); | |
| goto fail; | |
| } else if (*pos == '*') { | |
| bit_set_all(entry->day_of_week); | |
| pos++; | |
| if (*pos != '/') | |
| entry->flags |= CRON_WILD_DOW; | |
| } else if (isalpha((int) *pos)) { | |
| if (!strncasecmp(pos, "mon", 3)) | |
| bit_set(entry->day_of_week, 1); | |
| else if (!strncasecmp(pos, "tue", 3)) | |
| bit_set(entry->day_of_week, 2); | |
| else if (!strncasecmp(pos, "wed", 3)) | |
| bit_set(entry->day_of_week, 3); | |
| else if (!strncasecmp(pos, "thu", 3)) | |
| bit_set(entry->day_of_week, 4); | |
| else if (!strncasecmp(pos, "fri", 3)) | |
| bit_set(entry->day_of_week, 5); | |
| else if (!strncasecmp(pos, "sat", 3)) | |
| bit_set(entry->day_of_week, 6); | |
| else if (!strncasecmp(pos, "sun", 3)) | |
| bit_set(entry->day_of_week, 7); | |
| else { | |
| error("bad day specification"); | |
| goto fail; | |
| } | |
| pos += 3; | |
| } else { | |
| if (_parse_range(entry->day_of_week, &pos)) | |
| goto fail; | |
| } | |
| if (_handle_step(entry->day_of_week, 1, &pos)) | |
| goto fail; | |
| if (bit_test(entry->day_of_week, 7)) | |
| bit_set(entry->day_of_week, 0); | |
| bit_clear(entry->day_of_week, 7); | |
| command: | |
| /* set initial cronspec */ | |
| entry->cronspec = xstrndup(pos_init, pos - pos_init); | |
| if (*pos != ' ' && *pos != '\t') | |
| goto fail; | |
| while (*pos == ' ' || *pos == '\t') | |
| pos++; | |
| entry->command = xstrdup(pos); | |
| if (!valid_cron_entry(entry)) | |
| goto fail; | |
| return entry; | |
| fail: | |
| error("something is broken"); | |
| free_cron_entry(entry); | |
| return NULL; | |
| } | |
| /* | |
| * Return an array pointing at each individual line in file. Function will | |
| * replace all newlines in file with NUL to avoid copying the entire buffer. | |
| * | |
| * Array is NULL terminated. | |
| */ | |
| extern char **convert_file_to_line_array(char *file, int *line_count) | |
| { | |
| int lines = 1; | |
| char **line_array = xcalloc(3, sizeof(char *)); | |
| char *ptr = file; | |
| line_array[0] = "\0"; | |
| line_array[1] = ptr; | |
| while (*ptr != '\0') { | |
| if (*ptr == '\n') { | |
| *ptr = '\0'; | |
| ptr++; | |
| lines++; | |
| if (*ptr == '\0') | |
| break; | |
| xrecalloc(line_array, lines + 2, sizeof(char *)); | |
| line_array[lines] = ptr; | |
| } else | |
| ptr++; | |
| } | |
| *line_count = lines; | |
| return line_array; | |
| } | |
| extern char *next_line(const void *buf, int size, void **state) | |
| { | |
| char *line; | |
| char *current, *ptr; | |
| if (!*state) /* initial state */ | |
| *state = (void *)buf; | |
| if ((*state - buf) >= size) /* final state */ | |
| return NULL; | |
| ptr = current = (char *) *state; | |
| while ((*ptr != '\n') && (ptr < ((char *) buf+size))) | |
| ptr++; | |
| line = xstrndup(current, ptr-current); | |
| /* | |
| * Advance state past newline | |
| */ | |
| *state = (ptr < ((char *) buf + size)) ? ptr+1 : ptr; | |
| return line; | |
| } | |
| /* | |
| * fixme parsing that can handle escapes, comments, quote marks | |
| * see get_argument() in sbatch/opt.c | |
| */ | |
| char *get_argument(char **pos) | |
| { | |
| char *start = *pos, *end; | |
| while (*start == ' ' || *start == '\t') | |
| start++; | |
| if (*start == '\0') | |
| return NULL; | |
| end = start; | |
| while (*end != '\0' && *end != ' ' && *end != '\t') | |
| end++; | |
| *pos = end; | |
| if (**pos != '\0') | |
| (*pos)++; | |
| return xstrndup(start, end - start); | |
| } | |
| static int _set_options(int argc, char **argv) | |
| { | |
| int opt_char; | |
| char *opt_string = NULL; | |
| struct option *optz = slurm_option_table_create(&opt, &opt_string); | |
| optind = 0; | |
| while ((opt_char = getopt_long(argc, argv, opt_string, | |
| optz, NULL)) != -1) { | |
| if (slurm_process_option(&opt, opt_char, optarg, false, false)) | |
| { | |
| slurm_option_table_destroy(optz); | |
| xfree(opt_string); | |
| return optind - 1; | |
| } | |
| } | |
| slurm_option_table_destroy(optz); | |
| xfree(opt_string); | |
| return optind; | |
| } | |
| extern int parse_scron_line(char *line, int lineno) | |
| { | |
| char *option; | |
| int i; | |
| int argc = 1; | |
| char **argv = xmalloc(sizeof(char *)); | |
| argv[0] = "scrontab"; | |
| while ((option = get_argument(&line))) { | |
| argc++; | |
| xrecalloc(argv, argc, sizeof(char *)); | |
| argv[argc - 1] = option; | |
| } | |
| if (argc > 1 && (i = _set_options(argc, argv)) < argc) { | |
| error("Invalid option found in #SCRON line: %s", argv[i]); | |
| for (i = 1; i < argc; i++) | |
| xfree(argv[i]); | |
| xfree(argv); | |
| return SLURM_ERROR; | |
| } | |
| for (i = 1; i < argc; i++) | |
| xfree(argv[i]); | |
| xfree(argv); | |
| return SLURM_SUCCESS; | |
| } |