libamxp  1.4.0
Patterns C Implementation
amxp_cron_parser.c
Go to the documentation of this file.
1 /****************************************************************************
2 **
3 ** SPDX-License-Identifier: BSD-2-Clause-Patent
4 **
5 ** SPDX-FileCopyrightText: Copyright (c) 2023 SoftAtHome
6 **
7 ** Redistribution and use in source and binary forms, with or without modification,
8 ** are permitted provided that the following conditions are met:
9 **
10 ** 1. Redistributions of source code must retain the above copyright notice,
11 ** this list of conditions and the following disclaimer.
12 **
13 ** 2. Redistributions in binary form must reproduce the above copyright notice,
14 ** this list of conditions and the following disclaimer in the documentation
15 ** and/or other materials provided with the distribution.
16 **
17 ** Subject to the terms and conditions of this license, each copyright holder
18 ** and contributor hereby grants to those receiving rights under this license
19 ** a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable
20 ** (except for failure to satisfy the conditions of this license) patent license
21 ** to make, have made, use, offer to sell, sell, import, and otherwise transfer
22 ** this software, where such license applies only to those patent claims, already
23 ** acquired or hereafter acquired, licensable by such copyright holder or contributor
24 ** that are necessarily infringed by:
25 **
26 ** (a) their Contribution(s) (the licensed copyrights of copyright holders and
27 ** non-copyrightable additions of contributors, in source or binary form) alone;
28 ** or
29 **
30 ** (b) combination of their Contribution(s) with the work of authorship to which
31 ** such Contribution(s) was added by such copyright holder or contributor, if,
32 ** at the time the Contribution is added, such addition causes such combination
33 ** to be necessarily infringed. The patent license shall not apply to any other
34 ** combinations which include the Contribution.
35 **
36 ** Except as expressly stated above, no rights or licenses from any copyright
37 ** holder or contributor is granted under this license, whether expressly, by
38 ** implication, estoppel or otherwise.
39 **
40 ** DISCLAIMER
41 **
42 ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
43 ** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
44 ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
45 ** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
46 ** LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
47 ** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
48 ** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
49 ** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
50 ** OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
51 ** USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
52 **
53 ****************************************************************************/
54 
55 #ifndef _GNU_SOURCE
56 #define _GNU_SOURCE
57 #endif
58 
59 #include <stdlib.h>
60 #include <stdio.h>
61 #include <errno.h>
62 #include <string.h>
63 #include <time.h>
64 
65 #include <amxc/amxc.h>
66 #include <amxp/amxp_cron.h>
67 #include <amxc/amxc_macros.h>
68 
69 #include "amxp_cron_parser_priv.h"
70 
71 static const char* const MONTHS_ARR[] = {
72  "FOO", "JAN", "FEB", "MAR", "APR",
73  "MAY", "JUN", "JUL", "AUG", "SEP",
74  "OCT", "NOV", "DEC"
75 };
76 
77 static const char* const DAYS_ARR[] = {
78  "SUN", "MON", "TUE", "WED",
79  "THU", "FRI", "SAT"
80 };
81 
82 static const char* const DAYS_ARR_FULL[] = {
83  "SUNDAY", "MONDAY", "TUESDAY", "WEDNESDAY",
84  "THURSDAY", "FRIDAY", "SATURDAY"
85 };
86 
87 #define CRON_MONTHS_ARR_LEN 13
88 #define CRON_DAYS_ARR_LEN 7
89 
90 static void cron_set_bit(uint8_t* rbyte, int idx) {
91  uint8_t j = (uint8_t) (idx / 8);
92  uint8_t k = (uint8_t) (idx % 8);
93 
94  rbyte[j] |= (1 << k);
95 }
96 
97 static void cron_del_bit(uint8_t* rbyte, int idx) {
98  uint8_t j = (uint8_t) (idx / 8);
99  uint8_t k = (uint8_t) (idx % 8);
100 
101  rbyte[j] &= ~(1 << k);
102 }
103 
104 static bool has_char(const char* str, char ch) {
105  size_t i = 0;
106  size_t len = 0;
107  bool rv = false;
108 
109  len = strlen(str);
110 
111  for(i = 0; i < len; i++) {
112  if(str[i] == ch) {
113  rv = true;
114  break;
115  }
116  }
117 
118  return rv;
119 }
120 
121 static uint32_t parse_uint(const char* str, bool* invalid) {
122  char* endptr = NULL;
123  int64_t value = 0;
124 
125  errno = 0;
126  *invalid = false;
127  if(str[0] == 0) {
128  *invalid = true;
129  } else {
130  value = strtol(str, &endptr, 10);
131  if((errno == ERANGE) || (*endptr != '\0') || (value < 0) || (value > INT32_MAX)) {
132  *invalid = true;
133  value = 0;
134  } else {
135  *invalid = false;
136  }
137  }
138  return (uint32_t) value;
139 }
140 
141 static void replace_ordinals(amxc_string_t* value,
142  const char* const* arr,
143  uint32_t arr_len) {
144  char* cur = NULL;
145  amxc_var_t num;
146  amxc_var_init(&num);
147  for(uint32_t i = 0; i < arr_len; i++) {
148  amxc_var_set(uint32_t, &num, i);
149  cur = amxc_var_dyncast(cstring_t, &num);
150  amxc_string_replace(value, arr[i], cur, UINT32_MAX);
151  free(cur);
152  }
153  amxc_var_clean(&num);
154 }
155 
156 static int split_string(amxc_llist_t* parts,
157  char* str,
158  const char sep,
159  uint32_t expected_len) {
160  uint32_t len = 0;
161  int rv = -1;
162  amxc_string_t strfield;
163  amxc_string_init(&strfield, 0);
164 
165  amxc_string_push_buffer(&strfield, str, strlen(str) + 1);
166  amxc_string_split_to_llist(&strfield, parts, sep);
167 
168  len = amxc_llist_size(parts);
169  when_false(len == expected_len, exit);
170 
171  rv = 0;
172 
173 exit:
174  if(rv != 0) {
175  amxc_llist_clean(parts, amxc_string_list_it_free);
176  }
177 
178  amxc_string_take_buffer(&strfield);
179  amxc_string_clean(&strfield);
180  return rv;
181 }
182 
183 static uint32_t* get_range(char* field,
184  uint32_t min,
185  uint32_t max,
186  const char** error) {
187  size_t len = 0;
188  uint32_t* res = (uint32_t*) calloc(2, sizeof(uint32_t));
189  bool invalid = true;
190 
191  when_null(res, exit);
192 
193  if((strlen(field) == 1) && (field[0] == '*')) {
194  res[0] = min;
195  res[1] = max - 1;
196  } else if(!has_char(field, '-')) {
197  uint32_t val = parse_uint(field, &invalid);
198  when_true_status(invalid, exit, *error = "Invalid value (1)");
199  res[0] = val;
200  res[1] = val;
201  } else {
202  amxc_llist_t parts;
203  amxc_llist_init(&parts);
204  when_failed_status(split_string(&parts, field, '-', 2), exit, *error = "Specified range requires two fields");
205  len = 0;
206  amxc_llist_for_each(it, &parts) {
207  amxc_string_t* strpart = amxc_string_from_llist_it(it);
208  amxc_string_trim(strpart, NULL);
209  res[len] = parse_uint(amxc_string_get(strpart, 0), &invalid);
210  if(invalid) {
211  *error = "Invalid value (2)";
212  break;
213  }
214  len++;
215  }
216  amxc_llist_clean(&parts, amxc_string_list_it_free);
217  when_true(invalid, exit);
218  }
219 
220  invalid = true;
221  when_true_status(res[0] >= max || res[1] >= max, exit, *error = "Specified range exceeds maximum");
222  when_true_status(res[0] < min || res[1] < min, exit, *error = "Specified range is less than minimum");
223  when_true_status(res[0] > res[1], exit, *error = "Specified range start exceeds range end");
224 
225  invalid = false;
226  *error = NULL;
227 
228 exit:
229  if(invalid && (res != NULL)) {
230  free(res);
231  res = NULL;
232  }
233 
234  return res;
235 }
236 
237 static uint32_t* get_range_increment(char* field,
238  uint32_t min,
239  uint32_t max,
240  uint32_t* increment,
241  const char** error) {
242  amxc_llist_it_t* it = NULL;
243  amxc_string_t* strpart = NULL;
244  amxc_llist_t parts;
245  bool invalid = true;
246  uint32_t* range = NULL;
247 
248  amxc_llist_init(&parts);
249  when_failed_status(split_string(&parts, field, '/', 2), exit, *error = "Incrementer must have two fields");
250 
251  it = amxc_llist_get_first(&parts);
252  strpart = amxc_string_from_llist_it(it);
253  range = get_range(strpart->buffer, min, max, error);
254  when_null(range, exit);
255  if(*error != NULL) {
256  free(range);
257  range = NULL;
258  goto exit;
259  }
260  if(!has_char(amxc_string_get(strpart, 0), '-')) {
261  range[1] = max - 1;
262  }
263 
264  it = amxc_llist_it_get_next(it);
265  strpart = amxc_string_from_llist_it(it);
266  *increment = parse_uint(amxc_string_get(strpart, 0), &invalid);
267  when_true_status(invalid, exit, *error = "Invalid value (3)");
268 
269 exit:
270  amxc_llist_clean(&parts, amxc_string_list_it_free);
271  return range;
272 }
273 
274 uint8_t cron_get_bit(const uint8_t* rbyte, int idx) {
275  uint8_t j = (uint8_t) (idx / 8);
276  uint8_t k = (uint8_t) (idx % 8);
277 
278  if(rbyte[j] & (1 << k)) {
279  return 1;
280  } else {
281  return 0;
282  }
283 }
284 
286  int retval = -1;
287  when_null(cron_expr, exit);
288  memset(cron_expr, 0, sizeof(amxp_cron_expr_t));
289 
290 exit:
291  return retval;
292 }
293 
294 int amxp_cron_set_hits(amxc_var_t* value,
295  uint8_t* target,
296  uint32_t min,
297  uint32_t max,
298  const char** error) {
299  amxc_var_t fields;
300  uint32_t delta = 1;
301  uint32_t* range = NULL;
302  char* field_expr = NULL;
303  int rv = -1;
304 
305  amxc_var_init(&fields);
306  amxc_var_set(csv_string_t, &fields, GET_CHAR(value, NULL));
307  amxc_var_cast(&fields, AMXC_VAR_ID_LIST);
308 
309  amxc_var_for_each(field, &fields) {
310  field_expr = amxc_var_take(cstring_t, field);
311  if(!has_char(field_expr, '/')) {
312  range = get_range(field_expr, min, max, error);
313  when_null(range, exit);
314  when_not_null_status(*error, exit, free(range));
315  } else {
316  range = get_range_increment(field_expr, min, max, &delta, error);
317  when_null(range, exit);
318  when_not_null_status(*error, exit, free(range));
319  if(delta == 0) {
320  *error = "Incrementer may not be zero";
321  free(range);
322  goto exit;
323  }
324  }
325 
326  for(size_t i = range[0]; i <= range[1]; i += delta) {
327  cron_set_bit(target, i);
328  }
329  free(range);
330  free(field_expr);
331  field_expr = NULL;
332  }
333 
334  rv = 0;
335 
336 exit:
337  free(field_expr);
338  amxc_var_clean(&fields);
339  return rv;
340 }
341 
342 int amxp_cron_set_months(amxc_var_t* value,
343  uint8_t* target,
344  uint32_t min,
345  uint32_t max,
346  const char** error) {
347  int rv = -1;
348  amxc_string_t str_value;
349  char* v = amxc_var_take(cstring_t, value);
350  amxc_string_init(&str_value, 0);
351 
352  amxc_string_push_buffer(&str_value, v, strlen(v) + 1);
353  amxc_string_to_upper(&str_value);
355  amxc_var_set(cstring_t, value, amxc_string_get(&str_value, 0));
356  rv = amxp_cron_set_hits(value, target, min, max, error);
357 
358  /* ... and then rotate it to the front of the months */
359  for(uint32_t i = 1; i <= max; i++) {
360  if(cron_get_bit(target, i)) {
361  cron_set_bit(target, i - 1);
362  cron_del_bit(target, i);
363  }
364  }
365 
366  amxc_string_clean(&str_value);
367  return rv;
368 }
369 
370 int amxp_cron_set_days_of_week(amxc_var_t* value,
371  uint8_t* target,
372  uint32_t min,
373  uint32_t max,
374  const char** error) {
375  int rv = -1;
376  amxc_string_t str_value;
377  char* v = amxc_var_take(cstring_t, value);
378 
379  amxc_string_init(&str_value, 0);
380 
381  amxc_string_push_buffer(&str_value, v, strlen(v) + 1);
382  amxc_string_to_upper(&str_value);
383 
384  if((amxc_string_text_length(&str_value) == 1) && (str_value.buffer[0] == '?')) {
385  str_value.buffer[0] = '*';
386  }
387 
390  amxc_var_set(cstring_t, value, amxc_string_get(&str_value, 0));
391 
392  rv = amxp_cron_set_hits(value, target, min, max, error);
393 
394  if(cron_get_bit(target, 7)) {
395  // Sunday can be represented as 0 or 7
396  cron_set_bit(target, 0);
397  cron_del_bit(target, 7);
398  }
399 
400  amxc_string_clean(&str_value);
401  return rv;
402 }
403 
404 int amxp_cron_set_days_of_month(amxc_var_t* value,
405  uint8_t* target,
406  uint32_t min,
407  uint32_t max,
408  const char** error) {
409  const char* field = GET_CHAR(value, NULL);
410 
411  if((strlen(field) == 1) && (field[0] == '?')) {
412  amxc_var_set(cstring_t, value, "*");
413  }
414 
415  return amxp_cron_set_hits(value, target, min, max, error);
416 }
Ambiorix cron expression parser.
#define CRON_DAYS_ARR_LEN
static const char *const DAYS_ARR[]
int amxp_cron_set_months(amxc_var_t *value, uint8_t *target, uint32_t min, uint32_t max, const char **error)
static uint32_t * get_range_increment(char *field, uint32_t min, uint32_t max, uint32_t *increment, const char **error)
static void cron_del_bit(uint8_t *rbyte, int idx)
int amxp_cron_set_days_of_week(amxc_var_t *value, uint8_t *target, uint32_t min, uint32_t max, const char **error)
int amxp_cron_set_hits(amxc_var_t *value, uint8_t *target, uint32_t min, uint32_t max, const char **error)
static bool has_char(const char *str, char ch)
static void cron_set_bit(uint8_t *rbyte, int idx)
static void replace_ordinals(amxc_string_t *value, const char *const *arr, uint32_t arr_len)
int amxp_cron_reset(amxp_cron_expr_t *cron_expr)
static int split_string(amxc_llist_t *parts, char *str, const char sep, uint32_t expected_len)
uint8_t cron_get_bit(const uint8_t *rbyte, int idx)
static uint32_t parse_uint(const char *str, bool *invalid)
static const char *const MONTHS_ARR[]
int amxp_cron_set_days_of_month(amxc_var_t *value, uint8_t *target, uint32_t min, uint32_t max, const char **error)
static const char *const DAYS_ARR_FULL[]
#define CRON_MONTHS_ARR_LEN
static uint32_t * get_range(char *field, uint32_t min, uint32_t max, const char **error)
Structure containing parsed cron expression.
Definition: amxp_cron.h:103