From b4b7383f27a57d453c324e7c9ff764e7a42dbf91 Mon Sep 17 00:00:00 2001 From: Rodrigo Speller Date: Fri, 7 Apr 2023 06:23:09 -0300 Subject: [PATCH 01/14] Service restart policy --- src/config/ServiceConfig.cpp | 2 +- src/config/ServiceConfig.h | 4 ++-- src/config/yaml/YamlConfigParser.cpp | 22 +++++++++---------- src/config/yaml/YamlConfigParser.h | 2 +- src/service/Service.cpp | 14 ++++++------ src/service/Service.h | 6 ++--- ...ceRestartMode.h => ServiceRestartPolicy.h} | 4 ++-- 7 files changed, 27 insertions(+), 27 deletions(-) rename src/service/{ServiceRestartMode.h => ServiceRestartPolicy.h} (82%) diff --git a/src/config/ServiceConfig.cpp b/src/config/ServiceConfig.cpp index 11368a8..4f10947 100644 --- a/src/config/ServiceConfig.cpp +++ b/src/config/ServiceConfig.cpp @@ -6,7 +6,7 @@ #include "ServiceConfig.h" #include "exception/AtoneException.h" -#include "service/ServiceRestartMode.h" +#include "service/ServiceRestartPolicy.h" namespace Atone { diff --git a/src/config/ServiceConfig.h b/src/config/ServiceConfig.h index 4589e29..78ee96c 100644 --- a/src/config/ServiceConfig.h +++ b/src/config/ServiceConfig.h @@ -5,7 +5,7 @@ #include "atone.h" -#include "service/ServiceRestartMode.h" +#include "service/ServiceRestartPolicy.h" namespace Atone { @@ -17,7 +17,7 @@ namespace Atone { size_t argc = 0; shared_ptr argv = NULL; std::vector depends_on; - ServiceRestartMode restart = ServiceRestartMode::No; + ServiceRestartPolicy restart = ServiceRestartPolicy::Never; void SetCommandArgs(const std::string &command); void SetCommandArgs(const size_t argc, char **argv); diff --git a/src/config/yaml/YamlConfigParser.cpp b/src/config/yaml/YamlConfigParser.cpp index 133f835..7649187 100644 --- a/src/config/yaml/YamlConfigParser.cpp +++ b/src/config/yaml/YamlConfigParser.cpp @@ -84,7 +84,7 @@ namespace Atone { if (depends_on) SetDependsOn(svcConfig, depends_on); auto restart = config["restart"]; - if (restart) SetRestartMode(svcConfig, restart); + if (restart) SetRestartPolicy(svcConfig, restart); return svcConfig; } @@ -136,33 +136,33 @@ namespace Atone { target.depends_on = value; } - void YamlConfigParser::SetRestartMode(ServiceConfig &target, const YAML::Node &restart) { + void YamlConfigParser::SetRestartPolicy(ServiceConfig &target, const YAML::Node &restart) { switch (restart.Type()) { case YAML::NodeType::Null: case YAML::NodeType::Undefined: - target.restart = ServiceRestartMode::No; + target.restart = ServiceRestartPolicy::Never; return; case YAML::NodeType::Scalar: break; default: - throw AtoneException("invalid restart"); + throw AtoneException("invalid restart policy"); } auto str_value = restart.as(); - ServiceRestartMode value; - if (str_value == "no") - value = ServiceRestartMode::No; + ServiceRestartPolicy value; + if (str_value == "never" || str_value == "no" /* legacy */) + value = ServiceRestartPolicy::Never; else if (str_value == "always") - value = ServiceRestartMode::Always; + value = ServiceRestartPolicy::Always; else if (str_value == "on-failure") - value = ServiceRestartMode::OnFailure; + value = ServiceRestartPolicy::OnFailure; else if (str_value == "unless-stopped") - value = ServiceRestartMode::UnlessStopped; + value = ServiceRestartPolicy::UnlessStopped; else - throw AtoneException("invalid restart"); + throw AtoneException("invalid restart policy"); target.restart = value; } diff --git a/src/config/yaml/YamlConfigParser.h b/src/config/yaml/YamlConfigParser.h index 03d01bc..1cd8526 100644 --- a/src/config/yaml/YamlConfigParser.h +++ b/src/config/yaml/YamlConfigParser.h @@ -21,6 +21,6 @@ namespace Atone { void SetCommandArgs(ServiceConfig &target, const YAML::Node &command); void SetDependsOn(ServiceConfig &target, const YAML::Node &depends_on); - void SetRestartMode(ServiceConfig &target, const YAML::Node &restart); + void SetRestartPolicy(ServiceConfig &target, const YAML::Node &restart); }; } diff --git a/src/service/Service.cpp b/src/service/Service.cpp index 822d71d..4be02a1 100644 --- a/src/service/Service.cpp +++ b/src/service/Service.cpp @@ -24,7 +24,7 @@ namespace Atone { size_t Service::argc() const { return config.argc; } shared_ptr Service::argv() const { return config.argv; } vector Service::dependsOn() const { return config.depends_on; } - ServiceRestartMode Service::restartMode() const { return config.restart; } + ServiceRestartPolicy Service::restartPolicy() const { return config.restart; } ////////////////////////////////////////////////////////////////////// @@ -41,19 +41,19 @@ namespace Atone { return false; } - switch (restartMode()) { - case ServiceRestartMode::No: + switch (restartPolicy()) { + case ServiceRestartPolicy::Never: return false; - case ServiceRestartMode::Always: + case ServiceRestartPolicy::Always: return true; - case ServiceRestartMode::OnFailure: + case ServiceRestartPolicy::OnFailure: return (exitCode() != EXIT_SUCCESS); break; - case ServiceRestartMode::UnlessStopped: + case ServiceRestartPolicy::UnlessStopped: return (status() != ServiceStatus::Stopped); break; default: - throw domain_error("invalid restart mode"); + throw domain_error("invalid restart policy"); } } diff --git a/src/service/Service.h b/src/service/Service.h index b52e13a..3c12405 100644 --- a/src/service/Service.h +++ b/src/service/Service.h @@ -97,10 +97,10 @@ namespace Atone { vector dependsOn() const; /** - * Gets the service restart mode. - * @return The service restart mode. + * Gets the service restart policy. + * @return The service restart policy. */ - ServiceRestartMode restartMode() const; + ServiceRestartPolicy restartPolicy() const; //////////////////////////////////////////////////////////////////// diff --git a/src/service/ServiceRestartMode.h b/src/service/ServiceRestartPolicy.h similarity index 82% rename from src/service/ServiceRestartMode.h rename to src/service/ServiceRestartPolicy.h index f8ddc2a..7717b74 100644 --- a/src/service/ServiceRestartMode.h +++ b/src/service/ServiceRestartPolicy.h @@ -5,8 +5,8 @@ namespace Atone { - enum class ServiceRestartMode { - No, + enum class ServiceRestartPolicy { + Never, Always, OnFailure, UnlessStopped, From 197678847feb081e0dc09167c2d902ff64bf9e85 Mon Sep 17 00:00:00 2001 From: Rodrigo Speller Date: Sat, 8 Apr 2023 00:15:22 -0300 Subject: [PATCH 02/14] 3rd-party cron original files (before changes). Source: https://github.com/vixie/cron/commit/f4311d3a1f4018b8a2927437216585d058b95681 --- 3rd-party/cron/src/cron/LICENSE | 15 + 3rd-party/cron/src/cron/bitstring.h | 143 ++++++ 3rd-party/cron/src/cron/cron.h | 35 ++ 3rd-party/cron/src/cron/entry.c | 588 ++++++++++++++++++++++ 3rd-party/cron/src/cron/externs.h | 120 +++++ 3rd-party/cron/src/cron/funcs.h | 80 +++ 3rd-party/cron/src/cron/globals.h | 84 ++++ 3rd-party/cron/src/cron/macros.h | 131 +++++ 3rd-party/cron/src/cron/misc.c | 736 ++++++++++++++++++++++++++++ 3rd-party/cron/src/cron/structs.h | 62 +++ 10 files changed, 1994 insertions(+) create mode 100644 3rd-party/cron/src/cron/LICENSE create mode 100644 3rd-party/cron/src/cron/bitstring.h create mode 100644 3rd-party/cron/src/cron/cron.h create mode 100644 3rd-party/cron/src/cron/entry.c create mode 100644 3rd-party/cron/src/cron/externs.h create mode 100644 3rd-party/cron/src/cron/funcs.h create mode 100644 3rd-party/cron/src/cron/globals.h create mode 100644 3rd-party/cron/src/cron/macros.h create mode 100644 3rd-party/cron/src/cron/misc.c create mode 100644 3rd-party/cron/src/cron/structs.h diff --git a/3rd-party/cron/src/cron/LICENSE b/3rd-party/cron/src/cron/LICENSE new file mode 100644 index 0000000..8c6adaa --- /dev/null +++ b/3rd-party/cron/src/cron/LICENSE @@ -0,0 +1,15 @@ +Copyright 1988,1990,1993,2021 by Paul Vixie ("VIXIE") +Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC") +Copyright (c) 1997,2000 by Internet Software Consortium, Inc. + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND VIXIE DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL VIXIE BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/3rd-party/cron/src/cron/bitstring.h b/3rd-party/cron/src/cron/bitstring.h new file mode 100644 index 0000000..88437e7 --- /dev/null +++ b/3rd-party/cron/src/cron/bitstring.h @@ -0,0 +1,143 @@ +/* + * Copyright (c) 1989, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Paul Vixie. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)bitstring.h 8.1 (Berkeley) 7/19/93 + */ + +#ifndef _BITSTRING_H_ +#define _BITSTRING_H_ + +typedef unsigned char bitstr_t; + +/* internal macros */ + /* byte of the bitstring bit is in */ +#define _bit_byte(bit) \ + ((bit) >> 3) + + /* mask for the bit within its byte */ +#define _bit_mask(bit) \ + (1 << ((bit)&0x7)) + +/* external macros */ + /* bytes in a bitstring of nbits bits */ +#define bitstr_size(nbits) \ + ((((nbits) - 1) >> 3) + 1) + + /* allocate a bitstring */ +#define bit_alloc(nbits) \ + (bitstr_t *)calloc(1, \ + (unsigned int)bitstr_size(nbits) * sizeof(bitstr_t)) + + /* allocate a bitstring on the stack */ +#define bit_decl(name, nbits) \ + (name)[bitstr_size(nbits)] + + /* is bit N of bitstring name set? */ +#define bit_test(name, bit) \ + ((name)[_bit_byte(bit)] & _bit_mask(bit)) + + /* set bit N of bitstring name */ +#define bit_set(name, bit) \ + (name)[_bit_byte(bit)] |= _bit_mask(bit) + + /* clear bit N of bitstring name */ +#define bit_clear(name, bit) \ + (name)[_bit_byte(bit)] &= ~_bit_mask(bit) + + /* clear bits start ... stop in bitstring */ +#define bit_nclear(name, start, stop) { \ + register bitstr_t *_name = name; \ + register int _start = start, _stop = stop; \ + register int _startbyte = _bit_byte(_start); \ + register int _stopbyte = _bit_byte(_stop); \ + if (_startbyte == _stopbyte) { \ + _name[_startbyte] &= ((0xff >> (8 - (_start&0x7))) | \ + (0xff << ((_stop&0x7) + 1))); \ + } else { \ + _name[_startbyte] &= 0xff >> (8 - (_start&0x7)); \ + while (++_startbyte < _stopbyte) \ + _name[_startbyte] = 0; \ + _name[_stopbyte] &= 0xff << ((_stop&0x7) + 1); \ + } \ +} + + /* set bits start ... stop in bitstring */ +#define bit_nset(name, start, stop) { \ + register bitstr_t *_name = name; \ + register int _start = start, _stop = stop; \ + register int _startbyte = _bit_byte(_start); \ + register int _stopbyte = _bit_byte(_stop); \ + if (_startbyte == _stopbyte) { \ + _name[_startbyte] |= ((0xff << (_start&0x7)) & \ + (0xff >> (7 - (_stop&0x7)))); \ + } else { \ + _name[_startbyte] |= 0xff << ((_start)&0x7); \ + while (++_startbyte < _stopbyte) \ + _name[_startbyte] = 0xff; \ + _name[_stopbyte] |= 0xff >> (7 - (_stop&0x7)); \ + } \ +} + + /* find first bit clear in name */ +#define bit_ffc(name, nbits, value) { \ + register bitstr_t *_name = name; \ + register int _byte, _nbits = nbits; \ + register int _stopbyte = _bit_byte(_nbits), _value = -1; \ + for (_byte = 0; _byte <= _stopbyte; ++_byte) \ + if (_name[_byte] != 0xff) { \ + _value = _byte << 3; \ + for (_stopbyte = _name[_byte]; (_stopbyte&0x1); \ + ++_value, _stopbyte >>= 1); \ + break; \ + } \ + *(value) = _value; \ +} + + /* find first bit set in name */ +#define bit_ffs(name, nbits, value) { \ + register bitstr_t *_name = name; \ + register int _byte, _nbits = nbits; \ + register int _stopbyte = _bit_byte(_nbits), _value = -1; \ + for (_byte = 0; _byte <= _stopbyte; ++_byte) \ + if (_name[_byte]) { \ + _value = _byte << 3; \ + for (_stopbyte = _name[_byte]; !(_stopbyte&0x1); \ + ++_value, _stopbyte >>= 1); \ + break; \ + } \ + *(value) = _value; \ +} + +#endif /* !_BITSTRING_H_ */ diff --git a/3rd-party/cron/src/cron/cron.h b/3rd-party/cron/src/cron/cron.h new file mode 100644 index 0000000..8864b87 --- /dev/null +++ b/3rd-party/cron/src/cron/cron.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 1988,1990,1993,1994,2021 by Paul Vixie ("VIXIE") + * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1997,2000 by Internet Software Consortium, Inc. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND VIXIE DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL VIXIE BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* cron.h - header for vixie's cron + * + * $Id: cron.h,v 1.6 2004/01/23 18:56:42 vixie Exp $ + * + * vix 14nov88 [rest of log is in RCS] + * vix 14jan87 [0 or 7 can be sunday; thanks, mwm@berkeley] + * vix 30dec86 [written] + */ + +#define CRON_VERSION "V4.999" +#include "config.h" +#include "externs.h" +#include "pathnames.h" +#include "macros.h" +#include "structs.h" +#include "funcs.h" +#include "globals.h" diff --git a/3rd-party/cron/src/cron/entry.c b/3rd-party/cron/src/cron/entry.c new file mode 100644 index 0000000..3062ba8 --- /dev/null +++ b/3rd-party/cron/src/cron/entry.c @@ -0,0 +1,588 @@ +/* + * Copyright (c) 1988,1990,1993,1994,2021 by Paul Vixie ("VIXIE") + * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1997,2000 by Internet Software Consortium, Inc. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND VIXIE DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL VIXIE BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#if !defined(lint) && !defined(LINT) +static char rcsid[] = "$Id: entry.c,v 1.17 2004/01/23 18:56:42 vixie Exp $"; +#endif + +/* vix 26jan87 [RCS'd; rest of log is in RCS file] + * vix 01jan87 [added line-level error recovery] + * vix 31dec86 [added /step to the from-to range, per bob@acornrc] + * vix 30dec86 [written] + */ + +#include "cron.h" + +typedef enum ecode { + e_none, e_minute, e_hour, e_dom, e_month, e_dow, + e_cmd, e_timespec, e_username, e_option, e_memory +} ecode_e; + +static const char *ecodes[] = + { + "no error", + "bad minute", + "bad hour", + "bad day-of-month", + "bad month", + "bad day-of-week", + "bad command", + "bad time specifier", + "bad username", + "bad option", + "out of memory" + }; + +static int get_list(bitstr_t *, int, int, const char *[], int, FILE *), + get_range(bitstr_t *, int, int, const char *[], int, FILE *), + get_number(int *, int, const char *[], int, FILE *, const char *), + set_element(bitstr_t *, int, int, int); + +void +free_entry(entry *e) { + free(e->cmd); + free(e->pwd); + env_free(e->envp); + free(e); +} + +/* return NULL if eof or syntax error occurs; + * otherwise return a pointer to a new entry. + */ +entry * +load_entry(FILE *file, void (*error_func)(), struct passwd *pw, char **envp) { + /* this function reads one crontab entry -- the next -- from a file. + * it skips any leading blank lines, ignores comments, and returns + * NULL if for any reason the entry can't be read and parsed. + * + * the entry is also parsed here. + * + * syntax: + * user crontab: + * minutes hours doms months dows cmd\n + * system crontab (/etc/crontab): + * minutes hours doms months dows USERNAME cmd\n + */ + + ecode_e ecode = e_none; + entry *e; + int ch; + char cmd[MAX_COMMAND]; + char envstr[MAX_ENVSTR]; + char **tenvp; + + Debug(DPARS, ("load_entry()...about to eat comments\n")) + + skip_comments(file); + + ch = get_char(file); + if (ch == EOF) + return (NULL); + + /* ch is now the first useful character of a useful line. + * it may be an @special or it may be the first character + * of a list of minutes. + */ + + e = (entry *) calloc(sizeof(entry), sizeof(char)); + + if (ch == '@') { + /* all of these should be flagged and load-limited; i.e., + * instead of @hourly meaning "0 * * * *" it should mean + * "close to the front of every hour but not 'til the + * system load is low". Problems are: how do you know + * what "low" means? (save me from /etc/cron.conf!) and: + * how to guarantee low variance (how low is low?), which + * means how to we run roughly every hour -- seems like + * we need to keep a history or let the first hour set + * the schedule, which means we aren't load-limited + * anymore. too much for my overloaded brain. (vix, jan90) + * HINT + */ + ch = get_string(cmd, MAX_COMMAND, file, " \t\n"); + if (!strcmp("reboot", cmd)) { + e->flags |= WHEN_REBOOT; + } else if (!strcmp("yearly", cmd) || !strcmp("annually", cmd)){ + bit_set(e->minute, 0); + bit_set(e->hour, 0); + bit_set(e->dom, 0); + bit_set(e->month, 0); + bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1)); + e->flags |= DOW_STAR; + } else if (!strcmp("monthly", cmd)) { + bit_set(e->minute, 0); + bit_set(e->hour, 0); + bit_set(e->dom, 0); + bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1)); + bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1)); + e->flags |= DOW_STAR; + } else if (!strcmp("weekly", cmd)) { + bit_set(e->minute, 0); + bit_set(e->hour, 0); + bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1)); + bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1)); + bit_set(e->dow, 0); + e->flags |= DOW_STAR; + } else if (!strcmp("daily", cmd) || !strcmp("midnight", cmd)) { + bit_set(e->minute, 0); + bit_set(e->hour, 0); + bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1)); + bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1)); + bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1)); + } else if (!strcmp("hourly", cmd)) { + bit_set(e->minute, 0); + bit_nset(e->hour, 0, (LAST_HOUR-FIRST_HOUR+1)); + bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1)); + bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1)); + bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1)); + e->flags |= HR_STAR; + } else { + ecode = e_timespec; + goto eof; + } + /* Advance past whitespace between shortcut and + * username/command. + */ + Skip_Blanks(ch, file); + if (ch == EOF || ch == '\n') { + ecode = e_cmd; + goto eof; + } + } else { + Debug(DPARS, ("load_entry()...about to parse numerics\n")) + + if (ch == '*') + e->flags |= MIN_STAR; + ch = get_list(e->minute, FIRST_MINUTE, LAST_MINUTE, + PPC_NULL, ch, file); + if (ch == EOF) { + ecode = e_minute; + goto eof; + } + + /* hours + */ + + if (ch == '*') + e->flags |= HR_STAR; + ch = get_list(e->hour, FIRST_HOUR, LAST_HOUR, + PPC_NULL, ch, file); + if (ch == EOF) { + ecode = e_hour; + goto eof; + } + + /* DOM (days of month) + */ + + if (ch == '*') + e->flags |= DOM_STAR; + ch = get_list(e->dom, FIRST_DOM, LAST_DOM, + PPC_NULL, ch, file); + if (ch == EOF) { + ecode = e_dom; + goto eof; + } + + /* month + */ + + ch = get_list(e->month, FIRST_MONTH, LAST_MONTH, + MonthNames, ch, file); + if (ch == EOF) { + ecode = e_month; + goto eof; + } + + /* DOW (days of week) + */ + + if (ch == '*') + e->flags |= DOW_STAR; + ch = get_list(e->dow, FIRST_DOW, LAST_DOW, + DowNames, ch, file); + if (ch == EOF) { + ecode = e_dow; + goto eof; + } + } + + /* make sundays equivalent */ + if (bit_test(e->dow, 0) || bit_test(e->dow, 7)) { + bit_set(e->dow, 0); + bit_set(e->dow, 7); + } + + /* check for permature EOL and catch a common typo */ + if (ch == '\n' || ch == '*') { + ecode = e_cmd; + goto eof; + } + + /* ch is the first character of a command, or a username */ + unget_char(ch, file); + + if (!pw) { + char *username = cmd; /* temp buffer */ + + Debug(DPARS, ("load_entry()...about to parse username\n")) + ch = get_string(username, MAX_COMMAND, file, " \t\n"); + + Debug(DPARS, ("load_entry()...got %s\n",username)) + if (ch == EOF || ch == '\n' || ch == '*') { + ecode = e_cmd; + goto eof; + } + + /* Need to have consumed blanks before checking for options + * below. */ + Skip_Blanks(ch, file) + unget_char(ch, file); + + pw = getpwnam(username); + if (pw == NULL) { + ecode = e_username; + goto eof; + } + Debug(DPARS, ("load_entry()...uid %ld, gid %ld\n", + (long)pw->pw_uid, (long)pw->pw_gid)) + } + + if ((e->pwd = pw_dup(pw)) == NULL) { + ecode = e_memory; + goto eof; + } + bzero(e->pwd->pw_passwd, strlen(e->pwd->pw_passwd)); + + /* copy and fix up environment. some variables are just defaults and + * others are overrides. + */ + if ((e->envp = env_copy(envp)) == NULL) { + ecode = e_memory; + goto eof; + } + if (!env_get("SHELL", e->envp)) { + if (glue_strings(envstr, sizeof envstr, "SHELL", + _PATH_BSHELL, '=')) { + if ((tenvp = env_set(e->envp, envstr)) == NULL) { + ecode = e_memory; + goto eof; + } + e->envp = tenvp; + } else + log_it("CRON", getpid(), "error", "can't set SHELL"); + } + if (!env_get("HOME", e->envp)) { + if (glue_strings(envstr, sizeof envstr, "HOME", + pw->pw_dir, '=')) { + if ((tenvp = env_set(e->envp, envstr)) == NULL) { + ecode = e_memory; + goto eof; + } + e->envp = tenvp; + } else + log_it("CRON", getpid(), "error", "can't set HOME"); + } +#ifndef LOGIN_CAP + /* If login.conf is in used we will get the default PATH later. */ + if (!env_get("PATH", e->envp)) { + if (glue_strings(envstr, sizeof envstr, "PATH", + _PATH_DEFPATH, '=')) { + if ((tenvp = env_set(e->envp, envstr)) == NULL) { + ecode = e_memory; + goto eof; + } + e->envp = tenvp; + } else + log_it("CRON", getpid(), "error", "can't set PATH"); + } +#endif /* LOGIN_CAP */ + if (glue_strings(envstr, sizeof envstr, "LOGNAME", + pw->pw_name, '=')) { + if ((tenvp = env_set(e->envp, envstr)) == NULL) { + ecode = e_memory; + goto eof; + } + e->envp = tenvp; + } else + log_it("CRON", getpid(), "error", "can't set LOGNAME"); +#if defined(BSD) || defined(__linux) + if (glue_strings(envstr, sizeof envstr, "USER", + pw->pw_name, '=')) { + if ((tenvp = env_set(e->envp, envstr)) == NULL) { + ecode = e_memory; + goto eof; + } + e->envp = tenvp; + } else + log_it("CRON", getpid(), "error", "can't set USER"); +#endif + + Debug(DPARS, ("load_entry()...about to parse command\n")) + + /* If the first character of the command is '-' it is a cron option. + */ + while ((ch = get_char(file)) == '-') { + switch (ch = get_char(file)) { + case 'q': + e->flags |= DONT_LOG; + Skip_Nonblanks(ch, file) + break; + default: + ecode = e_option; + goto eof; + } + Skip_Blanks(ch, file) + if (ch == EOF || ch == '\n') { + ecode = e_cmd; + goto eof; + } + } + unget_char(ch, file); + + /* Everything up to the next \n or EOF is part of the command... + * too bad we don't know in advance how long it will be, since we + * need to malloc a string for it... so, we limit it to MAX_COMMAND. + */ + ch = get_string(cmd, MAX_COMMAND, file, "\n"); + + /* a file without a \n before the EOF is rude, so we'll complain... + */ + if (ch == EOF) { + ecode = e_cmd; + goto eof; + } + + /* got the command in the 'cmd' string; save it in *e. + */ + if ((e->cmd = strdup(cmd)) == NULL) { + ecode = e_memory; + goto eof; + } + + Debug(DPARS, ("load_entry()...returning successfully\n")) + + /* success, fini, return pointer to the entry we just created... + */ + return (e); + + eof: + if (e->envp) + env_free(e->envp); + if (e->pwd) + free(e->pwd); + if (e->cmd) + free(e->cmd); + free(e); + while (ch != '\n' && !feof(file)) + ch = get_char(file); + if (ecode != e_none && error_func) + (*error_func)(ecodes[(int)ecode]); + return (NULL); +} + +static int +get_list(bitstr_t *bits, int low, int high, const char *names[], + int ch, FILE *file) +{ + int done; + + /* we know that we point to a non-blank character here; + * must do a Skip_Blanks before we exit, so that the + * next call (or the code that picks up the cmd) can + * assume the same thing. + */ + + Debug(DPARS|DEXT, ("get_list()...entered\n")) + + /* list = range {"," range} + */ + + /* clear the bit string, since the default is 'off'. + */ + bit_nclear(bits, 0, (high-low+1)); + + /* process all ranges + */ + done = FALSE; + while (!done) { + if (EOF == (ch = get_range(bits, low, high, names, ch, file))) + return (EOF); + if (ch == ',') + ch = get_char(file); + else + done = TRUE; + } + + /* exiting. skip to some blanks, then skip over the blanks. + */ + Skip_Nonblanks(ch, file) + Skip_Blanks(ch, file) + + Debug(DPARS|DEXT, ("get_list()...exiting w/ %02x\n", ch)) + + return (ch); +} + + +static int +get_range(bitstr_t *bits, int low, int high, const char *names[], + int ch, FILE *file) +{ + /* range = number | number "-" number [ "/" number ] + */ + + int i, num1, num2, num3; + + Debug(DPARS|DEXT, ("get_range()...entering, exit won't show\n")) + + if (ch == '*') { + /* '*' means "first-last" but can still be modified by /step + */ + num1 = low; + num2 = high; + ch = get_char(file); + if (ch == EOF) + return (EOF); + } else { + ch = get_number(&num1, low, names, ch, file, ",- \t\n"); + if (ch == EOF) + return (EOF); + + if (ch != '-') { + /* not a range, it's a single number. + */ + if (EOF == set_element(bits, low, high, num1)) { + unget_char(ch, file); + return (EOF); + } + return (ch); + } else { + /* eat the dash + */ + ch = get_char(file); + if (ch == EOF) + return (EOF); + + /* get the number following the dash + */ + ch = get_number(&num2, low, names, ch, file, "/, \t\n"); + if (ch == EOF || num1 > num2) + return (EOF); + } + } + + /* check for step size + */ + if (ch == '/') { + /* eat the slash + */ + ch = get_char(file); + if (ch == EOF) + return (EOF); + + /* get the step size -- note: we don't pass the + * names here, because the number is not an + * element id, it's a step size. 'low' is + * sent as a 0 since there is no offset either. + */ + ch = get_number(&num3, 0, PPC_NULL, ch, file, ", \t\n"); + if (ch == EOF || num3 == 0) + return (EOF); + } else { + /* no step. default==1. + */ + num3 = 1; + } + + /* range. set all elements from num1 to num2, stepping + * by num3. (the step is a downward-compatible extension + * proposed conceptually by bob@acornrc, syntactically + * designed then implemented by paul vixie). + */ + for (i = num1; i <= num2; i += num3) + if (EOF == set_element(bits, low, high, i)) { + unget_char(ch, file); + return (EOF); + } + + return (ch); +} + +static int +get_number(int *numptr, int low, const char *names[], int ch, FILE *file, + const char *terms) { + char temp[MAX_TEMPSTR], *pc; + int len, i; + + pc = temp; + len = 0; + + /* first look for a number */ + while (isdigit((unsigned char)ch)) { + if (++len >= MAX_TEMPSTR) + goto bad; + *pc++ = ch; + ch = get_char(file); + } + *pc = '\0'; + if (len != 0) { + /* got a number, check for valid terminator */ + if (!strchr(terms, ch)) + goto bad; + *numptr = atoi(temp); + return (ch); + } + + /* no numbers, look for a string if we have any */ + if (names) { + while (isalpha((unsigned char)ch)) { + if (++len >= MAX_TEMPSTR) + goto bad; + *pc++ = ch; + ch = get_char(file); + } + *pc = '\0'; + if (len != 0 && strchr(terms, ch)) { + for (i = 0; names[i] != NULL; i++) { + Debug(DPARS|DEXT, + ("get_num, compare(%s,%s)\n", names[i], + temp)) + if (!strcasecmp(names[i], temp)) { + *numptr = i+low; + return (ch); + } + } + } + } + +bad: + unget_char(ch, file); + return (EOF); +} + +static int +set_element(bitstr_t *bits, int low, int high, int number) { + Debug(DPARS|DEXT, ("set_element(?,%d,%d,%d)\n", low, high, number)) + + if (number < low || number > high) + return (EOF); + + bit_set(bits, (number-low)); + return (OK); +} diff --git a/3rd-party/cron/src/cron/externs.h b/3rd-party/cron/src/cron/externs.h new file mode 100644 index 0000000..93339ab --- /dev/null +++ b/3rd-party/cron/src/cron/externs.h @@ -0,0 +1,120 @@ +/* + * Copyright (c) 1993,1994,2021 by Paul Vixie ("VIXIE") + * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1997,2000 by Internet Software Consortium, Inc. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND VIXIE DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL VIXIE BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* reorder these #include's at your peril */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#ifndef isascii +#define isascii(c) ((unsigned)(c)<=0177) +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(SYSLOG) +# include +#endif + +#if defined(LOGIN_CAP) +# include +#endif /*LOGIN_CAP*/ + +#if defined(BSD_AUTH) +# include +#endif /*BSD_AUTH*/ + +#define DIR_T struct dirent +#define WAIT_T int +#define SIG_T sig_t +#define TIME_T time_t +#define PID_T pid_t + +#ifndef TZNAME_ALREADY_DEFINED +extern char *tzname[2]; +#endif +#define TZONE(tm) tzname[(tm).tm_isdst] + +#if (defined(BSD)) && (BSD >= 198606) || defined(__linux) +# define HAVE_FCHOWN +# define HAVE_FCHMOD +#endif + +#if (defined(BSD)) && (BSD >= 199103) || defined(__linux) +# define HAVE_SAVED_UIDS +#endif + +#define MY_UID(pw) getuid() +#define MY_GID(pw) getgid() + +/* getopt() isn't part of POSIX. some systems define it in anyway. + * of those that do, some complain that our definition is different and some + * do not. to add to the misery and confusion, some systems define getopt() + * in ways that we cannot predict or comprehend, yet do not define the adjunct + * external variables needed for the interface. + */ +#if (!defined(BSD) || (BSD < 198911)) +int getopt(int, char * const *, const char *); +#endif + +#if (!defined(BSD) || (BSD < 199103)) +extern char *optarg; +extern int optind, opterr, optopt; +#endif + +/* digital unix needs this but does not give us a way to identify it. + */ +extern int flock(int, int); + +/* not all systems who provide flock() provide these definitions. + */ +#ifndef LOCK_SH +# define LOCK_SH 1 +#endif +#ifndef LOCK_EX +# define LOCK_EX 2 +#endif +#ifndef LOCK_NB +# define LOCK_NB 4 +#endif +#ifndef LOCK_UN +# define LOCK_UN 8 +#endif + +#ifndef WCOREDUMP +# define WCOREDUMP(st) (((st) & 0200) != 0) +#endif diff --git a/3rd-party/cron/src/cron/funcs.h b/3rd-party/cron/src/cron/funcs.h new file mode 100644 index 0000000..13c1656 --- /dev/null +++ b/3rd-party/cron/src/cron/funcs.h @@ -0,0 +1,80 @@ +/* + * $Id: funcs.h,v 1.9 2004/01/23 18:56:42 vixie Exp $ + */ + +/* + * Copyright (c) 2021 by Paul Vixie ("VIXIE") + * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1997,2000 by Internet Software Consortium, Inc. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND VIXIE DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL VIXIE BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* Notes: + * This file has to be included by cron.h after data structure defs. + * We should reorg this into sections by module. + */ + +void set_cron_uid(void), + set_cron_cwd(void), + load_database(cron_db *), + open_logfile(void), + sigpipe_func(void), + job_add(entry *, user *), + do_command(entry *, user *), + link_user(cron_db *, user *), + unlink_user(cron_db *, user *), + free_user(user *), + env_free(char **), + unget_char(int, FILE *), + free_entry(entry *), + acquire_daemonlock(int), + skip_comments(FILE *), + log_it(const char *, int, const char *, const char *), + log_close(void); + +int job_runqueue(void), + set_debug_flags(const char *), + get_char(FILE *), + get_string(char *, int, FILE *, char *), + swap_uids(void), + swap_uids_back(void), + load_env(char *, FILE *), + cron_pclose(FILE *), + glue_strings(char *, size_t, const char *, const char *, char), + strcmp_until(const char *, const char *, char), + allowed(const char * ,const char * ,const char *), + strdtb(char *); + +size_t strlens(const char *, ...); + +char *env_get(char *, char **), + *arpadate(time_t *), + *mkprints(unsigned char *, unsigned int), + *first_word(char *, char *), + **env_init(void), + **env_copy(char **), + **env_set(char **, char *); + +user *load_user(int, struct passwd *, const char *), + *find_user(cron_db *, const char *); + +entry *load_entry(FILE *, void (*)(), struct passwd *, char **); + +FILE *cron_popen(char *, char *, struct passwd *); + +struct passwd *pw_dup(const struct passwd *); + +#ifndef HAVE_TM_GMTOFF +long get_gmtoff(time_t *, struct tm *); +#endif diff --git a/3rd-party/cron/src/cron/globals.h b/3rd-party/cron/src/cron/globals.h new file mode 100644 index 0000000..8370a00 --- /dev/null +++ b/3rd-party/cron/src/cron/globals.h @@ -0,0 +1,84 @@ +/* + * $Id: globals.h,v 1.10 2004/01/23 19:03:33 vixie Exp $ + */ + +/* + * Copyright (c) 2021 by Paul Vixie ("VIXIE") + * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1997,2000 by Internet Software Consortium, Inc. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND VIXIE DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL VIXIE BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifdef MAIN_PROGRAM +# define XTRN +# define INIT(x) = x +#else +# define XTRN extern +# define INIT(x) +#endif + +XTRN const char *copyright[] +#ifdef MAIN_PROGRAM + = { + "@(#) Vixie Cron", + "@(#) Copyright 1988,1989,1990,1993,1994,2021 by Paul Vixie", + "@(#) Copyright 1997,2000 by Internet Software Consortium, Inc.", + "@(#) Copyright 2004 by Internet Systems Consortium, Inc.", + "@(#) All rights reserved", + NULL + } +#endif + ; + +XTRN const char *MonthNames[] +#ifdef MAIN_PROGRAM + = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", + NULL + } +#endif + ; + +XTRN const char *DowNames[] +#ifdef MAIN_PROGRAM + = { + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun", + NULL + } +#endif + ; + +XTRN char *ProgramName INIT("amnesia"); +XTRN int LineNumber INIT(0); +XTRN time_t StartTime INIT(0); +XTRN int NoFork INIT(0); +XTRN const struct timespec ts_zero +#ifdef MAIN_PROGRAM += {.tv_sec = 0, .tv_nsec = 0} +#endif +; +#if DEBUGGING +XTRN int DebugFlags INIT(0); +XTRN const char *DebugFlagNames[] +#ifdef MAIN_PROGRAM + = { + "ext", "sch", "proc", "pars", "load", "misc", "test", "bit", + NULL + } +#endif + ; +#else +#define DebugFlags 0 +#endif /* DEBUGGING */ diff --git a/3rd-party/cron/src/cron/macros.h b/3rd-party/cron/src/cron/macros.h new file mode 100644 index 0000000..d677334 --- /dev/null +++ b/3rd-party/cron/src/cron/macros.h @@ -0,0 +1,131 @@ +/* + * $Id: macros.h,v 1.9 2004/01/23 18:56:43 vixie Exp $ + */ + +/* + * Copyright (c) 2021 by Paul Vixie ("VIXIE") + * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1997,2000 by Internet Software Consortium, Inc. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND VIXIE DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL VIXIE BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + + /* these are really immutable, and are + * defined for symbolic convenience only + * TRUE, FALSE, and ERR must be distinct + * ERR must be < OK. + */ +#define TRUE 1 +#define FALSE 0 + /* system calls return this on success */ +#define OK 0 + /* or this on error */ +#define ERR (-1) + + /* turn this on to get '-x' code */ +#ifndef DEBUGGING +#define DEBUGGING FALSE +#endif + +#define INIT_PID 1 /* parent of orphans */ +#define READ_PIPE 0 /* which end of a pipe pair do you read? */ +#define WRITE_PIPE 1 /* or write to? */ +#define STDIN 0 /* what is stdin's file descriptor? */ +#define STDOUT 1 /* stdout's? */ +#define STDERR 2 /* stderr's? */ +#define ERROR_EXIT 1 /* exit() with this will scare the shell */ +#define OK_EXIT 0 /* exit() with this is considered 'normal' */ +#define MAX_FNAME 100 /* max length of internally generated fn */ +#define MAX_COMMAND 1000 /* max length of internally generated cmd */ +#define MAX_ENVSTR 1000 /* max length of envvar=value\0 strings */ +#define MAX_TEMPSTR 100 /* obvious */ +#define MAX_UNAME 33 /* max length of username, should be overkill */ +#define ROOT_UID 0 /* don't change this, it really must be root */ +#define ROOT_USER "root" /* ditto */ + + /* NOTE: these correspond to DebugFlagNames, + * defined below. + */ +#define DEXT 0x0001 /* extend flag for other debug masks */ +#define DSCH 0x0002 /* scheduling debug mask */ +#define DPROC 0x0004 /* process control debug mask */ +#define DPARS 0x0008 /* parsing debug mask */ +#define DLOAD 0x0010 /* database loading debug mask */ +#define DMISC 0x0020 /* misc debug mask */ +#define DTEST 0x0040 /* test mode: don't execute any commands */ + +#define PPC_NULL ((const char **)NULL) + +#ifndef MAXHOSTNAMELEN +#define MAXHOSTNAMELEN 64 +#endif + +#define Skip_Blanks(c, f) \ + while (c == '\t' || c == ' ') \ + c = get_char(f); + +#define Skip_Nonblanks(c, f) \ + while (c!='\t' && c!=' ' && c!='\n' && c != EOF) \ + c = get_char(f); + +#if DEBUGGING +# define Debug(mask, message) \ + if ((DebugFlags & (mask)) != 0) \ + printf message; +#else /* !DEBUGGING */ +# define Debug(mask, message) \ + ; +#endif /* DEBUGGING */ + +#define MkUpper(ch) (islower(ch) ? toupper(ch) : ch) +#define Set_LineNum(ln) {Debug(DPARS|DEXT,("linenum=%d\n",ln)); \ + LineNumber = ln; \ + } + +#ifdef HAVE_TM_GMTOFF +#define get_gmtoff(c, t) ((t)->tm_gmtoff) +#endif + +#define SECONDS_PER_MINUTE 60 +#define SECONDS_PER_HOUR 3600 + +#define FIRST_MINUTE 0 +#define LAST_MINUTE 59 +#define MINUTE_COUNT (LAST_MINUTE - FIRST_MINUTE + 1) + +#define FIRST_HOUR 0 +#define LAST_HOUR 23 +#define HOUR_COUNT (LAST_HOUR - FIRST_HOUR + 1) + +#define FIRST_DOM 1 +#define LAST_DOM 31 +#define DOM_COUNT (LAST_DOM - FIRST_DOM + 1) + +#define FIRST_MONTH 1 +#define LAST_MONTH 12 +#define MONTH_COUNT (LAST_MONTH - FIRST_MONTH + 1) + +/* note on DOW: 0 and 7 are both Sunday, for compatibility reasons. */ +#define FIRST_DOW 0 +#define LAST_DOW 7 +#define DOW_COUNT (LAST_DOW - FIRST_DOW + 1) + +/* + * Because crontab/at files may be owned by their respective users we + * take extreme care in opening them. If the OS lacks the O_NOFOLLOW + * we will just have to live without it. In order for this to be an + * issue an attacker would have to subvert group CRON_GROUP. + */ +#ifndef O_NOFOLLOW +#define O_NOFOLLOW 0 +#endif diff --git a/3rd-party/cron/src/cron/misc.c b/3rd-party/cron/src/cron/misc.c new file mode 100644 index 0000000..a5f5a08 --- /dev/null +++ b/3rd-party/cron/src/cron/misc.c @@ -0,0 +1,736 @@ +/* + * Copyright (c) 1988,1990,1993,1994,2021 by Paul Vixie ("VIXIE") + * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1997,2000 by Internet Software Consortium, Inc. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND VIXIE DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL VIXIE BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#if !defined(lint) && !defined(LINT) +static char rcsid[] = "$Id: misc.c,v 1.16 2004/01/23 18:56:43 vixie Exp $"; +#endif + +/* vix 26jan87 [RCS has the rest of the log] + * vix 30dec86 [written] + */ + +#include "cron.h" +#include + +#if defined(SYSLOG) && defined(LOG_FILE) +# undef LOG_FILE +#endif + +#if defined(LOG_DAEMON) && !defined(LOG_CRON) +# define LOG_CRON LOG_DAEMON +#endif + +#ifndef FACILITY +#define FACILITY LOG_CRON +#endif + +static int LogFD = ERR; + +#if defined(SYSLOG) +static int syslog_open = FALSE; +#endif + +/* + * glue_strings is the overflow-safe equivalent of + * sprintf(buffer, "%s%c%s", a, separator, b); + * + * returns 1 on success, 0 on failure. 'buffer' MUST NOT be used if + * glue_strings fails. + */ +int +glue_strings(char *buffer, size_t buffer_size, const char *a, const char *b, + char separator) +{ + char *buf; + char *buf_end; + + if (buffer_size <= 0) + return (0); + buf_end = buffer + buffer_size; + buf = buffer; + + for ( /* nothing */; buf < buf_end && *a != '\0'; buf++, a++ ) + *buf = *a; + if (buf == buf_end) + return (0); + if (separator != '/' || buf == buffer || buf[-1] != '/') + *buf++ = separator; + if (buf == buf_end) + return (0); + for ( /* nothing */; buf < buf_end && *b != '\0'; buf++, b++ ) + *buf = *b; + if (buf == buf_end) + return (0); + *buf = '\0'; + return (1); +} + +int +strcmp_until(const char *left, const char *right, char until) { + while (*left && *left != until && *left == *right) { + left++; + right++; + } + + if ((*left=='\0' || *left == until) && + (*right=='\0' || *right == until)) { + return (0); + } + return (*left - *right); +} + +/* strdtb(s) - delete trailing blanks in string 's' and return new length + */ +int +strdtb(char *s) { + char *x = s; + + /* scan forward to the null + */ + while (*x) + x++; + + /* scan backward to either the first character before the string, + * or the last non-blank in the string, whichever comes first. + */ + do {x--;} + while (x >= s && isspace((unsigned char)*x)); + + /* one character beyond where we stopped above is where the null + * goes. + */ + *++x = '\0'; + + /* the difference between the position of the null character and + * the position of the first character of the string is the length. + */ + return (x - s); +} + +int +set_debug_flags(const char *flags) { + /* debug flags are of the form flag[,flag ...] + * + * if an error occurs, print a message to stdout and return FALSE. + * otherwise return TRUE after setting ERROR_FLAGS. + */ + +#if !DEBUGGING + + printf("this program was compiled without debugging enabled\n"); + return (FALSE); + +#else /* DEBUGGING */ + + const char *pc = flags; + + DebugFlags = 0; + + while (*pc) { + const char **test; + int mask; + + /* try to find debug flag name in our list. + */ + for (test = DebugFlagNames, mask = 1; + *test != NULL && strcmp_until(*test, pc, ','); + test++, mask <<= 1) + NULL; + + if (!*test) { + fprintf(stderr, + "unrecognized debug flag <%s> <%s>\n", + flags, pc); + return (FALSE); + } + + DebugFlags |= mask; + + /* skip to the next flag + */ + while (*pc && *pc != ',') + pc++; + if (*pc == ',') + pc++; + } + + if (DebugFlags) { + int flag; + + fprintf(stderr, "debug flags enabled:"); + + for (flag = 0; DebugFlagNames[flag]; flag++) + if (DebugFlags & (1 << flag)) + fprintf(stderr, " %s", DebugFlagNames[flag]); + fprintf(stderr, "\n"); + } + + return (TRUE); + +#endif /* DEBUGGING */ +} + +void +set_cron_uid(void) { +#if defined(BSD) || defined(POSIX) + if (seteuid(ROOT_UID) < OK) { + perror("seteuid"); + exit(ERROR_EXIT); + } +#else + if (setuid(ROOT_UID) < OK) { + perror("setuid"); + exit(ERROR_EXIT); + } +#endif +} + +void +set_cron_cwd(void) { + struct stat sb; + struct group *grp = NULL; + +#ifdef CRON_GROUP + grp = getgrnam(CRON_GROUP); +#endif + /* first check for CRONDIR ("/var/cron" or some such) + */ + if (stat(CRONDIR, &sb) < OK && errno == ENOENT) { + perror(CRONDIR); + if (OK == mkdir(CRONDIR, 0710)) { + fprintf(stderr, "%s: created\n", CRONDIR); + stat(CRONDIR, &sb); + } else { + fprintf(stderr, "%s: ", CRONDIR); + perror("mkdir"); + exit(ERROR_EXIT); + } + } + if (!S_ISDIR(sb.st_mode)) { + fprintf(stderr, "'%s' is not a directory, bailing out.\n", + CRONDIR); + exit(ERROR_EXIT); + } + if (chdir(CRONDIR) < OK) { + fprintf(stderr, "cannot chdir(%s), bailing out.\n", CRONDIR); + perror(CRONDIR); + exit(ERROR_EXIT); + } + + /* CRONDIR okay (now==CWD), now look at SPOOL_DIR ("tabs" or some such) + */ + if (stat(SPOOL_DIR, &sb) < OK && errno == ENOENT) { + perror(SPOOL_DIR); + if (OK == mkdir(SPOOL_DIR, 0700)) { + fprintf(stderr, "%s: created\n", SPOOL_DIR); + stat(SPOOL_DIR, &sb); + } else { + fprintf(stderr, "%s: ", SPOOL_DIR); + perror("mkdir"); + exit(ERROR_EXIT); + } + } + if (!S_ISDIR(sb.st_mode)) { + fprintf(stderr, "'%s' is not a directory, bailing out.\n", + SPOOL_DIR); + exit(ERROR_EXIT); + } + if (grp != NULL) { + if (sb.st_gid != grp->gr_gid) + chown(SPOOL_DIR, -1, grp->gr_gid); + if (sb.st_mode != 01730) + chmod(SPOOL_DIR, 01730); + } +} + +/* acquire_daemonlock() - write our PID into /etc/cron.pid, unless + * another daemon is already running, which we detect here. + * + * note: main() calls us twice; once before forking, once after. + * we maintain static storage of the file pointer so that we + * can rewrite our PID into _PATH_CRON_PID after the fork. + */ +void +acquire_daemonlock(int closeflag) { + static int fd = -1; + char buf[3*MAX_FNAME]; + const char *pidfile; + char *ep; + ssize_t num; + + if (closeflag) { + /* close stashed fd for child so we don't leak it. */ + if (fd != -1) { + close(fd); + fd = -1; + } + return; + } + + if (fd == -1) { + pidfile = _PATH_CRON_PID; + /* Initial mode is 0600 to prevent flock() race/DoS. */ + if ((fd = open(pidfile, O_RDWR|O_CREAT, 0600)) == -1) { + sprintf(buf, "can't open or create %s: %s", + pidfile, strerror(errno)); + fprintf(stderr, "%s: %s\n", ProgramName, buf); + log_it("CRON", getpid(), "DEATH", buf); + exit(ERROR_EXIT); + } + + if (flock(fd, LOCK_EX|LOCK_NB) < OK) { + int save_errno = errno; + long otherpid = -1; + + bzero(buf, sizeof(buf)); + if ((num = read(fd, buf, sizeof(buf) - 1)) > 0 && + (otherpid = strtol(buf, &ep, 10)) > 0 && + ep != buf && *ep == '\n' && otherpid != LONG_MAX) { + sprintf(buf, + "can't lock %s, otherpid may be %ld: %s", + pidfile, otherpid, strerror(save_errno)); + } else { + sprintf(buf, + "can't lock %s, otherpid unknown: %s", + pidfile, strerror(save_errno)); + } + sprintf(buf, "can't lock %s, otherpid may be %ld: %s", + pidfile, otherpid, strerror(save_errno)); + fprintf(stderr, "%s: %s\n", ProgramName, buf); + log_it("CRON", getpid(), "DEATH", buf); + exit(ERROR_EXIT); + } + (void) fchmod(fd, 0644); + (void) fcntl(fd, F_SETFD, 1); + } + + sprintf(buf, "%ld\n", (long)getpid()); + (void) lseek(fd, (off_t)0, SEEK_SET); + num = write(fd, buf, strlen(buf)); + (void) ftruncate(fd, num); + + /* abandon fd even though the file is open. we need to keep + * it open and locked, but we don't need the handles elsewhere. + */ +} + +/* get_char(file) : like getc() but increment LineNumber on newlines + */ +int +get_char(FILE *file) { + int ch; + + ch = getc(file); + if (ch == '\n') + Set_LineNum(LineNumber + 1) + return (ch); +} + +/* unget_char(ch, file) : like ungetc but do LineNumber processing + */ +void +unget_char(int ch, FILE *file) { + ungetc(ch, file); + if (ch == '\n') + Set_LineNum(LineNumber - 1) +} + +/* get_string(str, max, file, termstr) : like fgets() but + * (1) has terminator string which should include \n + * (2) will always leave room for the null + * (3) uses get_char() so LineNumber will be accurate + * (4) returns EOF or terminating character, whichever + */ +int +get_string(char *string, int size, FILE *file, char *terms) { + int ch; + + while (EOF != (ch = get_char(file)) && !strchr(terms, ch)) { + if (size > 1) { + *string++ = (char) ch; + size--; + } + } + + if (size > 0) + *string = '\0'; + + return (ch); +} + +/* skip_comments(file) : read past comment (if any) + */ +void +skip_comments(FILE *file) { + int ch; + + while (EOF != (ch = get_char(file))) { + /* ch is now the first character of a line. + */ + + while (ch == ' ' || ch == '\t') + ch = get_char(file); + + if (ch == EOF) + break; + + /* ch is now the first non-blank character of a line. + */ + + if (ch != '\n' && ch != '#') + break; + + /* ch must be a newline or comment as first non-blank + * character on a line. + */ + + while (ch != '\n' && ch != EOF) + ch = get_char(file); + + /* ch is now the newline of a line which we're going to + * ignore. + */ + } + if (ch != EOF) + unget_char(ch, file); +} + +/* int in_file(const char *string, FILE *file, int error) + * return TRUE if one of the lines in file matches string exactly, + * FALSE if no lines match, and error on error. + */ +static int +in_file(const char *string, FILE *file, int error) +{ + char line[MAX_TEMPSTR]; + char *endp; + + if (fseek(file, 0L, SEEK_SET)) + return (error); + while (fgets(line, MAX_TEMPSTR, file)) { + if (line[0] != '\0') { + endp = &line[strlen(line) - 1]; + if (*endp != '\n') + return (error); + *endp = '\0'; + if (0 == strcmp(line, string)) + return (TRUE); + } + } + if (ferror(file)) + return (error); + return (FALSE); +} + +/* int allowed(const char *username, const char *allow_file, const char *deny_file) + * returns TRUE if (allow_file exists and user is listed) + * or (deny_file exists and user is NOT listed). + * root is always allowed. + */ +int +allowed(const char *username, const char *allow_file, const char *deny_file) { + FILE *fp; + int isallowed; + + if (strcmp(username, ROOT_USER) == 0) + return (TRUE); + isallowed = FALSE; + if ((fp = fopen(allow_file, "r")) != NULL) { + isallowed = in_file(username, fp, FALSE); + fclose(fp); + } else if ((fp = fopen(deny_file, "r")) != NULL) { + isallowed = !in_file(username, fp, FALSE); + fclose(fp); + } + return (isallowed); +} + +void +log_it(const char *username, PID_T xpid, const char *event, const char *detail) { +#if defined(LOG_FILE) || DEBUGGING + PID_T pid = xpid; +#endif +#if defined(LOG_FILE) + char *msg; + TIME_T now = time((TIME_T) 0); + struct tm *t = localtime(&now); +#endif /*LOG_FILE*/ + +#if defined(LOG_FILE) + /* we assume that MAX_TEMPSTR will hold the date, time, &punctuation. + */ + msg = malloc(strlen(username) + + strlen(event) + + strlen(detail) + + MAX_TEMPSTR); + if (msg == NULL) + return; + + if (LogFD < OK) { + LogFD = open(LOG_FILE, O_WRONLY|O_APPEND|O_CREAT, 0600); + if (LogFD < OK) { + fprintf(stderr, "%s: can't open log file\n", + ProgramName); + perror(LOG_FILE); + } else { + (void) fcntl(LogFD, F_SETFD, 1); + } + } + + /* we have to sprintf() it because fprintf() doesn't always write + * everything out in one chunk and this has to be atomically appended + * to the log file. + */ + sprintf(msg, "%s (%02d/%02d-%02d:%02d:%02d-%d) %s (%s)\n", + username, + t->tm_mon+1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec, pid, + event, detail); + + /* we have to run strlen() because sprintf() returns (char*) on old BSD + */ + if (LogFD < OK || write(LogFD, msg, strlen(msg)) < OK) { + if (LogFD >= OK) + perror(LOG_FILE); + fprintf(stderr, "%s: can't write to log file\n", ProgramName); + write(STDERR, msg, strlen(msg)); + } + + free(msg); +#endif /*LOG_FILE*/ + +#if defined(SYSLOG) + if (!syslog_open) { +# ifdef LOG_DAEMON + openlog(ProgramName, LOG_PID, FACILITY); +# else + openlog(ProgramName, LOG_PID); +# endif + syslog_open = TRUE; /* assume openlog success */ + } + + syslog(LOG_INFO, "(%s) %s (%s)", username, event, detail); + +#endif /*SYSLOG*/ + +#if DEBUGGING + if (DebugFlags) { + fprintf(stderr, "log_it: (%s %ld) %s (%s)\n", + username, (long)pid, event, detail); + } +#endif +} + +void +log_close(void) { + if (LogFD != ERR) { + close(LogFD); + LogFD = ERR; + } +#if defined(SYSLOG) + closelog(); + syslog_open = FALSE; +#endif /*SYSLOG*/ +} + +/* char *first_word(char *s, char *t) + * return pointer to first word + * parameters: + * s - string we want the first word of + * t - terminators, implicitly including \0 + * warnings: + * (1) this routine is fairly slow + * (2) it returns a pointer to static storage + */ +char * +first_word(char *s, char *t) { + static char retbuf[2][MAX_TEMPSTR + 1]; /* sure wish C had GC */ + static int retsel = 0; + char *rb, *rp; + + /* select a return buffer */ + retsel = 1-retsel; + rb = &retbuf[retsel][0]; + rp = rb; + + /* skip any leading terminators */ + while (*s && (NULL != strchr(t, *s))) { + s++; + } + + /* copy until next terminator or full buffer */ + while (*s && (NULL == strchr(t, *s)) && (rp < &rb[MAX_TEMPSTR])) { + *rp++ = *s++; + } + + /* finish the return-string and return it */ + *rp = '\0'; + return (rb); +} + +/* warning: + * heavily ascii-dependent. + */ +void +mkprint(dst, src, len) + char *dst; + unsigned char *src; + int len; +{ + /* + * XXX + * We know this routine can't overflow the dst buffer because mkprints() + * allocated enough space for the worst case. + */ + while (len-- > 0) + { + unsigned char ch = *src++; + + if (ch < ' ') { /* control character */ + *dst++ = '^'; + *dst++ = ch + '@'; + } else if (ch < 0177) { /* printable */ + *dst++ = ch; + } else if (ch == 0177) { /* delete/rubout */ + *dst++ = '^'; + *dst++ = '?'; + } else { /* parity character */ + sprintf(dst, "\\%03o", ch); + dst += 4; + } + } + *dst = '\0'; +} + +/* warning: + * returns a pointer to malloc'd storage, you must call free yourself. + */ +char * +mkprints(src, len) + unsigned char *src; + unsigned int len; +{ + char *dst = malloc(len*4 + 1); + + if (dst) + mkprint(dst, src, len); + + return (dst); +} + +#ifdef MAIL_DATE +/* Sat, 27 Feb 1993 11:44:51 -0800 (CST) + * 1234567890123456789012345678901234567 + */ +char * +arpadate(clock) + time_t *clock; +{ + time_t t = clock ? *clock : time((TIME_T) 0); + struct tm tm = *localtime(&t); + long gmtoff = get_gmtoff(&t, &tm); + int hours = gmtoff / SECONDS_PER_HOUR; + int minutes = (gmtoff - (hours * SECONDS_PER_HOUR)) / SECONDS_PER_MINUTE; + static char ret[64]; /* zone name might be >3 chars */ + + (void) sprintf(ret, "%s, %2d %s %2d %02d:%02d:%02d %.2d%.2d (%s)", + DowNames[tm.tm_wday], + tm.tm_mday, + MonthNames[tm.tm_mon], + tm.tm_year + 1900, + tm.tm_hour, + tm.tm_min, + tm.tm_sec, + hours, + minutes, + TZONE(*tm)); + return (ret); +} +#endif /*MAIL_DATE*/ + +#ifdef HAVE_SAVED_UIDS +static uid_t save_euid; +static gid_t save_egid; + +int swap_uids(void) { + save_egid = getegid(); + save_euid = geteuid(); + return ((setegid(getgid()) || seteuid(getuid())) ? -1 : 0); +} + +int swap_uids_back(void) { + return ((setegid(getgid()) || seteuid(getuid())) ? -1 : 0); +} + +#else /*HAVE_SAVED_UIDS*/ + +int swap_uids(void) { + return ((setregid(getegid(), getgid()) || setreuid(geteuid(), getuid())) + ? -1 : 0); +} + +int swap_uids_back(void) { + return (swap_uids()); +} +#endif /*HAVE_SAVED_UIDS*/ + +size_t +strlens(const char *last, ...) { + va_list ap; + size_t ret = 0; + const char *str; + + va_start(ap, last); + for (str = last; str != NULL; str = va_arg(ap, const char *)) + ret += strlen(str); + va_end(ap); + return (ret); +} + +/* Return the offset from GMT in seconds (algorithm taken from sendmail). + * + * warning: + * clobbers the static storage space used by localtime() and gmtime(). + * If the local pointer is non-NULL it *must* point to a local copy. + */ +#ifndef HAVE_TM_GMTOFF +long get_gmtoff(time_t *clock, struct tm *local) +{ + struct tm gmt; + long offset; + + gmt = *gmtime(clock); + if (local == NULL) + local = localtime(clock); + + offset = (local->tm_sec - gmt.tm_sec) + + ((local->tm_min - gmt.tm_min) * 60) + + ((local->tm_hour - gmt.tm_hour) * 3600); + + /* Timezone may cause year rollover to happen on a different day. */ + if (local->tm_year < gmt.tm_year) + offset -= 24 * 3600; + else if (local->tm_year > gmt.tm_year) + offset -= 24 * 3600; + else if (local->tm_yday < gmt.tm_yday) + offset -= 24 * 3600; + else if (local->tm_yday > gmt.tm_yday) + offset += 24 * 3600; + + return (offset); +} +#endif /* HAVE_TM_GMTOFF */ diff --git a/3rd-party/cron/src/cron/structs.h b/3rd-party/cron/src/cron/structs.h new file mode 100644 index 0000000..638a01a --- /dev/null +++ b/3rd-party/cron/src/cron/structs.h @@ -0,0 +1,62 @@ +/* + * $Id: structs.h,v 1.7 2004/01/23 18:56:43 vixie Exp $ + */ + +/* + * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1997,2000 by Internet Software Consortium, Inc. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +typedef struct _entry { + struct _entry *next; + struct passwd *pwd; + char **envp; + char *cmd; + bitstr_t bit_decl(minute, MINUTE_COUNT); + bitstr_t bit_decl(hour, HOUR_COUNT); + bitstr_t bit_decl(dom, DOM_COUNT); + bitstr_t bit_decl(month, MONTH_COUNT); + bitstr_t bit_decl(dow, DOW_COUNT); + int flags; +#define MIN_STAR 0x01 +#define HR_STAR 0x02 +#define DOM_STAR 0x04 +#define DOW_STAR 0x08 +#define WHEN_REBOOT 0x10 +#define DONT_LOG 0x20 +} entry; + + /* the crontab database will be a list of the + * following structure, one element per user + * plus one for the system. + * + * These are the crontabs. + */ + +typedef struct _user { + struct _user *next, *prev; /* links */ + char *name; + struct timespec mtim; /* last modtime of crontab */ + entry *crontab; /* this person's crontab */ +} user; + +typedef struct _cron_db { + user *head, *tail; /* links */ + struct timespec mtim; /* last modtime on spooldir */ +} cron_db; + /* in the C tradition, we only create + * variables for the main program, just + * extern them elsewhere. + */ From c86ced9db4a4b7aa5db3dfbd9f9658a9bb1265e4 Mon Sep 17 00:00:00 2001 From: Rodrigo Speller Date: Sat, 8 Apr 2023 02:11:08 -0300 Subject: [PATCH 03/14] Implements cronutils --- 3rd-party/cron/.gitignore | 1 + 3rd-party/cron/include/cronutils.h | 94 ++++ 3rd-party/cron/makefile | 101 +++++ 3rd-party/cron/src/README.md | 38 ++ 3rd-party/cron/src/bitstring.h | 1 + 3rd-party/cron/src/cron/cron.h | 2 - 3rd-party/cron/src/cron/entry.c | 164 +------ 3rd-party/cron/src/cron/externs.h | 91 ---- 3rd-party/cron/src/cron/funcs.h | 54 +-- 3rd-party/cron/src/cron/globals.h | 21 - 3rd-party/cron/src/cron/misc.c | 665 ----------------------------- 3rd-party/cron/src/cron/structs.h | 28 -- 3rd-party/cron/src/cronutils.c | 187 ++++++++ 3rd-party/cron/src/fake/stdio.c | 32 ++ 3rd-party/cron/src/fake/stdio.h | 20 + 3rd-party/cron/src/stdio.h | 1 + makefile | 9 + src/atone.h | 1 + 18 files changed, 490 insertions(+), 1020 deletions(-) create mode 100644 3rd-party/cron/.gitignore create mode 100644 3rd-party/cron/include/cronutils.h create mode 100644 3rd-party/cron/makefile create mode 100644 3rd-party/cron/src/README.md create mode 100644 3rd-party/cron/src/bitstring.h create mode 100644 3rd-party/cron/src/cronutils.c create mode 100644 3rd-party/cron/src/fake/stdio.c create mode 100644 3rd-party/cron/src/fake/stdio.h create mode 100644 3rd-party/cron/src/stdio.h diff --git a/3rd-party/cron/.gitignore b/3rd-party/cron/.gitignore new file mode 100644 index 0000000..567609b --- /dev/null +++ b/3rd-party/cron/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/3rd-party/cron/include/cronutils.h b/3rd-party/cron/include/cronutils.h new file mode 100644 index 0000000..bc10e57 --- /dev/null +++ b/3rd-party/cron/include/cronutils.h @@ -0,0 +1,94 @@ +// Copyright (c) Rodrigo Speller. All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +#ifndef CRONUTILS_H +#define CRONUTILS_H + +#ifndef CRON_API_EXPORT + +#ifdef __cplusplus +#define CRON_API_EXPORT extern "C" +#else +#define CRON_API_EXPORT +#endif + +#endif + +#include + +/** + * Cron expression state. + * + * This struct is opaque and should not be accessed directly. + */ +typedef struct cron_expr_t { + void *expr_st_ptr; +} cron_expr_t; + +/** + * Cron time state. + * + * This struct is opaque and should not be accessed directly. + */ +typedef struct cron_time_t { + void *time_st_ptr; +} cron_time_t; + +/** + * Allocate a new cron expression state. + */ +CRON_API_EXPORT +cron_expr_t *cron_expr_alloc(void); + +/** + * Free a cron expression state. + */ +CRON_API_EXPORT +void cron_expr_free(cron_expr_t *expr_ptr); + +/** + * Parse a cron expression string into a cron expression state. If an + * error occurs, the error_func is called with the error cause message. + */ +CRON_API_EXPORT +void cron_expr_parse( + char const *expr_str, + cron_expr_t *expr_ptr, + void (*error_func)(const char *)); + +/** + * Match a cron expression state against a cron time state to determine + * if the time matches the expression. + */ +CRON_API_EXPORT +int cron_expr_match( + const cron_expr_t *expr_ptr, + const cron_time_t *time_ptr); + +/** + * Match a cron expression state to determine if that expression matches + * the "@reboot" expression. This is a special expression that matches + * only once, when the cron is started. + */ +CRON_API_EXPORT +int cron_expr_match_reboot(const cron_expr_t *expr_ptr); + +/** + * Allocate a new cron time state. + */ +CRON_API_EXPORT +cron_time_t *cron_time_alloc(void); + +/** + * Free a cron time state. + */ +CRON_API_EXPORT +void cron_time_free(cron_time_t *time_ptr); + +/** + * Set the time of a cron time state. + */ +CRON_API_EXPORT +void cron_time_set(cron_time_t *time_ptr, const time_t *timer); + +#endif diff --git a/3rd-party/cron/makefile b/3rd-party/cron/makefile new file mode 100644 index 0000000..663b71a --- /dev/null +++ b/3rd-party/cron/makefile @@ -0,0 +1,101 @@ +# project targets + +all: libcron +libcron: build +build: before-build build-core after-build +rebuild: clean build + +.PHONY: clean build rebuild libcron + +# project configuration + +INCLUDE_PATH+=$(SRCDIR) +INCLUDE_PATH+=include +ARFLAGS=rc +#CFLAGS+=-Wall +CFLAGS+=-fdiagnostics-color=always +CFLAGS+=-std=gnu17 +CFLAGS+=-MMD + +#----------------------------------------------------------------------- + +# default directories + +SRCDIR?=src +BUILDDIR?=build +TARGETDIR?=$(BUILDDIR)/$(TARGET) +OUTDIR?=$(TARGETDIR)/bin +OBJDIR?=$(TARGETDIR)/obj + +# cleanup + +CLEAN+=$(OUTDIR) +CLEAN+=$(OBJDIR) + +# input parameters + +DEBUG?=0 +VERBOSE?=0 + +ifneq ($(DEBUG), 0) + TARGET=debug + CFLAGS+=-g + CFLAGS+=-Og +else + TARGET=release + CFLAGS+=-Os +endif + +ifneq ($(VERBOSE), 0) + CFLAGS+=-v +endif + +# artifacts + +SOURCES=$(shell find "$(SRCDIR)" -name *.c) +OBJECTS=$(SOURCES:%.c=$(OBJDIR)/%.o) + +# expand variables + +CFLAGS+=$(foreach d, $(INCLUDE_PATH), -I$d) + +# internal targets + +# BUILD + +before-build: + @echo "Building..." + @echo " Target: $(TARGET)" + @echo " Output directory: $(OUTDIR)" + @-mkdir -p $(OUTDIR) $(dir $(OBJECTS)) + +build-core: $(OUTDIR)/libcron.a + +$(OUTDIR)/libcron.a: $(OBJECTS) + @echo " Creating library $@" + @$(AR) $(ARFLAGS) $(OUTDIR)/libcron.a $(OBJECTS) + +$(OBJECTS): $(OBJDIR)/%.o: %.c + @echo " Compiling $<" + @$(CC) $(CFLAGS) -o $@ -c $< + +after-build: + @echo "Build completed" + +#CLEAN + +clean: + @-rm -rv -- $(CLEAN) || true + +clean-all: + @-rm -rv -- $(BUILDDIR) || true + +$(TARGETDIR)/.marker: makefile + @-mkdir -p $(TARGETDIR) + @touch $@ + $(MAKE) clean + +# includes + +-include $(OBJECTS:.o=.d) +-include $(TARGETDIR)/.marker diff --git a/3rd-party/cron/src/README.md b/3rd-party/cron/src/README.md new file mode 100644 index 0000000..095d89f --- /dev/null +++ b/3rd-party/cron/src/README.md @@ -0,0 +1,38 @@ +# Vixie Cron + +Atone uses the Vixie Cron code to parse cron expressions. + +The original Vixie Cron code is available at [Vixie Cron on GitHub](https://github.com/vixie/cron/tree/f4311d3a1f4018b8a2927437216585d058b95681). + +## Changes + +The following changes have been made to the original code: + +- Only the necessary files have been included in the Atone source tree. +- The included files have been stripped of unecessary code to only parse the cron expressions. +- In addition to strip unnecessary lines, only one line of the original source code has been changed to allow parsing only the cron expressions, without commands: + + ```diff + --- a/3rd-party/cron/src/cron/entry.c + +++ b/3rd-party/cron/src/cron/entry.c + + /* DOW (days of week) + */ + if (ch == '*') + e->flags |= DOW_STAR; + ch = get_list(e->dow, FIRST_DOW, LAST_DOW, + DowNames, ch, file); + -if (ch == EOF) { + +if (ch != EOF) { + ecode = e_dow; + goto eof; + } + ``` + +## License + +Copyright 1988,1990,1993,2021 by Paul Vixie ("VIXIE")
+Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
+Copyright (c) 1997,2000 by Internet Software Consortium, Inc. + +Vixie Cron is licensed under a permissive license. See the [LICENSE](cron/LICENSE) file for details. diff --git a/3rd-party/cron/src/bitstring.h b/3rd-party/cron/src/bitstring.h new file mode 100644 index 0000000..2e22aaf --- /dev/null +++ b/3rd-party/cron/src/bitstring.h @@ -0,0 +1 @@ +#include "cron/bitstring.h" diff --git a/3rd-party/cron/src/cron/cron.h b/3rd-party/cron/src/cron/cron.h index 8864b87..c7ca5b5 100644 --- a/3rd-party/cron/src/cron/cron.h +++ b/3rd-party/cron/src/cron/cron.h @@ -26,9 +26,7 @@ */ #define CRON_VERSION "V4.999" -#include "config.h" #include "externs.h" -#include "pathnames.h" #include "macros.h" #include "structs.h" #include "funcs.h" diff --git a/3rd-party/cron/src/cron/entry.c b/3rd-party/cron/src/cron/entry.c index 3062ba8..fdf19f3 100644 --- a/3rd-party/cron/src/cron/entry.c +++ b/3rd-party/cron/src/cron/entry.c @@ -55,9 +55,6 @@ static int get_list(bitstr_t *, int, int, const char *[], int, FILE *), void free_entry(entry *e) { - free(e->cmd); - free(e->pwd); - env_free(e->envp); free(e); } @@ -83,13 +80,9 @@ load_entry(FILE *file, void (*error_func)(), struct passwd *pw, char **envp) { entry *e; int ch; char cmd[MAX_COMMAND]; - char envstr[MAX_ENVSTR]; - char **tenvp; Debug(DPARS, ("load_entry()...about to eat comments\n")) - skip_comments(file); - ch = get_char(file); if (ch == EOF) return (NULL); @@ -216,7 +209,7 @@ load_entry(FILE *file, void (*error_func)(), struct passwd *pw, char **envp) { e->flags |= DOW_STAR; ch = get_list(e->dow, FIRST_DOW, LAST_DOW, DowNames, ch, file); - if (ch == EOF) { + if (ch != EOF) { ecode = e_dow; goto eof; } @@ -228,153 +221,6 @@ load_entry(FILE *file, void (*error_func)(), struct passwd *pw, char **envp) { bit_set(e->dow, 7); } - /* check for permature EOL and catch a common typo */ - if (ch == '\n' || ch == '*') { - ecode = e_cmd; - goto eof; - } - - /* ch is the first character of a command, or a username */ - unget_char(ch, file); - - if (!pw) { - char *username = cmd; /* temp buffer */ - - Debug(DPARS, ("load_entry()...about to parse username\n")) - ch = get_string(username, MAX_COMMAND, file, " \t\n"); - - Debug(DPARS, ("load_entry()...got %s\n",username)) - if (ch == EOF || ch == '\n' || ch == '*') { - ecode = e_cmd; - goto eof; - } - - /* Need to have consumed blanks before checking for options - * below. */ - Skip_Blanks(ch, file) - unget_char(ch, file); - - pw = getpwnam(username); - if (pw == NULL) { - ecode = e_username; - goto eof; - } - Debug(DPARS, ("load_entry()...uid %ld, gid %ld\n", - (long)pw->pw_uid, (long)pw->pw_gid)) - } - - if ((e->pwd = pw_dup(pw)) == NULL) { - ecode = e_memory; - goto eof; - } - bzero(e->pwd->pw_passwd, strlen(e->pwd->pw_passwd)); - - /* copy and fix up environment. some variables are just defaults and - * others are overrides. - */ - if ((e->envp = env_copy(envp)) == NULL) { - ecode = e_memory; - goto eof; - } - if (!env_get("SHELL", e->envp)) { - if (glue_strings(envstr, sizeof envstr, "SHELL", - _PATH_BSHELL, '=')) { - if ((tenvp = env_set(e->envp, envstr)) == NULL) { - ecode = e_memory; - goto eof; - } - e->envp = tenvp; - } else - log_it("CRON", getpid(), "error", "can't set SHELL"); - } - if (!env_get("HOME", e->envp)) { - if (glue_strings(envstr, sizeof envstr, "HOME", - pw->pw_dir, '=')) { - if ((tenvp = env_set(e->envp, envstr)) == NULL) { - ecode = e_memory; - goto eof; - } - e->envp = tenvp; - } else - log_it("CRON", getpid(), "error", "can't set HOME"); - } -#ifndef LOGIN_CAP - /* If login.conf is in used we will get the default PATH later. */ - if (!env_get("PATH", e->envp)) { - if (glue_strings(envstr, sizeof envstr, "PATH", - _PATH_DEFPATH, '=')) { - if ((tenvp = env_set(e->envp, envstr)) == NULL) { - ecode = e_memory; - goto eof; - } - e->envp = tenvp; - } else - log_it("CRON", getpid(), "error", "can't set PATH"); - } -#endif /* LOGIN_CAP */ - if (glue_strings(envstr, sizeof envstr, "LOGNAME", - pw->pw_name, '=')) { - if ((tenvp = env_set(e->envp, envstr)) == NULL) { - ecode = e_memory; - goto eof; - } - e->envp = tenvp; - } else - log_it("CRON", getpid(), "error", "can't set LOGNAME"); -#if defined(BSD) || defined(__linux) - if (glue_strings(envstr, sizeof envstr, "USER", - pw->pw_name, '=')) { - if ((tenvp = env_set(e->envp, envstr)) == NULL) { - ecode = e_memory; - goto eof; - } - e->envp = tenvp; - } else - log_it("CRON", getpid(), "error", "can't set USER"); -#endif - - Debug(DPARS, ("load_entry()...about to parse command\n")) - - /* If the first character of the command is '-' it is a cron option. - */ - while ((ch = get_char(file)) == '-') { - switch (ch = get_char(file)) { - case 'q': - e->flags |= DONT_LOG; - Skip_Nonblanks(ch, file) - break; - default: - ecode = e_option; - goto eof; - } - Skip_Blanks(ch, file) - if (ch == EOF || ch == '\n') { - ecode = e_cmd; - goto eof; - } - } - unget_char(ch, file); - - /* Everything up to the next \n or EOF is part of the command... - * too bad we don't know in advance how long it will be, since we - * need to malloc a string for it... so, we limit it to MAX_COMMAND. - */ - ch = get_string(cmd, MAX_COMMAND, file, "\n"); - - /* a file without a \n before the EOF is rude, so we'll complain... - */ - if (ch == EOF) { - ecode = e_cmd; - goto eof; - } - - /* got the command in the 'cmd' string; save it in *e. - */ - if ((e->cmd = strdup(cmd)) == NULL) { - ecode = e_memory; - goto eof; - } - Debug(DPARS, ("load_entry()...returning successfully\n")) /* success, fini, return pointer to the entry we just created... @@ -382,12 +228,6 @@ load_entry(FILE *file, void (*error_func)(), struct passwd *pw, char **envp) { return (e); eof: - if (e->envp) - env_free(e->envp); - if (e->pwd) - free(e->pwd); - if (e->cmd) - free(e->cmd); free(e); while (ch != '\n' && !feof(file)) ch = get_char(file); @@ -457,8 +297,6 @@ get_range(bitstr_t *bits, int low, int high, const char *names[], num1 = low; num2 = high; ch = get_char(file); - if (ch == EOF) - return (EOF); } else { ch = get_number(&num1, low, names, ch, file, ",- \t\n"); if (ch == EOF) diff --git a/3rd-party/cron/src/cron/externs.h b/3rd-party/cron/src/cron/externs.h index 93339ab..43485b4 100644 --- a/3rd-party/cron/src/cron/externs.h +++ b/3rd-party/cron/src/cron/externs.h @@ -18,103 +18,12 @@ /* reorder these #include's at your peril */ -#include -#include -#include -#include -#include -#include -#include - #include #include -#ifndef isascii -#define isascii(c) ((unsigned)(c)<=0177) -#endif #include -#include -#include -#include -#include #include -#include #include #include #include #include #include -#include -#include - -#if defined(SYSLOG) -# include -#endif - -#if defined(LOGIN_CAP) -# include -#endif /*LOGIN_CAP*/ - -#if defined(BSD_AUTH) -# include -#endif /*BSD_AUTH*/ - -#define DIR_T struct dirent -#define WAIT_T int -#define SIG_T sig_t -#define TIME_T time_t -#define PID_T pid_t - -#ifndef TZNAME_ALREADY_DEFINED -extern char *tzname[2]; -#endif -#define TZONE(tm) tzname[(tm).tm_isdst] - -#if (defined(BSD)) && (BSD >= 198606) || defined(__linux) -# define HAVE_FCHOWN -# define HAVE_FCHMOD -#endif - -#if (defined(BSD)) && (BSD >= 199103) || defined(__linux) -# define HAVE_SAVED_UIDS -#endif - -#define MY_UID(pw) getuid() -#define MY_GID(pw) getgid() - -/* getopt() isn't part of POSIX. some systems define it in anyway. - * of those that do, some complain that our definition is different and some - * do not. to add to the misery and confusion, some systems define getopt() - * in ways that we cannot predict or comprehend, yet do not define the adjunct - * external variables needed for the interface. - */ -#if (!defined(BSD) || (BSD < 198911)) -int getopt(int, char * const *, const char *); -#endif - -#if (!defined(BSD) || (BSD < 199103)) -extern char *optarg; -extern int optind, opterr, optopt; -#endif - -/* digital unix needs this but does not give us a way to identify it. - */ -extern int flock(int, int); - -/* not all systems who provide flock() provide these definitions. - */ -#ifndef LOCK_SH -# define LOCK_SH 1 -#endif -#ifndef LOCK_EX -# define LOCK_EX 2 -#endif -#ifndef LOCK_NB -# define LOCK_NB 4 -#endif -#ifndef LOCK_UN -# define LOCK_UN 8 -#endif - -#ifndef WCOREDUMP -# define WCOREDUMP(st) (((st) & 0200) != 0) -#endif diff --git a/3rd-party/cron/src/cron/funcs.h b/3rd-party/cron/src/cron/funcs.h index 13c1656..5cc71f5 100644 --- a/3rd-party/cron/src/cron/funcs.h +++ b/3rd-party/cron/src/cron/funcs.h @@ -25,56 +25,10 @@ * We should reorg this into sections by module. */ -void set_cron_uid(void), - set_cron_cwd(void), - load_database(cron_db *), - open_logfile(void), - sigpipe_func(void), - job_add(entry *, user *), - do_command(entry *, user *), - link_user(cron_db *, user *), - unlink_user(cron_db *, user *), - free_user(user *), - env_free(char **), - unget_char(int, FILE *), - free_entry(entry *), - acquire_daemonlock(int), - skip_comments(FILE *), - log_it(const char *, int, const char *, const char *), - log_close(void); +void unget_char(int, FILE *), + free_entry(entry *); -int job_runqueue(void), - set_debug_flags(const char *), - get_char(FILE *), - get_string(char *, int, FILE *, char *), - swap_uids(void), - swap_uids_back(void), - load_env(char *, FILE *), - cron_pclose(FILE *), - glue_strings(char *, size_t, const char *, const char *, char), - strcmp_until(const char *, const char *, char), - allowed(const char * ,const char * ,const char *), - strdtb(char *); - -size_t strlens(const char *, ...); - -char *env_get(char *, char **), - *arpadate(time_t *), - *mkprints(unsigned char *, unsigned int), - *first_word(char *, char *), - **env_init(void), - **env_copy(char **), - **env_set(char **, char *); - -user *load_user(int, struct passwd *, const char *), - *find_user(cron_db *, const char *); +int get_char(FILE *), + get_string(char *, int, FILE *, char *); entry *load_entry(FILE *, void (*)(), struct passwd *, char **); - -FILE *cron_popen(char *, char *, struct passwd *); - -struct passwd *pw_dup(const struct passwd *); - -#ifndef HAVE_TM_GMTOFF -long get_gmtoff(time_t *, struct tm *); -#endif diff --git a/3rd-party/cron/src/cron/globals.h b/3rd-party/cron/src/cron/globals.h index 8370a00..1ff14c2 100644 --- a/3rd-party/cron/src/cron/globals.h +++ b/3rd-party/cron/src/cron/globals.h @@ -28,19 +28,6 @@ # define INIT(x) #endif -XTRN const char *copyright[] -#ifdef MAIN_PROGRAM - = { - "@(#) Vixie Cron", - "@(#) Copyright 1988,1989,1990,1993,1994,2021 by Paul Vixie", - "@(#) Copyright 1997,2000 by Internet Software Consortium, Inc.", - "@(#) Copyright 2004 by Internet Systems Consortium, Inc.", - "@(#) All rights reserved", - NULL - } -#endif - ; - XTRN const char *MonthNames[] #ifdef MAIN_PROGRAM = { @@ -60,15 +47,7 @@ XTRN const char *DowNames[] #endif ; -XTRN char *ProgramName INIT("amnesia"); XTRN int LineNumber INIT(0); -XTRN time_t StartTime INIT(0); -XTRN int NoFork INIT(0); -XTRN const struct timespec ts_zero -#ifdef MAIN_PROGRAM -= {.tv_sec = 0, .tv_nsec = 0} -#endif -; #if DEBUGGING XTRN int DebugFlags INIT(0); XTRN const char *DebugFlagNames[] diff --git a/3rd-party/cron/src/cron/misc.c b/3rd-party/cron/src/cron/misc.c index a5f5a08..4718203 100644 --- a/3rd-party/cron/src/cron/misc.c +++ b/3rd-party/cron/src/cron/misc.c @@ -25,309 +25,6 @@ static char rcsid[] = "$Id: misc.c,v 1.16 2004/01/23 18:56:43 vixie Exp $"; */ #include "cron.h" -#include - -#if defined(SYSLOG) && defined(LOG_FILE) -# undef LOG_FILE -#endif - -#if defined(LOG_DAEMON) && !defined(LOG_CRON) -# define LOG_CRON LOG_DAEMON -#endif - -#ifndef FACILITY -#define FACILITY LOG_CRON -#endif - -static int LogFD = ERR; - -#if defined(SYSLOG) -static int syslog_open = FALSE; -#endif - -/* - * glue_strings is the overflow-safe equivalent of - * sprintf(buffer, "%s%c%s", a, separator, b); - * - * returns 1 on success, 0 on failure. 'buffer' MUST NOT be used if - * glue_strings fails. - */ -int -glue_strings(char *buffer, size_t buffer_size, const char *a, const char *b, - char separator) -{ - char *buf; - char *buf_end; - - if (buffer_size <= 0) - return (0); - buf_end = buffer + buffer_size; - buf = buffer; - - for ( /* nothing */; buf < buf_end && *a != '\0'; buf++, a++ ) - *buf = *a; - if (buf == buf_end) - return (0); - if (separator != '/' || buf == buffer || buf[-1] != '/') - *buf++ = separator; - if (buf == buf_end) - return (0); - for ( /* nothing */; buf < buf_end && *b != '\0'; buf++, b++ ) - *buf = *b; - if (buf == buf_end) - return (0); - *buf = '\0'; - return (1); -} - -int -strcmp_until(const char *left, const char *right, char until) { - while (*left && *left != until && *left == *right) { - left++; - right++; - } - - if ((*left=='\0' || *left == until) && - (*right=='\0' || *right == until)) { - return (0); - } - return (*left - *right); -} - -/* strdtb(s) - delete trailing blanks in string 's' and return new length - */ -int -strdtb(char *s) { - char *x = s; - - /* scan forward to the null - */ - while (*x) - x++; - - /* scan backward to either the first character before the string, - * or the last non-blank in the string, whichever comes first. - */ - do {x--;} - while (x >= s && isspace((unsigned char)*x)); - - /* one character beyond where we stopped above is where the null - * goes. - */ - *++x = '\0'; - - /* the difference between the position of the null character and - * the position of the first character of the string is the length. - */ - return (x - s); -} - -int -set_debug_flags(const char *flags) { - /* debug flags are of the form flag[,flag ...] - * - * if an error occurs, print a message to stdout and return FALSE. - * otherwise return TRUE after setting ERROR_FLAGS. - */ - -#if !DEBUGGING - - printf("this program was compiled without debugging enabled\n"); - return (FALSE); - -#else /* DEBUGGING */ - - const char *pc = flags; - - DebugFlags = 0; - - while (*pc) { - const char **test; - int mask; - - /* try to find debug flag name in our list. - */ - for (test = DebugFlagNames, mask = 1; - *test != NULL && strcmp_until(*test, pc, ','); - test++, mask <<= 1) - NULL; - - if (!*test) { - fprintf(stderr, - "unrecognized debug flag <%s> <%s>\n", - flags, pc); - return (FALSE); - } - - DebugFlags |= mask; - - /* skip to the next flag - */ - while (*pc && *pc != ',') - pc++; - if (*pc == ',') - pc++; - } - - if (DebugFlags) { - int flag; - - fprintf(stderr, "debug flags enabled:"); - - for (flag = 0; DebugFlagNames[flag]; flag++) - if (DebugFlags & (1 << flag)) - fprintf(stderr, " %s", DebugFlagNames[flag]); - fprintf(stderr, "\n"); - } - - return (TRUE); - -#endif /* DEBUGGING */ -} - -void -set_cron_uid(void) { -#if defined(BSD) || defined(POSIX) - if (seteuid(ROOT_UID) < OK) { - perror("seteuid"); - exit(ERROR_EXIT); - } -#else - if (setuid(ROOT_UID) < OK) { - perror("setuid"); - exit(ERROR_EXIT); - } -#endif -} - -void -set_cron_cwd(void) { - struct stat sb; - struct group *grp = NULL; - -#ifdef CRON_GROUP - grp = getgrnam(CRON_GROUP); -#endif - /* first check for CRONDIR ("/var/cron" or some such) - */ - if (stat(CRONDIR, &sb) < OK && errno == ENOENT) { - perror(CRONDIR); - if (OK == mkdir(CRONDIR, 0710)) { - fprintf(stderr, "%s: created\n", CRONDIR); - stat(CRONDIR, &sb); - } else { - fprintf(stderr, "%s: ", CRONDIR); - perror("mkdir"); - exit(ERROR_EXIT); - } - } - if (!S_ISDIR(sb.st_mode)) { - fprintf(stderr, "'%s' is not a directory, bailing out.\n", - CRONDIR); - exit(ERROR_EXIT); - } - if (chdir(CRONDIR) < OK) { - fprintf(stderr, "cannot chdir(%s), bailing out.\n", CRONDIR); - perror(CRONDIR); - exit(ERROR_EXIT); - } - - /* CRONDIR okay (now==CWD), now look at SPOOL_DIR ("tabs" or some such) - */ - if (stat(SPOOL_DIR, &sb) < OK && errno == ENOENT) { - perror(SPOOL_DIR); - if (OK == mkdir(SPOOL_DIR, 0700)) { - fprintf(stderr, "%s: created\n", SPOOL_DIR); - stat(SPOOL_DIR, &sb); - } else { - fprintf(stderr, "%s: ", SPOOL_DIR); - perror("mkdir"); - exit(ERROR_EXIT); - } - } - if (!S_ISDIR(sb.st_mode)) { - fprintf(stderr, "'%s' is not a directory, bailing out.\n", - SPOOL_DIR); - exit(ERROR_EXIT); - } - if (grp != NULL) { - if (sb.st_gid != grp->gr_gid) - chown(SPOOL_DIR, -1, grp->gr_gid); - if (sb.st_mode != 01730) - chmod(SPOOL_DIR, 01730); - } -} - -/* acquire_daemonlock() - write our PID into /etc/cron.pid, unless - * another daemon is already running, which we detect here. - * - * note: main() calls us twice; once before forking, once after. - * we maintain static storage of the file pointer so that we - * can rewrite our PID into _PATH_CRON_PID after the fork. - */ -void -acquire_daemonlock(int closeflag) { - static int fd = -1; - char buf[3*MAX_FNAME]; - const char *pidfile; - char *ep; - ssize_t num; - - if (closeflag) { - /* close stashed fd for child so we don't leak it. */ - if (fd != -1) { - close(fd); - fd = -1; - } - return; - } - - if (fd == -1) { - pidfile = _PATH_CRON_PID; - /* Initial mode is 0600 to prevent flock() race/DoS. */ - if ((fd = open(pidfile, O_RDWR|O_CREAT, 0600)) == -1) { - sprintf(buf, "can't open or create %s: %s", - pidfile, strerror(errno)); - fprintf(stderr, "%s: %s\n", ProgramName, buf); - log_it("CRON", getpid(), "DEATH", buf); - exit(ERROR_EXIT); - } - - if (flock(fd, LOCK_EX|LOCK_NB) < OK) { - int save_errno = errno; - long otherpid = -1; - - bzero(buf, sizeof(buf)); - if ((num = read(fd, buf, sizeof(buf) - 1)) > 0 && - (otherpid = strtol(buf, &ep, 10)) > 0 && - ep != buf && *ep == '\n' && otherpid != LONG_MAX) { - sprintf(buf, - "can't lock %s, otherpid may be %ld: %s", - pidfile, otherpid, strerror(save_errno)); - } else { - sprintf(buf, - "can't lock %s, otherpid unknown: %s", - pidfile, strerror(save_errno)); - } - sprintf(buf, "can't lock %s, otherpid may be %ld: %s", - pidfile, otherpid, strerror(save_errno)); - fprintf(stderr, "%s: %s\n", ProgramName, buf); - log_it("CRON", getpid(), "DEATH", buf); - exit(ERROR_EXIT); - } - (void) fchmod(fd, 0644); - (void) fcntl(fd, F_SETFD, 1); - } - - sprintf(buf, "%ld\n", (long)getpid()); - (void) lseek(fd, (off_t)0, SEEK_SET); - num = write(fd, buf, strlen(buf)); - (void) ftruncate(fd, num); - - /* abandon fd even though the file is open. we need to keep - * it open and locked, but we don't need the handles elsewhere. - */ -} /* get_char(file) : like getc() but increment LineNumber on newlines */ @@ -372,365 +69,3 @@ get_string(char *string, int size, FILE *file, char *terms) { return (ch); } - -/* skip_comments(file) : read past comment (if any) - */ -void -skip_comments(FILE *file) { - int ch; - - while (EOF != (ch = get_char(file))) { - /* ch is now the first character of a line. - */ - - while (ch == ' ' || ch == '\t') - ch = get_char(file); - - if (ch == EOF) - break; - - /* ch is now the first non-blank character of a line. - */ - - if (ch != '\n' && ch != '#') - break; - - /* ch must be a newline or comment as first non-blank - * character on a line. - */ - - while (ch != '\n' && ch != EOF) - ch = get_char(file); - - /* ch is now the newline of a line which we're going to - * ignore. - */ - } - if (ch != EOF) - unget_char(ch, file); -} - -/* int in_file(const char *string, FILE *file, int error) - * return TRUE if one of the lines in file matches string exactly, - * FALSE if no lines match, and error on error. - */ -static int -in_file(const char *string, FILE *file, int error) -{ - char line[MAX_TEMPSTR]; - char *endp; - - if (fseek(file, 0L, SEEK_SET)) - return (error); - while (fgets(line, MAX_TEMPSTR, file)) { - if (line[0] != '\0') { - endp = &line[strlen(line) - 1]; - if (*endp != '\n') - return (error); - *endp = '\0'; - if (0 == strcmp(line, string)) - return (TRUE); - } - } - if (ferror(file)) - return (error); - return (FALSE); -} - -/* int allowed(const char *username, const char *allow_file, const char *deny_file) - * returns TRUE if (allow_file exists and user is listed) - * or (deny_file exists and user is NOT listed). - * root is always allowed. - */ -int -allowed(const char *username, const char *allow_file, const char *deny_file) { - FILE *fp; - int isallowed; - - if (strcmp(username, ROOT_USER) == 0) - return (TRUE); - isallowed = FALSE; - if ((fp = fopen(allow_file, "r")) != NULL) { - isallowed = in_file(username, fp, FALSE); - fclose(fp); - } else if ((fp = fopen(deny_file, "r")) != NULL) { - isallowed = !in_file(username, fp, FALSE); - fclose(fp); - } - return (isallowed); -} - -void -log_it(const char *username, PID_T xpid, const char *event, const char *detail) { -#if defined(LOG_FILE) || DEBUGGING - PID_T pid = xpid; -#endif -#if defined(LOG_FILE) - char *msg; - TIME_T now = time((TIME_T) 0); - struct tm *t = localtime(&now); -#endif /*LOG_FILE*/ - -#if defined(LOG_FILE) - /* we assume that MAX_TEMPSTR will hold the date, time, &punctuation. - */ - msg = malloc(strlen(username) - + strlen(event) - + strlen(detail) - + MAX_TEMPSTR); - if (msg == NULL) - return; - - if (LogFD < OK) { - LogFD = open(LOG_FILE, O_WRONLY|O_APPEND|O_CREAT, 0600); - if (LogFD < OK) { - fprintf(stderr, "%s: can't open log file\n", - ProgramName); - perror(LOG_FILE); - } else { - (void) fcntl(LogFD, F_SETFD, 1); - } - } - - /* we have to sprintf() it because fprintf() doesn't always write - * everything out in one chunk and this has to be atomically appended - * to the log file. - */ - sprintf(msg, "%s (%02d/%02d-%02d:%02d:%02d-%d) %s (%s)\n", - username, - t->tm_mon+1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec, pid, - event, detail); - - /* we have to run strlen() because sprintf() returns (char*) on old BSD - */ - if (LogFD < OK || write(LogFD, msg, strlen(msg)) < OK) { - if (LogFD >= OK) - perror(LOG_FILE); - fprintf(stderr, "%s: can't write to log file\n", ProgramName); - write(STDERR, msg, strlen(msg)); - } - - free(msg); -#endif /*LOG_FILE*/ - -#if defined(SYSLOG) - if (!syslog_open) { -# ifdef LOG_DAEMON - openlog(ProgramName, LOG_PID, FACILITY); -# else - openlog(ProgramName, LOG_PID); -# endif - syslog_open = TRUE; /* assume openlog success */ - } - - syslog(LOG_INFO, "(%s) %s (%s)", username, event, detail); - -#endif /*SYSLOG*/ - -#if DEBUGGING - if (DebugFlags) { - fprintf(stderr, "log_it: (%s %ld) %s (%s)\n", - username, (long)pid, event, detail); - } -#endif -} - -void -log_close(void) { - if (LogFD != ERR) { - close(LogFD); - LogFD = ERR; - } -#if defined(SYSLOG) - closelog(); - syslog_open = FALSE; -#endif /*SYSLOG*/ -} - -/* char *first_word(char *s, char *t) - * return pointer to first word - * parameters: - * s - string we want the first word of - * t - terminators, implicitly including \0 - * warnings: - * (1) this routine is fairly slow - * (2) it returns a pointer to static storage - */ -char * -first_word(char *s, char *t) { - static char retbuf[2][MAX_TEMPSTR + 1]; /* sure wish C had GC */ - static int retsel = 0; - char *rb, *rp; - - /* select a return buffer */ - retsel = 1-retsel; - rb = &retbuf[retsel][0]; - rp = rb; - - /* skip any leading terminators */ - while (*s && (NULL != strchr(t, *s))) { - s++; - } - - /* copy until next terminator or full buffer */ - while (*s && (NULL == strchr(t, *s)) && (rp < &rb[MAX_TEMPSTR])) { - *rp++ = *s++; - } - - /* finish the return-string and return it */ - *rp = '\0'; - return (rb); -} - -/* warning: - * heavily ascii-dependent. - */ -void -mkprint(dst, src, len) - char *dst; - unsigned char *src; - int len; -{ - /* - * XXX - * We know this routine can't overflow the dst buffer because mkprints() - * allocated enough space for the worst case. - */ - while (len-- > 0) - { - unsigned char ch = *src++; - - if (ch < ' ') { /* control character */ - *dst++ = '^'; - *dst++ = ch + '@'; - } else if (ch < 0177) { /* printable */ - *dst++ = ch; - } else if (ch == 0177) { /* delete/rubout */ - *dst++ = '^'; - *dst++ = '?'; - } else { /* parity character */ - sprintf(dst, "\\%03o", ch); - dst += 4; - } - } - *dst = '\0'; -} - -/* warning: - * returns a pointer to malloc'd storage, you must call free yourself. - */ -char * -mkprints(src, len) - unsigned char *src; - unsigned int len; -{ - char *dst = malloc(len*4 + 1); - - if (dst) - mkprint(dst, src, len); - - return (dst); -} - -#ifdef MAIL_DATE -/* Sat, 27 Feb 1993 11:44:51 -0800 (CST) - * 1234567890123456789012345678901234567 - */ -char * -arpadate(clock) - time_t *clock; -{ - time_t t = clock ? *clock : time((TIME_T) 0); - struct tm tm = *localtime(&t); - long gmtoff = get_gmtoff(&t, &tm); - int hours = gmtoff / SECONDS_PER_HOUR; - int minutes = (gmtoff - (hours * SECONDS_PER_HOUR)) / SECONDS_PER_MINUTE; - static char ret[64]; /* zone name might be >3 chars */ - - (void) sprintf(ret, "%s, %2d %s %2d %02d:%02d:%02d %.2d%.2d (%s)", - DowNames[tm.tm_wday], - tm.tm_mday, - MonthNames[tm.tm_mon], - tm.tm_year + 1900, - tm.tm_hour, - tm.tm_min, - tm.tm_sec, - hours, - minutes, - TZONE(*tm)); - return (ret); -} -#endif /*MAIL_DATE*/ - -#ifdef HAVE_SAVED_UIDS -static uid_t save_euid; -static gid_t save_egid; - -int swap_uids(void) { - save_egid = getegid(); - save_euid = geteuid(); - return ((setegid(getgid()) || seteuid(getuid())) ? -1 : 0); -} - -int swap_uids_back(void) { - return ((setegid(getgid()) || seteuid(getuid())) ? -1 : 0); -} - -#else /*HAVE_SAVED_UIDS*/ - -int swap_uids(void) { - return ((setregid(getegid(), getgid()) || setreuid(geteuid(), getuid())) - ? -1 : 0); -} - -int swap_uids_back(void) { - return (swap_uids()); -} -#endif /*HAVE_SAVED_UIDS*/ - -size_t -strlens(const char *last, ...) { - va_list ap; - size_t ret = 0; - const char *str; - - va_start(ap, last); - for (str = last; str != NULL; str = va_arg(ap, const char *)) - ret += strlen(str); - va_end(ap); - return (ret); -} - -/* Return the offset from GMT in seconds (algorithm taken from sendmail). - * - * warning: - * clobbers the static storage space used by localtime() and gmtime(). - * If the local pointer is non-NULL it *must* point to a local copy. - */ -#ifndef HAVE_TM_GMTOFF -long get_gmtoff(time_t *clock, struct tm *local) -{ - struct tm gmt; - long offset; - - gmt = *gmtime(clock); - if (local == NULL) - local = localtime(clock); - - offset = (local->tm_sec - gmt.tm_sec) + - ((local->tm_min - gmt.tm_min) * 60) + - ((local->tm_hour - gmt.tm_hour) * 3600); - - /* Timezone may cause year rollover to happen on a different day. */ - if (local->tm_year < gmt.tm_year) - offset -= 24 * 3600; - else if (local->tm_year > gmt.tm_year) - offset -= 24 * 3600; - else if (local->tm_yday < gmt.tm_yday) - offset -= 24 * 3600; - else if (local->tm_yday > gmt.tm_yday) - offset += 24 * 3600; - - return (offset); -} -#endif /* HAVE_TM_GMTOFF */ diff --git a/3rd-party/cron/src/cron/structs.h b/3rd-party/cron/src/cron/structs.h index 638a01a..271a862 100644 --- a/3rd-party/cron/src/cron/structs.h +++ b/3rd-party/cron/src/cron/structs.h @@ -20,10 +20,6 @@ */ typedef struct _entry { - struct _entry *next; - struct passwd *pwd; - char **envp; - char *cmd; bitstr_t bit_decl(minute, MINUTE_COUNT); bitstr_t bit_decl(hour, HOUR_COUNT); bitstr_t bit_decl(dom, DOM_COUNT); @@ -35,28 +31,4 @@ typedef struct _entry { #define DOM_STAR 0x04 #define DOW_STAR 0x08 #define WHEN_REBOOT 0x10 -#define DONT_LOG 0x20 } entry; - - /* the crontab database will be a list of the - * following structure, one element per user - * plus one for the system. - * - * These are the crontabs. - */ - -typedef struct _user { - struct _user *next, *prev; /* links */ - char *name; - struct timespec mtim; /* last modtime of crontab */ - entry *crontab; /* this person's crontab */ -} user; - -typedef struct _cron_db { - user *head, *tail; /* links */ - struct timespec mtim; /* last modtime on spooldir */ -} cron_db; - /* in the C tradition, we only create - * variables for the main program, just - * extern them elsewhere. - */ diff --git a/3rd-party/cron/src/cronutils.c b/3rd-party/cron/src/cronutils.c new file mode 100644 index 0000000..7d989be --- /dev/null +++ b/3rd-party/cron/src/cronutils.c @@ -0,0 +1,187 @@ +// Copyright (c) Rodrigo Speller. All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +#define MAIN_PROGRAM + +#include + +#include "cron/cron.h" + +typedef struct __cron_time_state_t { + int minute; + int hour; + int dom; + int month; + int dow; +} __cron_time_state_t; + +cron_expr_t *cron_expr_alloc() { + return calloc(1, sizeof(cron_expr_t)); +} + +void cron_expr_free(cron_expr_t *expr_ptr) { + if (expr_ptr == NULL) { + return; + } + + free_entry(expr_ptr->expr_st_ptr); + free(expr_ptr); +} + +void cron_expr_parse(char const *expr_str, cron_expr_t *expr_ptr, void (*error_func)(const char *)) { + if (expr_ptr == NULL) { + return; + } + + FILE file = { expr_str, expr_str }; + entry *entry_ptr = load_entry(&file, error_func, NULL, NULL); + + if (entry_ptr == NULL) { + return; + } + + if (expr_ptr->expr_st_ptr != NULL) { + free_entry(expr_ptr->expr_st_ptr); + } + + expr_ptr->expr_st_ptr = entry_ptr; +} + +int cron_expr_match(const cron_expr_t *expr_ptr, const cron_time_t *time_ptr) { + if (expr_ptr == NULL || time_ptr == NULL) { + return FALSE; + } + + entry *entry_ptr = expr_ptr->expr_st_ptr; + + if (entry_ptr == NULL || time_ptr->time_st_ptr == NULL) { + return FALSE; + } + + __cron_time_state_t time_st = *(__cron_time_state_t*)time_ptr->time_st_ptr; + + /** + * Begin: extracted from 'find_jobs' at cron.c. + * https://github.com/vixie/cron/blob/f4311d3a1f4018b8a2927437216585d058b95681/cron.c#L299-L324 + * + * Copyright (c) 1988,1990,1993,1994,2021 by Paul Vixie ("VIXIE") + * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1997,2000 by Internet Software Consortium, Inc. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND VIXIE DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL VIXIE BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Changes from original: + * - Only the time test logic is extracted. + * - Conditional 'doWild' and 'doNonWild' parameters are removed (always TRUE). + * - Comments about 'dom' and 'dow' validation are changed of place to be more clear. + */ + if (bit_test(entry_ptr->minute, time_st.minute) && + bit_test(entry_ptr->hour, time_st.hour) && + bit_test(entry_ptr->month, time_st.month) && + /* the dom/dow situation is odd. '* * 1,15 * Sun' will run on the + * first and fifteenth AND every Sunday; '* * * * Sun' will run *only* + * on Sundays; '* * 1,15 * *' will run *only* the 1st and 15th. this + * is why we keep 'e->dow_star' and 'e->dom_star'. yes, it's bizarre. + * like many bizarre things, it's the standard. + */ + ( ((entry_ptr->flags & DOM_STAR) || (entry_ptr->flags & DOW_STAR)) + ? (bit_test(entry_ptr->dow,time_st.dow) && bit_test(entry_ptr->dom,time_st.dom)) + : (bit_test(entry_ptr->dow,time_st.dow) || bit_test(entry_ptr->dom,time_st.dom)) + ) + ) { + return TRUE; + } + /** End: extracted from 'find_jobs' at cron.c. */ + + return FALSE; +} + +int cron_expr_match_reboot(const cron_expr_t *expr_ptr) { + if (expr_ptr == NULL) { + return FALSE; + } + + entry *entry_ptr = expr_ptr->expr_st_ptr; + + if (entry_ptr == NULL) { + return FALSE; + } + + return !!(entry_ptr->flags & WHEN_REBOOT); +} + +cron_time_t *cron_time_alloc() { + return calloc(1, sizeof(cron_time_t)); +} + +void cron_time_free(cron_time_t *time_ptr) { + if (time_ptr == NULL) { + return; + } + + free(time_ptr->time_st_ptr); + free(time_ptr); +} + +void cron_time_set(cron_time_t *cron_time, const time_t *timer) { + if (cron_time == NULL) { + return; + } + + struct tm *tm = gmtime(timer); + + if (tm == NULL) { + return; + } + + __cron_time_state_t *time_st = calloc(1, sizeof(__cron_time_state_t)); + + if (time_st == NULL) { + return; + } + + /** + * Begin: extracted from 'find_jobs' at cron.c. + * https://github.com/vixie/cron/blob/f4311d3a1f4018b8a2927437216585d058b95681/cron.c#L287-L293 + * + * Copyright (c) 1988,1990,1993,1994,2021 by Paul Vixie ("VIXIE") + * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1997,2000 by Internet Software Consortium, Inc. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND VIXIE DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL VIXIE BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + **/ + /* make 0-based values out of these so we can use them as indicies + */ + time_st->minute = tm->tm_min -FIRST_MINUTE; + time_st->hour = tm->tm_hour -FIRST_HOUR; + time_st->dom = tm->tm_mday -FIRST_DOM; + time_st->month = tm->tm_mon +1 /* 0..11 -> 1..12 */ -FIRST_MONTH; + time_st->dow = tm->tm_wday -FIRST_DOW; + /** End: extracted from find_jobs at cron.c **/ + + if (cron_time->time_st_ptr != NULL) { + free(cron_time->time_st_ptr); + } + + cron_time->time_st_ptr = time_st; +} diff --git a/3rd-party/cron/src/fake/stdio.c b/3rd-party/cron/src/fake/stdio.c new file mode 100644 index 0000000..325f245 --- /dev/null +++ b/3rd-party/cron/src/fake/stdio.c @@ -0,0 +1,32 @@ +// Copyright (c) Rodrigo Speller. All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +/* + * Implementation of stdio.h for use with libcron. + * Fake FILE struct and functions to read from a string. +*/ + +#include + +int feof(FILE *stream) { + if (*stream->current == 0) { + return EOF; + } + return 0; +} + +int getc(FILE *stream) { + if (*stream->current == 0) { + return EOF; + } + return *stream->current++; +} + +int ungetc(int c, FILE *stream) { + if (stream->current == stream->begin || *(stream->current - 1) != c) { + return EOF; + } + + --stream->current; + return c; +} diff --git a/3rd-party/cron/src/fake/stdio.h b/3rd-party/cron/src/fake/stdio.h new file mode 100644 index 0000000..7eac337 --- /dev/null +++ b/3rd-party/cron/src/fake/stdio.h @@ -0,0 +1,20 @@ +// Copyright (c) Rodrigo Speller. All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +/* + * Implementation of stdio.h for use with libcron. + * Fake FILE struct and functions to read from a string. +*/ + +#define EOF (-1) + +typedef struct __in_memory_string_fake_file { + char const *current; + const char const *begin; +} __in_memory_string_fake_file; + +#define FILE __in_memory_string_fake_file + +int feof(FILE *stream), + getc(FILE *stream), + ungetc(int c, FILE *stream); diff --git a/3rd-party/cron/src/stdio.h b/3rd-party/cron/src/stdio.h new file mode 100644 index 0000000..4ea46f5 --- /dev/null +++ b/3rd-party/cron/src/stdio.h @@ -0,0 +1 @@ +#include "fake/stdio.h" diff --git a/makefile b/makefile index 5f0c003..20fc42a 100644 --- a/makefile +++ b/makefile @@ -11,13 +11,16 @@ rebuild: clean build PRECOMPILE+=src/atone.h +LIBRARY+=cron LIBRARY+=optparse LIBRARY+=yaml-cpp +LIBRARY_PATH+=3rd-party/cron/$(BUILDDIR)/$(TARGET)/bin LIBRARY_PATH+=3rd-party/optparse/$(BUILDDIR)/$(TARGET)/bin LIBRARY_PATH+=3rd-party/yaml-cpp/$(BUILDDIR)/$(TARGET)/bin INCLUDE_PATH+=$(SRCDIR) +INCLUDE_PATH+=3rd-party/cron/include INCLUDE_PATH+=3rd-party/optparse/include INCLUDE_PATH+=3rd-party/yaml-cpp/include @@ -114,9 +117,14 @@ after-build: # 3rd-party 3rd-party: \ + 3rd-party/cron/$(BUILDDIR)/$(TARGET)/bin/libcron.a \ 3rd-party/optparse/$(BUILDDIR)/$(TARGET)/bin/liboptparse.a \ 3rd-party/yaml-cpp/$(BUILDDIR)/$(TARGET)/bin/libyaml-cpp.a +3rd-party/cron/$(BUILDDIR)/$(TARGET)/bin/libcron.a: + @echo "Building libcron" + @$(MAKE) -C 3rd-party/cron + 3rd-party/optparse/$(BUILDDIR)/$(TARGET)/bin/liboptparse.a: @echo "Building optparse" @$(MAKE) -C 3rd-party/optparse @@ -132,6 +140,7 @@ clean: clean-all: @-rm -rv -- $(BUILDDIR) || true + @$(MAKE) -C 3rd-party/cron clean-all @$(MAKE) -C 3rd-party/optparse clean-all @$(MAKE) -C 3rd-party/yaml-cpp clean-all diff --git a/src/atone.h b/src/atone.h index 6ef7033..6db4172 100644 --- a/src/atone.h +++ b/src/atone.h @@ -35,6 +35,7 @@ using namespace std; // 3rd-party libs +#include "cronutils.h" #include "optparse.h" #include "yaml-cpp/yaml.h" From fcf909204d630b13119abd4df7449737aaaccb54 Mon Sep 17 00:00:00 2001 From: Rodrigo Speller Date: Sat, 8 Apr 2023 16:18:20 -0300 Subject: [PATCH 04/14] Service restart policy renamed to "no" --- src/config/ServiceConfig.h | 2 +- src/config/yaml/YamlConfigParser.cpp | 6 +++--- src/service/Service.cpp | 2 +- src/service/ServiceRestartPolicy.h | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/config/ServiceConfig.h b/src/config/ServiceConfig.h index 78ee96c..ac76a24 100644 --- a/src/config/ServiceConfig.h +++ b/src/config/ServiceConfig.h @@ -17,7 +17,7 @@ namespace Atone { size_t argc = 0; shared_ptr argv = NULL; std::vector depends_on; - ServiceRestartPolicy restart = ServiceRestartPolicy::Never; + ServiceRestartPolicy restart = ServiceRestartPolicy::No; void SetCommandArgs(const std::string &command); void SetCommandArgs(const size_t argc, char **argv); diff --git a/src/config/yaml/YamlConfigParser.cpp b/src/config/yaml/YamlConfigParser.cpp index 7649187..6bc8550 100644 --- a/src/config/yaml/YamlConfigParser.cpp +++ b/src/config/yaml/YamlConfigParser.cpp @@ -140,7 +140,7 @@ namespace Atone { switch (restart.Type()) { case YAML::NodeType::Null: case YAML::NodeType::Undefined: - target.restart = ServiceRestartPolicy::Never; + target.restart = ServiceRestartPolicy::No; return; case YAML::NodeType::Scalar: @@ -153,8 +153,8 @@ namespace Atone { auto str_value = restart.as(); ServiceRestartPolicy value; - if (str_value == "never" || str_value == "no" /* legacy */) - value = ServiceRestartPolicy::Never; + if (str_value == "no") + value = ServiceRestartPolicy::No; else if (str_value == "always") value = ServiceRestartPolicy::Always; else if (str_value == "on-failure") diff --git a/src/service/Service.cpp b/src/service/Service.cpp index 4be02a1..a00dd76 100644 --- a/src/service/Service.cpp +++ b/src/service/Service.cpp @@ -42,7 +42,7 @@ namespace Atone { } switch (restartPolicy()) { - case ServiceRestartPolicy::Never: + case ServiceRestartPolicy::No: return false; case ServiceRestartPolicy::Always: return true; diff --git a/src/service/ServiceRestartPolicy.h b/src/service/ServiceRestartPolicy.h index 7717b74..ced2994 100644 --- a/src/service/ServiceRestartPolicy.h +++ b/src/service/ServiceRestartPolicy.h @@ -6,7 +6,7 @@ namespace Atone { enum class ServiceRestartPolicy { - Never, + No, Always, OnFailure, UnlessStopped, From 13544ca9255cf3809134008c661c5f5ae51cbda3 Mon Sep 17 00:00:00 2001 From: Rodrigo Speller Date: Mon, 10 Apr 2023 01:23:57 -0300 Subject: [PATCH 05/14] fix: Timespec cron parsing --- 3rd-party/cron/src/README.md | 40 +++++++++++++++++++++++---------- 3rd-party/cron/src/cron/entry.c | 4 ++-- 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/3rd-party/cron/src/README.md b/3rd-party/cron/src/README.md index 095d89f..bec9c7f 100644 --- a/3rd-party/cron/src/README.md +++ b/3rd-party/cron/src/README.md @@ -10,23 +10,39 @@ The following changes have been made to the original code: - Only the necessary files have been included in the Atone source tree. - The included files have been stripped of unecessary code to only parse the cron expressions. -- In addition to strip unnecessary lines, only one line of the original source code has been changed to allow parsing only the cron expressions, without commands: +- In addition to strip unnecessary lines, only these lines of the original source code has been changed to allow parsing only cron expressions, without commands: ```diff --- a/3rd-party/cron/src/cron/entry.c +++ b/3rd-party/cron/src/cron/entry.c - /* DOW (days of week) - */ - if (ch == '*') - e->flags |= DOW_STAR; - ch = get_list(e->dow, FIRST_DOW, LAST_DOW, - DowNames, ch, file); - -if (ch == EOF) { - +if (ch != EOF) { - ecode = e_dow; - goto eof; - } + + Skip_Blanks(ch, file); + - if (ch == EOF || ch == '\n') { + - ecode = e_cmd; + + if (ch != EOF) { + + ecode = e_timespec; + goto eof; + } + ``` + + And: + + ```diff + --- a/3rd-party/cron/src/cron/entry.c + +++ b/3rd-party/cron/src/cron/entry.c + + /* DOW (days of week) + */ + if (ch == '*') + e->flags |= DOW_STAR; + ch = get_list(e->dow, FIRST_DOW, LAST_DOW, + DowNames, ch, file); + - if (ch == EOF) { + + if (ch != EOF) { + ecode = e_dow; + goto eof; + } ``` ## License diff --git a/3rd-party/cron/src/cron/entry.c b/3rd-party/cron/src/cron/entry.c index fdf19f3..b09ddde 100644 --- a/3rd-party/cron/src/cron/entry.c +++ b/3rd-party/cron/src/cron/entry.c @@ -152,8 +152,8 @@ load_entry(FILE *file, void (*error_func)(), struct passwd *pw, char **envp) { * username/command. */ Skip_Blanks(ch, file); - if (ch == EOF || ch == '\n') { - ecode = e_cmd; + if (ch != EOF) { + ecode = e_timespec; goto eof; } } else { From 6a45ac6eac8fd38510673d78b40496112fdfe9be Mon Sep 17 00:00:00 2001 From: Rodrigo Speller Date: Mon, 10 Apr 2023 01:27:58 -0300 Subject: [PATCH 06/14] fix: Logging timestamps --- src/logging/OutputLogger.cpp | 11 ++++------- src/logging/TerminalLogger.cpp | 11 ++++------- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/src/logging/OutputLogger.cpp b/src/logging/OutputLogger.cpp index 3756f3b..e9b7f68 100644 --- a/src/logging/OutputLogger.cpp +++ b/src/logging/OutputLogger.cpp @@ -42,14 +42,11 @@ namespace Atone { void OutputLogger::print_time() { - timeval tv; - gettimeofday(&tv, 0); + timespec time; + clock_gettime(CLOCK_REALTIME, &time); - time_t rawtime = tv.tv_sec; struct tm *timeinfo; - - time(&rawtime); - timeinfo = localtime(&rawtime); + timeinfo = localtime(&time.tv_sec); printf( "[%i-%02i-%02i %02i:%02i:%02i.%03i]", timeinfo->tm_year + 1900, @@ -58,7 +55,7 @@ namespace Atone { timeinfo->tm_hour, timeinfo->tm_min, timeinfo->tm_sec, - (int)tv.tv_usec / 1000 + (int)time.tv_nsec / 1000000 ); } diff --git a/src/logging/TerminalLogger.cpp b/src/logging/TerminalLogger.cpp index 5dc3eb9..4fc30b3 100644 --- a/src/logging/TerminalLogger.cpp +++ b/src/logging/TerminalLogger.cpp @@ -68,14 +68,11 @@ namespace Atone { void TerminalLogger::print_time() { - timeval tv; - gettimeofday(&tv, 0); + timespec time; + clock_gettime(CLOCK_REALTIME, &time); - time_t rawtime = tv.tv_sec; struct tm *timeinfo; - - time(&rawtime); - timeinfo = localtime(&rawtime); + timeinfo = localtime(&time.tv_sec); printf( "[%i-%02i-%02i %02i:%02i:%02i.%03i]", timeinfo->tm_year + 1900, @@ -84,7 +81,7 @@ namespace Atone { timeinfo->tm_hour, timeinfo->tm_min, timeinfo->tm_sec, - (int)tv.tv_usec / 1000 + (int)time.tv_nsec / 1000000 ); } From 6dc5bb379f8b9ef04932de4dc5643c7da1dc7104 Mon Sep 17 00:00:00 2001 From: Rodrigo Speller Date: Mon, 10 Apr 2023 02:54:08 -0300 Subject: [PATCH 07/14] Implement service scheduler based on cron expressions --- 3rd-party/cron/include/cronutils.h | 20 +++- 3rd-party/cron/src/cronutils.c | 140 ++++++++++++++++++++++++++- src/atone.h | 1 + src/config/ServiceConfig.h | 4 +- src/config/yaml/YamlConfigParser.cpp | 36 +++++++ src/config/yaml/YamlConfigParser.h | 1 + src/programs/SupervisorProgram.cpp | 34 ++++++- src/service/Service.cpp | 13 ++- src/service/Service.h | 18 +++- src/service/ServiceScheduler.cpp | 70 ++++++++++++++ src/service/ServiceScheduler.h | 33 +++++++ src/service/ServicesManager.cpp | 41 +++++++- src/service/ServicesManager.h | 6 +- src/system/Supervisor.cpp | 40 ++++++++ src/system/Supervisor.h | 4 + src/utils/CronExpression.cpp | 40 ++++++++ src/utils/CronExpression.h | 31 ++++++ src/utils/CronTime.cpp | 65 +++++++++++++ src/utils/CronTime.h | 38 ++++++++ src/utils/constants.h | 2 + 20 files changed, 618 insertions(+), 19 deletions(-) create mode 100644 src/service/ServiceScheduler.cpp create mode 100644 src/service/ServiceScheduler.h create mode 100644 src/utils/CronExpression.cpp create mode 100644 src/utils/CronExpression.h create mode 100644 src/utils/CronTime.cpp create mode 100644 src/utils/CronTime.h diff --git a/3rd-party/cron/include/cronutils.h b/3rd-party/cron/include/cronutils.h index bc10e57..d324b79 100644 --- a/3rd-party/cron/include/cronutils.h +++ b/3rd-party/cron/include/cronutils.h @@ -38,7 +38,7 @@ typedef struct cron_time_t { * Allocate a new cron expression state. */ CRON_API_EXPORT -cron_expr_t *cron_expr_alloc(void); +cron_expr_t *cron_expr_alloc(const cron_expr_t *const clone_expr_ptr); /** * Free a cron expression state. @@ -74,10 +74,12 @@ CRON_API_EXPORT int cron_expr_match_reboot(const cron_expr_t *expr_ptr); /** - * Allocate a new cron time state. + * Allocate a new cron time state. If 'clone_time_ptr' is not NULL, the + * new cron time state will be initialized as a clone of the given cron + * time state. */ CRON_API_EXPORT -cron_time_t *cron_time_alloc(void); +cron_time_t *cron_time_alloc(const cron_time_t *const clone_time_ptr); /** * Free a cron time state. @@ -91,4 +93,16 @@ void cron_time_free(cron_time_t *time_ptr); CRON_API_EXPORT void cron_time_set(cron_time_t *time_ptr, const time_t *timer); +/** + * Compare two cron time states. + */ +CRON_API_EXPORT +int cron_time_cmp(const cron_time_t *const a, const cron_time_t *const b); + +/** + * Increment a cron time state by one minute. + */ +CRON_API_EXPORT +void cron_time_inc(cron_time_t *const cron_time); + #endif diff --git a/3rd-party/cron/src/cronutils.c b/3rd-party/cron/src/cronutils.c index 7d989be..e1fd3cb 100644 --- a/3rd-party/cron/src/cronutils.c +++ b/3rd-party/cron/src/cronutils.c @@ -13,10 +13,23 @@ typedef struct __cron_time_state_t { int dom; int month; int dow; + int year; } __cron_time_state_t; -cron_expr_t *cron_expr_alloc() { - return calloc(1, sizeof(cron_expr_t)); +cron_expr_t *cron_expr_alloc(const cron_expr_t *const clone_expr_ptr) { + cron_expr_t *expr = calloc(1, sizeof(cron_expr_t)); + + if (expr == NULL) { + return NULL; + } + + if (clone_expr_ptr != NULL && clone_expr_ptr->expr_st_ptr != NULL) { + entry *expr_st = calloc(1, sizeof(entry)); + *expr_st = *(entry*)clone_expr_ptr->expr_st_ptr; + expr->expr_st_ptr = expr_st; + } + + return expr; } void cron_expr_free(cron_expr_t *expr_ptr) { @@ -120,8 +133,20 @@ int cron_expr_match_reboot(const cron_expr_t *expr_ptr) { return !!(entry_ptr->flags & WHEN_REBOOT); } -cron_time_t *cron_time_alloc() { - return calloc(1, sizeof(cron_time_t)); +cron_time_t *cron_time_alloc(const cron_time_t *const clone_time_ptr) { + cron_time_t *cron = calloc(1, sizeof(cron_time_t)); + + if (cron == NULL) { + return NULL; + } + + if (clone_time_ptr != NULL && clone_time_ptr->time_st_ptr != NULL) { + __cron_time_state_t *time_st = calloc(1, sizeof(__cron_time_state_t)); + *time_st = *(__cron_time_state_t*)clone_time_ptr->time_st_ptr; + cron->time_st_ptr = time_st; + } + + return cron; } void cron_time_free(cron_time_t *time_ptr) { @@ -177,6 +202,7 @@ void cron_time_set(cron_time_t *cron_time, const time_t *timer) { time_st->dom = tm->tm_mday -FIRST_DOM; time_st->month = tm->tm_mon +1 /* 0..11 -> 1..12 */ -FIRST_MONTH; time_st->dow = tm->tm_wday -FIRST_DOW; + time_st->year = tm->tm_year; /** End: extracted from find_jobs at cron.c **/ if (cron_time->time_st_ptr != NULL) { @@ -185,3 +211,109 @@ void cron_time_set(cron_time_t *cron_time, const time_t *timer) { cron_time->time_st_ptr = time_st; } + +#define cron_int_cmp(a, b, fallback) ((a) < (b) ? -1 : (a) > (b) ? 1 : fallback) + +int cron_time_cmp(const cron_time_t * const op1, const cron_time_t * const op2) { + if (op1 == NULL || op2 == NULL) { + return -1; + } + + __cron_time_state_t *op1_time_st = op1->time_st_ptr; + __cron_time_state_t *op2_time_st = op2->time_st_ptr; + + if (op1_time_st == NULL || op2_time_st == NULL) { + return -1; + } + + int cmp = cron_int_cmp( + op1_time_st->year, + op2_time_st->year, + cron_int_cmp( + op1_time_st->month, + op2_time_st->month, + cron_int_cmp( + op1_time_st->dom, + op2_time_st->dom, + cron_int_cmp( + op1_time_st->hour, + op2_time_st->hour, + cron_int_cmp( + op1_time_st->minute, + op2_time_st->minute, + 0 + ) + ) + ) + ) + ); + + return cmp; +} + +void cron_time_inc(cron_time_t * const cron_time) { + if (cron_time == NULL) { + return; + } + + __cron_time_state_t *time_st = cron_time->time_st_ptr; + + if (time_st == NULL) { + return; + } + time_st->minute++; + if (time_st->minute >= 60) { + time_st->minute = 0; + time_st->hour++; + + if (time_st->hour >= 24) { + time_st->hour = 0; + time_st->dow++; + + if (time_st->dow >= 7) { + time_st->dow = 0; + } + + time_st->dom++; + + if (time_st->dom >= 29) { + switch (time_st->month) { + case 1: // feb + if (time_st->year % 4 == 0 && (time_st->year % 100 != 0 || time_st->year % 400 == 0)) { + if (time_st->dom >= 29) { + time_st->dom = 0; + } + } + else { + if (time_st->dom >= 28) { + time_st->dom = 0; + } + } + break; + case 3: // apr + case 5: // jun + case 8: // sep + case 10: // nov + if (time_st->dom >= 30) { + time_st->dom = 0; + } + break; + default: + if (time_st->dom >= 31) { + time_st->dom = 0; + } + break; + } + + if (time_st->dom == 0) { + time_st->month++; + + if (time_st->month >= 12) { + time_st->month = 0; + time_st->year++; + } + } + } + } + } +} diff --git a/src/atone.h b/src/atone.h index 6db4172..64183b6 100644 --- a/src/atone.h +++ b/src/atone.h @@ -30,6 +30,7 @@ using namespace std; #include #include #include +#include #include #include diff --git a/src/config/ServiceConfig.h b/src/config/ServiceConfig.h index ac76a24..65f92f2 100644 --- a/src/config/ServiceConfig.h +++ b/src/config/ServiceConfig.h @@ -18,12 +18,10 @@ namespace Atone { shared_ptr argv = NULL; std::vector depends_on; ServiceRestartPolicy restart = ServiceRestartPolicy::No; + std::vector schedule; void SetCommandArgs(const std::string &command); void SetCommandArgs(const size_t argc, char **argv); - - private: - void FreeCommandArgs(); }; } \ No newline at end of file diff --git a/src/config/yaml/YamlConfigParser.cpp b/src/config/yaml/YamlConfigParser.cpp index 6bc8550..53fdc3f 100644 --- a/src/config/yaml/YamlConfigParser.cpp +++ b/src/config/yaml/YamlConfigParser.cpp @@ -86,6 +86,9 @@ namespace Atone { auto restart = config["restart"]; if (restart) SetRestartPolicy(svcConfig, restart); + auto schedule = config["schedule"]; + if (schedule) SetSchedule(svcConfig, schedule); + return svcConfig; } @@ -166,4 +169,37 @@ namespace Atone { target.restart = value; } + + void YamlConfigParser::SetSchedule(ServiceConfig &target, const YAML::Node &schedule) { + switch (schedule.Type()) { + case YAML::NodeType::Null: + case YAML::NodeType::Undefined: + target.depends_on = std::vector(); + return; + + case YAML::NodeType::Scalar: + target.schedule = std::vector{schedule.as()}; + return; + + case YAML::NodeType::Sequence: + break; + + default: + throw AtoneException("invalid schedule"); + } + + size_t sz = schedule.size(); + auto value = std::vector(sz); + for (size_t i = 0; i < sz; i++) { + auto node = schedule[i]; + + if (node.Type() != YAML::NodeType::Scalar) + throw AtoneException("invalid schedule"); + + value[i] = node.as(); + } + + target.schedule = value; + } + } diff --git a/src/config/yaml/YamlConfigParser.h b/src/config/yaml/YamlConfigParser.h index 1cd8526..bd50cb6 100644 --- a/src/config/yaml/YamlConfigParser.h +++ b/src/config/yaml/YamlConfigParser.h @@ -22,5 +22,6 @@ namespace Atone { void SetCommandArgs(ServiceConfig &target, const YAML::Node &command); void SetDependsOn(ServiceConfig &target, const YAML::Node &depends_on); void SetRestartPolicy(ServiceConfig &target, const YAML::Node &restart); + void SetSchedule(ServiceConfig &target, const YAML::Node &schedule); }; } diff --git a/src/programs/SupervisorProgram.cpp b/src/programs/SupervisorProgram.cpp index 25ec461..7b8392f 100644 --- a/src/programs/SupervisorProgram.cpp +++ b/src/programs/SupervisorProgram.cpp @@ -11,6 +11,7 @@ #include "logging/Log.h" #include "system/Supervisor.h" #include "utils/constants.h" +#include "utils/CronTime.h" #include "utils/time.h" namespace Atone { @@ -71,7 +72,7 @@ namespace Atone { } // starts the services - services->Start(); + services->Bootstrap(); } /** @@ -81,6 +82,8 @@ namespace Atone { bool SupervisorProgram::MainLoop(Context &context) { auto &services = context.services; + CronTime *last_schedule_time = nullptr; + // wait signal loop while (true) { siginfo_t siginfo; @@ -117,6 +120,35 @@ namespace Atone { } // any other signal default: + if (signum == ATONE_SCHEDULER_SIGNAL) { + Log::debug("signal received: %s", strsignal(signum)); + + timespec time; + clock_gettime(CLOCK_REALTIME, &time); + + if (last_schedule_time == nullptr) { + last_schedule_time = new CronTime(&time.tv_sec); + services->CheckSchedule(last_schedule_time, last_schedule_time); + } else { + auto previous_time = last_schedule_time; + auto current_time = new CronTime(&time.tv_sec); + last_schedule_time = current_time; + + if (*current_time > *previous_time) { + previous_time->IncrementTick(); + services->CheckSchedule(previous_time, current_time); + } + + delete previous_time; + } + + // schedule next tick + time_t next_tick = time.tv_sec - (time.tv_sec % 60) + 60; + Supervisor::RequestSchedulerTick(next_tick); + + break; + } + Log::debug("unhandled signal received: %s", strsignal(signum)); break; } diff --git a/src/service/Service.cpp b/src/service/Service.cpp index a00dd76..ae4fe26 100644 --- a/src/service/Service.cpp +++ b/src/service/Service.cpp @@ -15,7 +15,13 @@ namespace Atone { Service::Service(const ServicesManager *manager, const ServiceConfig &config) - : manager(manager), config(config) { + : manager(manager) + , config(config) + , _scheduler(new ServiceScheduler(config.schedule)) { + } + + Service::~Service() { + delete _scheduler; } ////////////////////////////////////////////////////////////////////// @@ -25,6 +31,7 @@ namespace Atone { shared_ptr Service::argv() const { return config.argv; } vector Service::dependsOn() const { return config.depends_on; } ServiceRestartPolicy Service::restartPolicy() const { return config.restart; } + const ServiceScheduler *Service::scheduler() const { return _scheduler; } ////////////////////////////////////////////////////////////////////// @@ -77,7 +84,6 @@ namespace Atone { dependency->Start(callstack); break; case ServiceStatus::Stopped: - case ServiceStatus::Broken: if (dependency->canRestart()) { dependency->Start(callstack); } @@ -85,6 +91,9 @@ namespace Atone { throw domain_error("invalid dependency state"); } break; + case ServiceStatus::Broken: + dependency->Start(callstack); + break; default: throw domain_error("invalid dependency status"); } diff --git a/src/service/Service.h b/src/service/Service.h index 3c12405..a581686 100644 --- a/src/service/Service.h +++ b/src/service/Service.h @@ -10,6 +10,7 @@ namespace Atone { } #include "config/ServiceConfig.h" +#include "service/ServiceScheduler.h" #include "service/ServicesManager.h" #include "service/ServiceStatus.h" @@ -43,6 +44,9 @@ namespace Atone { /** The service configuration */ const ServiceConfig config; + /** The service scheduler. */ + const ServiceScheduler *_scheduler; + /** The service execution state. */ ServiceExecutionState state; @@ -70,6 +74,11 @@ namespace Atone { */ Service(const ServicesManager *manager, const ServiceConfig &config); + /** + * Destroy the Service instance. + */ + ~Service(); + //////////////////////////////////////////////////////////////////// /** @@ -95,13 +104,20 @@ namespace Atone { * @return The service dependencies. */ vector dependsOn() const; - + /** * Gets the service restart policy. * @return The service restart policy. */ ServiceRestartPolicy restartPolicy() const; + /** + * Gets the service scheduler to check if the service should be started + * based on the current time. + * @return The service scheduler. + */ + const ServiceScheduler *scheduler() const; + //////////////////////////////////////////////////////////////////// /** diff --git a/src/service/ServiceScheduler.cpp b/src/service/ServiceScheduler.cpp new file mode 100644 index 0000000..93bc902 --- /dev/null +++ b/src/service/ServiceScheduler.cpp @@ -0,0 +1,70 @@ +// Copyright (c) Rodrigo Speller. All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +#include "atone.h" + +#include "ServiceScheduler.h" + +#include "logging/Log.h" +#include "service/Service.h" +#include "utils/CronExpression.h" + +namespace Atone { + + /** + * Creates a new service scheduler, parsing the given cron expressions. + */ + ServiceScheduler::ServiceScheduler(const vector &schedules) { + vector expressions; + + if (schedules.empty()) { + _on_boot = true; + } else { + _on_boot = false; + + for (auto schedule : schedules) { + auto expr = new CronExpression(schedule.c_str()); + + if (expr->isReboot()) { + _on_boot = true; + continue; + } + + expressions.push_back(expr); + } + } + + _expressions = expressions; + } + + /** + * Destroys the service scheduler. + */ + ServiceScheduler::~ServiceScheduler() { + for (auto expr : _expressions) { + delete expr; + } + } + + /** + * Checks if the scheduler is configured to reboot the system. + */ + bool ServiceScheduler::onBoot() const { + return _on_boot; + } + + /** + * Checks if the timer is compatible with the service schedule. + * @return Returns true if the timer is compatible with the service schedule, otherwise false. + */ + bool ServiceScheduler::CheckTime(const CronTime *time) const { + for (auto expression : _expressions) { + if (expression->Matches(time)) { + return true; + } + } + + return false; + } + +} diff --git a/src/service/ServiceScheduler.h b/src/service/ServiceScheduler.h new file mode 100644 index 0000000..b02d5bb --- /dev/null +++ b/src/service/ServiceScheduler.h @@ -0,0 +1,33 @@ +// Copyright (c) Rodrigo Speller. All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +#pragma once + +#include "atone.h" + +#include "utils/CronExpression.h" +#include "utils/CronTime.h" + +namespace Atone { + class ServiceScheduler; +} + +#include "service/Service.h" + +namespace Atone { + + class ServiceScheduler { + private: + bool _on_boot; + vector _expressions; + + public: + ServiceScheduler(const vector &config); + ~ServiceScheduler(); + + bool onBoot() const; + + bool CheckTime(const CronTime *time) const; + }; + +} diff --git a/src/service/ServicesManager.cpp b/src/service/ServicesManager.cpp index 0be89e0..4036fce 100644 --- a/src/service/ServicesManager.cpp +++ b/src/service/ServicesManager.cpp @@ -8,6 +8,7 @@ #include "exception/AtoneException.h" #include "logging/Log.h" #include "service/Service.h" +#include "utils/CronTime.h" namespace Atone { /** @@ -85,10 +86,13 @@ namespace Atone { /** * Request to start all services. */ - void ServicesManager::Start() { + void ServicesManager::Bootstrap() { for (auto entry : services) { auto service = entry.second; - service->Start(); + + if (service->scheduler()->onBoot()) { + service->Start(); + } } } @@ -154,4 +158,35 @@ namespace Atone { return false; } -} \ No newline at end of file + + /** + * Request to check if any scheduled service must be started. The services + * will schedule will be checked between the begin and end time (inclusive). + * + * The services will be started in the order they are scheduled. + */ + void ServicesManager::CheckSchedule(CronTime *begin, CronTime *end) { + auto fulfill_services_set = unordered_set(); + auto fulfill_services = vector(); + + auto safe_begin = *begin; + while (safe_begin <= *end) { + for (auto entry : services) { + auto service = entry.second; + + if (service->scheduler()->CheckTime(&safe_begin)) { + if (fulfill_services_set.insert(service).second) { + fulfill_services.push_back(service); + } + } + } + + safe_begin.IncrementTick(); + } + + for (auto service : fulfill_services) { + service->Start(); + } + } + +} diff --git a/src/service/ServicesManager.h b/src/service/ServicesManager.h index 8d4ef0e..fd5e66a 100644 --- a/src/service/ServicesManager.h +++ b/src/service/ServicesManager.h @@ -9,8 +9,9 @@ namespace Atone { class ServicesManager; } -#include "config/AtoneOptions.h" +#include "config/ServiceConfig.h" #include "service/Service.h" +#include "utils/CronTime.h" namespace Atone { @@ -26,7 +27,8 @@ namespace Atone { bool TryGetService(const pid_t pid, Service *&result) const; bool TryGetService(const std::string &name, Service *&result) const; - void Start(); + void Bootstrap(); + void CheckSchedule(CronTime *begin, CronTime *end); bool Stop(); bool CheckAllServices() const; bool CheckService(Service *service) const; diff --git a/src/system/Supervisor.cpp b/src/system/Supervisor.cpp index bbc2417..c5974ee 100644 --- a/src/system/Supervisor.cpp +++ b/src/system/Supervisor.cpp @@ -6,13 +6,17 @@ #include "Supervisor.h" #include "logging/Log.h" +#include "utils/constants.h" #include "utils/time.h" namespace Atone { + Supervisor *Supervisor::instance = nullptr; Supervisor::Supervisor() { + // setup the signal handler + // Atone must handle all possible signals if (sigfillset(&atoneSigset)) { Log::crit("sigfillset failed: %s", strerror(errno)); @@ -30,6 +34,18 @@ namespace Atone { signal(i, UnexpectedSignalHandler); } + // setup the scheduler + + sigevent scheduler_event; + scheduler_event.sigev_notify = SIGEV_SIGNAL; + scheduler_event.sigev_signo = ATONE_SCHEDULER_SIGNAL; + scheduler_event.sigev_value.sival_ptr = &schedulerTimer; + + if (timer_create(CLOCK_REALTIME, &scheduler_event, &schedulerTimer) != 0) { + auto _errno = errno; + Log::crit("timer create failed", strerror(_errno)); + throw std::system_error(_errno, std::system_category(), "timer create failed"); + } } Supervisor::~Supervisor() { @@ -49,6 +65,10 @@ namespace Atone { if (instance == nullptr) { Log::debug("supervisor: initializing..."); instance = new Supervisor(); + + // Request the first scheduler tick + RequestSchedulerTick(0); + Log::debug("supervisor: ready"); } else { @@ -78,6 +98,26 @@ namespace Atone { Log::crit("unexpected signal received: %s (%d)", strsignal(signum), signum); } + /** + * Request a scheduler tick at given absolute time. This replaces any + * previous request. + */ + void Supervisor::RequestSchedulerTick(time_t time) { + RequireInstance(); + + itimerspec tspec; + tspec.it_interval = {}; + + tspec.it_value.tv_sec = time; + tspec.it_value.tv_nsec = 1; /* to avoid disarming the timer */ + + if (timer_settime(instance->schedulerTimer, TIMER_ABSTIME, &tspec, NULL) == -1) { + auto _errno = errno; + Log::crit("timer settime failed", strerror(_errno)); + throw std::system_error(_errno, std::system_category(), "timer settime failed"); + } + } + /** * Reaps zombie processes. * @param pid Process ID to reap. The semantics of pid are the same diff --git a/src/system/Supervisor.h b/src/system/Supervisor.h index dc2cb16..27ffc7e 100644 --- a/src/system/Supervisor.h +++ b/src/system/Supervisor.h @@ -19,6 +19,9 @@ namespace Atone { /** Signals to restore after process fork. */ sigset_t spawnSigset = sigset_t(); + /** Timer used to implement the scheduler. */ + timer_t schedulerTimer = nullptr; + Supervisor(); ~Supervisor(); static void RequireInstance(); @@ -36,6 +39,7 @@ namespace Atone { static bool SendSignal(const pid_t pid, const int signum); static int WaitSignal(siginfo_t *info); static int WaitSignal(siginfo_t *info, const timespec &timeout); + static void RequestSchedulerTick(time_t next_tick); }; } diff --git a/src/utils/CronExpression.cpp b/src/utils/CronExpression.cpp new file mode 100644 index 0000000..f9b9186 --- /dev/null +++ b/src/utils/CronExpression.cpp @@ -0,0 +1,40 @@ +// Copyright (c) Rodrigo Speller. All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +#include "atone.h" + +#include "CronExpression.h" + +#include "CronTime.h" + +namespace Atone { + + CronExpression::CronExpression(const CronExpression &expr) { + _expr = cron_expr_alloc(expr._expr); + } + + CronExpression::CronExpression(const char *const expr) { + _expr = cron_expr_alloc(NULL); + + cron_expr_parse(expr, _expr, [](const char *err) { + throw domain_error(err); + }); + } + + CronExpression::~CronExpression() { + cron_expr_free(_expr); + } + + bool CronExpression::isReboot() const { + return cron_expr_match_reboot(_expr) != 0; + } + + bool CronExpression::Matches(const CronTime *time) const { + return time->Matches(_expr); + } + + bool CronExpression::Matches(const cron_time_t *time) const { + return cron_expr_match(_expr, time) != 0; + } + +} diff --git a/src/utils/CronExpression.h b/src/utils/CronExpression.h new file mode 100644 index 0000000..1e1c93c --- /dev/null +++ b/src/utils/CronExpression.h @@ -0,0 +1,31 @@ +// Copyright (c) Rodrigo Speller. All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +#pragma once + +#include "atone.h" + +namespace Atone { + class CronExpression; +} + +#include "CronTime.h" + +namespace Atone { + + class CronExpression { + private: + cron_expr_t *_expr; + + public: + CronExpression(const CronExpression &expr); + CronExpression(const char *const expr); + ~CronExpression(); + + bool isReboot() const; + + bool Matches(const CronTime *time) const; + bool Matches(const cron_time_t *time) const; + }; + +} diff --git a/src/utils/CronTime.cpp b/src/utils/CronTime.cpp new file mode 100644 index 0000000..b29555c --- /dev/null +++ b/src/utils/CronTime.cpp @@ -0,0 +1,65 @@ +// Copyright (c) Rodrigo Speller. All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +#include "atone.h" + +#include "CronTime.h" + +#include "CronExpression.h" + +namespace Atone { + + CronTime::CronTime(const CronTime &time) { + _time = cron_time_alloc(time._time); + } + + CronTime::CronTime(const time_t *const time) { + _time = cron_time_alloc(NULL); + cron_time_set(_time, time); + } + + CronTime::CronTime(const cron_time_t *const time) { + _time = cron_time_alloc(time); + } + + CronTime::~CronTime() { + cron_time_free(_time); + } + + void CronTime::IncrementTick() { + cron_time_inc(_time); + } + + bool CronTime::Matches(const CronExpression *expr) const { + return expr->Matches(_time); + } + + bool CronTime::Matches(const cron_expr_t *expr) const { + return cron_expr_match(expr, _time) != 0; + } + + bool CronTime::operator == (const CronTime &other) const { + return cron_time_cmp(_time, other._time) == 0; + } + + bool CronTime::operator != (const CronTime &other) const { + return cron_time_cmp(_time, other._time) != 0; + } + + bool CronTime::operator > (const CronTime &other) const { + return cron_time_cmp(_time, other._time) > 0; + } + + bool CronTime::operator >= (const CronTime &other) const { + return cron_time_cmp(_time, other._time) >= 0; + } + + bool CronTime::operator < (const CronTime &other) const { + return cron_time_cmp(_time, other._time) < 0; + } + + bool CronTime::operator <= (const CronTime &other) const { + return cron_time_cmp(_time, other._time) <= 0; + } + +} diff --git a/src/utils/CronTime.h b/src/utils/CronTime.h new file mode 100644 index 0000000..a2881dc --- /dev/null +++ b/src/utils/CronTime.h @@ -0,0 +1,38 @@ +// Copyright (c) Rodrigo Speller. All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +#pragma once + +#include "atone.h" + +namespace Atone { + class CronTime; +} + +#include "CronExpression.h" + +namespace Atone { + + class CronTime { + private: + cron_time_t *_time; + + public: + CronTime(const CronTime &time); + CronTime(const time_t *const time); + CronTime(const cron_time_t *const time); + ~CronTime(); + + void IncrementTick(); + bool Matches(const cron_expr_t *expr) const; + bool Matches(const CronExpression *expr) const; + + bool operator == (const CronTime &other) const; + bool operator != (const CronTime &other) const; + bool operator < (const CronTime &other) const; + bool operator <= (const CronTime &other) const; + bool operator > (const CronTime &other) const; + bool operator >= (const CronTime &other) const; + }; + +} diff --git a/src/utils/constants.h b/src/utils/constants.h index 0f81b71..37a862a 100644 --- a/src/utils/constants.h +++ b/src/utils/constants.h @@ -6,6 +6,8 @@ #define ATONE_OPTION_DEFAULT_CONFIGFILE "/etc/atone.yaml" #define ATONE_OPTION_DEFAULT_SERVICENAME "main" +#define ATONE_SCHEDULER_SIGNAL (SIGRTMAX) + #define ATONE_TERM_CSI_RESET "\x1B[0m" #define ATONE_TERM_CSI_BOLD "\x1B[1m" #define ATONE_TERM_CSI_DIM "\x1B[2m" From f8e1d9fe3ade9cb36a92db597bdcf1648e947ce2 Mon Sep 17 00:00:00 2001 From: Rodrigo Speller Date: Mon, 10 Apr 2023 02:55:04 -0300 Subject: [PATCH 08/14] Atone configuration file doc --- README.md | 2 ++ docs/atone-configuration-file.md | 60 ++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 docs/atone-configuration-file.md diff --git a/README.md b/README.md index da54b90..303f52d 100644 --- a/README.md +++ b/README.md @@ -126,6 +126,8 @@ services: restart: always ``` +To learn more about the configuration file, see the [Atone configuration file](docs/atone-configuration-file.md). + # How to code, build, run and debug Atone project is written in C++ and uses the [Makefile](https://www.gnu.org/software/make/) build system. This project diff --git a/docs/atone-configuration-file.md b/docs/atone-configuration-file.md new file mode 100644 index 0000000..0a4bfde --- /dev/null +++ b/docs/atone-configuration-file.md @@ -0,0 +1,60 @@ +# Atone configuration file + +Atone uses a YAML file to configure the services that needs to be started and managed by Atone. The default configuration file is `/etc/atone.yaml`. But, you can set a custom configuration file by setting the `--config=` argument when executing `atone` command. + +If you're new to YAML, you can learn about it on [Learn YAML in five minutes](https://www.codeproject.com/Articles/1214409/Learn-YAML-in-five-minutes) or [YAML.org](https://yaml.org/). + +## Atone settings + +### `settings` + +General settings section for Atone. + +### `settings.workdir` + +The working directory for Atone. This is the directory where the services will be executed by default. By default, the working directory is where the configuration file is located. + +## Sevices + +### `services` + +The services section is where you define the services that will be managed by Atone. + +### `services.` + +Each service is defined by a key that will be used to identify the service. The value of the key is a map with the service settings. + +### `services..command` + +The command to be executed by the service. The process started by the command will be monitored and managed by Atone. + +### `services..depends_on` + +A list of services that the service depends on. The service will be started after all the services listed here are started. + +### `services..env` + +> Not implemented yet. + +### `services..restart` + +The restart policy for the service to automatically restart the service when it exits. + +The value must be one of: + +* `no`: Do not automatically restart the container. This is the default. +* `always`: Always restart the service. +* `on-failure`: Restart the service if it exits with a non-zero exit code. +* `unless-stopped`: Restart the service unless it was explicitly stopped. + +### `services..schedule` + +A list of cron-like expression schedules that the service will be started. The service will be started when one of the schedules matches the current time. The syntax of the expression is the same as the [cron](https://en.wikipedia.org/wiki/Cron) command (with only five fields of the time expression). + +Only one instance of the service will be started. If the service is already running, the service will not be started again. + +Not setting the `schedule` value means that the service will be started when Atone starts. The same as setting the `schedule` value to `@reboot`. + +### `services..workdir` + +> Not implemented yet. From 31c38f7746f472b6bc198a33a5d9ccbbab3e9ddc Mon Sep 17 00:00:00 2001 From: Rodrigo Speller Date: Mon, 10 Apr 2023 02:55:43 -0300 Subject: [PATCH 09/14] Chain makefile with 3rd-party dependencies --- makefile | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/makefile b/makefile index 20fc42a..8a4ef20 100644 --- a/makefile +++ b/makefile @@ -5,7 +5,9 @@ atone: build build: before-build 3rd-party build-core after-build rebuild: clean build -.PHONY: clean build rebuild atone +FORCE: ; + +.PHONY: clean build rebuild atone 3rd-party # project configuration @@ -99,7 +101,7 @@ before-build: build-core: $(PCHS) $(OUTDIR)/atone -$(OUTDIR)/atone: $(OBJECTS) +$(OUTDIR)/atone: $(OBJECTS) 3rd-party @echo " Linking $@" @$(CXX) $(LDFLAGS) -o $(OUTDIR)/atone $(OBJECTS) $(LDLIBS) @@ -121,15 +123,15 @@ after-build: 3rd-party/optparse/$(BUILDDIR)/$(TARGET)/bin/liboptparse.a \ 3rd-party/yaml-cpp/$(BUILDDIR)/$(TARGET)/bin/libyaml-cpp.a -3rd-party/cron/$(BUILDDIR)/$(TARGET)/bin/libcron.a: +3rd-party/cron/$(BUILDDIR)/$(TARGET)/bin/libcron.a: FORCE @echo "Building libcron" @$(MAKE) -C 3rd-party/cron -3rd-party/optparse/$(BUILDDIR)/$(TARGET)/bin/liboptparse.a: +3rd-party/optparse/$(BUILDDIR)/$(TARGET)/bin/liboptparse.a: FORCE @echo "Building optparse" @$(MAKE) -C 3rd-party/optparse -3rd-party/yaml-cpp/$(BUILDDIR)/$(TARGET)/bin/libyaml-cpp.a: +3rd-party/yaml-cpp/$(BUILDDIR)/$(TARGET)/bin/libyaml-cpp.a: FORCE @echo "Building yaml-cpp" @$(MAKE) -C 3rd-party/yaml-cpp From e850df088ad495763f685dd13c2b6e98895a7d5a Mon Sep 17 00:00:00 2001 From: Rodrigo Speller Date: Mon, 10 Apr 2023 05:33:54 -0300 Subject: [PATCH 10/14] Make the project target to C17 or C++17 --- .vscode/c_cpp_properties.json | 5 +- 3rd-party/cron/makefile | 2 +- 3rd-party/cron/src/README.md | 84 +++++++++++++++++++------------ 3rd-party/cron/src/cron/externs.h | 1 + 4 files changed, 57 insertions(+), 35 deletions(-) diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 6afa679..a0bba95 100755 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -4,13 +4,14 @@ "name": "default", "includePath": [ "src", + "3rd-party/cron/include", "3rd-party/optparse/include", "3rd-party/yaml-cpp/include" ], "intelliSenseMode": "linux-gcc-x64", "compilerPath": "/usr/bin/gcc", - "cStandard": "gnu17", - "cppStandard": "gnu++17" + "cStandard": "c17", + "cppStandard": "c++17" } ], "version": 4 diff --git a/3rd-party/cron/makefile b/3rd-party/cron/makefile index 663b71a..f447df6 100644 --- a/3rd-party/cron/makefile +++ b/3rd-party/cron/makefile @@ -14,7 +14,7 @@ INCLUDE_PATH+=include ARFLAGS=rc #CFLAGS+=-Wall CFLAGS+=-fdiagnostics-color=always -CFLAGS+=-std=gnu17 +CFLAGS+=-std=c17 CFLAGS+=-MMD #----------------------------------------------------------------------- diff --git a/3rd-party/cron/src/README.md b/3rd-party/cron/src/README.md index bec9c7f..cb1419c 100644 --- a/3rd-party/cron/src/README.md +++ b/3rd-party/cron/src/README.md @@ -12,38 +12,58 @@ The following changes have been made to the original code: - The included files have been stripped of unecessary code to only parse the cron expressions. - In addition to strip unnecessary lines, only these lines of the original source code has been changed to allow parsing only cron expressions, without commands: - ```diff - --- a/3rd-party/cron/src/cron/entry.c - +++ b/3rd-party/cron/src/cron/entry.c - - - Skip_Blanks(ch, file); - - if (ch == EOF || ch == '\n') { - - ecode = e_cmd; - + if (ch != EOF) { - + ecode = e_timespec; - goto eof; - } - ``` - - And: - - ```diff - --- a/3rd-party/cron/src/cron/entry.c - +++ b/3rd-party/cron/src/cron/entry.c - - /* DOW (days of week) - */ - if (ch == '*') - e->flags |= DOW_STAR; - ch = get_list(e->dow, FIRST_DOW, LAST_DOW, - DowNames, ch, file); - - if (ch == EOF) { - + if (ch != EOF) { - ecode = e_dow; - goto eof; - } - ``` + - Parses only the first five fields of the cron expression: + + ```diff + --- a/3rd-party/cron/src/cron/entry.c + +++ b/3rd-party/cron/src/cron/entry.c + + + Skip_Blanks(ch, file); + - if (ch == EOF || ch == '\n') { + - ecode = e_cmd; + + if (ch != EOF) { + + ecode = e_timespec; + goto eof; + } + ``` + + And: + + ```diff + --- a/3rd-party/cron/src/cron/entry.c + +++ b/3rd-party/cron/src/cron/entry.c + + /* DOW (days of week) + */ + if (ch == '*') + e->flags |= DOW_STAR; + ch = get_list(e->dow, FIRST_DOW, LAST_DOW, + DowNames, ch, file); + - if (ch == EOF) { + + if (ch != EOF) { + ecode = e_dow; + goto eof; + } + ``` + + - Make the project target to C17, instead of GNU17: + + ```diff + --- a/3rd-party/cron/src/cron/externs.h + +++ b/3rd-party/cron/src/cron/externs.h + + #include + #include + #include + #include + #include + #include + #include + #include + + #include + #include + ``` ## License diff --git a/3rd-party/cron/src/cron/externs.h b/3rd-party/cron/src/cron/externs.h index 43485b4..f80bcee 100644 --- a/3rd-party/cron/src/cron/externs.h +++ b/3rd-party/cron/src/cron/externs.h @@ -26,4 +26,5 @@ #include #include #include +#include #include From a150226a9b232d28a4491bf4dcf0c38acc949ff9 Mon Sep 17 00:00:00 2001 From: Rodrigo Speller Date: Mon, 10 Apr 2023 08:28:41 -0300 Subject: [PATCH 11/14] fix: Add librt link --- makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/makefile b/makefile index 8a4ef20..64e77a9 100644 --- a/makefile +++ b/makefile @@ -13,6 +13,8 @@ FORCE: ; PRECOMPILE+=src/atone.h +LIBRARY+=rt + LIBRARY+=cron LIBRARY+=optparse LIBRARY+=yaml-cpp From 46a9ae17a982c13c62d53a875c9af4f0c40f87c5 Mon Sep 17 00:00:00 2001 From: Rodrigo Speller Date: Mon, 10 Apr 2023 08:45:20 -0300 Subject: [PATCH 12/14] Lauch on containers --- .vscode/launch.json | 76 +++++++++++++++++++- .vscode/tasks.json | 150 ++++++++++++++++++++++++++++++++++++++++ Dockerfile.debug-alpine | 12 ++++ Dockerfile.debug-debian | 18 +++++ 4 files changed, 255 insertions(+), 1 deletion(-) create mode 100644 Dockerfile.debug-alpine create mode 100644 Dockerfile.debug-debian diff --git a/.vscode/launch.json b/.vscode/launch.json index d8d9d2a..cbfb0c6 100755 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -6,6 +6,7 @@ "configurations": [ { "name": "Atone: Debug", + "preLaunchTask": "Atone: Build (debug)", "type": "cppdbg", "request": "launch", "program": "${workspaceFolder}/build/debug/bin/atone", @@ -23,10 +24,83 @@ "description": "Enable pretty-printing for gdb", "text": "-enable-pretty-printing", "ignoreFailures": true + }, + { + "description": "Set the output of numbers to hexadecimal", + "text": "set output-radix 16" } ], - "preLaunchTask": "Atone: Build (debug)", "miDebuggerPath": "/usr/bin/gdb" + }, + { + "name": "Atone: Debug In Container (Debian)", + "preLaunchTask": "Atone: Build (debug in container - Debian)", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/build/debug-docker/debian/debug/bin/atone", + "cwd": "${workspaceFolder}", + "args": [ + "--config=${workspaceFolder}/sample/config-file/atone.yaml" + ], + "stopAtEntry": false, + "environment": [], + "externalConsole": true, + "pipeTransport": { + "debuggerPath": "/usr/bin/gdb", + "pipeProgram": "docker", + "pipeArgs": [ + "run", + "--privileged", + "-v", "${workspaceFolder}:${workspaceFolder}", + "--name", "atone_debug_debian", + "--rm", + "-w", "${workspaceFolder}", + "-i", "atone_debug_debian", + "sh", "-c" + ], + "pipeCwd": "${workspaceRoot}" + }, + "MIMode": "gdb", + "setupCommands": [{ + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + }], + }, + { + "name": "Atone: Debug In Container (Alpine)", + "preLaunchTask": "Atone: Build (debug in container - Alpine)", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/build/debug-docker/alpine/debug/bin/atone", + "cwd": "${workspaceFolder}", + "args": [ + "--config=${workspaceFolder}/sample/config-file/atone.yaml" + ], + "stopAtEntry": false, + "environment": [], + "externalConsole": true, + "pipeTransport": { + "debuggerPath": "/usr/bin/gdb", + "pipeProgram": "docker", + "pipeArgs": [ + "run", + "--privileged", + "-v", "${workspaceFolder}:${workspaceFolder}", + "--name", "atone_debug_alpine", + "--rm", + "-w", "${workspaceFolder}", + "-i", "atone_debug_alpine", + "sh", "-c" + ], + "pipeCwd": "${workspaceRoot}" + }, + "MIMode": "gdb", + "setupCommands": [{ + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + }], } ] } \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json index fed7d11..5a72007 100755 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -13,6 +13,156 @@ "kind": "build", "isDefault": true }, + }, + // Debian + { + "label": "Atone: Build (debug in container - Debian)", + "dependsOn": [ "Atone: Build Debug Container Image (Debian)" ], + "type": "shell", + "command": "docker", + "args": [ + // docker + "run", + "--privileged", + "--volume", "${workspaceFolder}:${workspaceFolder}", + "atone_debug_debian", + // make + "make", + "-C", "${workspaceFolder}", + "DEBUG=1", + "BUILDDIR=build/debug-docker/debian" + ], + "group": { + "kind": "build", + }, + "problemMatcher": { + "owner": "cpp", + "fileLocation": [ + "relative", + "${workspaceFolder}" + ], + "pattern": { + "regexp": "^(.*):(\\d+):(\\d+):\\s+(warning|error):\\s+(.*)$", + "file": 1, + "line": 2, + "column": 3, + "severity": 4, + "message": 5 + } + }, + }, + { + "label": "Atone: Clean (debug in container - Debian)", + "dependsOn": [ "Atone: Build Debug Container Image (Debian)" ], + "type": "shell", + "command": "docker", + "args": [ + // docker + "run", + "--privileged", + "--volume", "${workspaceFolder}:${workspaceFolder}", + "atone_debug_debian", + // make + "make", + "-C", "${workspaceFolder}", + "DEBUG=1", + "BUILDDIR=build/debug-docker/debian", + "clean-all" + ], + "group": { + "kind": "none", + }, + }, + { + "label": "Atone: Build Debug Container Image (Debian)", + "type": "shell", + "command": "docker", + "args": [ + // docker + "build", + "--tag", "atone_debug_debian", + "-", + "<", + "${workspaceFolder}/Dockerfile.debug-debian" + ], + "group": { + "kind": "build", + } + }, + // Alpine + { + "label": "Atone: Build (debug in container - Alpine)", + "dependsOn": [ "Atone: Build Debug Container Image (Alpine)" ], + "type": "shell", + "command": "docker", + "args": [ + // docker + "run", + "--privileged", + "--volume", "${workspaceFolder}:${workspaceFolder}", + "atone_debug_alpine", + // make + "make", + "-C", "${workspaceFolder}", + "DEBUG=1", + "BUILDDIR=build/debug-docker/alpine" + ], + "group": { + "kind": "build", + }, + "problemMatcher": { + "owner": "cpp", + "fileLocation": [ + "relative", + "${workspaceFolder}" + ], + "pattern": { + "regexp": "^(.*):(\\d+):(\\d+):\\s+(warning|error):\\s+(.*)$", + "file": 1, + "line": 2, + "column": 3, + "severity": 4, + "message": 5 + } + }, + }, + { + "label": "Atone: Clean (debug in container - Alpine)", + "dependsOn": [ "Atone: Build Debug Container Image (Alpine)" ], + "type": "shell", + "command": "docker", + "args": [ + // docker + "run", + "--privileged", + "--volume", "${workspaceFolder}:${workspaceFolder}", + "atone_debug_alpine", + // make + "make", + "-C", "${workspaceFolder}", + "DEBUG=1", + "BUILDDIR=build/debug-docker/alpine", + "clean-all" + ], + "group": { + "kind": "none", + }, + }, + { + "label": "Atone: Build Debug Container Image (Alpine)", + "type": "shell", + "command": "docker", + "args": [ + // docker + "build", + "--tag", "atone_debug_alpine", + "-", + "<", + "${workspaceFolder}/Dockerfile.debug-alpine" + ], + "group": { + "kind": "build", + } } ], "version": "2.0.0" diff --git a/Dockerfile.debug-alpine b/Dockerfile.debug-alpine new file mode 100644 index 0000000..55a5c3e --- /dev/null +++ b/Dockerfile.debug-alpine @@ -0,0 +1,12 @@ +# Copyright (c) Rodrigo Speller. All rights reserved. +# Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +FROM alpine:3.13 + +RUN apk --no-cache --update add \ + alpine-sdk + +RUN apk --no-cache --update add \ + gdb + +WORKDIR /root diff --git a/Dockerfile.debug-debian b/Dockerfile.debug-debian new file mode 100644 index 0000000..16259ab --- /dev/null +++ b/Dockerfile.debug-debian @@ -0,0 +1,18 @@ +# Copyright (c) Rodrigo Speller. All rights reserved. +# Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +FROM debian:bullseye + +ARG DEBIAN_FRONTEND=noninteractive + +RUN apt-get update && apt-get install -y \ + g++ \ + gettext-base \ + git \ + make + +RUN apt-get update && apt-get install -y \ + gdb \ + gdbserver + +WORKDIR /root From 8e1f6ba3c1ce13b26e5cab7d0ede9781583654c2 Mon Sep 17 00:00:00 2001 From: Rodrigo Speller Date: Mon, 10 Apr 2023 08:58:06 -0300 Subject: [PATCH 13/14] Atone development mode --- .vscode/tasks.json | 2 ++ makefile | 4 ++++ src/programs/SupervisorProgram.cpp | 6 ++++++ 3 files changed, 12 insertions(+) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 5a72007..5aafa46 100755 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -30,6 +30,7 @@ "make", "-C", "${workspaceFolder}", "DEBUG=1", + "ATONE_DEVELOPMENT_MODE=1", "BUILDDIR=build/debug-docker/debian" ], "group": { @@ -105,6 +106,7 @@ "make", "-C", "${workspaceFolder}", "DEBUG=1", + "ATONE_DEVELOPMENT_MODE=1", "BUILDDIR=build/debug-docker/alpine" ], "group": { diff --git a/makefile b/makefile index 64e77a9..31d23e2 100644 --- a/makefile +++ b/makefile @@ -77,6 +77,10 @@ ifdef ATONE_BUILD_NUMBER CXXFLAGS+=-D "ATONE_BUILD_NUMBER=\"$(ATONE_BUILD_NUMBER)\"" endif +ifdef ATONE_DEVELOPMENT_MODE + CXXFLAGS+=-D "ATONE_DEVELOPMENT_MODE=\"$(ATONE_DEVELOPMENT_MODE)\"" +endif + # artifacts SOURCES=$(shell find "$(SRCDIR)" -name *.cpp) diff --git a/src/programs/SupervisorProgram.cpp b/src/programs/SupervisorProgram.cpp index 7b8392f..ee9f47e 100644 --- a/src/programs/SupervisorProgram.cpp +++ b/src/programs/SupervisorProgram.cpp @@ -30,11 +30,17 @@ namespace Atone { Log::info("starting supervisor... (PID=%i)", pid); +#ifndef ATONE_DEVELOPMENT_MODE + // atone supervisor must be executed as an init process (pid = 1) // to adopt orphan processes, to be able to terminate, kill and reap them if (pid != 1) { throw AtoneException("atone must run as init process (pid must be 1)"); } +#else + Log::warn("atone is running as non-init process (development mode)"); +#endif + // execution context auto context = Context::FromOptions(options); From 73cfcbec3c938e977a4ce86569e37f9fa2db227f Mon Sep 17 00:00:00 2001 From: Rodrigo Speller Date: Tue, 11 Apr 2023 02:01:51 -0300 Subject: [PATCH 14/14] Update README --- README.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 303f52d..86d4a3c 100644 --- a/README.md +++ b/README.md @@ -11,15 +11,12 @@ To learn more about all the features of Atone, see the list of features. * Orphan-zombie process reaping * Services spawn +* Services schedule * Single-service mode * Multi-service mode * Automatic service restart * Signal forwarding -## Future features - -* Service schedule - # How to install To install Atone, you must execute the *install script* or download a specific version from the [releases page](https://github.com/rodrigo-speller/atone/releases). @@ -124,6 +121,13 @@ services: ten-seconds-svc: command: /bin/sh -c "echo 'Service 2'; sleep 10" restart: always + + # An example of a service scheduled to be executed every 5 minutes. + # The service sleeps for 10 seconds then exits. + scheduled-svc: + # The schedule is a cron expression. + schedule: "*/5 * * * *" + command: /bin/sh -c "echo 'Scheduled service'; sleep 10" ``` To learn more about the configuration file, see the [Atone configuration file](docs/atone-configuration-file.md).