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