diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-14 13:40:54 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-14 13:40:54 +0000 |
commit | 317c0644ccf108aa23ef3fd8358bd66c2840bfc0 (patch) | |
tree | c417b3d25c86b775989cb5ac042f37611b626c8a /src/util.c | |
parent | Initial commit. (diff) | |
download | redis-317c0644ccf108aa23ef3fd8358bd66c2840bfc0.tar.xz redis-317c0644ccf108aa23ef3fd8358bd66c2840bfc0.zip |
Adding upstream version 5:7.2.4.upstream/5%7.2.4
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/util.c')
-rw-r--r-- | src/util.c | 1431 |
1 files changed, 1431 insertions, 0 deletions
diff --git a/src/util.c b/src/util.c new file mode 100644 index 0000000..26d92b9 --- /dev/null +++ b/src/util.c @@ -0,0 +1,1431 @@ +/* + * Copyright (c) 2009-2012, Salvatore Sanfilippo <antirez at gmail dot com> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Redis 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 COPYRIGHT HOLDERS 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 COPYRIGHT OWNER 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. + */ + +#include "fmacros.h" +#include "fpconv_dtoa.h" +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <ctype.h> +#include <limits.h> +#include <math.h> +#include <unistd.h> +#include <sys/time.h> +#include <float.h> +#include <stdint.h> +#include <errno.h> +#include <time.h> +#include <sys/stat.h> +#include <dirent.h> +#include <fcntl.h> +#include <libgen.h> + +#include "util.h" +#include "sha256.h" +#include "config.h" + +#define UNUSED(x) ((void)(x)) + +/* Glob-style pattern matching. */ +static int stringmatchlen_impl(const char *pattern, int patternLen, + const char *string, int stringLen, int nocase, int *skipLongerMatches) +{ + while(patternLen && stringLen) { + switch(pattern[0]) { + case '*': + while (patternLen && pattern[1] == '*') { + pattern++; + patternLen--; + } + if (patternLen == 1) + return 1; /* match */ + while(stringLen) { + if (stringmatchlen_impl(pattern+1, patternLen-1, + string, stringLen, nocase, skipLongerMatches)) + return 1; /* match */ + if (*skipLongerMatches) + return 0; /* no match */ + string++; + stringLen--; + } + /* There was no match for the rest of the pattern starting + * from anywhere in the rest of the string. If there were + * any '*' earlier in the pattern, we can terminate the + * search early without trying to match them to longer + * substrings. This is because a longer match for the + * earlier part of the pattern would require the rest of the + * pattern to match starting later in the string, and we + * have just determined that there is no match for the rest + * of the pattern starting from anywhere in the current + * string. */ + *skipLongerMatches = 1; + return 0; /* no match */ + break; + case '?': + string++; + stringLen--; + break; + case '[': + { + int not, match; + + pattern++; + patternLen--; + not = pattern[0] == '^'; + if (not) { + pattern++; + patternLen--; + } + match = 0; + while(1) { + if (pattern[0] == '\\' && patternLen >= 2) { + pattern++; + patternLen--; + if (pattern[0] == string[0]) + match = 1; + } else if (pattern[0] == ']') { + break; + } else if (patternLen == 0) { + pattern--; + patternLen++; + break; + } else if (patternLen >= 3 && pattern[1] == '-') { + int start = pattern[0]; + int end = pattern[2]; + int c = string[0]; + if (start > end) { + int t = start; + start = end; + end = t; + } + if (nocase) { + start = tolower(start); + end = tolower(end); + c = tolower(c); + } + pattern += 2; + patternLen -= 2; + if (c >= start && c <= end) + match = 1; + } else { + if (!nocase) { + if (pattern[0] == string[0]) + match = 1; + } else { + if (tolower((int)pattern[0]) == tolower((int)string[0])) + match = 1; + } + } + pattern++; + patternLen--; + } + if (not) + match = !match; + if (!match) + return 0; /* no match */ + string++; + stringLen--; + break; + } + case '\\': + if (patternLen >= 2) { + pattern++; + patternLen--; + } + /* fall through */ + default: + if (!nocase) { + if (pattern[0] != string[0]) + return 0; /* no match */ + } else { + if (tolower((int)pattern[0]) != tolower((int)string[0])) + return 0; /* no match */ + } + string++; + stringLen--; + break; + } + pattern++; + patternLen--; + if (stringLen == 0) { + while(*pattern == '*') { + pattern++; + patternLen--; + } + break; + } + } + if (patternLen == 0 && stringLen == 0) + return 1; + return 0; +} + +int stringmatchlen(const char *pattern, int patternLen, + const char *string, int stringLen, int nocase) { + int skipLongerMatches = 0; + return stringmatchlen_impl(pattern,patternLen,string,stringLen,nocase,&skipLongerMatches); +} + +int stringmatch(const char *pattern, const char *string, int nocase) { + return stringmatchlen(pattern,strlen(pattern),string,strlen(string),nocase); +} + +/* Fuzz stringmatchlen() trying to crash it with bad input. */ +int stringmatchlen_fuzz_test(void) { + char str[32]; + char pat[32]; + int cycles = 10000000; + int total_matches = 0; + while(cycles--) { + int strlen = rand() % sizeof(str); + int patlen = rand() % sizeof(pat); + for (int j = 0; j < strlen; j++) str[j] = rand() % 128; + for (int j = 0; j < patlen; j++) pat[j] = rand() % 128; + total_matches += stringmatchlen(pat, patlen, str, strlen, 0); + } + return total_matches; +} + + +/* Convert a string representing an amount of memory into the number of + * bytes, so for instance memtoull("1Gb") will return 1073741824 that is + * (1024*1024*1024). + * + * On parsing error, if *err is not NULL, it's set to 1, otherwise it's + * set to 0. On error the function return value is 0, regardless of the + * fact 'err' is NULL or not. */ +unsigned long long memtoull(const char *p, int *err) { + const char *u; + char buf[128]; + long mul; /* unit multiplier */ + unsigned long long val; + unsigned int digits; + + if (err) *err = 0; + + /* Search the first non digit character. */ + u = p; + if (*u == '-') { + if (err) *err = 1; + return 0; + } + while(*u && isdigit(*u)) u++; + if (*u == '\0' || !strcasecmp(u,"b")) { + mul = 1; + } else if (!strcasecmp(u,"k")) { + mul = 1000; + } else if (!strcasecmp(u,"kb")) { + mul = 1024; + } else if (!strcasecmp(u,"m")) { + mul = 1000*1000; + } else if (!strcasecmp(u,"mb")) { + mul = 1024*1024; + } else if (!strcasecmp(u,"g")) { + mul = 1000L*1000*1000; + } else if (!strcasecmp(u,"gb")) { + mul = 1024L*1024*1024; + } else { + if (err) *err = 1; + return 0; + } + + /* Copy the digits into a buffer, we'll use strtoll() to convert + * the digit (without the unit) into a number. */ + digits = u-p; + if (digits >= sizeof(buf)) { + if (err) *err = 1; + return 0; + } + memcpy(buf,p,digits); + buf[digits] = '\0'; + + char *endptr; + errno = 0; + val = strtoull(buf,&endptr,10); + if ((val == 0 && errno == EINVAL) || *endptr != '\0') { + if (err) *err = 1; + return 0; + } + return val*mul; +} + +/* Search a memory buffer for any set of bytes, like strpbrk(). + * Returns pointer to first found char or NULL. + */ +const char *mempbrk(const char *s, size_t len, const char *chars, size_t charslen) { + for (size_t j = 0; j < len; j++) { + for (size_t n = 0; n < charslen; n++) + if (s[j] == chars[n]) return &s[j]; + } + + return NULL; +} + +/* Modify the buffer replacing all occurrences of chars from the 'from' + * set with the corresponding char in the 'to' set. Always returns s. + */ +char *memmapchars(char *s, size_t len, const char *from, const char *to, size_t setlen) { + for (size_t j = 0; j < len; j++) { + for (size_t i = 0; i < setlen; i++) { + if (s[j] == from[i]) { + s[j] = to[i]; + break; + } + } + } + return s; +} + +/* Return the number of digits of 'v' when converted to string in radix 10. + * See ll2string() for more information. */ +uint32_t digits10(uint64_t v) { + if (v < 10) return 1; + if (v < 100) return 2; + if (v < 1000) return 3; + if (v < 1000000000000UL) { + if (v < 100000000UL) { + if (v < 1000000) { + if (v < 10000) return 4; + return 5 + (v >= 100000); + } + return 7 + (v >= 10000000UL); + } + if (v < 10000000000UL) { + return 9 + (v >= 1000000000UL); + } + return 11 + (v >= 100000000000UL); + } + return 12 + digits10(v / 1000000000000UL); +} + +/* Like digits10() but for signed values. */ +uint32_t sdigits10(int64_t v) { + if (v < 0) { + /* Abs value of LLONG_MIN requires special handling. */ + uint64_t uv = (v != LLONG_MIN) ? + (uint64_t)-v : ((uint64_t) LLONG_MAX)+1; + return digits10(uv)+1; /* +1 for the minus. */ + } else { + return digits10(v); + } +} + +/* Convert a long long into a string. Returns the number of + * characters needed to represent the number. + * If the buffer is not big enough to store the string, 0 is returned. */ +int ll2string(char *dst, size_t dstlen, long long svalue) { + unsigned long long value; + int negative = 0; + + /* The ull2string function with 64bit unsigned integers for simplicity, so + * we convert the number here and remember if it is negative. */ + if (svalue < 0) { + if (svalue != LLONG_MIN) { + value = -svalue; + } else { + value = ((unsigned long long) LLONG_MAX)+1; + } + if (dstlen < 2) + goto err; + negative = 1; + dst[0] = '-'; + dst++; + dstlen--; + } else { + value = svalue; + } + + /* Converts the unsigned long long value to string*/ + int length = ull2string(dst, dstlen, value); + if (length == 0) return 0; + return length + negative; + +err: + /* force add Null termination */ + if (dstlen > 0) + dst[0] = '\0'; + return 0; +} + +/* Convert a unsigned long long into a string. Returns the number of + * characters needed to represent the number. + * If the buffer is not big enough to store the string, 0 is returned. + * + * Based on the following article (that apparently does not provide a + * novel approach but only publicizes an already used technique): + * + * https://www.facebook.com/notes/facebook-engineering/three-optimization-tips-for-c/10151361643253920 */ +int ull2string(char *dst, size_t dstlen, unsigned long long value) { + static const char digits[201] = + "0001020304050607080910111213141516171819" + "2021222324252627282930313233343536373839" + "4041424344454647484950515253545556575859" + "6061626364656667686970717273747576777879" + "8081828384858687888990919293949596979899"; + + /* Check length. */ + uint32_t length = digits10(value); + if (length >= dstlen) goto err;; + + /* Null term. */ + uint32_t next = length - 1; + dst[next + 1] = '\0'; + while (value >= 100) { + int const i = (value % 100) * 2; + value /= 100; + dst[next] = digits[i + 1]; + dst[next - 1] = digits[i]; + next -= 2; + } + + /* Handle last 1-2 digits. */ + if (value < 10) { + dst[next] = '0' + (uint32_t) value; + } else { + int i = (uint32_t) value * 2; + dst[next] = digits[i + 1]; + dst[next - 1] = digits[i]; + } + return length; +err: + /* force add Null termination */ + if (dstlen > 0) + dst[0] = '\0'; + return 0; +} + +/* Convert a string into a long long. Returns 1 if the string could be parsed + * into a (non-overflowing) long long, 0 otherwise. The value will be set to + * the parsed value when appropriate. + * + * Note that this function demands that the string strictly represents + * a long long: no spaces or other characters before or after the string + * representing the number are accepted, nor zeroes at the start if not + * for the string "0" representing the zero number. + * + * Because of its strictness, it is safe to use this function to check if + * you can convert a string into a long long, and obtain back the string + * from the number without any loss in the string representation. */ +int string2ll(const char *s, size_t slen, long long *value) { + const char *p = s; + size_t plen = 0; + int negative = 0; + unsigned long long v; + + /* A string of zero length or excessive length is not a valid number. */ + if (plen == slen || slen >= LONG_STR_SIZE) + return 0; + + /* Special case: first and only digit is 0. */ + if (slen == 1 && p[0] == '0') { + if (value != NULL) *value = 0; + return 1; + } + + /* Handle negative numbers: just set a flag and continue like if it + * was a positive number. Later convert into negative. */ + if (p[0] == '-') { + negative = 1; + p++; plen++; + + /* Abort on only a negative sign. */ + if (plen == slen) + return 0; + } + + /* First digit should be 1-9, otherwise the string should just be 0. */ + if (p[0] >= '1' && p[0] <= '9') { + v = p[0]-'0'; + p++; plen++; + } else { + return 0; + } + + /* Parse all the other digits, checking for overflow at every step. */ + while (plen < slen && p[0] >= '0' && p[0] <= '9') { + if (v > (ULLONG_MAX / 10)) /* Overflow. */ + return 0; + v *= 10; + + if (v > (ULLONG_MAX - (p[0]-'0'))) /* Overflow. */ + return 0; + v += p[0]-'0'; + + p++; plen++; + } + + /* Return if not all bytes were used. */ + if (plen < slen) + return 0; + + /* Convert to negative if needed, and do the final overflow check when + * converting from unsigned long long to long long. */ + if (negative) { + if (v > ((unsigned long long)(-(LLONG_MIN+1))+1)) /* Overflow. */ + return 0; + if (value != NULL) *value = -v; + } else { + if (v > LLONG_MAX) /* Overflow. */ + return 0; + if (value != NULL) *value = v; + } + return 1; +} + +/* Helper function to convert a string to an unsigned long long value. + * The function attempts to use the faster string2ll() function inside + * Redis: if it fails, strtoull() is used instead. The function returns + * 1 if the conversion happened successfully or 0 if the number is + * invalid or out of range. */ +int string2ull(const char *s, unsigned long long *value) { + long long ll; + if (string2ll(s,strlen(s),&ll)) { + if (ll < 0) return 0; /* Negative values are out of range. */ + *value = ll; + return 1; + } + errno = 0; + char *endptr = NULL; + *value = strtoull(s,&endptr,10); + if (errno == EINVAL || errno == ERANGE || !(*s != '\0' && *endptr == '\0')) + return 0; /* strtoull() failed. */ + return 1; /* Conversion done! */ +} + +/* Convert a string into a long. Returns 1 if the string could be parsed into a + * (non-overflowing) long, 0 otherwise. The value will be set to the parsed + * value when appropriate. */ +int string2l(const char *s, size_t slen, long *lval) { + long long llval; + + if (!string2ll(s,slen,&llval)) + return 0; + + if (llval < LONG_MIN || llval > LONG_MAX) + return 0; + + *lval = (long)llval; + return 1; +} + +/* Convert a string into a double. Returns 1 if the string could be parsed + * into a (non-overflowing) double, 0 otherwise. The value will be set to + * the parsed value when appropriate. + * + * Note that this function demands that the string strictly represents + * a double: no spaces or other characters before or after the string + * representing the number are accepted. */ +int string2ld(const char *s, size_t slen, long double *dp) { + char buf[MAX_LONG_DOUBLE_CHARS]; + long double value; + char *eptr; + + if (slen == 0 || slen >= sizeof(buf)) return 0; + memcpy(buf,s,slen); + buf[slen] = '\0'; + + errno = 0; + value = strtold(buf, &eptr); + if (isspace(buf[0]) || eptr[0] != '\0' || + (size_t)(eptr-buf) != slen || + (errno == ERANGE && + (value == HUGE_VAL || value == -HUGE_VAL || fpclassify(value) == FP_ZERO)) || + errno == EINVAL || + isnan(value)) + return 0; + + if (dp) *dp = value; + return 1; +} + +/* Convert a string into a double. Returns 1 if the string could be parsed + * into a (non-overflowing) double, 0 otherwise. The value will be set to + * the parsed value when appropriate. + * + * Note that this function demands that the string strictly represents + * a double: no spaces or other characters before or after the string + * representing the number are accepted. */ +int string2d(const char *s, size_t slen, double *dp) { + errno = 0; + char *eptr; + *dp = strtod(s, &eptr); + if (slen == 0 || + isspace(((const char*)s)[0]) || + (size_t)(eptr-(char*)s) != slen || + (errno == ERANGE && + (*dp == HUGE_VAL || *dp == -HUGE_VAL || fpclassify(*dp) == FP_ZERO)) || + isnan(*dp)) + return 0; + return 1; +} + +/* Returns 1 if the double value can safely be represented in long long without + * precision loss, in which case the corresponding long long is stored in the out variable. */ +int double2ll(double d, long long *out) { +#if (DBL_MANT_DIG >= 52) && (DBL_MANT_DIG <= 63) && (LLONG_MAX == 0x7fffffffffffffffLL) + /* Check if the float is in a safe range to be casted into a + * long long. We are assuming that long long is 64 bit here. + * Also we are assuming that there are no implementations around where + * double has precision < 52 bit. + * + * Under this assumptions we test if a double is inside a range + * where casting to long long is safe. Then using two castings we + * make sure the decimal part is zero. If all this is true we can use + * integer without precision loss. + * + * Note that numbers above 2^52 and below 2^63 use all the fraction bits as real part, + * and the exponent bits are positive, which means the "decimal" part must be 0. + * i.e. all double values in that range are representable as a long without precision loss, + * but not all long values in that range can be represented as a double. + * we only care about the first part here. */ + if (d < (double)(-LLONG_MAX/2) || d > (double)(LLONG_MAX/2)) + return 0; + long long ll = d; + if (ll == d) { + *out = ll; + return 1; + } +#endif + return 0; +} + +/* Convert a double to a string representation. Returns the number of bytes + * required. The representation should always be parsable by strtod(3). + * This function does not support human-friendly formatting like ld2string + * does. It is intended mainly to be used inside t_zset.c when writing scores + * into a listpack representing a sorted set. */ +int d2string(char *buf, size_t len, double value) { + if (isnan(value)) { + /* Libc in some systems will format nan in a different way, + * like nan, -nan, NAN, nan(char-sequence). + * So we normalize it and create a single nan form in an explicit way. */ + len = snprintf(buf,len,"nan"); + } else if (isinf(value)) { + /* Libc in odd systems (Hi Solaris!) will format infinite in a + * different way, so better to handle it in an explicit way. */ + if (value < 0) + len = snprintf(buf,len,"-inf"); + else + len = snprintf(buf,len,"inf"); + } else if (value == 0) { + /* See: http://en.wikipedia.org/wiki/Signed_zero, "Comparisons". */ + if (1.0/value < 0) + len = snprintf(buf,len,"-0"); + else + len = snprintf(buf,len,"0"); + } else { + long long lvalue; + /* Integer printing function is much faster, check if we can safely use it. */ + if (double2ll(value, &lvalue)) + len = ll2string(buf,len,lvalue); + else { + len = fpconv_dtoa(value, buf); + buf[len] = '\0'; + } + } + + return len; +} + +/* Convert a double into a string with 'fractional_digits' digits after the dot precision. + * This is an optimized version of snprintf "%.<fractional_digits>f". + * We convert the double to long and multiply it by 10 ^ <fractional_digits> to shift + * the decimal places. + * Note that multiply it of input value by 10 ^ <fractional_digits> can overflow but on the scenario + * that we currently use within redis this that is not possible. + * After we get the long representation we use the logic from ull2string function on this file + * which is based on the following article: + * https://www.facebook.com/notes/facebook-engineering/three-optimization-tips-for-c/10151361643253920 + * + * Input values: + * char: the buffer to store the string representation + * dstlen: the buffer length + * dvalue: the input double + * fractional_digits: the number of fractional digits after the dot precision. between 1 and 17 + * + * Return values: + * Returns the number of characters needed to represent the number. + * If the buffer is not big enough to store the string, 0 is returned. + */ +int fixedpoint_d2string(char *dst, size_t dstlen, double dvalue, int fractional_digits) { + if (fractional_digits < 1 || fractional_digits > 17) + goto err; + /* min size of 2 ( due to 0. ) + n fractional_digitits + \0 */ + if ((int)dstlen < (fractional_digits+3)) + goto err; + if (dvalue == 0) { + dst[0] = '0'; + dst[1] = '.'; + memset(dst + 2, '0', fractional_digits); + dst[fractional_digits+2] = '\0'; + return fractional_digits + 2; + } + /* scale and round */ + static double powers_of_ten[] = {1.0, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, + 10000000.0, 100000000.0, 1000000000.0, 10000000000.0, 100000000000.0, 1000000000000.0, + 10000000000000.0, 100000000000000.0, 1000000000000000.0, 10000000000000000.0, + 100000000000000000.0 }; + long long svalue = llrint(dvalue * powers_of_ten[fractional_digits]); + unsigned long long value; + /* write sign */ + int negative = 0; + if (svalue < 0) { + if (svalue != LLONG_MIN) { + value = -svalue; + } else { + value = ((unsigned long long) LLONG_MAX)+1; + } + if (dstlen < 2) + goto err; + negative = 1; + dst[0] = '-'; + dst++; + dstlen--; + } else { + value = svalue; + } + + static const char digitsd[201] = + "0001020304050607080910111213141516171819" + "2021222324252627282930313233343536373839" + "4041424344454647484950515253545556575859" + "6061626364656667686970717273747576777879" + "8081828384858687888990919293949596979899"; + + /* Check length. */ + uint32_t ndigits = digits10(value); + if (ndigits >= dstlen) goto err; + int integer_digits = ndigits - fractional_digits; + /* Fractional only check to avoid representing 0.7750 as .7750. + * This means we need to increment the length and store 0 as the first character. + */ + if (integer_digits < 1) { + dst[0] = '0'; + integer_digits = 1; + } + dst[integer_digits] = '.'; + int size = integer_digits + 1 + fractional_digits; + /* fill with 0 from fractional digits until size */ + memset(dst + integer_digits + 1, '0', fractional_digits); + int next = size - 1; + while (value >= 100) { + int const i = (value % 100) * 2; + value /= 100; + dst[next] = digitsd[i + 1]; + dst[next - 1] = digitsd[i]; + next -= 2; + /* dot position */ + if (next == integer_digits) { + next--; + } + } + + /* Handle last 1-2 digits. */ + if (value < 10) { + dst[next] = '0' + (uint32_t) value; + } else { + int i = (uint32_t) value * 2; + dst[next] = digitsd[i + 1]; + dst[next - 1] = digitsd[i]; + } + /* Null term. */ + dst[size] = '\0'; + return size + negative; +err: + /* force add Null termination */ + if (dstlen > 0) + dst[0] = '\0'; + return 0; +} + +/* Trims off trailing zeros from a string representing a double. */ +int trimDoubleString(char *buf, size_t len) { + if (strchr(buf,'.') != NULL) { + char *p = buf+len-1; + while(*p == '0') { + p--; + len--; + } + if (*p == '.') len--; + } + buf[len] = '\0'; + return len; +} + +/* Create a string object from a long double. + * If mode is humanfriendly it does not use exponential format and trims trailing + * zeroes at the end (may result in loss of precision). + * If mode is default exp format is used and the output of snprintf() + * is not modified (may result in loss of precision). + * If mode is hex hexadecimal format is used (no loss of precision) + * + * The function returns the length of the string or zero if there was not + * enough buffer room to store it. */ +int ld2string(char *buf, size_t len, long double value, ld2string_mode mode) { + size_t l = 0; + + if (isinf(value)) { + /* Libc in odd systems (Hi Solaris!) will format infinite in a + * different way, so better to handle it in an explicit way. */ + if (len < 5) goto err; /* No room. 5 is "-inf\0" */ + if (value > 0) { + memcpy(buf,"inf",3); + l = 3; + } else { + memcpy(buf,"-inf",4); + l = 4; + } + } else if (isnan(value)) { + /* Libc in some systems will format nan in a different way, + * like nan, -nan, NAN, nan(char-sequence). + * So we normalize it and create a single nan form in an explicit way. */ + if (len < 4) goto err; /* No room. 4 is "nan\0" */ + memcpy(buf, "nan", 3); + l = 3; + } else { + switch (mode) { + case LD_STR_AUTO: + l = snprintf(buf,len,"%.17Lg",value); + if (l+1 > len) goto err;; /* No room. */ + break; + case LD_STR_HEX: + l = snprintf(buf,len,"%La",value); + if (l+1 > len) goto err; /* No room. */ + break; + case LD_STR_HUMAN: + /* We use 17 digits precision since with 128 bit floats that precision + * after rounding is able to represent most small decimal numbers in a + * way that is "non surprising" for the user (that is, most small + * decimal numbers will be represented in a way that when converted + * back into a string are exactly the same as what the user typed.) */ + l = snprintf(buf,len,"%.17Lf",value); + if (l+1 > len) goto err; /* No room. */ + /* Now remove trailing zeroes after the '.' */ + if (strchr(buf,'.') != NULL) { + char *p = buf+l-1; + while(*p == '0') { + p--; + l--; + } + if (*p == '.') l--; + } + if (l == 2 && buf[0] == '-' && buf[1] == '0') { + buf[0] = '0'; + l = 1; + } + break; + default: goto err; /* Invalid mode. */ + } + } + buf[l] = '\0'; + return l; +err: + /* force add Null termination */ + if (len > 0) + buf[0] = '\0'; + return 0; +} + +/* Get random bytes, attempts to get an initial seed from /dev/urandom and + * the uses a one way hash function in counter mode to generate a random + * stream. However if /dev/urandom is not available, a weaker seed is used. + * + * This function is not thread safe, since the state is global. */ +void getRandomBytes(unsigned char *p, size_t len) { + /* Global state. */ + static int seed_initialized = 0; + static unsigned char seed[64]; /* 512 bit internal block size. */ + static uint64_t counter = 0; /* The counter we hash with the seed. */ + + if (!seed_initialized) { + /* Initialize a seed and use SHA1 in counter mode, where we hash + * the same seed with a progressive counter. For the goals of this + * function we just need non-colliding strings, there are no + * cryptographic security needs. */ + FILE *fp = fopen("/dev/urandom","r"); + if (fp == NULL || fread(seed,sizeof(seed),1,fp) != 1) { + /* Revert to a weaker seed, and in this case reseed again + * at every call.*/ + for (unsigned int j = 0; j < sizeof(seed); j++) { + struct timeval tv; + gettimeofday(&tv,NULL); + pid_t pid = getpid(); + seed[j] = tv.tv_sec ^ tv.tv_usec ^ pid ^ (long)fp; + } + } else { + seed_initialized = 1; + } + if (fp) fclose(fp); + } + + while(len) { + /* This implements SHA256-HMAC. */ + unsigned char digest[SHA256_BLOCK_SIZE]; + unsigned char kxor[64]; + unsigned int copylen = + len > SHA256_BLOCK_SIZE ? SHA256_BLOCK_SIZE : len; + + /* IKEY: key xored with 0x36. */ + memcpy(kxor,seed,sizeof(kxor)); + for (unsigned int i = 0; i < sizeof(kxor); i++) kxor[i] ^= 0x36; + + /* Obtain HASH(IKEY||MESSAGE). */ + SHA256_CTX ctx; + sha256_init(&ctx); + sha256_update(&ctx,kxor,sizeof(kxor)); + sha256_update(&ctx,(unsigned char*)&counter,sizeof(counter)); + sha256_final(&ctx,digest); + + /* OKEY: key xored with 0x5c. */ + memcpy(kxor,seed,sizeof(kxor)); + for (unsigned int i = 0; i < sizeof(kxor); i++) kxor[i] ^= 0x5C; + + /* Obtain HASH(OKEY || HASH(IKEY||MESSAGE)). */ + sha256_init(&ctx); + sha256_update(&ctx,kxor,sizeof(kxor)); + sha256_update(&ctx,digest,SHA256_BLOCK_SIZE); + sha256_final(&ctx,digest); + + /* Increment the counter for the next iteration. */ + counter++; + + memcpy(p,digest,copylen); + len -= copylen; + p += copylen; + } +} + +/* Generate the Redis "Run ID", a SHA1-sized random number that identifies a + * given execution of Redis, so that if you are talking with an instance + * having run_id == A, and you reconnect and it has run_id == B, you can be + * sure that it is either a different instance or it was restarted. */ +void getRandomHexChars(char *p, size_t len) { + char *charset = "0123456789abcdef"; + size_t j; + + getRandomBytes((unsigned char*)p,len); + for (j = 0; j < len; j++) p[j] = charset[p[j] & 0x0F]; +} + +/* Given the filename, return the absolute path as an SDS string, or NULL + * if it fails for some reason. Note that "filename" may be an absolute path + * already, this will be detected and handled correctly. + * + * The function does not try to normalize everything, but only the obvious + * case of one or more "../" appearing at the start of "filename" + * relative path. */ +sds getAbsolutePath(char *filename) { + char cwd[1024]; + sds abspath; + sds relpath = sdsnew(filename); + + relpath = sdstrim(relpath," \r\n\t"); + if (relpath[0] == '/') return relpath; /* Path is already absolute. */ + + /* If path is relative, join cwd and relative path. */ + if (getcwd(cwd,sizeof(cwd)) == NULL) { + sdsfree(relpath); + return NULL; + } + abspath = sdsnew(cwd); + if (sdslen(abspath) && abspath[sdslen(abspath)-1] != '/') + abspath = sdscat(abspath,"/"); + + /* At this point we have the current path always ending with "/", and + * the trimmed relative path. Try to normalize the obvious case of + * trailing ../ elements at the start of the path. + * + * For every "../" we find in the filename, we remove it and also remove + * the last element of the cwd, unless the current cwd is "/". */ + while (sdslen(relpath) >= 3 && + relpath[0] == '.' && relpath[1] == '.' && relpath[2] == '/') + { + sdsrange(relpath,3,-1); + if (sdslen(abspath) > 1) { + char *p = abspath + sdslen(abspath)-2; + int trimlen = 1; + + while(*p != '/') { + p--; + trimlen++; + } + sdsrange(abspath,0,-(trimlen+1)); + } + } + + /* Finally glue the two parts together. */ + abspath = sdscatsds(abspath,relpath); + sdsfree(relpath); + return abspath; +} + +/* + * Gets the proper timezone in a more portable fashion + * i.e timezone variables are linux specific. + */ +long getTimeZone(void) { +#if defined(__linux__) || defined(__sun) + return timezone; +#else + struct timeval tv; + struct timezone tz; + + gettimeofday(&tv, &tz); + + return tz.tz_minuteswest * 60L; +#endif +} + +/* Return true if the specified path is just a file basename without any + * relative or absolute path. This function just checks that no / or \ + * character exists inside the specified path, that's enough in the + * environments where Redis runs. */ +int pathIsBaseName(char *path) { + return strchr(path,'/') == NULL && strchr(path,'\\') == NULL; +} + +int fileExist(char *filename) { + struct stat statbuf; + return stat(filename, &statbuf) == 0 && S_ISREG(statbuf.st_mode); +} + +int dirExists(char *dname) { + struct stat statbuf; + return stat(dname, &statbuf) == 0 && S_ISDIR(statbuf.st_mode); +} + +int dirCreateIfMissing(char *dname) { + if (mkdir(dname, 0755) != 0) { + if (errno != EEXIST) { + return -1; + } else if (!dirExists(dname)) { + errno = ENOTDIR; + return -1; + } + } + return 0; +} + +int dirRemove(char *dname) { + DIR *dir; + struct stat stat_entry; + struct dirent *entry; + char full_path[PATH_MAX + 1]; + + if ((dir = opendir(dname)) == NULL) { + return -1; + } + + while ((entry = readdir(dir)) != NULL) { + if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) continue; + + snprintf(full_path, sizeof(full_path), "%s/%s", dname, entry->d_name); + + int fd = open(full_path, O_RDONLY|O_NONBLOCK); + if (fd == -1) { + closedir(dir); + return -1; + } + + if (fstat(fd, &stat_entry) == -1) { + close(fd); + closedir(dir); + return -1; + } + close(fd); + + if (S_ISDIR(stat_entry.st_mode) != 0) { + if (dirRemove(full_path) == -1) { + return -1; + } + continue; + } + + if (unlink(full_path) != 0) { + closedir(dir); + return -1; + } + } + + if (rmdir(dname) != 0) { + closedir(dir); + return -1; + } + + closedir(dir); + return 0; +} + +sds makePath(char *path, char *filename) { + return sdscatfmt(sdsempty(), "%s/%s", path, filename); +} + +/* Given the filename, sync the corresponding directory. + * + * Usually a portable and safe pattern to overwrite existing files would be like: + * 1. create a new temp file (on the same file system!) + * 2. write data to the temp file + * 3. fsync() the temp file + * 4. rename the temp file to the appropriate name + * 5. fsync() the containing directory */ +int fsyncFileDir(const char *filename) { +#ifdef _AIX + /* AIX is unable to fsync a directory */ + return 0; +#endif + char temp_filename[PATH_MAX + 1]; + char *dname; + int dir_fd; + + if (strlen(filename) > PATH_MAX) { + errno = ENAMETOOLONG; + return -1; + } + + /* In the glibc implementation dirname may modify their argument. */ + memcpy(temp_filename, filename, strlen(filename) + 1); + dname = dirname(temp_filename); + + dir_fd = open(dname, O_RDONLY); + if (dir_fd == -1) { + /* Some OSs don't allow us to open directories at all, just + * ignore the error in that case */ + if (errno == EISDIR) { + return 0; + } + return -1; + } + /* Some OSs don't allow us to fsync directories at all, so we can ignore + * those errors. */ + if (redis_fsync(dir_fd) == -1 && !(errno == EBADF || errno == EINVAL)) { + int save_errno = errno; + close(dir_fd); + errno = save_errno; + return -1; + } + + close(dir_fd); + return 0; +} + + /* free OS pages backed by file */ +int reclaimFilePageCache(int fd, size_t offset, size_t length) { +#ifdef HAVE_FADVISE + int ret = posix_fadvise(fd, offset, length, POSIX_FADV_DONTNEED); + if (ret) return -1; + return 0; +#else + UNUSED(fd); + UNUSED(offset); + UNUSED(length); + return 0; +#endif +} + +#ifdef REDIS_TEST +#include <assert.h> +#include <sys/mman.h> +#include "testhelp.h" + +static void test_string2ll(void) { + char buf[32]; + long long v; + + /* May not start with +. */ + redis_strlcpy(buf,"+1",sizeof(buf)); + assert(string2ll(buf,strlen(buf),&v) == 0); + + /* Leading space. */ + redis_strlcpy(buf," 1",sizeof(buf)); + assert(string2ll(buf,strlen(buf),&v) == 0); + + /* Trailing space. */ + redis_strlcpy(buf,"1 ",sizeof(buf)); + assert(string2ll(buf,strlen(buf),&v) == 0); + + /* May not start with 0. */ + redis_strlcpy(buf,"01",sizeof(buf)); + assert(string2ll(buf,strlen(buf),&v) == 0); + + redis_strlcpy(buf,"-1",sizeof(buf)); + assert(string2ll(buf,strlen(buf),&v) == 1); + assert(v == -1); + + redis_strlcpy(buf,"0",sizeof(buf)); + assert(string2ll(buf,strlen(buf),&v) == 1); + assert(v == 0); + + redis_strlcpy(buf,"1",sizeof(buf)); + assert(string2ll(buf,strlen(buf),&v) == 1); + assert(v == 1); + + redis_strlcpy(buf,"99",sizeof(buf)); + assert(string2ll(buf,strlen(buf),&v) == 1); + assert(v == 99); + + redis_strlcpy(buf,"-99",sizeof(buf)); + assert(string2ll(buf,strlen(buf),&v) == 1); + assert(v == -99); + + redis_strlcpy(buf,"-9223372036854775808",sizeof(buf)); + assert(string2ll(buf,strlen(buf),&v) == 1); + assert(v == LLONG_MIN); + + redis_strlcpy(buf,"-9223372036854775809",sizeof(buf)); /* overflow */ + assert(string2ll(buf,strlen(buf),&v) == 0); + + redis_strlcpy(buf,"9223372036854775807",sizeof(buf)); + assert(string2ll(buf,strlen(buf),&v) == 1); + assert(v == LLONG_MAX); + + redis_strlcpy(buf,"9223372036854775808",sizeof(buf)); /* overflow */ + assert(string2ll(buf,strlen(buf),&v) == 0); +} + +static void test_string2l(void) { + char buf[32]; + long v; + + /* May not start with +. */ + redis_strlcpy(buf,"+1",sizeof(buf)); + assert(string2l(buf,strlen(buf),&v) == 0); + + /* May not start with 0. */ + redis_strlcpy(buf,"01",sizeof(buf)); + assert(string2l(buf,strlen(buf),&v) == 0); + + redis_strlcpy(buf,"-1",sizeof(buf)); + assert(string2l(buf,strlen(buf),&v) == 1); + assert(v == -1); + + redis_strlcpy(buf,"0",sizeof(buf)); + assert(string2l(buf,strlen(buf),&v) == 1); + assert(v == 0); + + redis_strlcpy(buf,"1",sizeof(buf)); + assert(string2l(buf,strlen(buf),&v) == 1); + assert(v == 1); + + redis_strlcpy(buf,"99",sizeof(buf)); + assert(string2l(buf,strlen(buf),&v) == 1); + assert(v == 99); + + redis_strlcpy(buf,"-99",sizeof(buf)); + assert(string2l(buf,strlen(buf),&v) == 1); + assert(v == -99); + +#if LONG_MAX != LLONG_MAX + redis_strlcpy(buf,"-2147483648",sizeof(buf)); + assert(string2l(buf,strlen(buf),&v) == 1); + assert(v == LONG_MIN); + + redis_strlcpy(buf,"-2147483649",sizeof(buf)); /* overflow */ + assert(string2l(buf,strlen(buf),&v) == 0); + + redis_strlcpy(buf,"2147483647",sizeof(buf)); + assert(string2l(buf,strlen(buf),&v) == 1); + assert(v == LONG_MAX); + + redis_strlcpy(buf,"2147483648",sizeof(buf)); /* overflow */ + assert(string2l(buf,strlen(buf),&v) == 0); +#endif +} + +static void test_ll2string(void) { + char buf[32]; + long long v; + int sz; + + v = 0; + sz = ll2string(buf, sizeof buf, v); + assert(sz == 1); + assert(!strcmp(buf, "0")); + + v = -1; + sz = ll2string(buf, sizeof buf, v); + assert(sz == 2); + assert(!strcmp(buf, "-1")); + + v = 99; + sz = ll2string(buf, sizeof buf, v); + assert(sz == 2); + assert(!strcmp(buf, "99")); + + v = -99; + sz = ll2string(buf, sizeof buf, v); + assert(sz == 3); + assert(!strcmp(buf, "-99")); + + v = -2147483648; + sz = ll2string(buf, sizeof buf, v); + assert(sz == 11); + assert(!strcmp(buf, "-2147483648")); + + v = LLONG_MIN; + sz = ll2string(buf, sizeof buf, v); + assert(sz == 20); + assert(!strcmp(buf, "-9223372036854775808")); + + v = LLONG_MAX; + sz = ll2string(buf, sizeof buf, v); + assert(sz == 19); + assert(!strcmp(buf, "9223372036854775807")); +} + +static void test_ld2string(void) { + char buf[32]; + long double v; + int sz; + + v = 0.0 / 0.0; + sz = ld2string(buf, sizeof(buf), v, LD_STR_AUTO); + assert(sz == 3); + assert(!strcmp(buf, "nan")); +} + +static void test_fixedpoint_d2string(void) { + char buf[32]; + double v; + int sz; + v = 0.0; + sz = fixedpoint_d2string(buf, sizeof buf, v, 4); + assert(sz == 6); + assert(!strcmp(buf, "0.0000")); + sz = fixedpoint_d2string(buf, sizeof buf, v, 1); + assert(sz == 3); + assert(!strcmp(buf, "0.0")); + /* set junk in buffer */ + memset(buf,'A',32); + v = 0.0001; + sz = fixedpoint_d2string(buf, sizeof buf, v, 4); + assert(sz == 6); + assert(buf[sz] == '\0'); + assert(!strcmp(buf, "0.0001")); + /* set junk in buffer */ + memset(buf,'A',32); + v = 6.0642951598391699e-05; + sz = fixedpoint_d2string(buf, sizeof buf, v, 4); + assert(sz == 6); + assert(buf[sz] == '\0'); + assert(!strcmp(buf, "0.0001")); + v = 0.01; + sz = fixedpoint_d2string(buf, sizeof buf, v, 4); + assert(sz == 6); + assert(!strcmp(buf, "0.0100")); + sz = fixedpoint_d2string(buf, sizeof buf, v, 1); + assert(sz == 3); + assert(!strcmp(buf, "0.0")); + v = -0.01; + sz = fixedpoint_d2string(buf, sizeof buf, v, 4); + assert(sz == 7); + assert(!strcmp(buf, "-0.0100")); + v = -0.1; + sz = fixedpoint_d2string(buf, sizeof buf, v, 1); + assert(sz == 4); + assert(!strcmp(buf, "-0.1")); + v = 0.1; + sz = fixedpoint_d2string(buf, sizeof buf, v, 1); + assert(sz == 3); + assert(!strcmp(buf, "0.1")); + v = 0.01; + sz = fixedpoint_d2string(buf, sizeof buf, v, 17); + assert(sz == 19); + assert(!strcmp(buf, "0.01000000000000000")); + v = 10.01; + sz = fixedpoint_d2string(buf, sizeof buf, v, 4); + assert(sz == 7); + assert(!strcmp(buf, "10.0100")); + /* negative tests */ + sz = fixedpoint_d2string(buf, sizeof buf, v, 18); + assert(sz == 0); + sz = fixedpoint_d2string(buf, sizeof buf, v, 0); + assert(sz == 0); + sz = fixedpoint_d2string(buf, 1, v, 1); + assert(sz == 0); +} + +#if defined(__linux__) +/* Since fadvise and mincore is only supported in specific platforms like + * Linux, we only verify the fadvise mechanism works in Linux */ +static int cache_exist(int fd) { + unsigned char flag; + void *m = mmap(NULL, 4096, PROT_READ, MAP_SHARED, fd, 0); + assert(m); + assert(mincore(m, 4096, &flag) == 0); + munmap(m, 4096); + /* the least significant bit of the byte will be set if the corresponding + * page is currently resident in memory */ + return flag&1; +} + +static void test_reclaimFilePageCache(void) { + char *tmpfile = "/tmp/redis-reclaim-cache-test"; + int fd = open(tmpfile, O_RDWR|O_CREAT, 0644); + assert(fd >= 0); + + /* test write file */ + char buf[4] = "foo"; + assert(write(fd, buf, sizeof(buf)) > 0); + assert(cache_exist(fd)); + assert(redis_fsync(fd) == 0); + assert(reclaimFilePageCache(fd, 0, 0) == 0); + assert(!cache_exist(fd)); + + /* test read file */ + assert(pread(fd, buf, sizeof(buf), 0) > 0); + assert(cache_exist(fd)); + assert(reclaimFilePageCache(fd, 0, 0) == 0); + assert(!cache_exist(fd)); + + unlink(tmpfile); + printf("reclaimFilePageCach test is ok\n"); +} +#endif + +int utilTest(int argc, char **argv, int flags) { + UNUSED(argc); + UNUSED(argv); + UNUSED(flags); + + test_string2ll(); + test_string2l(); + test_ll2string(); + test_ld2string(); + test_fixedpoint_d2string(); +#if defined(__linux__) + if (!(flags & REDIS_TEST_VALGRIND)) { + test_reclaimFilePageCache(); + } +#endif + printf("Done testing util\n"); + return 0; +} +#endif + + |