diff --git a/Makefile b/Makefile index 4783d2e..98f70f9 100644 --- a/Makefile +++ b/Makefile @@ -5,6 +5,7 @@ SRC_DIR ?= src INC_DIRS ?= include SRCS != find '$(SRC_DIR)' -iname '*.c' +SRCS_H != find $(INC_DIRS) -iname '*.h' OBJS := $(SRCS:%=$(BUILD_DIR)/%.o) DEPS := $(SRCS:%=$(BUILD_DIR)/%.d) @@ -32,6 +33,15 @@ $(BUILD_DIR)/%.c.o: %.c $(CC) $(CFLAGS) -c $< -o $@ +# =====MAINTENANCE===== +.PHONY: lint +lint: + clang-format -n --Werror $(SRCS) $(SRCS_H) + +.PHONY: fmt +fmt: + clang-format -i $(SRCS) $(SRCS_H) + .PHONY: clean clean: rm -rf $(BUILD_DIR) diff --git a/include/vieter_cron.h b/include/vieter_cron.h index 9d378bd..25db1fd 100644 --- a/include/vieter_cron.h +++ b/include/vieter_cron.h @@ -8,31 +8,31 @@ #include typedef enum cron_parse_error { - cron_parse_ok = 0, - cron_parse_invalid_expression = 1, - cron_parse_invalid_number = 2, - cron_parse_out_of_range = 3, - cron_parse_too_many_parts = 4, - cron_parse_not_enough_parts = 5 + cron_parse_ok = 0, + cron_parse_invalid_expression = 1, + cron_parse_invalid_number = 2, + cron_parse_out_of_range = 3, + cron_parse_too_many_parts = 4, + cron_parse_not_enough_parts = 5 } cron_parse_error; typedef struct cron_expression { - uint8_t *minutes; - uint8_t *hours; - uint8_t *days; - uint8_t *months; - uint8_t minute_count; - uint8_t hour_count; - uint8_t day_count; - uint8_t month_count; + uint8_t *minutes; + uint8_t *hours; + uint8_t *days; + uint8_t *months; + uint8_t minute_count; + uint8_t hour_count; + uint8_t day_count; + uint8_t month_count; } cron_expression; typedef struct cron_simple_time { - int year; - int month; - int day; - int hour; - int minute; + int year; + int month; + int day; + int hour; + int minute; } cron_simple_time; cron_expression *ce_init(); @@ -40,11 +40,11 @@ cron_expression *ce_init(); void cron_ce_free(cron_expression *ce); void cron_ce_next(cron_simple_time *out, cron_expression *ce, - cron_simple_time *ref); + cron_simple_time *ref); void cron_ce_next_from_now(cron_simple_time *out, cron_expression *ce); enum cron_parse_error cron_ce_parse_expression(cron_expression *out, - const char *expression); + const char *expression); #endif diff --git a/src/cron/expression.c b/src/cron/expression.c index f7660d5..4261366 100644 --- a/src/cron/expression.c +++ b/src/cron/expression.c @@ -6,113 +6,110 @@ const uint8_t month_days[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; cron_expression *ce_init() { return malloc(sizeof(cron_expression)); } void ce_free(cron_expression *ce) { - free(ce->months); - free(ce->days); - free(ce->hours); - free(ce->minutes); - free(ce); + free(ce->months); + free(ce->days); + free(ce->hours); + free(ce->minutes); + free(ce); } void ce_next(cron_simple_time *out, cron_expression *ce, - cron_simple_time *ref) { - // For all of these values, the rule is the following: if their value is - // the length of their respective array in the CronExpression object, that - // means we've looped back around. This means that the "bigger" value has - // to be incremented by one. For example, if the minutes have looped - // around, that means that the hour has to be incremented as well. - uint8_t month_index = 0; - uint8_t day_index = 0; - uint8_t hour_index = 0; - uint8_t minute_index = 0; + cron_simple_time *ref) { + // For all of these values, the rule is the following: if their value is + // the length of their respective array in the CronExpression object, that + // means we've looped back around. This means that the "bigger" value has + // to be incremented by one. For example, if the minutes have looped + // around, that means that the hour has to be incremented as well. + uint8_t month_index = 0; + uint8_t day_index = 0; + uint8_t hour_index = 0; + uint8_t minute_index = 0; - // This chain is the same logic multiple times, namely that if a "bigger" - // value loops around, then the smaller value will always reset as well. - // For example, if we're going to a new day, the hour & minute will always - // be their smallest value again. - while (month_index < ce->month_count && - ref->month > ce->months[month_index]) { - month_index++; - } + // This chain is the same logic multiple times, namely that if a "bigger" + // value loops around, then the smaller value will always reset as well. + // For example, if we're going to a new day, the hour & minute will always + // be their smallest value again. + while (month_index < ce->month_count && + ref->month > ce->months[month_index]) { + month_index++; + } - if (month_index < ce->month_count && - ref->month == ce->months[month_index]) { - while (day_index < ce->day_count && ref->day > ce->days[day_index]) { - day_index++; - } + if (month_index < ce->month_count && ref->month == ce->months[month_index]) { + while (day_index < ce->day_count && ref->day > ce->days[day_index]) { + day_index++; + } - if (day_index < ce->day_count && ref->day == ce->days[day_index]) { - while (hour_index < ce->hour_count && - ref->hour > ce->hours[hour_index]) { - hour_index++; - } + if (day_index < ce->day_count && ref->day == ce->days[day_index]) { + while (hour_index < ce->hour_count && ref->hour > ce->hours[hour_index]) { + hour_index++; + } - if (hour_index < ce->hour_count && - ref->hour == ce->hours[hour_index]) { - // Minute is the only value where we explicitely make sure we - // can't match sref's value exactly. This is to ensure we only - // return values in the future. - while (minute_index < ce->minute_count && - ref->minute >= ce->minutes[minute_index]) { - minute_index++; - } - } - } - } + if (hour_index < ce->hour_count && ref->hour == ce->hours[hour_index]) { + // Minute is the only value where we explicitely make sure we + // can't match sref's value exactly. This is to ensure we only + // return values in the future. + while (minute_index < ce->minute_count && + ref->minute >= ce->minutes[minute_index]) { + minute_index++; + } + } + } + } - // Here, we increment the "bigger" values by one if the smaller ones loop - // around. The order is important, as it allows a sort-of waterfall effect - // to occur which updates all values if required. - if (minute_index == ce->minute_count && hour_index < ce->hour_count) { - hour_index++; - } + // Here, we increment the "bigger" values by one if the smaller ones loop + // around. The order is important, as it allows a sort-of waterfall effect + // to occur which updates all values if required. + if (minute_index == ce->minute_count && hour_index < ce->hour_count) { + hour_index++; + } - if (hour_index == ce->hour_count && day_index < ce->day_count) { - day_index++; - } + if (hour_index == ce->hour_count && day_index < ce->day_count) { + day_index++; + } - if (day_index == ce->day_count && month_index < ce->month_count) { - month_index++; - } + if (day_index == ce->day_count && month_index < ce->month_count) { + month_index++; + } - out->minute = ce->minutes[minute_index % ce->minute_count]; - out->hour = ce->hours[hour_index % ce->hour_count]; - out->day = ce->days[day_index % ce->day_count]; + out->minute = ce->minutes[minute_index % ce->minute_count]; + out->hour = ce->hours[hour_index % ce->hour_count]; + out->day = ce->days[day_index % ce->day_count]; - // Sometimes, we end up with a day that does not exist within the selected - // month, e.g. day 30 in February. When this occurs, we reset day back to - // the smallest value & loop over to the next month that does have this - // day. - if (out->day > month_days[ce->months[month_index % ce->month_count] - 1]) { - out->day = ce->days[0]; - month_index++; + // Sometimes, we end up with a day that does not exist within the selected + // month, e.g. day 30 in February. When this occurs, we reset day back to + // the smallest value & loop over to the next month that does have this + // day. + if (out->day > month_days[ce->months[month_index % ce->month_count] - 1]) { + out->day = ce->days[0]; + month_index++; - while (out->day > - month_days[ce->months[month_index % ce->month_count] - 1]) { - month_index++; - } - } + while (out->day > + month_days[ce->months[month_index % ce->month_count] - 1]) { + month_index++; + } + } - out->month = ce->months[month_index % ce->month_count]; + out->month = ce->months[month_index % ce->month_count]; - if (month_index >= ce->month_count) { - out->year = ref->year + 1; - } else { - out->year = ref->year; - } + if (month_index >= ce->month_count) { + out->year = ref->year + 1; + } else { + out->year = ref->year; + } } void ce_next_from_now(cron_simple_time *out, cron_expression *ce) { - time_t t = time(NULL); - struct tm gm; - gmtime_r(&t, &gm); + time_t t = time(NULL); + struct tm gm; + gmtime_r(&t, &gm); - cron_simple_time ref = {// tm_year contains years since 1900 - .year = 1900 + gm.tm_year, - // tm_mon goes from 0 to 11 - .month = gm.tm_mon + 1, - .day = gm.tm_mday, - .hour = gm.tm_hour, - .minute = gm.tm_min}; + cron_simple_time ref = {// tm_year contains years since 1900 + .year = 1900 + gm.tm_year, + // tm_mon goes from 0 to 11 + .month = gm.tm_mon + 1, + .day = gm.tm_mday, + .hour = gm.tm_hour, + .minute = gm.tm_min}; - ce_next(out, ce, &ref); + ce_next(out, ce, &ref); } diff --git a/src/cron/parse.c b/src/cron/parse.c index da5bcfe..74f0bb7 100644 --- a/src/cron/parse.c +++ b/src/cron/parse.c @@ -2,7 +2,7 @@ // This prefix is needed to properly compile const uint8_t parse_month_days[] = {31, 28, 31, 30, 31, 30, - 31, 31, 30, 31, 30, 31}; + 31, 31, 30, 31, 30, 31}; // Allowed value ranges for the minute, hour, day and month field const uint8_t min[4] = {0, 0, 1, 1}; @@ -14,14 +14,14 @@ const uint8_t max_parts = 4; // Convert a string into a uint8_t value by parsing it using atoi and checking // whether it's contained within the given range #define SAFE_ATOI(v, s, min, max) \ - int _##v = atoi(s); \ - if ((_##v) == 0 && strcmp((s), "0") != 0) { \ - return cron_parse_invalid_number; \ - } \ - if (((_##v) < (min)) || ((_##v) > (max))) { \ - return cron_parse_out_of_range; \ - } \ - v = (uint8_t)(_##v); + int _##v = atoi(s); \ + if ((_##v) == 0 && strcmp((s), "0") != 0) { \ + return cron_parse_invalid_number; \ + } \ + if (((_##v) < (min)) || ((_##v) > (max))) { \ + return cron_parse_out_of_range; \ + } \ + v = (uint8_t)(_##v); #define MAX(x, y) (((x) > (y)) ? (x) : (y)) @@ -42,77 +42,77 @@ const uint8_t max_parts = 4; * - a-b/c */ cron_parse_error ce_parse_range(uint64_t *out, char *s, uint8_t min, - uint8_t max) { - size_t slash_index = 0, dash_index = 0; - size_t s_index = 0; - char cur_char; - bool is_valid_character; + uint8_t max) { + size_t slash_index = 0, dash_index = 0; + size_t s_index = 0; + char cur_char; + bool is_valid_character; - while ((cur_char = s[s_index]) != '\0') { - is_valid_character = cur_char == '/' || cur_char == '-' || - cur_char == '*' || - (cur_char >= '0' && cur_char <= '9'); + while ((cur_char = s[s_index]) != '\0') { + is_valid_character = cur_char == '/' || cur_char == '-' || + cur_char == '*' || + (cur_char >= '0' && cur_char <= '9'); - if (!is_valid_character) { - return cron_parse_invalid_expression; - } + if (!is_valid_character) { + return cron_parse_invalid_expression; + } - if (cur_char == '/') { - if (s_index == 0 || slash_index != 0) { - return cron_parse_invalid_expression; - } + if (cur_char == '/') { + if (s_index == 0 || slash_index != 0) { + return cron_parse_invalid_expression; + } - slash_index = s_index; + slash_index = s_index; - s[s_index] = '\0'; - } else if (cur_char == '-') { - // At most one dash is allowed, and it must be before the slash - if (s_index == 0 || dash_index != 0 || slash_index != 0) { - return cron_parse_invalid_expression; - } + s[s_index] = '\0'; + } else if (cur_char == '-') { + // At most one dash is allowed, and it must be before the slash + if (s_index == 0 || dash_index != 0 || slash_index != 0) { + return cron_parse_invalid_expression; + } - dash_index = s_index; + dash_index = s_index; - s[s_index] = '\0'; - } + s[s_index] = '\0'; + } - s_index++; - } + s_index++; + } - uint8_t start; - uint8_t end = max; - uint8_t interval = 0; + uint8_t start; + uint8_t end = max; + uint8_t interval = 0; - if (s[0] == '*') { - if (s[1] != '\0' || dash_index != 0) { - return cron_parse_invalid_expression; - } + if (s[0] == '*') { + if (s[1] != '\0' || dash_index != 0) { + return cron_parse_invalid_expression; + } - start = min; - interval = 1; - } else { - SAFE_ATOI(start, s, min, max); + start = min; + interval = 1; + } else { + SAFE_ATOI(start, s, min, max); - if (dash_index > 0) { - SAFE_ATOI(end, &s[dash_index + 1], min, max); - interval = 1; - } - } + if (dash_index > 0) { + SAFE_ATOI(end, &s[dash_index + 1], min, max); + interval = 1; + } + } - if (slash_index > 0) { - SAFE_ATOI(interval, &s[slash_index + 1], 1, max - min); - } + if (slash_index > 0) { + SAFE_ATOI(interval, &s[slash_index + 1], 1, max - min); + } - if (interval == 0) { - *out |= ((uint64_t)1) << (start - min); - } else { - while (start <= end) { - *out |= ((uint64_t)1) << (start - min); - start += interval; - } - } + if (interval == 0) { + *out |= ((uint64_t)1) << (start - min); + } else { + while (start <= end) { + *out |= ((uint64_t)1) << (start - min); + start += interval; + } + } - return cron_parse_ok; + return cron_parse_ok; } /* @@ -121,26 +121,26 @@ cron_parse_error ce_parse_range(uint64_t *out, char *s, uint8_t min, * expressions, separated by commas. */ cron_parse_error ce_parse_part(uint64_t *out, char *s, uint8_t min, - uint8_t max) { - *out = 0; + uint8_t max) { + *out = 0; - char *next; - cron_parse_error res; + char *next; + cron_parse_error res; - while ((next = strchr(s, ',')) != NULL) { - next[0] = '\0'; + while ((next = strchr(s, ',')) != NULL) { + next[0] = '\0'; - res = ce_parse_range(out, s, min, max); + res = ce_parse_range(out, s, min, max); - if (res != cron_parse_ok) { - return res; - } + if (res != cron_parse_ok) { + return res; + } - s = next + 1; - } + s = next + 1; + } - // Make sure to parse the final range as well - return ce_parse_range(out, s, min, max); + // Make sure to parse the final range as well + return ce_parse_range(out, s, min, max); } /* @@ -149,16 +149,16 @@ cron_parse_error ce_parse_part(uint64_t *out, char *s, uint8_t min, * to be dependent on GCC-specific extensions. */ uint8_t uint64_t_popcount(uint64_t n) { - uint8_t set_bits = 0; + uint8_t set_bits = 0; - while (n != 0) { - // This sets the least significant bit to zero (very cool) - n &= n - 1; + while (n != 0) { + // This sets the least significant bit to zero (very cool) + n &= n - 1; - set_bits++; - } + set_bits++; + } - return set_bits; + return set_bits; } /* @@ -166,170 +166,168 @@ uint8_t uint64_t_popcount(uint64_t n) { * it represents. */ uint8_t bf_to_nums(uint8_t **out, uint64_t bf, uint8_t min, uint8_t max) { - // Each bit field only has `max - min + 1` meaningful bits. All other bits - // should be ignored, and can be any value. By shifting the bit field back - // and forth, we set these excessive bits to zero, ensuring popcount returns - // the correct value. - uint8_t excess_bits = 64 - (max - min + 1); - bf = (bf << excess_bits) >> excess_bits; + // Each bit field only has `max - min + 1` meaningful bits. All other bits + // should be ignored, and can be any value. By shifting the bit field back + // and forth, we set these excessive bits to zero, ensuring popcount returns + // the correct value. + uint8_t excess_bits = 64 - (max - min + 1); + bf = (bf << excess_bits) >> excess_bits; - uint8_t size = uint64_t_popcount(bf); - uint8_t *buf = malloc(size * sizeof(uint8_t)); + uint8_t size = uint64_t_popcount(bf); + uint8_t *buf = malloc(size * sizeof(uint8_t)); - uint8_t bit_index = 0, buf_index = 0; + uint8_t bit_index = 0, buf_index = 0; - while (buf_index < size && bit_index <= max - min) { - if (((uint64_t)1 << bit_index) & bf) { - // Resize buffer if needed - buf[buf_index] = min + bit_index; - buf_index++; - } + while (buf_index < size && bit_index <= max - min) { + if (((uint64_t)1 << bit_index) & bf) { + // Resize buffer if needed + buf[buf_index] = min + bit_index; + buf_index++; + } - bit_index++; - } + bit_index++; + } - *out = buf; + *out = buf; - return size; + return size; } /* * Parse a cron expression string into a cron_expression struct. */ cron_parse_error ce_parse_expression(cron_expression *out, - const char *expression) { - // The parsing functions modify the input string in-place - char *s = strdup(expression); - char *orig_s = s; + const char *expression) { + // The parsing functions modify the input string in-place + char *s = strdup(expression); + char *orig_s = s; - cron_parse_error res = cron_parse_ok; + cron_parse_error res = cron_parse_ok; - // First we divide the input string into its parts, divided by spaces. - // Each part is delimited by a NULL byte. - uint8_t part_count = 0; - char *parts[max_parts]; - char *next_space; + // First we divide the input string into its parts, divided by spaces. + // Each part is delimited by a NULL byte. + uint8_t part_count = 0; + char *parts[max_parts]; + char *next_space; - // Skip leading spaces - size_t offset = 0; + // Skip leading spaces + size_t offset = 0; - while (s[offset] == ' ') { - offset++; - } + while (s[offset] == ' ') { + offset++; + } - s += offset; + s += offset; - while (part_count < max_parts && ((next_space = strchr(s, ' ')) != NULL)) { - next_space[0] = '\0'; + while (part_count < max_parts && ((next_space = strchr(s, ' ')) != NULL)) { + next_space[0] = '\0'; - parts[part_count] = s; - part_count++; + parts[part_count] = s; + part_count++; - // Skip multiple spaces - offset = 1; - while (next_space[offset] == ' ') { - offset++; - } - s = next_space + offset; - } + // Skip multiple spaces + offset = 1; + while (next_space[offset] == ' ') { + offset++; + } + s = next_space + offset; + } - // Each iteration of the loop skips all trailing spaces. This means that, if - // s[0] isn't '\0', there's still another part before the end of the string. - if (s[0] != '\0') { - if (part_count == max_parts) { - res = cron_parse_too_many_parts; - goto end; - } + // Each iteration of the loop skips all trailing spaces. This means that, if + // s[0] isn't '\0', there's still another part before the end of the string. + if (s[0] != '\0') { + if (part_count == max_parts) { + res = cron_parse_too_many_parts; + goto end; + } - parts[part_count] = s; - part_count++; - } + parts[part_count] = s; + part_count++; + } - if (part_count < min_parts) { - res = cron_parse_not_enough_parts; - goto end; - } + if (part_count < min_parts) { + res = cron_parse_not_enough_parts; + goto end; + } - // We now parse the parts in reverse. This is because the month part - // determines the maximum value of the day part. + // We now parse the parts in reverse. This is because the month part + // determines the maximum value of the day part. - uint64_t bit_field = 0; + uint64_t bit_field = 0; - // Months - if (part_count >= 4) { - res = ce_parse_part(&bit_field, parts[3], min[3], max[3]); + // Months + if (part_count >= 4) { + res = ce_parse_part(&bit_field, parts[3], min[3], max[3]); - if (res != cron_parse_ok) { - goto end; - } + if (res != cron_parse_ok) { + goto end; + } - out->month_count = bf_to_nums(&out->months, bit_field, min[3], max[3]); - } - // If months aren't provided, they're replaced with a * - else { - out->month_count = bf_to_nums(&out->months, ~0, min[3], max[3]); - } + out->month_count = bf_to_nums(&out->months, bit_field, min[3], max[3]); + } + // If months aren't provided, they're replaced with a * + else { + out->month_count = bf_to_nums(&out->months, ~0, min[3], max[3]); + } - // Determine what the largest allowed day value is, given the months - uint8_t max_day_value = 0; + // Determine what the largest allowed day value is, given the months + uint8_t max_day_value = 0; - for (uint8_t i = 0; i < out->month_count; i++) { - max_day_value = - MAX(max_day_value, parse_month_days[out->months[i] - 1]); - } + for (uint8_t i = 0; i < out->month_count; i++) { + max_day_value = MAX(max_day_value, parse_month_days[out->months[i] - 1]); + } - // Days - if (part_count >= 3) { - bit_field = 0; + // Days + if (part_count >= 3) { + bit_field = 0; - res = ce_parse_part(&bit_field, parts[2], min[2], max_day_value); + res = ce_parse_part(&bit_field, parts[2], min[2], max_day_value); - if (res != cron_parse_ok) { - free(out->months); + if (res != cron_parse_ok) { + free(out->months); - goto end; - } + goto end; + } - out->day_count = - bf_to_nums(&out->days, bit_field, min[2], max_day_value); - } - // If days aren't provided, they're replaced with a * - else { - out->day_count = bf_to_nums(&out->days, ~0, min[2], max_day_value); - } + out->day_count = bf_to_nums(&out->days, bit_field, min[2], max_day_value); + } + // If days aren't provided, they're replaced with a * + else { + out->day_count = bf_to_nums(&out->days, ~0, min[2], max_day_value); + } - // Hours - bit_field = 0; + // Hours + bit_field = 0; - res = ce_parse_part(&bit_field, parts[1], min[1], max[1]); + res = ce_parse_part(&bit_field, parts[1], min[1], max[1]); - if (res != cron_parse_ok) { - free(out->months); - free(out->days); + if (res != cron_parse_ok) { + free(out->months); + free(out->days); - goto end; - } + goto end; + } - out->hour_count = bf_to_nums(&out->hours, bit_field, min[1], max[1]); + out->hour_count = bf_to_nums(&out->hours, bit_field, min[1], max[1]); - // Minutes - bit_field = 0; + // Minutes + bit_field = 0; - res = ce_parse_part(&bit_field, parts[0], min[0], max[0]); + res = ce_parse_part(&bit_field, parts[0], min[0], max[0]); - if (res != cron_parse_ok) { - free(out->months); - free(out->days); - free(out->hours); + if (res != cron_parse_ok) { + free(out->months); + free(out->days); + free(out->hours); - goto end; - } + goto end; + } - out->minute_count = bf_to_nums(&out->minutes, bit_field, min[0], max[0]); + out->minute_count = bf_to_nums(&out->minutes, bit_field, min[0], max[0]); end: - // s is cloned - free(orig_s); + // s is cloned + free(orig_s); - return res; + return res; }