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 "server.h" 31 #include <math.h> /* isnan(), isinf() */ 32 33 /*----------------------------------------------------------------------------- 34 * String Commands 35 *----------------------------------------------------------------------------*/ 36 37 static int checkStringLength(client *c, long long size) { 38 if (size > 512*1024*1024) { 39 addReplyError(c,"string exceeds maximum allowed size (512MB)"); 40 return C_ERR; 41 } 42 return C_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 OBJ_SET_NO_FLAGS 0 62 #define OBJ_SET_NX (1<<0) /* Set if key not exists. */ 63 #define OBJ_SET_XX (1<<1) /* Set if key exists. */ 64 #define OBJ_SET_EX (1<<2) /* Set if time in seconds is given */ 65 #define OBJ_SET_PX (1<<3) /* Set if time in ms in given */ 66 67 void setGenericCommand(client *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) != C_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 & OBJ_SET_NX && lookupKeyWrite(c->db,key) != NULL) || 81 (flags & OBJ_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(NOTIFY_STRING,"set",key,c->db->id); 90 if (expire) notifyKeyspaceEvent(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(client *c) { 97 int j; 98 robj *expire = NULL; 99 int unit = UNIT_SECONDS; 100 int flags = OBJ_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 & OBJ_SET_XX)) 109 { 110 flags |= OBJ_SET_NX; 111 } else if ((a[0] == 'x' || a[0] == 'X') && 112 (a[1] == 'x' || a[1] == 'X') && a[2] == '\0' && 113 !(flags & OBJ_SET_NX)) 114 { 115 flags |= OBJ_SET_XX; 116 } else if ((a[0] == 'e' || a[0] == 'E') && 117 (a[1] == 'x' || a[1] == 'X') && a[2] == '\0' && 118 !(flags & OBJ_SET_PX) && next) 119 { 120 flags |= OBJ_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 & OBJ_SET_EX) && next) 127 { 128 flags |= OBJ_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(client *c) { 143 c->argv[2] = tryObjectEncoding(c->argv[2]); 144 setGenericCommand(c,OBJ_SET_NX,c->argv[1],c->argv[2],NULL,0,shared.cone,shared.czero); 145 } 146 147 void setexCommand(client *c) { 148 c->argv[3] = tryObjectEncoding(c->argv[3]); 149 setGenericCommand(c,OBJ_SET_NO_FLAGS,c->argv[1],c->argv[3],c->argv[2],UNIT_SECONDS,NULL,NULL); 150 } 151 152 void psetexCommand(client *c) { 153 c->argv[3] = tryObjectEncoding(c->argv[3]); 154 setGenericCommand(c,OBJ_SET_NO_FLAGS,c->argv[1],c->argv[3],c->argv[2],UNIT_MILLISECONDS,NULL,NULL); 155 } 156 157 int getGenericCommand(client *c) { 158 robj *o; 159 160 if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL) 161 return C_OK; 162 163 if (o->type != OBJ_STRING) { 164 addReply(c,shared.wrongtypeerr); 165 return C_ERR; 166 } else { 167 addReplyBulk(c,o); 168 return C_OK; 169 } 170 } 171 172 void getCommand(client *c) { 173 getGenericCommand(c); 174 } 175 176 void getsetCommand(client *c) { 177 if (getGenericCommand(c) == C_ERR) return; 178 c->argv[2] = tryObjectEncoding(c->argv[2]); 179 setKey(c->db,c->argv[1],c->argv[2]); 180 notifyKeyspaceEvent(NOTIFY_STRING,"set",c->argv[1],c->db->id); 181 server.dirty++; 182 } 183 184 void setrangeCommand(client *c) { 185 robj *o; 186 long offset; 187 sds value = c->argv[3]->ptr; 188 189 if (getLongFromObjectOrReply(c,c->argv[2],&offset,NULL) != C_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)) != C_OK) 207 return; 208 209 o = createObject(OBJ_STRING,sdsnewlen(NULL, offset+sdslen(value))); 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,OBJ_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)) != C_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(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(client *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) != C_OK) 251 return; 252 if (getLongLongFromObjectOrReply(c,c->argv[3],&end,NULL) != C_OK) 253 return; 254 if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptybulk)) == NULL || 255 checkType(c,o,OBJ_STRING)) return; 256 257 if (o->encoding == OBJ_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 && end < 0 && start > end) { 267 addReply(c,shared.emptybulk); 268 return; 269 } 270 if (start < 0) start = strlen+start; 271 if (end < 0) end = strlen+end; 272 if (start < 0) start = 0; 273 if (end < 0) end = 0; 274 if ((unsigned long long)end >= strlen) end = strlen-1; 275 276 /* Precondition: end >= 0 && end < strlen, so the only condition where 277 * nothing can be returned is: start > end. */ 278 if (start > end || strlen == 0) { 279 addReply(c,shared.emptybulk); 280 } else { 281 addReplyBulkCBuffer(c,(char*)str+start,end-start+1); 282 } 283 } 284 285 void mgetCommand(client *c) { 286 int j; 287 288 addReplyMultiBulkLen(c,c->argc-1); 289 for (j = 1; j < c->argc; j++) { 290 robj *o = lookupKeyRead(c->db,c->argv[j]); 291 if (o == NULL) { 292 addReply(c,shared.nullbulk); 293 } else { 294 if (o->type != OBJ_STRING) { 295 addReply(c,shared.nullbulk); 296 } else { 297 addReplyBulk(c,o); 298 } 299 } 300 } 301 } 302 303 void msetGenericCommand(client *c, int nx) { 304 int j, busykeys = 0; 305 306 if ((c->argc % 2) == 0) { 307 addReplyError(c,"wrong number of arguments for MSET"); 308 return; 309 } 310 /* Handle the NX flag. The MSETNX semantic is to return zero and don't 311 * set nothing at all if at least one already key exists. */ 312 if (nx) { 313 for (j = 1; j < c->argc; j += 2) { 314 if (lookupKeyWrite(c->db,c->argv[j]) != NULL) { 315 busykeys++; 316 } 317 } 318 if (busykeys) { 319 addReply(c, shared.czero); 320 return; 321 } 322 } 323 324 for (j = 1; j < c->argc; j += 2) { 325 c->argv[j+1] = tryObjectEncoding(c->argv[j+1]); 326 setKey(c->db,c->argv[j],c->argv[j+1]); 327 notifyKeyspaceEvent(NOTIFY_STRING,"set",c->argv[j],c->db->id); 328 } 329 server.dirty += (c->argc-1)/2; 330 addReply(c, nx ? shared.cone : shared.ok); 331 } 332 333 void msetCommand(client *c) { 334 msetGenericCommand(c,0); 335 } 336 337 void msetnxCommand(client *c) { 338 msetGenericCommand(c,1); 339 } 340 341 void incrDecrCommand(client *c, long long incr) { 342 long long value, oldvalue; 343 robj *o, *new; 344 345 o = lookupKeyWrite(c->db,c->argv[1]); 346 if (o != NULL && checkType(c,o,OBJ_STRING)) return; 347 if (getLongLongFromObjectOrReply(c,o,&value,NULL) != C_OK) return; 348 349 oldvalue = value; 350 if ((incr < 0 && oldvalue < 0 && incr < (LLONG_MIN-oldvalue)) || 351 (incr > 0 && oldvalue > 0 && incr > (LLONG_MAX-oldvalue))) { 352 addReplyError(c,"increment or decrement would overflow"); 353 return; 354 } 355 value += incr; 356 357 if (o && o->refcount == 1 && o->encoding == OBJ_ENCODING_INT && 358 (value < 0 || value >= OBJ_SHARED_INTEGERS) && 359 value >= LONG_MIN && value <= LONG_MAX) 360 { 361 new = o; 362 o->ptr = (void*)((long)value); 363 } else { 364 new = createStringObjectFromLongLong(value); 365 if (o) { 366 dbOverwrite(c->db,c->argv[1],new); 367 } else { 368 dbAdd(c->db,c->argv[1],new); 369 } 370 } 371 signalModifiedKey(c->db,c->argv[1]); 372 notifyKeyspaceEvent(NOTIFY_STRING,"incrby",c->argv[1],c->db->id); 373 server.dirty++; 374 addReply(c,shared.colon); 375 addReply(c,new); 376 addReply(c,shared.crlf); 377 } 378 379 void incrCommand(client *c) { 380 incrDecrCommand(c,1); 381 } 382 383 void decrCommand(client *c) { 384 incrDecrCommand(c,-1); 385 } 386 387 void incrbyCommand(client *c) { 388 long long incr; 389 390 if (getLongLongFromObjectOrReply(c, c->argv[2], &incr, NULL) != C_OK) return; 391 incrDecrCommand(c,incr); 392 } 393 394 void decrbyCommand(client *c) { 395 long long incr; 396 397 if (getLongLongFromObjectOrReply(c, c->argv[2], &incr, NULL) != C_OK) return; 398 incrDecrCommand(c,-incr); 399 } 400 401 void incrbyfloatCommand(client *c) { 402 long double incr, value; 403 robj *o, *new, *aux; 404 405 o = lookupKeyWrite(c->db,c->argv[1]); 406 if (o != NULL && checkType(c,o,OBJ_STRING)) return; 407 if (getLongDoubleFromObjectOrReply(c,o,&value,NULL) != C_OK || 408 getLongDoubleFromObjectOrReply(c,c->argv[2],&incr,NULL) != C_OK) 409 return; 410 411 value += incr; 412 if (isnan(value) || isinf(value)) { 413 addReplyError(c,"increment would produce NaN or Infinity"); 414 return; 415 } 416 new = createStringObjectFromLongDouble(value,1); 417 if (o) 418 dbOverwrite(c->db,c->argv[1],new); 419 else 420 dbAdd(c->db,c->argv[1],new); 421 signalModifiedKey(c->db,c->argv[1]); 422 notifyKeyspaceEvent(NOTIFY_STRING,"incrbyfloat",c->argv[1],c->db->id); 423 server.dirty++; 424 addReplyBulk(c,new); 425 426 /* Always replicate INCRBYFLOAT as a SET command with the final value 427 * in order to make sure that differences in float precision or formatting 428 * will not create differences in replicas or after an AOF restart. */ 429 aux = createStringObject("SET",3); 430 rewriteClientCommandArgument(c,0,aux); 431 decrRefCount(aux); 432 rewriteClientCommandArgument(c,2,new); 433 } 434 435 void appendCommand(client *c) { 436 size_t totlen; 437 robj *o, *append; 438 439 o = lookupKeyWrite(c->db,c->argv[1]); 440 if (o == NULL) { 441 /* Create the key */ 442 c->argv[2] = tryObjectEncoding(c->argv[2]); 443 dbAdd(c->db,c->argv[1],c->argv[2]); 444 incrRefCount(c->argv[2]); 445 totlen = stringObjectLen(c->argv[2]); 446 } else { 447 /* Key exists, check type */ 448 if (checkType(c,o,OBJ_STRING)) 449 return; 450 451 /* "append" is an argument, so always an sds */ 452 append = c->argv[2]; 453 totlen = stringObjectLen(o)+sdslen(append->ptr); 454 if (checkStringLength(c,totlen) != C_OK) 455 return; 456 457 /* Append the value */ 458 o = dbUnshareStringValue(c->db,c->argv[1],o); 459 o->ptr = sdscatlen(o->ptr,append->ptr,sdslen(append->ptr)); 460 totlen = sdslen(o->ptr); 461 } 462 signalModifiedKey(c->db,c->argv[1]); 463 notifyKeyspaceEvent(NOTIFY_STRING,"append",c->argv[1],c->db->id); 464 server.dirty++; 465 addReplyLongLong(c,totlen); 466 } 467 468 void strlenCommand(client *c) { 469 robj *o; 470 if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL || 471 checkType(c,o,OBJ_STRING)) return; 472 addReplyLongLong(c,stringObjectLen(o)); 473 } 474