config memory limits: handle values larger than (signed) LLONG_MAX (#9313)

This aims to solve the issue in CONFIG SET maxmemory can only set maxmemory to up
to 9223372036854775807 (2^63) while the maxmemory should be ULLONG.
Added a memtoull function to convert a string representing an amount of memory
into the number of bytes (similar to memtoll but for ull). Also added ull2string to
convert a ULLong to string (Similar to ll2string).
This commit is contained in:
Wen Hui 2021-08-23 14:00:40 -04:00 committed by GitHub
parent 74590f8345
commit 641780a9c6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 78 additions and 54 deletions

View File

@ -559,8 +559,8 @@ void loadServerConfigFromString(char *config) {
"an invalid one, or 'master' which has no buffer limits.";
goto loaderr;
}
hard = memtoll(argv[2],NULL);
soft = memtoll(argv[3],NULL);
hard = memtoull(argv[2],NULL);
soft = memtoull(argv[3],NULL);
soft_seconds = atoi(argv[4]);
if (soft_seconds < 0) {
err = "Negative number of seconds in soft limit is invalid";
@ -744,8 +744,8 @@ void loadServerConfig(char *filename, char config_from_stdin, char *options) {
#define config_set_memory_field(_name,_var) \
} else if (!strcasecmp(c->argv[2]->ptr,_name)) { \
ll = memtoll(o->ptr,&err); \
if (err || ll < 0) goto badfmt; \
ll = memtoull(o->ptr,&err); \
if (err) goto badfmt; \
_var = ll;
#define config_set_special_field(_name) \
@ -855,22 +855,26 @@ void configSetCommand(client *c) {
* whole configuration string or accept it all, even if a single
* error in a single client class is present. */
for (j = 0; j < vlen; j++) {
long val;
if ((j % 4) == 0) {
int class = getClientTypeByName(v[j]);
if (class == -1 || class == CLIENT_TYPE_MASTER) {
sdsfreesplitres(v,vlen);
goto badfmt;
}
if (class == -1 || class == CLIENT_TYPE_MASTER)
break;
} else if ((j % 4) == 3) {
char *endptr;
long l = strtoll(v[j],&endptr,10);
if (l < 0 || *endptr != '\0')
break;
} else {
val = memtoll(v[j], &err);
if (err || val < 0) {
sdsfreesplitres(v,vlen);
goto badfmt;
}
memtoull(v[j], &err);
if (err)
break;
}
}
if (j < vlen) {
sdsfreesplitres(v,vlen);
goto badfmt;
}
/* Finally set the new config */
for (j = 0; j < vlen; j += 4) {
int class;
@ -878,8 +882,8 @@ void configSetCommand(client *c) {
int soft_seconds;
class = getClientTypeByName(v[j]);
hard = memtoll(v[j+1],NULL);
soft = memtoll(v[j+2],NULL);
hard = memtoull(v[j+1],NULL);
soft = memtoull(v[j+2],NULL);
soft_seconds = strtoll(v[j+3],NULL,10);
server.client_obuf_limits[class].hard_limit_bytes = hard;
@ -2123,22 +2127,22 @@ static int numericBoundaryCheck(typeData data, long long ll, const char **err) {
return 1;
}
static int numericConfigSet(typeData data, sds value, int update, const char **err) {
long long ll, prev = 0;
if (data.numeric.is_memory) {
int memerr;
ll = memtoll(value, &memerr);
if (memerr || ll < 0) {
ll = memtoull(value, &memerr);
if (memerr) {
*err = "argument must be a memory value";
return 0;
}
} else {
if (!string2ll(value, sdslen(value),&ll)) {
if (!string2ll(value, sdslen(value), &ll)) {
*err = "argument couldn't be parsed into an integer" ;
return 0;
}
}
if (!numericBoundaryCheck(data, ll, err))
return 0;
@ -2157,12 +2161,21 @@ static int numericConfigSet(typeData data, sds value, int update, const char **e
static void numericConfigGet(client *c, typeData data) {
char buf[128];
long long value = 0;
if (data.numeric.is_memory) {
unsigned long long value = 0;
GET_NUMERIC_TYPE(value)
GET_NUMERIC_TYPE(value)
ull2string(buf, sizeof(buf), value);
addReplyBulkCString(c, buf);
} else{
long long value = 0;
GET_NUMERIC_TYPE(value)
ll2string(buf, sizeof(buf), value);
addReplyBulkCString(c, buf);
ll2string(buf, sizeof(buf), value);
addReplyBulkCString(c, buf);
}
}
static void numericConfigRewrite(typeData data, const char *name, struct rewriteConfigState *state) {

View File

@ -185,18 +185,19 @@ int stringmatchlen_fuzz_test(void) {
return total_matches;
}
/* Convert a string representing an amount of memory into the number of
* bytes, so for instance memtoll("1Gb") will return 1073741824 that is
* 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. */
long long memtoll(const char *p, int *err) {
unsigned long long memtoull(const char *p, int *err) {
const char *u;
char buf[128];
long mul; /* unit multiplier */
long long val;
unsigned long long val;
unsigned int digits;
if (err) *err = 0;
@ -236,7 +237,7 @@ long long memtoll(const char *p, int *err) {
char *endptr;
errno = 0;
val = strtoll(buf,&endptr,10);
val = strtoull(buf,&endptr,10);
if ((val == 0 && errno == EINVAL) || *endptr != '\0') {
if (err) *err = 1;
return 0;
@ -307,26 +308,12 @@ uint32_t sdigits10(int64_t 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.
*
* 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
*
* Modified in order to handle signed integers since the original code was
* designed for unsigned integers. */
* If the buffer is not big enough to store the string, 0 is returned. */
int ll2string(char *dst, size_t dstlen, long long svalue) {
static const char digits[201] =
"0001020304050607080910111213141516171819"
"2021222324252627282930313233343536373839"
"4041424344454647484950515253545556575859"
"6061626364656667686970717273747576777879"
"8081828384858687888990919293949596979899";
int negative;
unsigned long long value;
int negative = 0;
/* The main loop works with 64bit unsigned integers for simplicity, so
/* 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) {
@ -334,20 +321,45 @@ int ll2string(char *dst, size_t dstlen, long long svalue) {
} else {
value = ((unsigned long long) LLONG_MAX)+1;
}
if (dstlen < 2)
return 0;
negative = 1;
dst[0] = '-';
dst++;
dstlen--;
} else {
value = svalue;
negative = 0;
}
/* Converts the unsigned long long value to string*/
int length = ull2string(dst, dstlen, value);
if (length == 0) return 0;
return length + negative;
}
/* 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 const length = digits10(value)+negative;
uint32_t length = digits10(value);
if (length >= dstlen) return 0;
/* Null term. */
uint32_t next = length;
dst[next] = '\0';
next--;
uint32_t next = length - 1;
dst[next + 1] = '\0';
while (value >= 100) {
int const i = (value % 100) * 2;
value /= 100;
@ -365,8 +377,6 @@ int ll2string(char *dst, size_t dstlen, long long svalue) {
dst[next - 1] = digits[i];
}
/* Add sign. */
if (negative) dst[0] = '-';
return length;
}

View File

@ -48,12 +48,13 @@ typedef enum {
int stringmatchlen(const char *p, int plen, const char *s, int slen, int nocase);
int stringmatch(const char *p, const char *s, int nocase);
int stringmatchlen_fuzz_test(void);
long long memtoll(const char *p, int *err);
unsigned long long memtoull(const char *p, int *err);
const char *mempbrk(const char *s, size_t len, const char *chars, size_t charslen);
char *memmapchars(char *s, size_t len, const char *from, const char *to, size_t setlen);
uint32_t digits10(uint64_t v);
uint32_t sdigits10(int64_t v);
int ll2string(char *s, size_t len, long long value);
int ull2string(char *s, size_t len, unsigned long long value);
int string2ll(const char *s, size_t slen, long long *value);
int string2ull(const char *s, unsigned long long *value);
int string2l(const char *s, size_t slen, long *value);