1 /* 2 * Copyright (c) 2009-2012, Salvatore Sanfilippo <antirez at gmail dot com> 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are met: 7 * 8 * * Redistributions of source code must retain the above copyright notice, 9 * this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * * Neither the name of Redis nor the names of its contributors may be used 14 * to endorse or promote products derived from this software without 15 * specific prior written permission. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 21 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 * POSSIBILITY OF SUCH DAMAGE. 28 */ 29 30 #include "redis.h" 31 #include <math.h> /* isnan(), isinf() */ 32 33 /*----------------------------------------------------------------------------- 34 * String Commands 35 *----------------------------------------------------------------------------*/ 36 37 static int checkStringLength(redisClient *c, long long size) { 38 if (size > 512*1024*1024) { 39 addReplyError(c,"string exceeds maximum allowed size (512MB)"); 40 return REDIS_ERR; 41 } 42 return REDIS_OK; 43 } 44 45 /* The setGenericCommand() function implements the SET operation with different 46 * options and variants. This function is called in order to implement the 47 * following commands: SET, SETEX, PSETEX, SETNX. 48 * 49 * 'flags' changes the behavior of the command (NX or XX, see belove). 50 * 51 * 'expire' represents an expire to set in form of a Redis object as passed 52 * by the user. It is interpreted according to the specified 'unit'. 53 * 54 * 'ok_reply' and 'abort_reply' is what the function will reply to the client 55 * if the operation is performed, or when it is not because of NX or 56 * XX flags. 57 * 58 * If ok_reply is NULL "+OK" is used. 59 * If abort_reply is NULL, "$-1" is used. */ 60 61 #define REDIS_SET_NO_FLAGS 0 62 #define REDIS_SET_NX (1<<0) /* Set if key not exists. */ 63 #define REDIS_SET_XX (1<<1) /* Set if key exists. */ 64 65 void setGenericCommand(redisClient *c, int flags, robj *key, robj *val, robj *expire, int unit, robj *ok_reply, robj *abort_reply) { 66 long long milliseconds = 0; /* initialized to avoid any harmness warning */ 67 68 if (expire) { 69 if (getLongLongFromObjectOrReply(c, expire, &milliseconds, NULL) != REDIS_OK) 70 return; 71 if (milliseconds <= 0) { 72 addReplyError(c,"invalid expire time in SETEX"); 73 return; 74 } 75 if (unit == UNIT_SECONDS) milliseconds *= 1000; 76 } 77 78 if ((flags & REDIS_SET_NX && lookupKeyWrite(c->db,key) != NULL) || 79 (flags & REDIS_SET_XX && lookupKeyWrite(c->db,key) == NULL)) 80 { 81 addReply(c, abort_reply ? abort_reply : shared.nullbulk); 82 return; 83 } 84 setKey(c->db,key,val); 85 server.dirty++; 86 if (expire) setExpire(c->db,key,mstime()+milliseconds); 87 notifyKeyspaceEvent(REDIS_NOTIFY_STRING,"set",key,c->db->id); 88 if (expire) notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC, 89 "expire",key,c->db->id); 90 addReply(c, ok_reply ? ok_reply : shared.ok); 91 } 92 93 /* SET key value [NX] [XX] [EX <seconds>] [PX <milliseconds>] */ 94 void setCommand(redisClient *c) { 95 int j; 96 robj *expire = NULL; 97 int unit = UNIT_SECONDS; 98 int flags = REDIS_SET_NO_FLAGS; 99 100 for (j = 3; j < c->argc; j++) { 101 char *a = c->argv[j]->ptr; 102 robj *next = (j == c->argc-1) ? NULL : c->argv[j+1]; 103 104 if ((a[0] == 'n' || a[0] == 'N') && 105 (a[1] == 'x' || a[1] == 'X') && a[2] == '\0') { 106 flags |= REDIS_SET_NX; 107 } else if ((a[0] == 'x' || a[0] == 'X') && 108 (a[1] == 'x' || a[1] == 'X') && a[2] == '\0') { 109 flags |= REDIS_SET_XX; 110 } else if ((a[0] == 'e' || a[0] == 'E') && 111 (a[1] == 'x' || a[1] == 'X') && a[2] == '\0' && next) { 112 unit = UNIT_SECONDS; 113 expire = next; 114 j++; 115 } else if ((a[0] == 'p' || a[0] == 'P') && 116 (a[1] == 'x' || a[1] == 'X') && a[2] == '\0' && next) { 117 unit = UNIT_MILLISECONDS; 118 expire = next; 119 j++; 120 } else { 121 addReply(c,shared.syntaxerr); 122 return; 123 } 124 } 125 126 c->argv[2] = tryObjectEncoding(c->argv[2]); 127 setGenericCommand(c,flags,c->argv[1],c->argv[2],expire,unit,NULL,NULL); 128 } 129 130 void setnxCommand(redisClient *c) { 131 c->argv[2] = tryObjectEncoding(c->argv[2]); 132 setGenericCommand(c,REDIS_SET_NX,c->argv[1],c->argv[2],NULL,0,shared.cone,shared.czero); 133 } 134 135 void setexCommand(redisClient *c) { 136 c->argv[3] = tryObjectEncoding(c->argv[3]); 137 setGenericCommand(c,REDIS_SET_NO_FLAGS,c->argv[1],c->argv[3],c->argv[2],UNIT_SECONDS,NULL,NULL); 138 } 139 140 void psetexCommand(redisClient *c) { 141 c->argv[3] = tryObjectEncoding(c->argv[3]); 142 setGenericCommand(c,REDIS_SET_NO_FLAGS,c->argv[1],c->argv[3],c->argv[2],UNIT_MILLISECONDS,NULL,NULL); 143 } 144 145 int getGenericCommand(redisClient *c) { 146 robj *o; 147 148 if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL) 149 return REDIS_OK; 150 151 if (o->type != REDIS_STRING) { 152 addReply(c,shared.wrongtypeerr); 153 return REDIS_ERR; 154 } else { 155 addReplyBulk(c,o); 156 return REDIS_OK; 157 } 158 } 159 160 void getCommand(redisClient *c) { 161 getGenericCommand(c); 162 } 163 164 void getsetCommand(redisClient *c) { 165 if (getGenericCommand(c) == REDIS_ERR) return; 166 c->argv[2] = tryObjectEncoding(c->argv[2]); 167 setKey(c->db,c->argv[1],c->argv[2]); 168 notifyKeyspaceEvent(REDIS_NOTIFY_STRING,"set",c->argv[1],c->db->id); 169 server.dirty++; 170 } 171 172 void setrangeCommand(redisClient *c) { 173 robj *o; 174 long offset; 175 sds value = c->argv[3]->ptr; 176 177 if (getLongFromObjectOrReply(c,c->argv[2],&offset,NULL) != REDIS_OK) 178 return; 179 180 if (offset < 0) { 181 addReplyError(c,"offset is out of range"); 182 return; 183 } 184 185 o = lookupKeyWrite(c->db,c->argv[1]); 186 if (o == NULL) { 187 /* Return 0 when setting nothing on a non-existing string */ 188 if (sdslen(value) == 0) { 189 addReply(c,shared.czero); 190 return; 191 } 192 193 /* Return when the resulting string exceeds allowed size */ 194 if (checkStringLength(c,offset+sdslen(value)) != REDIS_OK) 195 return; 196 197 o = createObject(REDIS_STRING,sdsempty()); 198 dbAdd(c->db,c->argv[1],o); 199 } else { 200 size_t olen; 201 202 /* Key exists, check type */ 203 if (checkType(c,o,REDIS_STRING)) 204 return; 205 206 /* Return existing string length when setting nothing */ 207 olen = stringObjectLen(o); 208 if (sdslen(value) == 0) { 209 addReplyLongLong(c,olen); 210 return; 211 } 212 213 /* Return when the resulting string exceeds allowed size */ 214 if (checkStringLength(c,offset+sdslen(value)) != REDIS_OK) 215 return; 216 217 /* Create a copy when the object is shared or encoded. */ 218 if (o->refcount != 1 || o->encoding != REDIS_ENCODING_RAW) { 219 robj *decoded = getDecodedObject(o); 220 o = createRawStringObject(decoded->ptr, sdslen(decoded->ptr)); 221 decrRefCount(decoded); 222 dbOverwrite(c->db,c->argv[1],o); 223 } 224 } 225 226 if (sdslen(value) > 0) { 227 o->ptr = sdsgrowzero(o->ptr,offset+sdslen(value)); 228 memcpy((char*)o->ptr+offset,value,sdslen(value)); 229 signalModifiedKey(c->db,c->argv[1]); 230 notifyKeyspaceEvent(REDIS_NOTIFY_STRING, 231 "setrange",c->argv[1],c->db->id); 232 server.dirty++; 233 } 234 addReplyLongLong(c,sdslen(o->ptr)); 235 } 236 237 void getrangeCommand(redisClient *c) { 238 robj *o; 239 long start, end; 240 char *str, llbuf[32]; 241 size_t strlen; 242 243 if (getLongFromObjectOrReply(c,c->argv[2],&start,NULL) != REDIS_OK) 244 return; 245 if (getLongFromObjectOrReply(c,c->argv[3],&end,NULL) != REDIS_OK) 246 return; 247 if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptybulk)) == NULL || 248 checkType(c,o,REDIS_STRING)) return; 249 250 if (o->encoding == REDIS_ENCODING_INT) { 251 str = llbuf; 252 strlen = ll2string(llbuf,sizeof(llbuf),(long)o->ptr); 253 } else { 254 str = o->ptr; 255 strlen = sdslen(str); 256 } 257 258 /* Convert negative indexes */ 259 if (start < 0) start = strlen+start; 260 if (end < 0) end = strlen+end; 261 if (start < 0) start = 0; 262 if (end < 0) end = 0; 263 if ((unsigned)end >= strlen) end = strlen-1; 264 265 /* Precondition: end >= 0 && end < strlen, so the only condition where 266 * nothing can be returned is: start > end. */ 267 if (start > end) { 268 addReply(c,shared.emptybulk); 269 } else { 270 addReplyBulkCBuffer(c,(char*)str+start,end-start+1); 271 } 272 } 273 274 void mgetCommand(redisClient *c) { 275 int j; 276 277 addReplyMultiBulkLen(c,c->argc-1); 278 for (j = 1; j < c->argc; j++) { 279 robj *o = lookupKeyRead(c->db,c->argv[j]); 280 if (o == NULL) { 281 addReply(c,shared.nullbulk); 282 } else { 283 if (o->type != REDIS_STRING) { 284 addReply(c,shared.nullbulk); 285 } else { 286 addReplyBulk(c,o); 287 } 288 } 289 } 290 } 291 292 void msetGenericCommand(redisClient *c, int nx) { 293 int j, busykeys = 0; 294 295 if ((c->argc % 2) == 0) { 296 addReplyError(c,"wrong number of arguments for MSET"); 297 return; 298 } 299 /* Handle the NX flag. The MSETNX semantic is to return zero and don't 300 * set nothing at all if at least one already key exists. */ 301 if (nx) { 302 for (j = 1; j < c->argc; j += 2) { 303 if (lookupKeyWrite(c->db,c->argv[j]) != NULL) { 304 busykeys++; 305 } 306 } 307 if (busykeys) { 308 addReply(c, shared.czero); 309 return; 310 } 311 } 312 313 for (j = 1; j < c->argc; j += 2) { 314 c->argv[j+1] = tryObjectEncoding(c->argv[j+1]); 315 setKey(c->db,c->argv[j],c->argv[j+1]); 316 notifyKeyspaceEvent(REDIS_NOTIFY_STRING,"set",c->argv[j],c->db->id); 317 } 318 server.dirty += (c->argc-1)/2; 319 addReply(c, nx ? shared.cone : shared.ok); 320 } 321 322 void msetCommand(redisClient *c) { 323 msetGenericCommand(c,0); 324 } 325 326 void msetnxCommand(redisClient *c) { 327 msetGenericCommand(c,1); 328 } 329 330 void incrDecrCommand(redisClient *c, long long incr) { 331 long long value, oldvalue; 332 robj *o, *new; 333 334 o = lookupKeyWrite(c->db,c->argv[1]); 335 if (o != NULL && checkType(c,o,REDIS_STRING)) return; 336 if (getLongLongFromObjectOrReply(c,o,&value,NULL) != REDIS_OK) return; 337 338 oldvalue = value; 339 if ((incr < 0 && oldvalue < 0 && incr < (LLONG_MIN-oldvalue)) || 340 (incr > 0 && oldvalue > 0 && incr > (LLONG_MAX-oldvalue))) { 341 addReplyError(c,"increment or decrement would overflow"); 342 return; 343 } 344 value += incr; 345 new = createStringObjectFromLongLong(value); 346 if (o) 347 dbOverwrite(c->db,c->argv[1],new); 348 else 349 dbAdd(c->db,c->argv[1],new); 350 signalModifiedKey(c->db,c->argv[1]); 351 notifyKeyspaceEvent(REDIS_NOTIFY_STRING,"incrby",c->argv[1],c->db->id); 352 server.dirty++; 353 addReply(c,shared.colon); 354 addReply(c,new); 355 addReply(c,shared.crlf); 356 } 357 358 void incrCommand(redisClient *c) { 359 incrDecrCommand(c,1); 360 } 361 362 void decrCommand(redisClient *c) { 363 incrDecrCommand(c,-1); 364 } 365 366 void incrbyCommand(redisClient *c) { 367 long long incr; 368 369 if (getLongLongFromObjectOrReply(c, c->argv[2], &incr, NULL) != REDIS_OK) return; 370 incrDecrCommand(c,incr); 371 } 372 373 void decrbyCommand(redisClient *c) { 374 long long incr; 375 376 if (getLongLongFromObjectOrReply(c, c->argv[2], &incr, NULL) != REDIS_OK) return; 377 incrDecrCommand(c,-incr); 378 } 379 380 void incrbyfloatCommand(redisClient *c) { 381 long double incr, value; 382 robj *o, *new, *aux; 383 384 o = lookupKeyWrite(c->db,c->argv[1]); 385 if (o != NULL && checkType(c,o,REDIS_STRING)) return; 386 if (getLongDoubleFromObjectOrReply(c,o,&value,NULL) != REDIS_OK || 387 getLongDoubleFromObjectOrReply(c,c->argv[2],&incr,NULL) != REDIS_OK) 388 return; 389 390 value += incr; 391 if (isnan(value) || isinf(value)) { 392 addReplyError(c,"increment would produce NaN or Infinity"); 393 return; 394 } 395 new = createStringObjectFromLongDouble(value); 396 if (o) 397 dbOverwrite(c->db,c->argv[1],new); 398 else 399 dbAdd(c->db,c->argv[1],new); 400 signalModifiedKey(c->db,c->argv[1]); 401 notifyKeyspaceEvent(REDIS_NOTIFY_STRING,"incrbyfloat",c->argv[1],c->db->id); 402 server.dirty++; 403 addReplyBulk(c,new); 404 405 /* Always replicate INCRBYFLOAT as a SET command with the final value 406 * in order to make sure that differences in float precision or formatting 407 * will not create differences in replicas or after an AOF restart. */ 408 aux = createStringObject("SET",3); 409 rewriteClientCommandArgument(c,0,aux); 410 decrRefCount(aux); 411 rewriteClientCommandArgument(c,2,new); 412 } 413 414 void appendCommand(redisClient *c) { 415 size_t totlen; 416 robj *o, *append; 417 418 o = lookupKeyWrite(c->db,c->argv[1]); 419 if (o == NULL) { 420 /* Create the key */ 421 c->argv[2] = tryObjectEncoding(c->argv[2]); 422 dbAdd(c->db,c->argv[1],c->argv[2]); 423 incrRefCount(c->argv[2]); 424 totlen = stringObjectLen(c->argv[2]); 425 } else { 426 /* Key exists, check type */ 427 if (checkType(c,o,REDIS_STRING)) 428 return; 429 430 /* "append" is an argument, so always an sds */ 431 append = c->argv[2]; 432 totlen = stringObjectLen(o)+sdslen(append->ptr); 433 if (checkStringLength(c,totlen) != REDIS_OK) 434 return; 435 436 /* If the object is shared or encoded, we have to make a copy */ 437 if (o->refcount != 1 || o->encoding != REDIS_ENCODING_RAW) { 438 robj *decoded = getDecodedObject(o); 439 o = createRawStringObject(decoded->ptr, sdslen(decoded->ptr)); 440 decrRefCount(decoded); 441 dbOverwrite(c->db,c->argv[1],o); 442 } 443 444 /* Append the value */ 445 o->ptr = sdscatlen(o->ptr,append->ptr,sdslen(append->ptr)); 446 totlen = sdslen(o->ptr); 447 } 448 signalModifiedKey(c->db,c->argv[1]); 449 notifyKeyspaceEvent(REDIS_NOTIFY_STRING,"append",c->argv[1],c->db->id); 450 server.dirty++; 451 addReplyLongLong(c,totlen); 452 } 453 454 void strlenCommand(redisClient *c) { 455 robj *o; 456 if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL || 457 checkType(c,o,REDIS_STRING)) return; 458 addReplyLongLong(c,stringObjectLen(o)); 459 } 460