diff --git a/README.md b/README.md index dcafa1f..78e12f2 100644 --- a/README.md +++ b/README.md @@ -26,13 +26,13 @@ Usage example Compilation and tests run examples ---------------------------------- - gcc ccronexpr.c ccronexpr_test.c -I. -Wall -Wextra -std=c89 && ./a.out - g++ ccronexpr.c ccronexpr_test.c -I. -Wall -Wextra -std=c++11 && ./a.out - g++ ccronexpr.c ccronexpr_test.c -I. -Wall -Wextra -std=c++11 -DCRON_COMPILE_AS_CXX && ./a.out + gcc ccronexpr.c ccronexpr_test.c -I. -Wall -Wextra -std=c89 -DCRON_TEST_MALLOC -o a.out && ./a.out + g++ ccronexpr.c ccronexpr_test.c -I. -Wall -Wextra -std=c++11 -DCRON_TEST_MALLOC -o a.out && ./a.out + g++ ccronexpr.c ccronexpr_test.c -I. -Wall -Wextra -std=c++11 -DCRON_TEST_MALLOC -DCRON_COMPILE_AS_CXX -o a.out && ./a.out - clang ccronexpr.c ccronexpr_test.c -I. -Wall -Wextra -std=c89 && ./a.out - clang++ ccronexpr.c ccronexpr_test.c -I. -Wall -Wextra -std=c++11 && ./a.out - clang++ ccronexpr.c ccronexpr_test.c -I. -Wall -Wextra -std=c++11 -DCRON_COMPILE_AS_CXX && ./a.out + clang ccronexpr.c ccronexpr_test.c -I. -Wall -Wextra -std=c89 -DCRON_TEST_MALLOC -o a.out && ./a.out + clang++ ccronexpr.c ccronexpr_test.c -I. -Wall -Wextra -std=c++11 -DCRON_TEST_MALLOC -o a.out && ./a.out + clang++ ccronexpr.c ccronexpr_test.c -I. -Wall -Wextra -std=c++11 -DCRON_TEST_MALLOC -DCRON_COMPILE_AS_CXX -o a.out && ./a.out cl ccronexpr.c ccronexpr_test.c /W4 /D_CRT_SECURE_NO_WARNINGS && ccronexpr.exe diff --git a/ccronexpr.c b/ccronexpr.c index ae95ae7..f50dc65 100644 --- a/ccronexpr.c +++ b/ccronexpr.c @@ -50,9 +50,9 @@ #define CRON_INVALID_INSTANT ((time_t) -1) -static const char* DAYS_ARR[] = {"SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"}; +static const char* DAYS_ARR[] = { "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" }; #define CRON_DAYS_ARR_LEN 7 -static const char* MONTHS_ARR[] = {"FOO", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"}; +static const char* MONTHS_ARR[] = { "FOO", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" }; #define CRON_MONTHS_ARR_LEN 13 #define CRON_MAX_STR_LEN_TO_SPLIT 256 @@ -88,26 +88,38 @@ time_t timegm(struct tm* __tp); #endif /* ANDROID */ time_t cron_mktime(struct tm* tm) { #ifndef ANDROID - return timegm(tm); + return timegm(tm); #else /* ANDROID */ /* https://github.com/adobe/chromium/blob/cfe5bf0b51b1f6b9fe239c2a3c2f2364da9967d7/base/os_compat_android.cc#L20 */ static const time_t kTimeMax = ~(1L << (sizeof (time_t) * CHAR_BIT - 1)); static const time_t kTimeMin = (1L << (sizeof (time_t) * CHAR_BIT - 1)); time64_t result = timegm64(tm); - if (result < kTimeMin || result > kTimeMax) - return -1; + if (result < kTimeMin || result > kTimeMax) return -1; return result; #endif /* ANDROID */ } #endif /* _WIN32 */ + +#ifndef CRON_TEST_MALLOC +#define cronFree(x) free(x); +#define cronMalloc(x) malloc(x); +#else +void* cronMalloc(size_t n); +void cronFree(void* p); +#endif + struct tm* cron_time(time_t* date, struct tm* out) { +#ifdef __MINGW32__ + (void)(out); /* To avoid unused warning */ + return gmtime(date); +#else /* __MINGW32__ */ #ifdef _WIN32 - errno_t err = gmtime_s(out, date); - return 0 == err ? out : NULL; + return gmtime_s(date, out); #else /* _WIN32 */ return gmtime_r(date, out); #endif /* _WIN32 */ +#endif /* __MINGW32__ */ } #else /* CRON_USE_LOCAL_TIME */ @@ -129,33 +141,32 @@ struct tm* cron_time(time_t* date, struct tm* out) { static void free_splitted(char** splitted, size_t len) { size_t i; - if(!splitted) return; - for(i = 0; i < len; i++) { + if (!splitted) return; + for (i = 0; i < len; i++) { if (splitted[i]) { - free(splitted[i]); + cronFree(splitted[i]); } - } - free(splitted); + } + cronFree(splitted); } static char* strdupl(const char* str, size_t len) { if (!str) return NULL; - char* res = (char*) malloc(len + 1); + char* res = (char*) cronMalloc(len + 1); if (!res) return NULL; memset(res, 0, len + 1); memcpy(res, str, len); return res; } - -static unsigned int next_set_bit(char* bits, unsigned int max, unsigned int from_index, int* notfound) { +static unsigned int next_set_bit(uint8_t* bits, unsigned int max, unsigned int from_index, int* notfound) { unsigned int i; if (!bits) { *notfound = 1; return 0; } for (i = from_index; i < max; i++) { - if (bits[i]) return i; + if (cron_getBit(bits, i)) return i; } *notfound = 1; return 0; @@ -182,14 +193,27 @@ static int add_to_field(struct tm* calendar, int field, int val) { return 1; } switch (field) { - case CRON_CF_SECOND: calendar->tm_sec = calendar->tm_sec + val; break; - case CRON_CF_MINUTE: calendar->tm_min = calendar->tm_min + val; break; - case CRON_CF_HOUR_OF_DAY: calendar->tm_hour = calendar->tm_hour + val; break; + case CRON_CF_SECOND: + calendar->tm_sec = calendar->tm_sec + val; + break; + case CRON_CF_MINUTE: + calendar->tm_min = calendar->tm_min + val; + break; + case CRON_CF_HOUR_OF_DAY: + calendar->tm_hour = calendar->tm_hour + val; + break; case CRON_CF_DAY_OF_WEEK: /* mkgmtime ignores this field */ - case CRON_CF_DAY_OF_MONTH: calendar->tm_mday = calendar->tm_mday + val; break; - case CRON_CF_MONTH: calendar->tm_mon = calendar->tm_mon + val; break; - case CRON_CF_YEAR: calendar->tm_year = calendar->tm_year + val; break; - default: return 1; /* unknown field */ + case CRON_CF_DAY_OF_MONTH: + calendar->tm_mday = calendar->tm_mday + val; + break; + case CRON_CF_MONTH: + calendar->tm_mon = calendar->tm_mon + val; + break; + case CRON_CF_YEAR: + calendar->tm_year = calendar->tm_year + val; + break; + default: + return 1; /* unknown field */ } time_t res = cron_mktime(calendar); if (CRON_INVALID_INSTANT == res) { @@ -206,14 +230,29 @@ static int reset(struct tm* calendar, int field) { return 1; } switch (field) { - case CRON_CF_SECOND: calendar->tm_sec = 0; break; - case CRON_CF_MINUTE: calendar->tm_min = 0; break; - case CRON_CF_HOUR_OF_DAY: calendar->tm_hour = 0; break; - case CRON_CF_DAY_OF_WEEK: calendar->tm_wday = 0; break; - case CRON_CF_DAY_OF_MONTH: calendar->tm_mday = 1; break; - case CRON_CF_MONTH: calendar->tm_mon = 0; break; - case CRON_CF_YEAR: calendar->tm_year = 0; break; - default: return 1; /* unknown field */ + case CRON_CF_SECOND: + calendar->tm_sec = 0; + break; + case CRON_CF_MINUTE: + calendar->tm_min = 0; + break; + case CRON_CF_HOUR_OF_DAY: + calendar->tm_hour = 0; + break; + case CRON_CF_DAY_OF_WEEK: + calendar->tm_wday = 0; + break; + case CRON_CF_DAY_OF_MONTH: + calendar->tm_mday = 1; + break; + case CRON_CF_MONTH: + calendar->tm_mon = 0; + break; + case CRON_CF_YEAR: + calendar->tm_year = 0; + break; + default: + return 1; /* unknown field */ } time_t res = cron_mktime(calendar); if (CRON_INVALID_INSTANT == res) { @@ -231,7 +270,7 @@ static int reset_all(struct tm* calendar, int* fields) { for (i = 0; i < CRON_CF_ARR_LEN; i++) { if (-1 != fields[i]) { res = reset(calendar, fields[i]); - if(0 != res) return res; + if (0 != res) return res; } } return 0; @@ -242,14 +281,29 @@ static int set_field(struct tm* calendar, int field, int val) { return 1; } switch (field) { - case CRON_CF_SECOND: calendar->tm_sec = val; break; - case CRON_CF_MINUTE: calendar->tm_min = val; break; - case CRON_CF_HOUR_OF_DAY: calendar->tm_hour = val; break; - case CRON_CF_DAY_OF_WEEK: calendar->tm_wday = val; break; - case CRON_CF_DAY_OF_MONTH: calendar->tm_mday = val; break; - case CRON_CF_MONTH: calendar->tm_mon = val; break; - case CRON_CF_YEAR: calendar->tm_year = val; break; - default: return 1; /* unknown field */ + case CRON_CF_SECOND: + calendar->tm_sec = val; + break; + case CRON_CF_MINUTE: + calendar->tm_min = val; + break; + case CRON_CF_HOUR_OF_DAY: + calendar->tm_hour = val; + break; + case CRON_CF_DAY_OF_WEEK: + calendar->tm_wday = val; + break; + case CRON_CF_DAY_OF_MONTH: + calendar->tm_mday = val; + break; + case CRON_CF_MONTH: + calendar->tm_mon = val; + break; + case CRON_CF_YEAR: + calendar->tm_year = val; + break; + default: + return 1; /* unknown field */ } time_t res = cron_mktime(calendar); if (CRON_INVALID_INSTANT == res) { @@ -258,13 +312,11 @@ static int set_field(struct tm* calendar, int field, int val) { return 0; } - /** * Search the bits provided for the next set bit after the value provided, * and reset the calendar. */ -static unsigned int find_next(char* bits, unsigned int max, unsigned int value, struct tm* calendar, - unsigned int field, unsigned int nextField, int* lower_orders, int* res_out) { +static unsigned int find_next(uint8_t* bits, unsigned int max, unsigned int value, struct tm* calendar, unsigned int field, unsigned int nextField, int* lower_orders, int* res_out) { int notfound = 0; int err = 0; unsigned int next_value = next_set_bit(bits, max, value, ¬found); @@ -284,20 +336,19 @@ static unsigned int find_next(char* bits, unsigned int max, unsigned int value, if (err) goto return_error; } return next_value; - + return_error: - *res_out = 1; - return 0; + *res_out = 1; + return 0; } -static unsigned int find_next_day(struct tm* calendar, char* days_of_month, - unsigned int day_of_month, char* days_of_week, unsigned int day_of_week, - int* resets, int* res_out) { +static unsigned int find_next_day(struct tm* calendar, uint8_t* days_of_month, unsigned int day_of_month, uint8_t* days_of_week, unsigned int day_of_week, int* resets, int* res_out) { int err; unsigned int count = 0; unsigned int max = 366; - while ((!days_of_month[day_of_month] || !days_of_week[day_of_week]) && count++ < max) { + while ((!cron_getBit(days_of_month, day_of_month) || !cron_getBit(days_of_week, day_of_week)) && count++ < max) { err = add_to_field(calendar, CRON_CF_DAY_OF_MONTH, 1); + if (err) goto return_error; day_of_month = calendar->tm_mday; day_of_week = calendar->tm_wday; @@ -306,8 +357,8 @@ static unsigned int find_next_day(struct tm* calendar, char* days_of_month, return day_of_month; return_error: - *res_out = 1; - return 0; + *res_out = 1; + return 0; } static int do_next(cron_expr* expr, struct tm* calendar, unsigned int dot) { @@ -326,10 +377,10 @@ static int do_next(cron_expr* expr, struct tm* calendar, unsigned int dot) { unsigned int update_day_of_month = 0; unsigned int month = 0; unsigned int update_month = 0; - - resets = (int*) malloc(CRON_CF_ARR_LEN * sizeof (int)); + + resets = (int*) cronMalloc(CRON_CF_ARR_LEN * sizeof(int)); if (!resets) goto return_result; - empty_list = (int*) malloc(CRON_CF_ARR_LEN * sizeof (int)); + empty_list = (int*) cronMalloc(CRON_CF_ARR_LEN * sizeof(int)); if (!empty_list) goto return_result; for (i = 0; i < CRON_CF_ARR_LEN; i++) { resets[i] = -1; @@ -374,7 +425,7 @@ static int do_next(cron_expr* expr, struct tm* calendar, unsigned int dot) { if (0 != res) goto return_result; } - month = calendar->tm_mon; + month = calendar->tm_mon; /*day already adds one if no day in same month is found*/ update_month = find_next(expr->months, CRON_MAX_MONTHS, month, calendar, CRON_CF_MONTH, CRON_CF_YEAR, resets, &res); if (0 != res) goto return_result; if (month != update_month) { @@ -386,18 +437,18 @@ static int do_next(cron_expr* expr, struct tm* calendar, unsigned int dot) { if (0 != res) goto return_result; } goto return_result; - + return_result: - if (!resets || !empty_list) { - res = -1; - } - if(resets) { - free(resets); - } - if (empty_list) { - free(empty_list); - } - return res; + if (!resets || !empty_list) { + res = -1; + } + if (resets) { + cronFree(resets); + } + if (empty_list) { + cronFree(empty_list); + } + return res; } static int to_upper(char* str) { @@ -411,7 +462,7 @@ static int to_upper(char* str) { static char* to_string(int num) { if (abs(num) >= CRON_MAX_NUM_TO_SRING) return NULL; - char* str = (char*) malloc(CRON_NUM_OF_DIGITS(num) + 1); + char* str = (char*) cronMalloc(CRON_NUM_OF_DIGITS(num) + 1); if (!str) return NULL; int res = sprintf(str, "%d", num); if (res < 0) return NULL; @@ -439,11 +490,11 @@ static char* str_replace(char *orig, const char *rep, const char *with) { /* first time through the loop, all the variable are set correctly from here on, - tmp points to the end of the result string - ins points to the next occurrence of rep in orig - orig points to the remainder of orig after "end of rep" - */ - tmp = result = (char*) malloc(strlen(orig) + (len_with - len_rep) * count + 1); + tmp points to the end of the result string + ins points to the next occurrence of rep in orig + orig points to the remainder of orig after "end of rep" + */ + tmp = result = (char*) cronMalloc(strlen(orig) + (len_with - len_rep) * count + 1); if (!result) return NULL; while (count--) { @@ -474,19 +525,19 @@ static char** split_str(const char* str, char del, size_t* len_out) { size_t i; size_t stlen = 0; size_t len = 0; - int accum = 0; + int accum = 0; char* buf = NULL; char** res = NULL; size_t bi = 0; size_t ri = 0; char* tmp; - - if(!str) goto return_error; + + if (!str) goto return_error; for (i = 0; '\0' != str[i]; i++) { stlen += 1; if (stlen >= CRON_MAX_STR_LEN_TO_SPLIT) goto return_error; } - + for (i = 0; i < stlen; i++) { if (del == str[i]) { if (accum > 0) { @@ -503,12 +554,12 @@ static char** split_str(const char* str, char del, size_t* len_out) { } if (0 == len) return NULL; - buf = (char*) malloc(stlen + 1); + buf = (char*) cronMalloc(stlen + 1); if (!buf) goto return_error; memset(buf, 0, stlen + 1); - res = (char**) malloc(len * sizeof(char*)); + res = (char**) cronMalloc(len * sizeof(char*)); if (!res) goto return_error; - + for (i = 0; i < stlen; i++) { if (del == str[i]) { if (bi > 0) { @@ -519,7 +570,7 @@ static char** split_str(const char* str, char del, size_t* len_out) { bi = 0; } } else if (!isspace(str[i])) { - buf[bi++] = str[i]; + buf[bi++] = str[i]; } } /* tail */ @@ -528,17 +579,17 @@ static char** split_str(const char* str, char del, size_t* len_out) { if (!tmp) goto return_error; res[ri++] = tmp; } - free(buf); + cronFree(buf); *len_out = len; return res; - + return_error: - if(buf) { - free(buf); - } - free_splitted(res, len); - *len_out = 0; - return NULL; + if (buf) { + cronFree(buf); + } + free_splitted(res, len); + *len_out = 0; + return NULL; } static char* replace_ordinals(char* value, const char** arr, size_t arr_len) { @@ -547,19 +598,19 @@ static char* replace_ordinals(char* value, const char** arr, size_t arr_len) { char* res = NULL; int first = 1; for (i = 0; i < arr_len; i++) { - char* strnum = to_string((int)i); + char* strnum = to_string((int) i); if (!strnum) { if (!first) { - free(cur); + cronFree(cur); } return NULL; } res = str_replace(cur, arr[i], strnum); - free(strnum); + cronFree(strnum); if (!first) { - free(cur); + cronFree(cur); } - if (!res) { + if (!res) { return NULL; } cur = res; @@ -570,7 +621,7 @@ static char* replace_ordinals(char* value, const char** arr, size_t arr_len) { return res; } -static int has_char(char* str, char ch) { +static int has_char(char* str, char ch) { size_t i; size_t len = 0; if (!str) return 0; @@ -582,10 +633,12 @@ static int has_char(char* str, char ch) { } static unsigned int* get_range(char* field, unsigned int min, unsigned int max, const char** error) { + char** parts = NULL; size_t len = 0; - unsigned int* res = (unsigned int*) malloc(2*sizeof (unsigned int)); - if(!res) goto return_error; + unsigned int* res = (unsigned int*) cronMalloc(2 * sizeof(unsigned int)); + if (!res) goto return_error; + res[0] = 0; res[1] = 0; if (1 == strlen(field) && '*' == field[0]) { @@ -598,9 +651,10 @@ static unsigned int* get_range(char* field, unsigned int min, unsigned int max, *error = "Unsigned integer parse error 1"; goto return_error; } + res[0] = val; res[1] = val; - } else { + } else { parts = split_str(field, '-', &len); if (0 == len || len > 2) { *error = "Specified range has more than two fields"; @@ -626,48 +680,82 @@ static unsigned int* get_range(char* field, unsigned int min, unsigned int max, *error = "Specified range is less than minimum"; goto return_error; } + free_splitted(parts, len); *error = NULL; return res; - + return_error: - free_splitted(parts, len); - if(res) { - free(res); - } - return NULL; + free_splitted(parts, len); + if (res) { + cronFree(res); + } + + return NULL; +} + +void cron_setBit(uint8_t* rbyte, int idx) { + + uint8_t j = idx / 8; + uint8_t k = idx % 8; + + rbyte[j] |= (1 << k); + +} + +void cron_delBit(uint8_t* rbyte, int idx) { + + uint8_t j = idx / 8; + uint8_t k = idx % 8; + + rbyte[j] &= ~(1 << k); + +} + +uint8_t cron_getBit(uint8_t* rbyte, int idx) { + + uint8_t j = idx / 8; + uint8_t k = idx % 8; + + if (rbyte[j] & (1 << k)) { + return 1; + } else { + return 0; + } + } -static char* set_number_hits(char* value, unsigned int min, unsigned int max, const char** error) { +void set_number_hits(const char* value, uint8_t* target, unsigned int min, unsigned int max, const char** error) { size_t i; unsigned int i1; - char* bits = (char*) malloc(max); - if (!bits) { - *error = "Memory allocation error"; - return NULL; - } - memset(bits, 0, max); size_t len = 0; + char** fields = split_str(value, ',', &len); if (!fields) { *error = "Comma split error"; goto return_result; } - + for (i = 0; i < len; i++) { if (!has_char(fields[i], '/')) { /* Not an incrementer so it must be a range (possibly empty) */ + unsigned int* range = get_range(fields[i], min, max, error); + if (*error) { if (range) { - free(range); + cronFree(range); } goto return_result; + } + for (i1 = range[0]; i1 <= range[1]; i1++) { - bits[i1] = 1; + cron_setBit(target, i1); + } - free(range); + cronFree(range); + } else { size_t len2 = 0; char** split = split_str(fields[i], '/', &len2); @@ -679,7 +767,7 @@ static char* set_number_hits(char* value, unsigned int min, unsigned int max, co unsigned int* range = get_range(split[0], min, max, error); if (*error) { if (range) { - free(range); + cronFree(range); } free_splitted(split, len2); goto return_result; @@ -691,86 +779,68 @@ static char* set_number_hits(char* value, unsigned int min, unsigned int max, co unsigned int delta = parse_uint(split[1], &err); if (err) { *error = "Unsigned integer parse error 4"; - free(range); + cronFree(range); free_splitted(split, len2); goto return_result; } for (i1 = range[0]; i1 <= range[1]; i1 += delta) { - bits[i1] = 1; + cron_setBit(target, i1); } free_splitted(split, len2); - free(range); + cronFree(range); + } } goto return_result; - + return_result: - free_splitted(fields, len); - return bits; + free_splitted(fields, len); + } -static char* set_months(char* value, const char** error) { +static void set_months(char* value, uint8_t* targ, const char** error) { int err; unsigned int i; unsigned int max = 12; - char* months = NULL; + char* replaced = NULL; - char* bits = (char*) malloc(CRON_MAX_MONTHS); - if (!bits) { - *error = "Months memory allocation error"; - return NULL; - } - memset(bits, 0, CRON_MAX_MONTHS); + err = to_upper(value); - if(err) goto return_error; + if (err) return; replaced = replace_ordinals(value, MONTHS_ARR, CRON_MONTHS_ARR_LEN); - if (!replaced) goto return_error; - /* Months start with 1 in Cron and 0 in Calendar, so push the values first into a longer bit set */ - months = set_number_hits(replaced, 1, max + 1, error); - free(replaced); - if (*error) goto return_error; + if (!replaced) return; + + set_number_hits(replaced, targ, 1, max + 1, error); + cronFree(replaced); + /* ... and then rotate it to the front of the months */ for (i = 1; i <= max; i++) { - if (months[i]) { - bits[i - 1] = 1; + if (cron_getBit(targ, i)) { + cron_setBit(targ, i - 1); + cron_delBit(targ, i); } } - free(months); - return bits; - - return_error: - if (months) { - free(months); - } - return bits; } -static char* set_days(char* field, int max, const char** error) { +static void set_days(char* field, uint8_t* targ, int max, const char** error) { if (1 == strlen(field) && '?' == field[0]) { field[0] = '*'; } - return set_number_hits(field, 0, max, error); + set_number_hits(field, targ, 0, max, error); } -static char* set_days_of_month(char* field, const char** error) { +static void set_days_of_month(char* field, uint8_t* targ, const char** error) { /* Days of month start with 1 (in Cron and Calendar) so add one */ - char* bits = set_days(field, CRON_MAX_DAYS_OF_MONTH, error); + set_days(field, targ, CRON_MAX_DAYS_OF_MONTH, error); /* ... and remove it from the front */ - if (bits) { - bits[0] = 0; + if (targ) { + cron_delBit(targ, 0); } - return bits; -} +} -cron_expr* cron_parse_expr(const char* expression, const char** error) { +void cron_parse_expr(const char* expression, cron_expr* target, const char** error) { const char* err_local; - char* seconds = NULL; - char* minutes = NULL; - char* hours = NULL; - char* days_of_week = NULL; - char* days_of_month = NULL; - char* months = NULL; size_t len = 0; char** fields = NULL; char* days_replaced = NULL; @@ -782,78 +852,61 @@ cron_expr* cron_parse_expr(const char* expression, const char** error) { *error = "Invalid NULL expression"; goto return_res; } + fields = split_str(expression, ' ', &len); if (len != 6) { *error = "Invalid number of fields, expression must consist of 6 fields"; goto return_res; } - seconds = set_number_hits(fields[0], 0, 60, error); + set_number_hits(fields[0], target->seconds, 0, 60, error); if (*error) goto return_res; - minutes = set_number_hits(fields[1], 0, 60, error); + set_number_hits(fields[1], target->minutes, 0, 60, error); if (*error) goto return_res; - hours = set_number_hits(fields[2], 0, 24, error); + set_number_hits(fields[2], target->hours, 0, 24, error); if (*error) goto return_res; to_upper(fields[5]); days_replaced = replace_ordinals(fields[5], DAYS_ARR, CRON_DAYS_ARR_LEN); - days_of_week = set_days(days_replaced, 8, error); - free(days_replaced); + set_days(days_replaced, target->days_of_week, 8, error); + cronFree(days_replaced); if (*error) goto return_res; - if (days_of_week[7]) { - /* Sunday can be represented as 0 or 7 */ - days_of_week[0] = 1; - days_of_week[7] = 0; + if (cron_getBit(target->days_of_week, 7)) { + /* Sunday can be represented as 0 or 7*/ + cron_setBit(target->days_of_week, 0); + cron_delBit(target->days_of_week, 7); } - days_of_month = set_days_of_month(fields[3], error); + set_days_of_month(fields[3], target->days_of_month, error); if (*error) goto return_res; - months = set_months(fields[4], error); + set_months(fields[4], target->months, error); if (*error) goto return_res; goto return_res; - + return_res: free_splitted(fields, len); - if(*error) { - if(seconds) free(seconds); - if(minutes) free(minutes); - if(hours) free(hours); - if(days_of_week) free(days_of_week); - if(days_of_month) free(days_of_month); - if(months) free(months); - return NULL; - } - cron_expr* res = (cron_expr*) malloc(sizeof (cron_expr)); - res->seconds = seconds; - res->minutes = minutes; - res->hours = hours; - res->days_of_week = days_of_week; - res->days_of_month = days_of_month; - res->months = months; - return res; - } time_t cron_next(cron_expr* expr, time_t date) { /* - The plan: + The plan: - 1 Round up to the next whole second + 1 Round up to the next whole second - 2 If seconds match move on, otherwise find the next match: - 2.1 If next match is in the next minute then roll forwards + 2 If seconds match move on, otherwise find the next match: + 2.1 If next match is in the next minute then roll forwards - 3 If minute matches move on, otherwise find the next match - 3.1 If next match is in the next hour then roll forwards - 3.2 Reset the seconds and go to 2 + 3 If minute matches move on, otherwise find the next match + 3.1 If next match is in the next hour then roll forwards + 3.2 Reset the seconds and go to 2 - 4 If hour matches move on, otherwise find the next match - 4.1 If next match is in the next day then roll forwards, - 4.2 Reset the minutes and seconds and go to 2 + 4 If hour matches move on, otherwise find the next match + 4.1 If next match is in the next day then roll forwards, + 4.2 Reset the minutes and seconds and go to 2 - ... + ... */ if (!expr) return CRON_INVALID_INSTANT; struct tm calval; - memset(&calval, 0, sizeof (struct tm)); + memset(&calval, 0, sizeof(struct tm)); struct tm* calendar = cron_time(&date, &calval); if (!calendar) return CRON_INVALID_INSTANT; time_t original = cron_mktime(calendar); @@ -875,25 +928,3 @@ time_t cron_next(cron_expr* expr, time_t date) { return cron_mktime(calendar); } -void cron_expr_free(cron_expr* expr) { - if (!expr) return; - if (expr->seconds) { - free(expr->seconds); - } - if (expr->minutes) { - free(expr->minutes); - } - if (expr->hours) { - free(expr->hours); - } - if (expr->days_of_week) { - free(expr->days_of_week); - } - if (expr->days_of_month) { - free(expr->days_of_month); - } - if (expr->months) { - free(expr->months); - } - free(expr); -} diff --git a/ccronexpr.h b/ccronexpr.h index 1201683..ca7e2d6 100644 --- a/ccronexpr.h +++ b/ccronexpr.h @@ -34,16 +34,27 @@ extern "C" { #include #endif /* ANDROID */ +#include /*added for use if uint*_t data types*/ + +#ifndef ARRAY_LEN +#define ARRAY_LEN(x) sizeof(x)/sizeof(x[0]) +#endif + +#ifdef __MINGW32__ +/* To avoid warning when building with mingw */ +time_t _mkgmtime(struct tm* tm); +#endif /* __MINGW32__ */ + /** * Parsed cron expression */ typedef struct { - char* seconds; - char* minutes; - char* hours; - char* days_of_week; - char* days_of_month; - char* months; + uint8_t seconds[8]; + uint8_t minutes[8]; + uint8_t hours[3]; + uint8_t days_of_week[1]; + uint8_t days_of_month[4]; + uint8_t months[2]; } cron_expr; /** @@ -58,7 +69,7 @@ typedef struct { * must be freed by client using 'cron_expr_free' function. * NULL is returned on error. */ -cron_expr* cron_parse_expr(const char* expression, const char** error); +void cron_parse_expr(const char* expression, cron_expr* target, const char** error); /** * Uses the specified expression to calculate the next 'fire' date after @@ -72,6 +83,13 @@ cron_expr* cron_parse_expr(const char* expression, const char** error); */ time_t cron_next(cron_expr* expr, time_t date); +/** + * uint8_t* replace char* for storing hit dates, set_bit and get_bit are used as handlers + */ +uint8_t cron_getBit(uint8_t* rbyte, int idx); +void cron_setBit(uint8_t* rbyte, int idx); +void cron_delBit(uint8_t* rbyte, int idx); + /** * Frees the memory allocated by the specified cron expression * @@ -80,7 +98,7 @@ time_t cron_next(cron_expr* expr, time_t date); void cron_expr_free(cron_expr* expr); #if defined(__cplusplus) && !defined(CRON_COMPILE_AS_CXX) -} // extern "C" +} /* extern "C"*/ #endif #endif /* CCRONEXPR_H */ diff --git a/ccronexpr_test.c b/ccronexpr_test.c index a445d48..1475bce 100644 --- a/ccronexpr_test.c +++ b/ccronexpr_test.c @@ -40,14 +40,33 @@ #define DATE_FORMAT "%Y-%m-%d_%H:%M:%S" +#ifdef CRON_TEST_MALLOC +static int cronAllocations = 0; +static int cronTotalAllocations = 0; +static int maxAlloc = 0; +void* cronMalloc(size_t n) { + cronAllocations++; + cronTotalAllocations++; + if (cronAllocations > maxAlloc) { + maxAlloc = cronAllocations; + } + return malloc(n); +} + +void cronFree(void* p) { + cronAllocations--; + free(p); +} +#endif + #ifndef ANDROID - #ifndef _WIN32 +#ifndef _WIN32 time_t timegm(struct tm* __tp); - #else /* _WIN32 */ +#else /* _WIN32 */ static time_t timegm(struct tm* tm) { return _mkgmtime(tm); } - #endif /* _WIN32 */ +#endif /* _WIN32 */ #else /* ANDROID */ static time_t timegm(struct tm * const t) { /* time_t is signed on Android. */ @@ -55,40 +74,46 @@ static time_t timegm(struct tm * const t) { static const time_t kTimeMin = (1L << (sizeof (time_t) * CHAR_BIT - 1)); time64_t result = timegm64(t); if (result < kTimeMin || result > kTimeMax) - return -1; + return -1; return result; } #endif static int crons_equal(cron_expr* cr1, cron_expr* cr2) { - int i; - for (i = 0; i < MAX_SECONDS; i++) { + unsigned int i; + for (i = 0; i < ARRAY_LEN(cr1->seconds); i++) { if (cr1->seconds[i] != cr2->seconds[i]) { + printf("seconds not equal @%d %02x != %02x", i, cr1->seconds[i], cr2->seconds[i]); return 0; } } - for (i = 0; i < CRON_MAX_MINUTES; i++) { + for (i = 0; i < ARRAY_LEN(cr1->minutes); i++) { if (cr1->minutes[i] != cr2->minutes[i]) { + printf("minutes not equal @%d %02x != %02x", i, cr1->minutes[i], cr2->minutes[i]); return 0; } } - for (i = 0; i < CRON_MAX_HOURS; i++) { + for (i = 0; i < ARRAY_LEN(cr1->hours); i++) { if (cr1->hours[i] != cr2->hours[i]) { + printf("hours not equal @%d %02x != %02x", i, cr1->hours[i], cr2->hours[i]); return 0; } } - for (i = 0; i < CRON_MAX_DAYS_OF_WEEK; i++) { + for (i = 0; i < ARRAY_LEN(cr1->days_of_week); i++) { if (cr1->days_of_week[i] != cr2->days_of_week[i]) { + printf("days_of_week not equal @%d %02x != %02x", i, cr1->days_of_week[i], cr2->days_of_week[i]); return 0; } } - for (i = 0; i < CRON_MAX_DAYS_OF_MONTH; i++) { + for (i = 0; i < ARRAY_LEN(cr1->days_of_month); i++) { if (cr1->days_of_month[i] != cr2->days_of_month[i]) { + printf("days_of_month not equal @%d %02x != %02x", i, cr1->days_of_month[i], cr2->days_of_month[i]); return 0; } } - for (i = 0; i < CRON_MAX_MONTHS; i++) { + for (i = 0; i < ARRAY_LEN(cr1->months); i++) { if (cr1->months[i] != cr2->months[i]) { + printf("months not equal @%d %02x != %02x", i, cr1->months[i], cr2->months[i]); return 0; } } @@ -96,37 +121,60 @@ static int crons_equal(cron_expr* cr1, cron_expr* cr2) { } int one_dec_num(const char ch) { - switch(ch) { - case '0': return 0; - case '1': return 1; - case '2': return 2; - case '3': return 3; - case '4': return 4; - case '5': return 5; - case '6': return 6; - case '7': return 7; - case '8': return 8; - case '9': return 9; - default: return -1; + switch (ch) { + case '0': + return 0; + case '1': + return 1; + case '2': + return 2; + case '3': + return 3; + case '4': + return 4; + case '5': + return 5; + case '6': + return 6; + case '7': + return 7; + case '8': + return 8; + case '9': + return 9; + default: + return -1; } } int two_dec_num(const char* first) { - return one_dec_num(first[0])*10 + one_dec_num(first[1]); + return one_dec_num(first[0]) * 10 + one_dec_num(first[1]); } /* strptime is not available in msvc */ /* 2012-07-01_09:53:50 */ /* 0123456789012345678 */ struct tm* poors_mans_strptime(const char* str) { - struct tm* cal = (struct tm*) malloc(sizeof (struct tm)); - switch(str[3]) { - case '7': cal->tm_year = 107; break; - case '8': cal->tm_year = 108; break; - case '9': cal->tm_year = 109; break; - case '0': cal->tm_year = 110; break; - case '1': cal->tm_year = 111; break; - case '2': cal->tm_year = 112; break; + struct tm* cal = (struct tm*) malloc(sizeof(struct tm)); + switch (str[3]) { + case '7': + cal->tm_year = 107; + break; + case '8': + cal->tm_year = 108; + break; + case '9': + cal->tm_year = 109; + break; + case '0': + cal->tm_year = 110; + break; + case '1': + cal->tm_year = 111; + break; + case '2': + cal->tm_year = 112; + break; } cal->tm_mon = two_dec_num(str + 5) - 1; cal->tm_mday = two_dec_num(str + 8); @@ -140,47 +188,56 @@ struct tm* poors_mans_strptime(const char* str) { void check_next(const char* pattern, const char* initial, const char* expected) { const char* err = NULL; - cron_expr* parsed = cron_parse_expr(pattern, &err); + cron_expr parsed; + memset(&parsed, 0, sizeof(parsed)); + cron_parse_expr(pattern, &parsed, &err); + struct tm* calinit = poors_mans_strptime(initial); time_t dateinit = timegm(calinit); assert(-1 != dateinit); - time_t datenext = cron_next(parsed, dateinit); + time_t datenext = cron_next(&parsed, dateinit); struct tm* calnext = gmtime(&datenext); assert(calnext); char* buffer = (char*) malloc(21); memset(buffer, 0, 21); strftime(buffer, 20, DATE_FORMAT, calnext); - if(0 != strcmp(expected, buffer)) { - puts(expected); - puts(buffer); + if (0 != strcmp(expected, buffer)) { + printf("Pattern: %s\n", pattern); + printf("Initial: %s\n", initial); + printf("Expected: %s\n", expected); + printf("Actual: %s\n", buffer); assert(0); } free(buffer); free(calinit); - cron_expr_free(parsed); } void check_same(const char* expr1, const char* expr2) { - cron_expr* parsed1 = cron_parse_expr(expr1, NULL); - cron_expr* parsed2 = cron_parse_expr(expr2, NULL); - assert(crons_equal(parsed1, parsed2)); - cron_expr_free(parsed1); - cron_expr_free(parsed2); + cron_expr parsed1; + memset(&parsed1, 0, sizeof(parsed1)); + cron_parse_expr(expr1, &parsed1, NULL); + cron_expr parsed2; + memset(&parsed2, 0, sizeof(parsed2)); + cron_parse_expr(expr2, &parsed2, NULL); + assert(crons_equal(&parsed1, &parsed2)); } void check_calc_invalid() { - cron_expr* parsed = cron_parse_expr("0 0 0 31 6 *", NULL); + cron_expr parsed; + memset(&parsed, 0, sizeof(parsed)); + cron_parse_expr("0 0 0 31 6 *", &parsed, NULL); struct tm * calinit = poors_mans_strptime("2012-07-01_09:53:50"); time_t dateinit = timegm(calinit); - time_t res = cron_next(parsed, dateinit); + time_t res = cron_next(&parsed, dateinit); assert(INVALID_INSTANT == res); free(calinit); - cron_expr_free(parsed); } void check_expr_invalid(const char* expr) { const char* err = NULL; - cron_parse_expr(expr, &err); + cron_expr test; + memset(&test, 0, sizeof(test)); + cron_parse_expr(expr, &test, &err); assert(err); } @@ -242,6 +299,7 @@ void test_expr() { } void test_parse() { + check_same("* * * 2 * *", "* * * 2 * ?"); check_same("57,59 * * * * *", "57/2 * * * * *"); check_same("1,3,5 * * * * *", "1-6/2 * * * * *"); @@ -252,7 +310,7 @@ void test_parse() { check_same("* * * * 1-12 *", "* * * * FEB,JAN,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC *"); check_same("* * * * 2 *", "* * * * Feb *"); check_same("* * * * 1 *", "* * * * 1 *"); - + check_expr_invalid("77 * * * * *"); check_expr_invalid("44-77 * * * * *"); check_expr_invalid("* 77 * * * *"); @@ -267,11 +325,66 @@ void test_parse() { check_expr_invalid("* * * * 11-13 *"); } +void test_bits() { + + uint8_t testbyte[8]; + memset(testbyte, 0, 8); + int err = 0; + int i; + + for (i = 0; i <= 63; i++) { + cron_setBit(testbyte, i); + if (!cron_getBit(testbyte, i)) { + printf("Bit set error! Bit: %d!\n", i); + err = 1; + } + cron_delBit(testbyte, i); + if (cron_getBit(testbyte, i)) { + printf("Bit clear error! Bit: %d!\n", i); + err = 1; + } + assert(!err); + } + + for (i = 0; i < 12; i++) { + cron_setBit(testbyte, i); + } + if (testbyte[0] != 0xff) { + err = 1; + } + if (testbyte[1] != 0x0f) { + err = 1; + } + + assert(!err); +} + +/* For this test to work you need to set "-DCRON_TEST_MALLOC=1"*/ +#ifdef CRON_TEST_MALLOC +void test_memory() { + cron_expr cron; + const char* err; + + cron_parse_expr("* * * * * *", &cron, &err); + if (cronAllocations != 0) { + printf("Allocations != 0 but %d", cronAllocations); + assert(0); + } + printf("Allocations: total: %d, max: %d", cronTotalAllocations, maxAlloc); +} +#endif + int main() { + + test_bits(); + test_expr(); test_parse(); check_calc_invalid(); - + #ifdef CRON_TEST_MALLOC + test_memory(); /* For this test to work you need to set "-DCRON_TEST_MALLOC=1"*/ + #endif + printf("\nAll OK!"); return 0; }