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 #define REDIS_SET_EX (1<<2) /* Set if time in seconds is given */ 65 #define REDIS_SET_PX (1<<3) /* Set if time in ms in given */ 66 67 void setGenericCommand(redisClient *c, int flags, robj *key, robj *val, robj *expire, int unit, robj *ok_reply, robj *abort_reply) { 68 long long milliseconds = 0; /* initialized to avoid any harmness warning */ 69 70 if (expire) { 71 if (getLongLongFromObjectOrReply(c, expire, &milliseconds, NULL) != REDIS_OK) 72 return; 73 if (milliseconds <= 0) { 74 addReplyErrorFormat(c,"invalid expire time in %s",c->cmd->name); 75 return; 76 } 77 if (unit == UNIT_SECONDS) milliseconds *= 1000; 78 } 79 80 if ((flags & REDIS_SET_NX && lookupKeyWrite(c->db,key) != NULL) || 81 (flags & REDIS_SET_XX && lookupKeyWrite(c->db,key) == NULL)) 82 { 83 addReply(c, abort_reply ? abort_reply : shared.nullbulk); 84 return; 85 } 86 setKey(c->db,key,val); 87 server.dirty++; 88 if (expire) setExpire(c->db,key,mstime()+milliseconds); 89 notifyKeyspaceEvent(REDIS_NOTIFY_STRING,"set",key,c->db->id); 90 if (expire) notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC, 91 "expire",key,c->db->id); 92 addReply(c, ok_reply ? ok_reply : shared.ok); 93 } 94 95 /* SET key value [NX] [XX] [EX <seconds>] [PX <milliseconds>] */ 96 void setCommand(redisClient *c) { 97 int j; 98 robj *expire = NULL; 99 int unit = UNIT_SECONDS; 100 int flags = REDIS_SET_NO_FLAGS; 101 102 for (j = 3; j < c->argc; j++) { 103 char *a = c->argv[j]->ptr; 104 robj *next = (j == c->argc-1) ? NULL : c->argv[j+1]; 105 106 if ((a[0] == 'n' || a[0] == 'N') && 107 (a[1] == 'x' || a[1] == 'X') && a[2] == '\0' && 108 !(flags & REDIS_SET_XX)) 109 { 110 flags |= REDIS_SET_NX; 111 } else if ((a[0] == 'x' || a[0] == 'X') && 112 (a[1] == 'x' || a[1] == 'X') && a[2] == '\0' && 113 !(flags & REDIS_SET_NX)) 114 { 115 flags |= REDIS_SET_XX; 116 } else if ((a[0] == 'e' || a[0] == 'E') && 117 (a[1] == 'x' || a[1] == 'X') && a[2] == '\0' && 118 !(flags & REDIS_SET_PX) && next) 119 { 120 flags |= REDIS_SET_EX; 121 unit = UNIT_SECONDS; 122 expire = next; 123 j++; 124 } else if ((a[0] == 'p' || a[0] == 'P') && 125 (a[1] == 'x' || a[1] == 'X') && a[2] == '\0' && 126 !(flags & REDIS_SET_EX) && next) 127 { 128 flags |= REDIS_SET_PX; 129 unit = UNIT_MILLISECONDS; 130 expire = next; 131 j++; 132 } else { 133 addReply(c,shared.syntaxerr); 134 return; 135 } 136 } 137 138 c->argv[2] = tryObjectEncoding(c->argv[2]); 139 setGenericCommand(c,flags,c->argv[1],c->argv[2],expire,unit,NULL,NULL); 140 } 141 142 void setnxCommand(redisClient *c) { 143 c->argv[2] = tryObjectEncoding(c->argv[2]); 144 setGenericCommand(c,REDIS_SET_NX,c->argv[1],c->argv[2],NULL,0,shared.cone,shared.czero); 145 } 146 147 void setexCommand(redisClient *c) { 148 c->argv[3] = tryObjectEncoding(c->argv[3]); 149 setGenericCommand(c,REDIS_SET_NO_FLAGS,c->argv[1],c->argv[3],c->argv[2],UNIT_SECONDS,NULL,NULL); 150 } 151 152 void psetexCommand(redisClient *c) { 153 c->argv[3] = tryObjectEncoding(c->argv[3]); 154 setGenericCommand(c,REDIS_SET_NO_FLAGS,c->argv[1],c->argv[3],c->argv[2],UNIT_MILLISECONDS,NULL,NULL); 155 } 156 157 int getGenericCommand(redisClient *c) { 158 robj *o; 159 160 if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL) 161 return REDIS_OK; 162 163 if (o->type != REDIS_STRING) { 164 addReply(c,shared.wrongtypeerr); 165 return REDIS_ERR; 166 } else { 167 addReplyBulk(c,o); 168 return REDIS_OK; 169 } 170 } 171 172 void getCommand(redisClient *c) { 173 getGenericCommand(c); 174 } 175 176 void getsetCommand(redisClient *c) { 177 if (getGenericCommand(c) == REDIS_ERR) return; 178 c->argv[2] = tryObjectEncoding(c->argv[2]); 179 setKey(c->db,c->argv[1],c->argv[2]); 180 notifyKeyspaceEvent(REDIS_NOTIFY_STRING,"set",c->argv[1],c->db->id); 181 server.dirty++; 182 } 183 184 void setrangeCommand(redisClient *c) { 185 robj *o; 186 long offset; 187 sds value = c->argv[3]->ptr; 188 189 if (getLongFromObjectOrReply(c,c->argv[2],&offset,NULL) != REDIS_OK) 190 return; 191 192 if (offset < 0) { 193 addReplyError(c,"offset is out of range"); 194 return; 195 } 196 197 o = lookupKeyWrite(c->db,c->argv[1]); 198 if (o == NULL) { 199 /* Return 0 when setting nothing on a non-existing string */ 200 if (sdslen(value) == 0) { 201 addReply(c,shared.czero); 202 return; 203 } 204 205 /* Return when the resulting string exceeds allowed size */ 206 if (checkStringLength(c,offset+sdslen(value)) != REDIS_OK) 207 return; 208 209 o = createObject(REDIS_STRING,sdsempty()); 210 dbAdd(c->db,c->argv[1],o); 211 } else { 212 size_t olen; 213 214 /* Key exists, check type */ 215 if (checkType(c,o,REDIS_STRING)) 216 return; 217 218 /* Return existing string length when setting nothing */ 219 olen = stringObjectLen(o); 220 if (sdslen(value) == 0) { 221 addReplyLongLong(c,olen); 222 return; 223 } 224 225 /* Return when the resulting string exceeds allowed size */ 226 if (checkStringLength(c,offset+sdslen(value)) != REDIS_OK) 227 return; 228 229 /* Create a copy when the object is shared or encoded. */ 230 o = dbUnshareStringValue(c->db,c->argv[1],o); 231 } 232 233 if (sdslen(value) > 0) { 234 o->ptr = sdsgrowzero(o->ptr,offset+sdslen(value)); 235 memcpy((char*)o->ptr+offset,value,sdslen(value)); 236 signalModifiedKey(c->db,c->argv[1]); 237 notifyKeyspaceEvent(REDIS_NOTIFY_STRING, 238 "setrange",c->argv[1],c->db->id); 239 server.dirty++; 240 } 241 addReplyLongLong(c,sdslen(o->ptr)); 242 } 243 244 void getrangeCommand(redisClient *c) { 245 robj *o; 246 long long start, end; 247 char *str, llbuf[32]; 248 size_t strlen; 249 250 if (getLongLongFromObjectOrReply(c,c->argv[2],&start,NULL) != REDIS_OK) 251 return; 252 if (getLongLongFromObjectOrReply(c,c->argv[3],&end,NULL) != REDIS_OK) 253 return; 254 if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptybulk)) == NULL || 255 checkType(c,o,REDIS_STRING)) return; 256 257 if (o->encoding == REDIS_ENCODING_INT) { 258 str = llbuf; 259 strlen = ll2string(llbuf,sizeof(llbuf),(long)o->ptr); 260 } else { 261 str = o->ptr; 262 strlen = sdslen(str); 263 } 264 265 /* Convert negative indexes */ 266 if (start < 0) start = strlen+start; 267 if (end < 0) end = strlen+end; 268 if (start < 0) start = 0; 269 if (end < 0) end = 0; 270 if ((unsigned long long)end >= strlen) end = strlen-1; 271 272 /* Precondition: end >= 0 && end < strlen, so the only condition where 273 * nothing can be returned is: start > end. */ 274 if (start > end || strlen == 0) { 275 addReply(c,shared.emptybulk); 276 } else { 277 addReplyBulkCBuffer(c,(char*)str+start,end-start+1); 278 } 279 } 280 281 void mgetCommand(redisClient *c) { 282 int j; 283 284 addReplyMultiBulkLen(c,c->argc-1); 285 for (j = 1; j < c->argc; j++) { 286 robj *o = lookupKeyRead(c->db,c->argv[j]); 287 if (o == NULL) { 288 addReply(c,shared.nullbulk); 289 } else { 290 if (o->type != REDIS_STRING) { 291 addReply(c,shared.nullbulk); 292 } else { 293 addReplyBulk(c,o); 294 } 295 } 296 } 297 } 298 299 void msetGenericCommand(redisClient *c, int nx) { 300 int j, busykeys = 0; 301 302 if ((c->argc % 2) == 0) { 303 addReplyError(c,"wrong number of arguments for MSET"); 304 return; 305 } 306 /* Handle the NX flag. The MSETNX semantic is to return zero and don't 307 * set nothing at all if at least one already key exists. */ 308 if (nx) { 309 for (j = 1; j < c->argc; j += 2) { 310 if (lookupKeyWrite(c->db,c->argv[j]) != NULL) { 311 busykeys++; 312 } 313 } 314 if (busykeys) { 315 addReply(c, shared.czero); 316 return; 317 } 318 } 319 320 for (j = 1; j < c->argc; j += 2) { 321 c->argv[j+1] = tryObjectEncoding(c->argv[j+1]); 322 setKey(c->db,c->argv[j],c->argv[j+1]); 323 notifyKeyspaceEvent(REDIS_NOTIFY_STRING,"set",c->argv[j],c->db->id); 324 } 325 server.dirty += (c->argc-1)/2; 326 addReply(c, nx ? shared.cone : shared.ok); 327 } 328 329 void msetCommand(redisClient *c) { 330 msetGenericCommand(c,0); 331 } 332 333 void msetnxCommand(redisClient *c) { 334 msetGenericCommand(c,1); 335 } 336 337 void incrDecrCommand(redisClient *c, long long incr) { 338 long long value, oldvalue; 339 robj *o, *new; 340 341 o = lookupKeyWrite(c->db,c->argv[1]); 342 if (o != NULL && checkType(c,o,REDIS_STRING)) return; 343 if (getLongLongFromObjectOrReply(c,o,&value,NULL) != REDIS_OK) return; 344 345 oldvalue = value; 346 if ((incr < 0 && oldvalue < 0 && incr < (LLONG_MIN-oldvalue)) || 347 (incr > 0 && oldvalue > 0 && incr > (LLONG_MAX-oldvalue))) { 348 addReplyError(c,"increment or decrement would overflow"); 349 return; 350 } 351 value += incr; 352 353 if (o && o->refcount == 1 && o->encoding == REDIS_ENCODING_INT && 354 (value < 0 || value >= REDIS_SHARED_INTEGERS) && 355 value >= LONG_MIN && value <= LONG_MAX) 356 { 357 new = o; 358 o->ptr = (void*)((long)value); 359 } else { 360 new = createStringObjectFromLongLong(value); 361 if (o) { 362 dbOverwrite(c->db,c->argv[1],new); 363 } else { 364 dbAdd(c->db,c->argv[1],new); 365 } 366 } 367 signalModifiedKey(c->db,c->argv[1]); 368 notifyKeyspaceEvent(REDIS_NOTIFY_STRING,"incrby",c->argv[1],c->db->id); 369 server.dirty++; 370 addReply(c,shared.colon); 371 addReply(c,new); 372 addReply(c,shared.crlf); 373 } 374 375 void incrCommand(redisClient *c) { 376 incrDecrCommand(c,1); 377 } 378 379 void decrCommand(redisClient *c) { 380 incrDecrCommand(c,-1); 381 } 382 383 void incrbyCommand(redisClient *c) { 384 long long incr; 385 386 if (getLongLongFromObjectOrReply(c, c->argv[2], &incr, NULL) != REDIS_OK) return; 387 incrDecrCommand(c,incr); 388 } 389 390 void decrbyCommand(redisClient *c) { 391 long long incr; 392 393 if (getLongLongFromObjectOrReply(c, c->argv[2], &incr, NULL) != REDIS_OK) return; 394 incrDecrCommand(c,-incr); 395 } 396 397 void incrbyfloatCommand(redisClient *c) { 398 long double incr, value; 399 robj *o, *new, *aux; 400 401 o = lookupKeyWrite(c->db,c->argv[1]); 402 if (o != NULL && checkType(c,o,REDIS_STRING)) return; 403 if (getLongDoubleFromObjectOrReply(c,o,&value,NULL) != REDIS_OK || 404 getLongDoubleFromObjectOrReply(c,c->argv[2],&incr,NULL) != REDIS_OK) 405 return; 406 407 value += incr; 408 if (isnan(value) || isinf(value)) { 409 addReplyError(c,"increment would produce NaN or Infinity"); 410 return; 411 } 412 new = createStringObjectFromLongDouble(value,1); 413 if (o) 414 dbOverwrite(c->db,c->argv[1],new); 415 else 416 dbAdd(c->db,c->argv[1],new); 417 signalModifiedKey(c->db,c->argv[1]); 418 notifyKeyspaceEvent(REDIS_NOTIFY_STRING,"incrbyfloat",c->argv[1],c->db->id); 419 server.dirty++; 420 addReplyBulk(c,new); 421 422 /* Always replicate INCRBYFLOAT as a SET command with the final value 423 * in order to make sure that differences in float precision or formatting 424 * will not create differences in replicas or after an AOF restart. */ 425 aux = createStringObject("SET",3); 426 rewriteClientCommandArgument(c,0,aux); 427 decrRefCount(aux); 428 rewriteClientCommandArgument(c,2,new); 429 } 430 431 void appendCommand(redisClient *c) { 432 size_t totlen; 433 robj *o, *append; 434 435 o = lookupKeyWrite(c->db,c->argv[1]); 436 if (o == NULL) { 437 /* Create the key */ 438 c->argv[2] = tryObjectEncoding(c->argv[2]); 439 dbAdd(c->db,c->argv[1],c->argv[2]); 440 incrRefCount(c->argv[2]); 441 totlen = stringObjectLen(c->argv[2]); 442 } else { 443 /* Key exists, check type */ 444 if (checkType(c,o,REDIS_STRING)) 445 return; 446 447 /* "append" is an argument, so always an sds */ 448 append = c->argv[2]; 449 totlen = stringObjectLen(o)+sdslen(append->ptr); 450 if (checkStringLength(c,totlen) != REDIS_OK) 451 return; 452 453 /* Append the value */ 454 o = dbUnshareStringValue(c->db,c->argv[1],o); 455 o->ptr = sdscatlen(o->ptr,append->ptr,sdslen(append->ptr)); 456 totlen = sdslen(o->ptr); 457 } 458 signalModifiedKey(c->db,c->argv[1]); 459 notifyKeyspaceEvent(REDIS_NOTIFY_STRING,"append",c->argv[1],c->db->id); 460 server.dirty++; 461 addReplyLongLong(c,totlen); 462 } 463 464 void strlenCommand(redisClient *c) { 465 robj *o; 466 if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL || 467 checkType(c,o,REDIS_STRING)) return; 468 addReplyLongLong(c,stringObjectLen(o)); 469 } 470