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,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; 305 306 if ((c->argc % 2) == 0) { 307 addReplyError(c,"wrong number of arguments for MSET"); 308 return; 309 } 310 311 /* Handle the NX flag. The MSETNX semantic is to return zero and don't 312 * set anything if at least one key alerady exists. */ 313 if (nx) { 314 for (j = 1; j < c->argc; j += 2) { 315 if (lookupKeyWrite(c->db,c->argv[j]) != NULL) { 316 addReply(c, shared.czero); 317 return; 318 } 319 } 320 } 321 322 for (j = 1; j < c->argc; j += 2) { 323 c->argv[j+1] = tryObjectEncoding(c->argv[j+1]); 324 setKey(c->db,c->argv[j],c->argv[j+1]); 325 notifyKeyspaceEvent(NOTIFY_STRING,"set",c->argv[j],c->db->id); 326 } 327 server.dirty += (c->argc-1)/2; 328 addReply(c, nx ? shared.cone : shared.ok); 329 } 330 331 void msetCommand(client *c) { 332 msetGenericCommand(c,0); 333 } 334 335 void msetnxCommand(client *c) { 336 msetGenericCommand(c,1); 337 } 338 339 void incrDecrCommand(client *c, long long incr) { 340 long long value, oldvalue; 341 robj *o, *new; 342 343 o = lookupKeyWrite(c->db,c->argv[1]); 344 if (o != NULL && checkType(c,o,OBJ_STRING)) return; 345 if (getLongLongFromObjectOrReply(c,o,&value,NULL) != C_OK) return; 346 347 oldvalue = value; 348 if ((incr < 0 && oldvalue < 0 && incr < (LLONG_MIN-oldvalue)) || 349 (incr > 0 && oldvalue > 0 && incr > (LLONG_MAX-oldvalue))) { 350 addReplyError(c,"increment or decrement would overflow"); 351 return; 352 } 353 value += incr; 354 355 if (o && o->refcount == 1 && o->encoding == OBJ_ENCODING_INT && 356 (value < 0 || value >= OBJ_SHARED_INTEGERS) && 357 value >= LONG_MIN && value <= LONG_MAX) 358 { 359 new = o; 360 o->ptr = (void*)((long)value); 361 } else { 362 new = createStringObjectFromLongLongForValue(value); 363 if (o) { 364 dbOverwrite(c->db,c->argv[1],new); 365 } else { 366 dbAdd(c->db,c->argv[1],new); 367 } 368 } 369 signalModifiedKey(c->db,c->argv[1]); 370 notifyKeyspaceEvent(NOTIFY_STRING,"incrby",c->argv[1],c->db->id); 371 server.dirty++; 372 addReply(c,shared.colon); 373 addReply(c,new); 374 addReply(c,shared.crlf); 375 } 376 377 void incrCommand(client *c) { 378 incrDecrCommand(c,1); 379 } 380 381 void decrCommand(client *c) { 382 incrDecrCommand(c,-1); 383 } 384 385 void incrbyCommand(client *c) { 386 long long incr; 387 388 if (getLongLongFromObjectOrReply(c, c->argv[2], &incr, NULL) != C_OK) return; 389 incrDecrCommand(c,incr); 390 } 391 392 void decrbyCommand(client *c) { 393 long long incr; 394 395 if (getLongLongFromObjectOrReply(c, c->argv[2], &incr, NULL) != C_OK) return; 396 incrDecrCommand(c,-incr); 397 } 398 399 void incrbyfloatCommand(client *c) { 400 long double incr, value; 401 robj *o, *new, *aux; 402 403 o = lookupKeyWrite(c->db,c->argv[1]); 404 if (o != NULL && checkType(c,o,OBJ_STRING)) return; 405 if (getLongDoubleFromObjectOrReply(c,o,&value,NULL) != C_OK || 406 getLongDoubleFromObjectOrReply(c,c->argv[2],&incr,NULL) != C_OK) 407 return; 408 409 value += incr; 410 if (isnan(value) || isinf(value)) { 411 addReplyError(c,"increment would produce NaN or Infinity"); 412 return; 413 } 414 new = createStringObjectFromLongDouble(value,1); 415 if (o) 416 dbOverwrite(c->db,c->argv[1],new); 417 else 418 dbAdd(c->db,c->argv[1],new); 419 signalModifiedKey(c->db,c->argv[1]); 420 notifyKeyspaceEvent(NOTIFY_STRING,"incrbyfloat",c->argv[1],c->db->id); 421 server.dirty++; 422 addReplyBulk(c,new); 423 424 /* Always replicate INCRBYFLOAT as a SET command with the final value 425 * in order to make sure that differences in float precision or formatting 426 * will not create differences in replicas or after an AOF restart. */ 427 aux = createStringObject("SET",3); 428 rewriteClientCommandArgument(c,0,aux); 429 decrRefCount(aux); 430 rewriteClientCommandArgument(c,2,new); 431 } 432 433 void appendCommand(client *c) { 434 size_t totlen; 435 robj *o, *append; 436 437 o = lookupKeyWrite(c->db,c->argv[1]); 438 if (o == NULL) { 439 /* Create the key */ 440 c->argv[2] = tryObjectEncoding(c->argv[2]); 441 dbAdd(c->db,c->argv[1],c->argv[2]); 442 incrRefCount(c->argv[2]); 443 totlen = stringObjectLen(c->argv[2]); 444 } else { 445 /* Key exists, check type */ 446 if (checkType(c,o,OBJ_STRING)) 447 return; 448 449 /* "append" is an argument, so always an sds */ 450 append = c->argv[2]; 451 totlen = stringObjectLen(o)+sdslen(append->ptr); 452 if (checkStringLength(c,totlen) != C_OK) 453 return; 454 455 /* Append the value */ 456 o = dbUnshareStringValue(c->db,c->argv[1],o); 457 o->ptr = sdscatlen(o->ptr,append->ptr,sdslen(append->ptr)); 458 totlen = sdslen(o->ptr); 459 } 460 signalModifiedKey(c->db,c->argv[1]); 461 notifyKeyspaceEvent(NOTIFY_STRING,"append",c->argv[1],c->db->id); 462 server.dirty++; 463 addReplyLongLong(c,totlen); 464 } 465 466 void strlenCommand(client *c) { 467 robj *o; 468 if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL || 469 checkType(c,o,OBJ_STRING)) return; 470 addReplyLongLong(c,stringObjectLen(o)); 471 } 472