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 addReplyErrorFormat(c,"invalid expire time in %s",c->cmd->name); 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 o = dbUnshareStringValue(c->db,c->argv[1],o); 219 } 220 221 if (sdslen(value) > 0) { 222 o->ptr = sdsgrowzero(o->ptr,offset+sdslen(value)); 223 memcpy((char*)o->ptr+offset,value,sdslen(value)); 224 signalModifiedKey(c->db,c->argv[1]); 225 notifyKeyspaceEvent(REDIS_NOTIFY_STRING, 226 "setrange",c->argv[1],c->db->id); 227 server.dirty++; 228 } 229 addReplyLongLong(c,sdslen(o->ptr)); 230 } 231 232 void getrangeCommand(redisClient *c) { 233 robj *o; 234 long long start, end; 235 char *str, llbuf[32]; 236 size_t strlen; 237 238 if (getLongLongFromObjectOrReply(c,c->argv[2],&start,NULL) != REDIS_OK) 239 return; 240 if (getLongLongFromObjectOrReply(c,c->argv[3],&end,NULL) != REDIS_OK) 241 return; 242 if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptybulk)) == NULL || 243 checkType(c,o,REDIS_STRING)) return; 244 245 if (o->encoding == REDIS_ENCODING_INT) { 246 str = llbuf; 247 strlen = ll2string(llbuf,sizeof(llbuf),(long)o->ptr); 248 } else { 249 str = o->ptr; 250 strlen = sdslen(str); 251 } 252 253 /* Convert negative indexes */ 254 if (start < 0) start = strlen+start; 255 if (end < 0) end = strlen+end; 256 if (start < 0) start = 0; 257 if (end < 0) end = 0; 258 if ((unsigned long long)end >= strlen) end = strlen-1; 259 260 /* Precondition: end >= 0 && end < strlen, so the only condition where 261 * nothing can be returned is: start > end. */ 262 if (start > end || strlen == 0) { 263 addReply(c,shared.emptybulk); 264 } else { 265 addReplyBulkCBuffer(c,(char*)str+start,end-start+1); 266 } 267 } 268 269 void mgetCommand(redisClient *c) { 270 int j; 271 272 addReplyMultiBulkLen(c,c->argc-1); 273 for (j = 1; j < c->argc; j++) { 274 robj *o = lookupKeyRead(c->db,c->argv[j]); 275 if (o == NULL) { 276 addReply(c,shared.nullbulk); 277 } else { 278 if (o->type != REDIS_STRING) { 279 addReply(c,shared.nullbulk); 280 } else { 281 addReplyBulk(c,o); 282 } 283 } 284 } 285 } 286 287 void msetGenericCommand(redisClient *c, int nx) { 288 int j, busykeys = 0; 289 290 if ((c->argc % 2) == 0) { 291 addReplyError(c,"wrong number of arguments for MSET"); 292 return; 293 } 294 /* Handle the NX flag. The MSETNX semantic is to return zero and don't 295 * set nothing at all if at least one already key exists. */ 296 if (nx) { 297 for (j = 1; j < c->argc; j += 2) { 298 if (lookupKeyWrite(c->db,c->argv[j]) != NULL) { 299 busykeys++; 300 } 301 } 302 if (busykeys) { 303 addReply(c, shared.czero); 304 return; 305 } 306 } 307 308 for (j = 1; j < c->argc; j += 2) { 309 c->argv[j+1] = tryObjectEncoding(c->argv[j+1]); 310 setKey(c->db,c->argv[j],c->argv[j+1]); 311 notifyKeyspaceEvent(REDIS_NOTIFY_STRING,"set",c->argv[j],c->db->id); 312 } 313 server.dirty += (c->argc-1)/2; 314 addReply(c, nx ? shared.cone : shared.ok); 315 } 316 317 void msetCommand(redisClient *c) { 318 msetGenericCommand(c,0); 319 } 320 321 void msetnxCommand(redisClient *c) { 322 msetGenericCommand(c,1); 323 } 324 325 void incrDecrCommand(redisClient *c, long long incr) { 326 long long value, oldvalue; 327 robj *o, *new; 328 329 o = lookupKeyWrite(c->db,c->argv[1]); 330 if (o != NULL && checkType(c,o,REDIS_STRING)) return; 331 if (getLongLongFromObjectOrReply(c,o,&value,NULL) != REDIS_OK) return; 332 333 oldvalue = value; 334 if ((incr < 0 && oldvalue < 0 && incr < (LLONG_MIN-oldvalue)) || 335 (incr > 0 && oldvalue > 0 && incr > (LLONG_MAX-oldvalue))) { 336 addReplyError(c,"increment or decrement would overflow"); 337 return; 338 } 339 value += incr; 340 new = createStringObjectFromLongLong(value); 341 if (o) 342 dbOverwrite(c->db,c->argv[1],new); 343 else 344 dbAdd(c->db,c->argv[1],new); 345 signalModifiedKey(c->db,c->argv[1]); 346 notifyKeyspaceEvent(REDIS_NOTIFY_STRING,"incrby",c->argv[1],c->db->id); 347 server.dirty++; 348 addReply(c,shared.colon); 349 addReply(c,new); 350 addReply(c,shared.crlf); 351 } 352 353 void incrCommand(redisClient *c) { 354 incrDecrCommand(c,1); 355 } 356 357 void decrCommand(redisClient *c) { 358 incrDecrCommand(c,-1); 359 } 360 361 void incrbyCommand(redisClient *c) { 362 long long incr; 363 364 if (getLongLongFromObjectOrReply(c, c->argv[2], &incr, NULL) != REDIS_OK) return; 365 incrDecrCommand(c,incr); 366 } 367 368 void decrbyCommand(redisClient *c) { 369 long long incr; 370 371 if (getLongLongFromObjectOrReply(c, c->argv[2], &incr, NULL) != REDIS_OK) return; 372 incrDecrCommand(c,-incr); 373 } 374 375 void incrbyfloatCommand(redisClient *c) { 376 long double incr, value; 377 robj *o, *new, *aux; 378 379 o = lookupKeyWrite(c->db,c->argv[1]); 380 if (o != NULL && checkType(c,o,REDIS_STRING)) return; 381 if (getLongDoubleFromObjectOrReply(c,o,&value,NULL) != REDIS_OK || 382 getLongDoubleFromObjectOrReply(c,c->argv[2],&incr,NULL) != REDIS_OK) 383 return; 384 385 value += incr; 386 if (isnan(value) || isinf(value)) { 387 addReplyError(c,"increment would produce NaN or Infinity"); 388 return; 389 } 390 new = createStringObjectFromLongDouble(value); 391 if (o) 392 dbOverwrite(c->db,c->argv[1],new); 393 else 394 dbAdd(c->db,c->argv[1],new); 395 signalModifiedKey(c->db,c->argv[1]); 396 notifyKeyspaceEvent(REDIS_NOTIFY_STRING,"incrbyfloat",c->argv[1],c->db->id); 397 server.dirty++; 398 addReplyBulk(c,new); 399 400 /* Always replicate INCRBYFLOAT as a SET command with the final value 401 * in order to make sure that differences in float precision or formatting 402 * will not create differences in replicas or after an AOF restart. */ 403 aux = createStringObject("SET",3); 404 rewriteClientCommandArgument(c,0,aux); 405 decrRefCount(aux); 406 rewriteClientCommandArgument(c,2,new); 407 } 408 409 void appendCommand(redisClient *c) { 410 size_t totlen; 411 robj *o, *append; 412 413 o = lookupKeyWrite(c->db,c->argv[1]); 414 if (o == NULL) { 415 /* Create the key */ 416 c->argv[2] = tryObjectEncoding(c->argv[2]); 417 dbAdd(c->db,c->argv[1],c->argv[2]); 418 incrRefCount(c->argv[2]); 419 totlen = stringObjectLen(c->argv[2]); 420 } else { 421 /* Key exists, check type */ 422 if (checkType(c,o,REDIS_STRING)) 423 return; 424 425 /* "append" is an argument, so always an sds */ 426 append = c->argv[2]; 427 totlen = stringObjectLen(o)+sdslen(append->ptr); 428 if (checkStringLength(c,totlen) != REDIS_OK) 429 return; 430 431 /* Append the value */ 432 o = dbUnshareStringValue(c->db,c->argv[1],o); 433 o->ptr = sdscatlen(o->ptr,append->ptr,sdslen(append->ptr)); 434 totlen = sdslen(o->ptr); 435 } 436 signalModifiedKey(c->db,c->argv[1]); 437 notifyKeyspaceEvent(REDIS_NOTIFY_STRING,"append",c->argv[1],c->db->id); 438 server.dirty++; 439 addReplyLongLong(c,totlen); 440 } 441 442 void strlenCommand(redisClient *c) { 443 robj *o; 444 if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL || 445 checkType(c,o,REDIS_STRING)) return; 446 addReplyLongLong(c,stringObjectLen(o)); 447 } 448