]> git.saurik.com Git - redis.git/blame - src/t_string.c
overflow detection in INCR family functions
[redis.git] / src / t_string.c
CommitLineData
3c1bf495 1#include <limits.h>
e2641e09 2#include "redis.h"
3
4/*-----------------------------------------------------------------------------
5 * String Commands
6 *----------------------------------------------------------------------------*/
7
9f9e1cea
PN
8static int checkStringLength(redisClient *c, long long size) {
9 if (size > 512*1024*1024) {
10 addReplyError(c,"string exceeds maximum allowed size (512MB)");
11 return REDIS_ERR;
12 }
13 return REDIS_OK;
14}
15
e2641e09 16void setGenericCommand(redisClient *c, int nx, robj *key, robj *val, robj *expire) {
17 int retval;
18 long seconds = 0; /* initialized to avoid an harmness warning */
19
20 if (expire) {
21 if (getLongFromObjectOrReply(c, expire, &seconds, NULL) != REDIS_OK)
22 return;
23 if (seconds <= 0) {
3ab20376 24 addReplyError(c,"invalid expire time in SETEX");
e2641e09 25 return;
26 }
27 }
28
e2641e09 29 retval = dbAdd(c->db,key,val);
30 if (retval == REDIS_ERR) {
31 if (!nx) {
32 dbReplace(c->db,key,val);
33 incrRefCount(val);
34 } else {
35 addReply(c,shared.czero);
36 return;
37 }
38 } else {
39 incrRefCount(val);
40 }
5b4bff9c 41 touchWatchedKey(c->db,key);
e2641e09 42 server.dirty++;
43 removeExpire(c->db,key);
44 if (expire) setExpire(c->db,key,time(NULL)+seconds);
45 addReply(c, nx ? shared.cone : shared.ok);
46}
47
48void setCommand(redisClient *c) {
75b41de8 49 c->argv[2] = tryObjectEncoding(c->argv[2]);
e2641e09 50 setGenericCommand(c,0,c->argv[1],c->argv[2],NULL);
51}
52
53void setnxCommand(redisClient *c) {
75b41de8 54 c->argv[2] = tryObjectEncoding(c->argv[2]);
e2641e09 55 setGenericCommand(c,1,c->argv[1],c->argv[2],NULL);
56}
57
58void setexCommand(redisClient *c) {
75b41de8 59 c->argv[3] = tryObjectEncoding(c->argv[3]);
e2641e09 60 setGenericCommand(c,0,c->argv[1],c->argv[3],c->argv[2]);
61}
62
63int getGenericCommand(redisClient *c) {
64 robj *o;
65
66 if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL)
67 return REDIS_OK;
68
69 if (o->type != REDIS_STRING) {
70 addReply(c,shared.wrongtypeerr);
71 return REDIS_ERR;
72 } else {
73 addReplyBulk(c,o);
74 return REDIS_OK;
75 }
76}
77
78void getCommand(redisClient *c) {
79 getGenericCommand(c);
80}
81
82void getsetCommand(redisClient *c) {
83 if (getGenericCommand(c) == REDIS_ERR) return;
75b41de8 84 c->argv[2] = tryObjectEncoding(c->argv[2]);
e2641e09 85 dbReplace(c->db,c->argv[1],c->argv[2]);
86 incrRefCount(c->argv[2]);
5b4bff9c 87 touchWatchedKey(c->db,c->argv[1]);
e2641e09 88 server.dirty++;
89 removeExpire(c->db,c->argv[1]);
3c1bf495
PN
90}
91
92static int getBitOffsetFromArgument(redisClient *c, robj *o, size_t *offset) {
93 long long loffset;
94 char *err = "bit offset is not an integer or out of range";
95
96 if (getLongLongFromObjectOrReply(c,o,&loffset,err) != REDIS_OK)
97 return REDIS_ERR;
98
648e9654 99 /* Limit offset to 512MB in bytes */
100 if ((loffset < 0) || ((unsigned long long)loffset >> 3) >= (512*1024*1024))
3c1bf495
PN
101 {
102 addReplyError(c,err);
103 return REDIS_ERR;
104 }
105
106 *offset = (size_t)loffset;
107 return REDIS_OK;
108}
109
110void setbitCommand(redisClient *c) {
111 robj *o;
eae33c1c 112 char *err = "bit is not an integer or out of range";
3c1bf495 113 size_t bitoffset;
30407e1f
PN
114 int byte, bit;
115 int byteval, bitval;
116 long on;
3c1bf495
PN
117
118 if (getBitOffsetFromArgument(c,c->argv[2],&bitoffset) != REDIS_OK)
119 return;
120
30407e1f 121 if (getLongFromObjectOrReply(c,c->argv[3],&on,err) != REDIS_OK)
eae33c1c
PN
122 return;
123
30407e1f
PN
124 /* Bits can only be set or cleared... */
125 if (on & ~1) {
eae33c1c 126 addReplyError(c,err);
3c1bf495
PN
127 return;
128 }
129
130 o = lookupKeyWrite(c->db,c->argv[1]);
131 if (o == NULL) {
eae33c1c 132 o = createObject(REDIS_STRING,sdsempty());
3c1bf495
PN
133 dbAdd(c->db,c->argv[1],o);
134 } else {
135 if (checkType(c,o,REDIS_STRING)) return;
136
137 /* Create a copy when the object is shared or encoded. */
138 if (o->refcount != 1 || o->encoding != REDIS_ENCODING_RAW) {
139 robj *decoded = getDecodedObject(o);
140 o = createStringObject(decoded->ptr, sdslen(decoded->ptr));
141 decrRefCount(decoded);
142 dbReplace(c->db,c->argv[1],o);
143 }
3c1bf495 144 }
eae33c1c 145
30407e1f 146 /* Grow sds value to the right length if necessary */
eae33c1c 147 byte = bitoffset >> 3;
cc209063 148 o->ptr = sdsgrowzero(o->ptr,byte+1);
eae33c1c 149
30407e1f
PN
150 /* Get current values */
151 byteval = ((char*)o->ptr)[byte];
152 bit = 7 - (bitoffset & 0x7);
153 bitval = byteval & (1 << bit);
154
155 /* Update byte with new bit value and return original value */
156 byteval &= ~(1 << bit);
157 byteval |= ((on & 0x1) << bit);
158 ((char*)o->ptr)[byte] = byteval;
3c1bf495
PN
159 touchWatchedKey(c->db,c->argv[1]);
160 server.dirty++;
30407e1f 161 addReply(c, bitval ? shared.cone : shared.czero);
3c1bf495
PN
162}
163
164void getbitCommand(redisClient *c) {
165 robj *o;
3c1bf495 166 char llbuf[32];
30407e1f
PN
167 size_t bitoffset;
168 size_t byte, bit;
169 size_t bitval = 0;
3c1bf495
PN
170
171 if (getBitOffsetFromArgument(c,c->argv[2],&bitoffset) != REDIS_OK)
172 return;
173
174 if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
175 checkType(c,o,REDIS_STRING)) return;
176
177 byte = bitoffset >> 3;
30407e1f 178 bit = 7 - (bitoffset & 0x7);
3c1bf495
PN
179 if (o->encoding != REDIS_ENCODING_RAW) {
180 if (byte < (size_t)ll2string(llbuf,sizeof(llbuf),(long)o->ptr))
30407e1f 181 bitval = llbuf[byte] & (1 << bit);
3c1bf495
PN
182 } else {
183 if (byte < sdslen(o->ptr))
30407e1f 184 bitval = ((char*)o->ptr)[byte] & (1 << bit);
3c1bf495 185 }
30407e1f
PN
186
187 addReply(c, bitval ? shared.cone : shared.czero);
e2641e09 188}
189
9f9e1cea
PN
190void setrangeCommand(redisClient *c) {
191 robj *o;
192 long offset;
193 sds value = c->argv[3]->ptr;
194
195 if (getLongFromObjectOrReply(c,c->argv[2],&offset,NULL) != REDIS_OK)
196 return;
197
8f8eeffe
PN
198 if (offset < 0) {
199 addReplyError(c,"offset is out of range");
200 return;
201 }
202
9f9e1cea
PN
203 o = lookupKeyWrite(c->db,c->argv[1]);
204 if (o == NULL) {
9f9e1cea
PN
205 /* Return 0 when setting nothing on a non-existing string */
206 if (sdslen(value) == 0) {
207 addReply(c,shared.czero);
208 return;
209 }
210
211 /* Return when the resulting string exceeds allowed size */
212 if (checkStringLength(c,offset+sdslen(value)) != REDIS_OK)
213 return;
214
215 o = createObject(REDIS_STRING,sdsempty());
216 dbAdd(c->db,c->argv[1],o);
217 } else {
ad1b4f4f 218 size_t olen;
9f9e1cea
PN
219
220 /* Key exists, check type */
221 if (checkType(c,o,REDIS_STRING))
222 return;
223
9f9e1cea 224 /* Return existing string length when setting nothing */
ad1b4f4f 225 olen = stringObjectLen(o);
9f9e1cea
PN
226 if (sdslen(value) == 0) {
227 addReplyLongLong(c,olen);
228 return;
229 }
230
9f9e1cea
PN
231 /* Return when the resulting string exceeds allowed size */
232 if (checkStringLength(c,offset+sdslen(value)) != REDIS_OK)
233 return;
234
235 /* Create a copy when the object is shared or encoded. */
236 if (o->refcount != 1 || o->encoding != REDIS_ENCODING_RAW) {
237 robj *decoded = getDecodedObject(o);
238 o = createStringObject(decoded->ptr, sdslen(decoded->ptr));
239 decrRefCount(decoded);
240 dbReplace(c->db,c->argv[1],o);
241 }
242 }
243
244 if (sdslen(value) > 0) {
245 o->ptr = sdsgrowzero(o->ptr,offset+sdslen(value));
246 memcpy((char*)o->ptr+offset,value,sdslen(value));
247 touchWatchedKey(c->db,c->argv[1]);
248 server.dirty++;
249 }
250 addReplyLongLong(c,sdslen(o->ptr));
251}
252
ef11bccc
PN
253void getrangeCommand(redisClient *c) {
254 robj *o;
255 long start, end;
256 char *str, llbuf[32];
257 size_t strlen;
258
259 if (getLongFromObjectOrReply(c,c->argv[2],&start,NULL) != REDIS_OK)
260 return;
261 if (getLongFromObjectOrReply(c,c->argv[3],&end,NULL) != REDIS_OK)
262 return;
263 if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
264 checkType(c,o,REDIS_STRING)) return;
265
266 if (o->encoding == REDIS_ENCODING_INT) {
267 str = llbuf;
268 strlen = ll2string(llbuf,sizeof(llbuf),(long)o->ptr);
269 } else {
270 str = o->ptr;
271 strlen = sdslen(str);
272 }
273
274 /* Convert negative indexes */
275 if (start < 0) start = strlen+start;
276 if (end < 0) end = strlen+end;
277 if (start < 0) start = 0;
278 if (end < 0) end = 0;
279 if ((unsigned)end >= strlen) end = strlen-1;
280
281 /* Precondition: end >= 0 && end < strlen, so the only condition where
282 * nothing can be returned is: start > end. */
283 if (start > end) {
284 addReply(c,shared.nullbulk);
285 } else {
286 addReplyBulkCBuffer(c,(char*)str+start,end-start+1);
287 }
288}
289
e2641e09 290void mgetCommand(redisClient *c) {
291 int j;
292
0537e7bf 293 addReplyMultiBulkLen(c,c->argc-1);
e2641e09 294 for (j = 1; j < c->argc; j++) {
295 robj *o = lookupKeyRead(c->db,c->argv[j]);
296 if (o == NULL) {
297 addReply(c,shared.nullbulk);
298 } else {
299 if (o->type != REDIS_STRING) {
300 addReply(c,shared.nullbulk);
301 } else {
302 addReplyBulk(c,o);
303 }
304 }
305 }
306}
307
308void msetGenericCommand(redisClient *c, int nx) {
309 int j, busykeys = 0;
310
311 if ((c->argc % 2) == 0) {
3ab20376 312 addReplyError(c,"wrong number of arguments for MSET");
e2641e09 313 return;
314 }
315 /* Handle the NX flag. The MSETNX semantic is to return zero and don't
316 * set nothing at all if at least one already key exists. */
317 if (nx) {
318 for (j = 1; j < c->argc; j += 2) {
319 if (lookupKeyWrite(c->db,c->argv[j]) != NULL) {
320 busykeys++;
321 }
322 }
323 }
324 if (busykeys) {
325 addReply(c, shared.czero);
326 return;
327 }
328
329 for (j = 1; j < c->argc; j += 2) {
330 c->argv[j+1] = tryObjectEncoding(c->argv[j+1]);
331 dbReplace(c->db,c->argv[j],c->argv[j+1]);
332 incrRefCount(c->argv[j+1]);
333 removeExpire(c->db,c->argv[j]);
5b4bff9c 334 touchWatchedKey(c->db,c->argv[j]);
e2641e09 335 }
336 server.dirty += (c->argc-1)/2;
337 addReply(c, nx ? shared.cone : shared.ok);
338}
339
340void msetCommand(redisClient *c) {
341 msetGenericCommand(c,0);
342}
343
344void msetnxCommand(redisClient *c) {
345 msetGenericCommand(c,1);
346}
347
348void incrDecrCommand(redisClient *c, long long incr) {
9d7165e8 349 long long value, oldvalue;
e2641e09 350 robj *o;
351
352 o = lookupKeyWrite(c->db,c->argv[1]);
353 if (o != NULL && checkType(c,o,REDIS_STRING)) return;
354 if (getLongLongFromObjectOrReply(c,o,&value,NULL) != REDIS_OK) return;
355
9d7165e8 356 oldvalue = value;
e2641e09 357 value += incr;
9d7165e8 358 if ((incr < 0 && value > oldvalue) || (incr > 0 && value < oldvalue)) {
359 addReplyError(c,"increment or decrement would overflow");
360 return;
361 }
e2641e09 362 o = createStringObjectFromLongLong(value);
363 dbReplace(c->db,c->argv[1],o);
5b4bff9c 364 touchWatchedKey(c->db,c->argv[1]);
e2641e09 365 server.dirty++;
366 addReply(c,shared.colon);
367 addReply(c,o);
368 addReply(c,shared.crlf);
369}
370
371void incrCommand(redisClient *c) {
372 incrDecrCommand(c,1);
373}
374
375void decrCommand(redisClient *c) {
376 incrDecrCommand(c,-1);
377}
378
379void incrbyCommand(redisClient *c) {
380 long long incr;
381
382 if (getLongLongFromObjectOrReply(c, c->argv[2], &incr, NULL) != REDIS_OK) return;
383 incrDecrCommand(c,incr);
384}
385
386void decrbyCommand(redisClient *c) {
387 long long incr;
388
389 if (getLongLongFromObjectOrReply(c, c->argv[2], &incr, NULL) != REDIS_OK) return;
390 incrDecrCommand(c,-incr);
391}
392
393void appendCommand(redisClient *c) {
e2641e09 394 size_t totlen;
076f88d6 395 robj *o, *append;
e2641e09 396
397 o = lookupKeyWrite(c->db,c->argv[1]);
398 if (o == NULL) {
399 /* Create the key */
1333f98d
PN
400 c->argv[2] = tryObjectEncoding(c->argv[2]);
401 dbAdd(c->db,c->argv[1],c->argv[2]);
e2641e09 402 incrRefCount(c->argv[2]);
403 totlen = stringObjectLen(c->argv[2]);
404 } else {
1333f98d
PN
405 /* Key exists, check type */
406 if (checkType(c,o,REDIS_STRING))
e2641e09 407 return;
076f88d6 408
1333f98d
PN
409 /* "append" is an argument, so always an sds */
410 append = c->argv[2];
411 totlen = stringObjectLen(o)+sdslen(append->ptr);
412 if (checkStringLength(c,totlen) != REDIS_OK)
076f88d6 413 return;
076f88d6
PN
414
415 /* If the object is shared or encoded, we have to make a copy */
e2641e09 416 if (o->refcount != 1 || o->encoding != REDIS_ENCODING_RAW) {
417 robj *decoded = getDecodedObject(o);
e2641e09 418 o = createStringObject(decoded->ptr, sdslen(decoded->ptr));
419 decrRefCount(decoded);
420 dbReplace(c->db,c->argv[1],o);
421 }
076f88d6
PN
422
423 /* Append the value */
424 o->ptr = sdscatlen(o->ptr,append->ptr,sdslen(append->ptr));
e2641e09 425 totlen = sdslen(o->ptr);
426 }
5b4bff9c 427 touchWatchedKey(c->db,c->argv[1]);
e2641e09 428 server.dirty++;
b70d3555 429 addReplyLongLong(c,totlen);
e2641e09 430}
431
80091bba 432void strlenCommand(redisClient *c) {
433 robj *o;
80091bba 434 if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
435 checkType(c,o,REDIS_STRING)) return;
ad1b4f4f 436 addReplyLongLong(c,stringObjectLen(o));
80091bba 437}
7ecd4644 438