+#include <limits.h>
#include "redis.h"
/*-----------------------------------------------------------------------------
* String Commands
*----------------------------------------------------------------------------*/
+static int checkStringLength(redisClient *c, long long size) {
+ if (size > 512*1024*1024) {
+ addReplyError(c,"string exceeds maximum allowed size (512MB)");
+ return REDIS_ERR;
+ }
+ return REDIS_OK;
+}
+
void setGenericCommand(redisClient *c, int nx, robj *key, robj *val, robj *expire) {
int retval;
long seconds = 0; /* initialized to avoid an harmness warning */
if (getLongFromObjectOrReply(c, expire, &seconds, NULL) != REDIS_OK)
return;
if (seconds <= 0) {
- addReplySds(c,sdsnew("-ERR invalid expire time in SETEX\r\n"));
+ addReplyError(c,"invalid expire time in SETEX");
return;
}
}
- touchWatchedKey(c->db,key);
- if (nx) deleteIfVolatile(c->db,key);
retval = dbAdd(c->db,key,val);
if (retval == REDIS_ERR) {
if (!nx) {
} else {
incrRefCount(val);
}
+ touchWatchedKey(c->db,key);
server.dirty++;
removeExpire(c->db,key);
if (expire) setExpire(c->db,key,time(NULL)+seconds);
}
void setCommand(redisClient *c) {
+ c->argv[2] = tryObjectEncoding(c->argv[2]);
setGenericCommand(c,0,c->argv[1],c->argv[2],NULL);
}
void setnxCommand(redisClient *c) {
+ c->argv[2] = tryObjectEncoding(c->argv[2]);
setGenericCommand(c,1,c->argv[1],c->argv[2],NULL);
}
void setexCommand(redisClient *c) {
+ c->argv[3] = tryObjectEncoding(c->argv[3]);
setGenericCommand(c,0,c->argv[1],c->argv[3],c->argv[2]);
}
void getsetCommand(redisClient *c) {
if (getGenericCommand(c) == REDIS_ERR) return;
+ c->argv[2] = tryObjectEncoding(c->argv[2]);
dbReplace(c->db,c->argv[1],c->argv[2]);
incrRefCount(c->argv[2]);
+ touchWatchedKey(c->db,c->argv[1]);
server.dirty++;
removeExpire(c->db,c->argv[1]);
}
+static int getBitOffsetFromArgument(redisClient *c, robj *o, size_t *offset) {
+ long long loffset;
+ char *err = "bit offset is not an integer or out of range";
+
+ if (getLongLongFromObjectOrReply(c,o,&loffset,err) != REDIS_OK)
+ return REDIS_ERR;
+
+ /* Limit offset to 512MB in bytes */
+ if ((loffset < 0) || ((unsigned long long)loffset >> 3) >= (512*1024*1024))
+ {
+ addReplyError(c,err);
+ return REDIS_ERR;
+ }
+
+ *offset = (size_t)loffset;
+ return REDIS_OK;
+}
+
+void setbitCommand(redisClient *c) {
+ robj *o;
+ char *err = "bit is not an integer or out of range";
+ size_t bitoffset;
+ int byte, bit;
+ int byteval, bitval;
+ long on;
+
+ if (getBitOffsetFromArgument(c,c->argv[2],&bitoffset) != REDIS_OK)
+ return;
+
+ if (getLongFromObjectOrReply(c,c->argv[3],&on,err) != REDIS_OK)
+ return;
+
+ /* Bits can only be set or cleared... */
+ if (on & ~1) {
+ addReplyError(c,err);
+ return;
+ }
+
+ o = lookupKeyWrite(c->db,c->argv[1]);
+ if (o == NULL) {
+ o = createObject(REDIS_STRING,sdsempty());
+ dbAdd(c->db,c->argv[1],o);
+ } else {
+ if (checkType(c,o,REDIS_STRING)) return;
+
+ /* Create a copy when the object is shared or encoded. */
+ if (o->refcount != 1 || o->encoding != REDIS_ENCODING_RAW) {
+ robj *decoded = getDecodedObject(o);
+ o = createStringObject(decoded->ptr, sdslen(decoded->ptr));
+ decrRefCount(decoded);
+ dbReplace(c->db,c->argv[1],o);
+ }
+ }
+
+ /* Grow sds value to the right length if necessary */
+ byte = bitoffset >> 3;
+ o->ptr = sdsgrowzero(o->ptr,byte+1);
+
+ /* Get current values */
+ byteval = ((char*)o->ptr)[byte];
+ bit = 7 - (bitoffset & 0x7);
+ bitval = byteval & (1 << bit);
+
+ /* Update byte with new bit value and return original value */
+ byteval &= ~(1 << bit);
+ byteval |= ((on & 0x1) << bit);
+ ((char*)o->ptr)[byte] = byteval;
+ touchWatchedKey(c->db,c->argv[1]);
+ server.dirty++;
+ addReply(c, bitval ? shared.cone : shared.czero);
+}
+
+void getbitCommand(redisClient *c) {
+ robj *o;
+ char llbuf[32];
+ size_t bitoffset;
+ size_t byte, bit;
+ size_t bitval = 0;
+
+ if (getBitOffsetFromArgument(c,c->argv[2],&bitoffset) != REDIS_OK)
+ return;
+
+ if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
+ checkType(c,o,REDIS_STRING)) return;
+
+ byte = bitoffset >> 3;
+ bit = 7 - (bitoffset & 0x7);
+ if (o->encoding != REDIS_ENCODING_RAW) {
+ if (byte < (size_t)ll2string(llbuf,sizeof(llbuf),(long)o->ptr))
+ bitval = llbuf[byte] & (1 << bit);
+ } else {
+ if (byte < sdslen(o->ptr))
+ bitval = ((char*)o->ptr)[byte] & (1 << bit);
+ }
+
+ addReply(c, bitval ? shared.cone : shared.czero);
+}
+
+void setrangeCommand(redisClient *c) {
+ robj *o;
+ long offset;
+ sds value = c->argv[3]->ptr;
+
+ if (getLongFromObjectOrReply(c,c->argv[2],&offset,NULL) != REDIS_OK)
+ return;
+
+ if (offset < 0) {
+ addReplyError(c,"offset is out of range");
+ return;
+ }
+
+ o = lookupKeyWrite(c->db,c->argv[1]);
+ if (o == NULL) {
+ /* Return 0 when setting nothing on a non-existing string */
+ if (sdslen(value) == 0) {
+ addReply(c,shared.czero);
+ return;
+ }
+
+ /* Return when the resulting string exceeds allowed size */
+ if (checkStringLength(c,offset+sdslen(value)) != REDIS_OK)
+ return;
+
+ o = createObject(REDIS_STRING,sdsempty());
+ dbAdd(c->db,c->argv[1],o);
+ } else {
+ int olen;
+
+ /* Key exists, check type */
+ if (checkType(c,o,REDIS_STRING))
+ return;
+
+ /* Find out existing value length */
+ if (o->encoding == REDIS_ENCODING_INT) {
+ char llbuf[32];
+ olen = ll2string(llbuf,sizeof(llbuf),(long)o->ptr);
+ } else {
+ olen = sdslen(o->ptr);
+ }
+
+ /* Return existing string length when setting nothing */
+ if (sdslen(value) == 0) {
+ addReplyLongLong(c,olen);
+ return;
+ }
+
+ /* Return when the resulting string exceeds allowed size */
+ if (checkStringLength(c,offset+sdslen(value)) != REDIS_OK)
+ return;
+
+ /* Create a copy when the object is shared or encoded. */
+ if (o->refcount != 1 || o->encoding != REDIS_ENCODING_RAW) {
+ robj *decoded = getDecodedObject(o);
+ o = createStringObject(decoded->ptr, sdslen(decoded->ptr));
+ decrRefCount(decoded);
+ dbReplace(c->db,c->argv[1],o);
+ }
+ }
+
+ if (sdslen(value) > 0) {
+ o->ptr = sdsgrowzero(o->ptr,offset+sdslen(value));
+ memcpy((char*)o->ptr+offset,value,sdslen(value));
+ touchWatchedKey(c->db,c->argv[1]);
+ server.dirty++;
+ }
+ addReplyLongLong(c,sdslen(o->ptr));
+}
+
+void getrangeCommand(redisClient *c) {
+ robj *o;
+ long start, end;
+ char *str, llbuf[32];
+ size_t strlen;
+
+ if (getLongFromObjectOrReply(c,c->argv[2],&start,NULL) != REDIS_OK)
+ return;
+ if (getLongFromObjectOrReply(c,c->argv[3],&end,NULL) != REDIS_OK)
+ return;
+ if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
+ checkType(c,o,REDIS_STRING)) return;
+
+ if (o->encoding == REDIS_ENCODING_INT) {
+ str = llbuf;
+ strlen = ll2string(llbuf,sizeof(llbuf),(long)o->ptr);
+ } else {
+ str = o->ptr;
+ strlen = sdslen(str);
+ }
+
+ /* Convert negative indexes */
+ if (start < 0) start = strlen+start;
+ if (end < 0) end = strlen+end;
+ if (start < 0) start = 0;
+ if (end < 0) end = 0;
+ if ((unsigned)end >= strlen) end = strlen-1;
+
+ /* Precondition: end >= 0 && end < strlen, so the only condition where
+ * nothing can be returned is: start > end. */
+ if (start > end) {
+ addReply(c,shared.nullbulk);
+ } else {
+ addReplyBulkCBuffer(c,(char*)str+start,end-start+1);
+ }
+}
+
void mgetCommand(redisClient *c) {
int j;
- addReplySds(c,sdscatprintf(sdsempty(),"*%d\r\n",c->argc-1));
+ addReplyMultiBulkLen(c,c->argc-1);
for (j = 1; j < c->argc; j++) {
robj *o = lookupKeyRead(c->db,c->argv[j]);
if (o == NULL) {
int j, busykeys = 0;
if ((c->argc % 2) == 0) {
- addReplySds(c,sdsnew("-ERR wrong number of arguments for MSET\r\n"));
+ addReplyError(c,"wrong number of arguments for MSET");
return;
}
/* Handle the NX flag. The MSETNX semantic is to return zero and don't
dbReplace(c->db,c->argv[j],c->argv[j+1]);
incrRefCount(c->argv[j+1]);
removeExpire(c->db,c->argv[j]);
+ touchWatchedKey(c->db,c->argv[j]);
}
server.dirty += (c->argc-1)/2;
addReply(c, nx ? shared.cone : shared.ok);
value += incr;
o = createStringObjectFromLongLong(value);
dbReplace(c->db,c->argv[1],o);
+ touchWatchedKey(c->db,c->argv[1]);
server.dirty++;
addReply(c,shared.colon);
addReply(c,o);
}
void appendCommand(redisClient *c) {
- int retval;
size_t totlen;
- robj *o;
+ robj *o, *append;
o = lookupKeyWrite(c->db,c->argv[1]);
if (o == NULL) {
/* Create the key */
- retval = dbAdd(c->db,c->argv[1],c->argv[2]);
+ c->argv[2] = tryObjectEncoding(c->argv[2]);
+ dbAdd(c->db,c->argv[1],c->argv[2]);
incrRefCount(c->argv[2]);
totlen = stringObjectLen(c->argv[2]);
} else {
- if (o->type != REDIS_STRING) {
- addReply(c,shared.wrongtypeerr);
+ /* Key exists, check type */
+ if (checkType(c,o,REDIS_STRING))
return;
- }
- /* If the object is specially encoded or shared we have to make
- * a copy */
+
+ /* "append" is an argument, so always an sds */
+ append = c->argv[2];
+ totlen = stringObjectLen(o)+sdslen(append->ptr);
+ if (checkStringLength(c,totlen) != REDIS_OK)
+ return;
+
+ /* If the object is shared or encoded, we have to make a copy */
if (o->refcount != 1 || o->encoding != REDIS_ENCODING_RAW) {
robj *decoded = getDecodedObject(o);
-
o = createStringObject(decoded->ptr, sdslen(decoded->ptr));
decrRefCount(decoded);
dbReplace(c->db,c->argv[1],o);
}
- /* APPEND! */
- if (c->argv[2]->encoding == REDIS_ENCODING_RAW) {
- o->ptr = sdscatlen(o->ptr,
- c->argv[2]->ptr, sdslen(c->argv[2]->ptr));
- } else {
- o->ptr = sdscatprintf(o->ptr, "%ld",
- (unsigned long) c->argv[2]->ptr);
- }
+
+ /* Append the value */
+ o->ptr = sdscatlen(o->ptr,append->ptr,sdslen(append->ptr));
totlen = sdslen(o->ptr);
}
+ touchWatchedKey(c->db,c->argv[1]);
server.dirty++;
- addReplySds(c,sdscatprintf(sdsempty(),":%lu\r\n",(unsigned long)totlen));
+ addReplyLongLong(c,totlen);
}
-void substrCommand(redisClient *c) {
+void strlenCommand(redisClient *c) {
robj *o;
- long start = atoi(c->argv[2]->ptr);
- long end = atoi(c->argv[3]->ptr);
- size_t rangelen, strlen;
- sds range;
- if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
+ if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
checkType(c,o,REDIS_STRING)) return;
- o = getDecodedObject(o);
- strlen = sdslen(o->ptr);
-
- /* convert negative indexes */
- if (start < 0) start = strlen+start;
- if (end < 0) end = strlen+end;
- if (start < 0) start = 0;
- if (end < 0) end = 0;
-
- /* indexes sanity checks */
- if (start > end || (size_t)start >= strlen) {
- /* Out of range start or start > end result in null reply */
- addReply(c,shared.nullbulk);
- decrRefCount(o);
- return;
+ if (o->encoding == REDIS_ENCODING_RAW) {
+ addReplyLongLong(c,sdslen(o->ptr));
+ } else if (o->encoding == REDIS_ENCODING_INT) {
+ char llbuf[32];
+ int len = ll2string(llbuf,sizeof(llbuf),(long)o->ptr);
+ addReplyLongLong(c,len);
+ } else {
+ redisPanic("Unknown string encoding");
}
- if ((size_t)end >= strlen) end = strlen-1;
- rangelen = (end-start)+1;
-
- /* Return the result */
- addReplySds(c,sdscatprintf(sdsempty(),"$%zu\r\n",rangelen));
- range = sdsnewlen((char*)o->ptr+start,rangelen);
- addReplySds(c,range);
- addReply(c,shared.crlf);
- decrRefCount(o);
}
-