/* * SHL - Utility Helpers * * Copyright (c) 2011-2013 David Herrmann * Dedicated to the Public Domain */ /* * Utility Helpers */ #include #include #include #include #include #include #include #include #include #include #include #include "shl_macro.h" #include "shl_util.h" /* * Strict atoi() * These helpers implement a strict version of atoi() (or strtol()). They only * parse digit/alpha characters. No whitespace or other characters are parsed. * The unsigned-variants explicitly forbid leading +/- signs. Use the signed * variants to allow these. * Base-prefix parsing is only done if base=0 is requested. Otherwise, * base-prefixes are forbidden. * The input string must be ASCII compatible (which includes UTF8). * * We also always check for overflows and return errors (but continue parsing!) * so callers can catch it correctly. * * Additionally, we allow "length" parameters so strings do not necessarily have * to be zero-terminated. We have wrappers which skip this by passing strlen(). */ int shl_ctoi(char ch, unsigned int base) { unsigned int v; switch (ch) { case '0'...'9': v = ch - '0'; break; case 'a'...'z': v = ch - 'a' + 10; break; case 'A'...'Z': v = ch - 'A' + 10; break; default: return -EINVAL; } if (v >= base) return -EINVAL; return v; } /* figure out base and skip prefix */ static unsigned int shl__skip_base(const char **str, size_t *len) { if (*len > 1) { if ((*str)[0] == '0') { if (shl_ctoi((*str)[1], 8) >= 0) { *str += 1; *len -= 1; return 8; } } } if (*len > 2) { if ((*str)[0] == '0' && (*str)[1] == 'x') { if (shl_ctoi((*str)[2], 16) >= 0) { *str += 2; *len -= 2; return 16; } } } return 10; } int shl_atoi_ulln(const char *str, size_t len, unsigned int base, const char **next, unsigned long long *out) { bool huge; uint32_t val1; unsigned long long val2; size_t pos; int r, c; /* We use u32 as storage first so we have fast mult-overflow checks. We * cast up to "unsigned long long" once we exceed UINT32_MAX. Overflow * checks will get pretty slow for non-power2 bases, though. */ huge = false; val1 = 0; val2 = 0; r = 0; if (base > 36) { if (next) *next = str; if (out) *out = 0; return -EINVAL; } if (base == 0) base = shl__skip_base(&str, &len); for (pos = 0; pos < len; ++pos) { c = shl_ctoi(str[pos], base); if (c < 0) break; /* skip calculations on error */ if (r < 0) continue; if (!huge) { val2 = val1; r = shl_mult_u32(&val1, base); if (r >= 0 && val1 + c >= val1) val1 += c; else huge = true; } if (huge) { r = shl_mult_ull(&val2, base); if (r >= 0 && val2 + c >= val2) val2 += c; } } if (next) *next = (char*)&str[pos]; if (out) { if (r < 0) *out = ULLONG_MAX; else if (huge) *out = val2; else *out = val1; } return r; } int shl_atoi_uln(const char *str, size_t len, unsigned int base, const char **next, unsigned long *out) { unsigned long long val; int r; r = shl_atoi_ulln(str, len, base, next, &val); if (r >= 0 && val > ULONG_MAX) r = -ERANGE; if (out) *out = shl_min(val, (unsigned long long)ULONG_MAX); return r; } int shl_atoi_un(const char *str, size_t len, unsigned int base, const char **next, unsigned int *out) { unsigned long long val; int r; r = shl_atoi_ulln(str, len, base, next, &val); if (r >= 0 && val > UINT_MAX) r = -ERANGE; if (out) *out = shl_min(val, (unsigned long long)UINT_MAX); return r; } int shl_atoi_zn(const char *str, size_t len, unsigned int base, const char **next, size_t *out) { unsigned long long val; int r; r = shl_atoi_ulln(str, len, base, next, &val); if (r >= 0 && val > SIZE_MAX) r = -ERANGE; if (out) *out = shl_min(val, (unsigned long long)SIZE_MAX); return r; } /* * Greedy Realloc * The greedy-realloc helpers simplify power-of-2 buffer allocations. If you * have a dynamic array, simply use shl_greedy_realloc() for re-allocations * and it makes sure your buffer-size is always a multiple of 2 and is big * enough for your new entries. * Default size is 64, but you can initialize your buffer to a bigger default * if you need. */ void *shl_greedy_realloc(void **mem, size_t *size, size_t need) { size_t nsize; void *p; if (*size >= need) return *mem; nsize = SHL_ALIGN_POWER2(shl_max_t(size_t, 64U, need)); if (nsize == 0) return NULL; p = realloc(*mem, nsize); if (!p) return NULL; *mem = p; *size = nsize; return p; } void *shl_greedy_realloc0(void **mem, size_t *size, size_t need) { size_t prev = *size; uint8_t *p; p = shl_greedy_realloc(mem, size, need); if (!p) return NULL; if (*size > prev) shl_memzero(&p[prev], *size - prev); return p; } void *shl_greedy_realloc_t(void **arr, size_t *cnt, size_t need, size_t ts) { size_t ncnt; void *p; if (*cnt >= need) return *arr; if (!ts) return NULL; ncnt = SHL_ALIGN_POWER2(shl_max_t(size_t, 64U, need)); if (ncnt == 0) return NULL; p = realloc(*arr, ncnt * ts); if (!p) return NULL; *arr = p; *cnt = ncnt; return p; } void *shl_greedy_realloc0_t(void **arr, size_t *cnt, size_t need, size_t ts) { size_t prev = *cnt; uint8_t *p; p = shl_greedy_realloc_t(arr, cnt, need, ts); if (!p) return NULL; if (*cnt > prev) shl_memzero(&p[prev * ts], (*cnt - prev) * ts); return p; } /* * String Helpers */ char *shl_strcat(const char *first, const char *second) { size_t flen, slen; char *str; if (!first) first = ""; if (!second) second = ""; flen = strlen(first); slen = strlen(second); if (flen + slen + 1 <= flen) return NULL; str = malloc(flen + slen + 1); if (!str) return NULL; strcpy(str, first); strcpy(&str[flen], second); return str; } char *shl_strjoin(const char *first, ...) { va_list args; size_t len, l; const char *arg; char *str, *p; va_start(args, first); for (arg = first, len = 0; arg; arg = va_arg(args, const char*)) { l = strlen(arg); if (len + l < len) return NULL; len += l; } va_end(args); str = malloc(len + 1); if (!str) return NULL; va_start(args, first); for (arg = first, p = str; arg; arg = va_arg(args, const char*)) p = stpcpy(p, arg); va_end(args); *p = 0; return str; } static int shl__split_push(char ***strv, size_t *strv_num, size_t *strv_size, const char *str, size_t len) { size_t strv_need; char *ns; strv_need = (*strv_num + 2) * sizeof(**strv); if (!shl_greedy_realloc0((void**)strv, strv_size, strv_need)) return -ENOMEM; ns = malloc(len + 1); memcpy(ns, str, len); ns[len] = 0; (*strv)[*strv_num] = ns; *strv_num += 1; return 0; } int shl_strsplit_n(const char *str, size_t len, const char *sep, char ***out) { char **strv; size_t i, j, strv_num, strv_size; const char *pos; int r; if (!out || !sep) return -EINVAL; if (!str) str = ""; strv_num = 0; strv_size = sizeof(*strv); strv = malloc(strv_size); if (!strv) return -ENOMEM; pos = str; for (i = 0; i < len; ++i) { for (j = 0; sep[j]; ++j) { if (str[i] != sep[j]) continue; /* ignore empty tokens */ if (pos != &str[i]) { r = shl__split_push(&strv, &strv_num, &strv_size, pos, &str[i] - pos); if (r < 0) goto error; } pos = &str[i + 1]; break; } } /* copy trailing token if available */ if (i > 0 && pos != &str[i]) { r = shl__split_push(&strv, &strv_num, &strv_size, pos, &str[i] - pos); if (r < 0) goto error; } if ((int)strv_num < (ssize_t)strv_num) { r = -ENOMEM; goto error; } strv[strv_num] = NULL; *out = strv; return strv_num; error: for (i = 0; i < strv_num; ++i) free(strv[i]); free(strv); return r; } int shl_strsplit(const char *str, const char *sep, char ***out) { return shl_strsplit_n(str, str ? strlen(str) : 0, sep, out); } /* * strv */ void shl_strv_free(char **strv) { unsigned int i; if (!strv) return; for (i = 0; strv[i]; ++i) free(strv[i]); free(strv); } /* * Quoted Strings */ char shl_qstr_unescape_char(char c) { switch (c) { case 'a': return '\a'; case 'b': return '\b'; case 'f': return '\f'; case 'n': return '\n'; case 'r': return '\r'; case 't': return '\t'; case 'v': return '\v'; case '"': return '"'; case '\'': return '\''; case '\\': return '\\'; default: return 0; } } void shl_qstr_decode_n(char *str, size_t length) { size_t i; bool escaped; char *pos, c, quoted; quoted = 0; escaped = false; pos = str; for (i = 0; i < length; ++i) { if (escaped) { escaped = false; c = shl_qstr_unescape_char(str[i]); if (c) { *pos++ = c; } else if (!str[i]) { /* ignore binary 0 */ } else { *pos++ = '\\'; *pos++ = str[i]; } } else if (quoted) { if (str[i] == '\\') escaped = true; else if (str[i] == '"' && quoted == '"') quoted = 0; else if (str[i] == '\'' && quoted == '\'') quoted = 0; else if (!str[i]) /* ignore binary 0 */ ; else *pos++ = str[i]; } else { if (str[i] == '\\') escaped = true; else if (str[i] == '"' || str[i] == '\'') quoted = str[i]; else if (!str[i]) /* ignore binary 0 */ ; else *pos++ = str[i]; } } if (escaped) *pos++ = '\\'; *pos = 0; } static int shl__qstr_push(char ***strv, size_t *strv_num, size_t *strv_size, const char *str, size_t len) { size_t strv_need; char *ns; strv_need = (*strv_num + 2) * sizeof(**strv); if (!shl_greedy_realloc0((void**)strv, strv_size, strv_need)) return -ENOMEM; ns = malloc(len + 1); memcpy(ns, str, len); ns[len] = 0; shl_qstr_decode_n(ns, len); (*strv)[*strv_num] = ns; *strv_num += 1; return 0; } int shl_qstr_tokenize_n(const char *str, size_t length, char ***out) { char **strv, quoted; size_t i, strv_num, strv_size; const char *pos; bool escaped; int r; if (!out) return -EINVAL; if (!str) str = ""; strv_num = 0; strv_size = sizeof(*strv); strv = malloc(strv_size); if (!strv) return -ENOMEM; quoted = 0; escaped = false; pos = str; for (i = 0; i < length; ++i) { if (escaped) { escaped = false; } else if (str[i] == '\\') { escaped = true; } else if (quoted) { if (str[i] == '"' && quoted == '"') quoted = 0; else if (str[i] == '\'' && quoted == '\'') quoted = 0; } else if (str[i] == '"') { quoted = '"'; } else if (str[i] == '\'') { quoted = '\''; } else if (str[i] == ' ') { /* ignore multiple separators */ if (pos != &str[i]) { r = shl__qstr_push(&strv, &strv_num, &strv_size, pos, &str[i] - pos); if (r < 0) goto error; } pos = &str[i + 1]; } } /* copy trailing token if available */ if (i > 0 && pos != &str[i]) { r = shl__qstr_push(&strv, &strv_num, &strv_size, pos, &str[i] - pos); if (r < 0) goto error; } if ((int)strv_num < (ssize_t)strv_num) { r = -ENOMEM; goto error; } strv[strv_num] = NULL; *out = strv; return strv_num; error: for (i = 0; i < strv_num; ++i) free(strv[i]); free(strv); return r; } int shl_qstr_tokenize(const char *str, char ***out) { return shl_qstr_tokenize_n(str, str ? strlen(str) : 0, out); } size_t shl__qstr_encode(char *dst, const char *src, bool need_quote) { size_t l = 0; if (need_quote) dst[l++] = '"'; for ( ; *src; ++src) { switch (*src) { case '\\': case '\"': dst[l++] = '\\'; dst[l++] = *src; break; default: dst[l++] = *src; break; } } if (need_quote) dst[l++] = '"'; return l; } size_t shl__qstr_length(const char *str, bool *need_quote) { size_t l = 0; *need_quote = false; do { switch (*str++) { case 0: return l; case ' ': case '\t': case '\n': case '\v': *need_quote = true; } } while (++l); return l - 1; } int shl_qstr_join(char **strv, char **out) { _shl_free_ char *line = NULL; size_t len, size, l, need; bool need_quote; len = 0; size = 0; if (!SHL_GREEDY_REALLOC_T(line, size, 1)) return -ENOMEM; *line = 0; for ( ; *strv; ++strv) { l = shl__qstr_length(*strv, &need_quote); /* at most 2 byte per char (escapes) */ if (l * 2 < l) return -ENOMEM; need = l * 2; /* on top of current length */ if (need + len < need) return -ENOMEM; need += len; /* at most 4 extra chars: 2 quotes + 0 + separator */ if (need + 4 < len) return -ENOMEM; need += 4; /* make sure line is big enough */ if (!SHL_GREEDY_REALLOC_T(line, size, need)) return -ENOMEM; if (len) line[len++] = ' '; len += shl__qstr_encode(line + len, *strv, need_quote); } if ((size_t)(int)len != len) return -ENOMEM; line[len] = 0; *out = line; line = NULL; return len; } /* * mkdir */ static int shl__is_dir(const char *path) { struct stat st; if (stat(path, &st) < 0) return -errno; return S_ISDIR(st.st_mode); } const char *shl__path_startswith(const char *path, const char *prefix) { size_t pathl, prefixl; if (!path) return NULL; if (!prefix) return path; if ((path[0] == '/') != (prefix[0] == '/')) return NULL; /* compare all components */ while (true) { path += strspn(path, "/"); prefix += strspn(prefix, "/"); if (*prefix == 0) return (char*)path; if (*path == 0) return NULL; pathl = strcspn(path, "/"); prefixl = strcspn(prefix, "/"); if (pathl != prefixl || memcmp(path, prefix, pathl)) return NULL; path += pathl; prefix += prefixl; } } int shl__mkdir_parents(const char *prefix, const char *path, mode_t mode) { const char *p, *e; char *t; int r; if (!shl__path_startswith(path, prefix)) return -ENOTDIR; e = strrchr(path, '/'); if (!e || e == path) return 0; p = strndup(path, e - path); r = shl__is_dir(p); free(p); if (r > 0) return 0; if (r == 0) return -ENOTDIR; t = alloca(strlen(path) + 1); p = path + strspn(path, "/"); while (true) { e = p + strcspn(p, "/"); p = e + strspn(e, "/"); if (*p == 0) return 0; memcpy(t, path, e - path); t[e - path] = 0; if (prefix && shl__path_startswith(prefix, t)) continue; r = mkdir(t, mode); if (r < 0 && errno != EEXIST) return -errno; } } static int shl__mkdir_p(const char *prefix, const char *path, mode_t mode) { int r; r = shl__mkdir_parents(prefix, path, mode); if (r < 0) return r; r = mkdir(path, mode); if (r < 0 && (errno != EEXIST || shl__is_dir(path) <= 0)) return -errno; return 0; } int shl_mkdir_p(const char *path, mode_t mode) { return shl__mkdir_p(NULL, path, mode); } int shl_mkdir_p_prefix(const char *prefix, const char *path, mode_t mode) { return shl__mkdir_p(prefix, path, mode); } /* * Time */ uint64_t shl_now(clockid_t clock) { struct timespec ts; clock_gettime(clock, &ts); return (uint64_t)ts.tv_sec * 1000000LL + (uint64_t)ts.tv_nsec / 1000LL; } /* * Ratelimit * Modelled after Linux' lib/ratelimit.c by Dave Young * , which is licensed GPLv2. */ bool shl_ratelimit_test(struct shl_ratelimit *r) { uint64_t ts; if (!r || r->interval <= 0 || r->burst <= 0) return true; ts = shl_now(CLOCK_MONOTONIC); if (r->begin <= 0 || r->begin + r->interval < ts) { r->begin = ts; r->num = 0; goto good; } else if (r->num < r->burst) { goto good; } return false; good: ++r->num; return true; }