git mess :)

This commit is contained in:
Ludovico Magnocavallo 2009-03-24 14:33:43 +01:00
commit 0c9ca0e11c
10 changed files with 384 additions and 351 deletions

8
.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
*.o
*.rdb
redis-cli
redis-server
redis-benchmark
doc-tools
mkrelease.sh
release

View File

@ -23,7 +23,7 @@ anet.o: anet.c anet.h
benchmark.o: benchmark.c ae.h anet.h sds.h adlist.h benchmark.o: benchmark.c ae.h anet.h sds.h adlist.h
dict.o: dict.c dict.h dict.o: dict.c dict.h
redis-cli.o: redis-cli.c anet.h sds.h adlist.h redis-cli.o: redis-cli.c anet.h sds.h adlist.h
redis.o: redis.c ae.h sds.h anet.h dict.h adlist.h redis.o: redis.c ae.h sds.h anet.h dict.h adlist.h zmalloc.c zmalloc.h
sds.o: sds.c sds.h sds.o: sds.c sds.h
sha1.o: sha1.c sha1.h sha1.o: sha1.c sha1.h
zmalloc.o: zmalloc.c zmalloc.o: zmalloc.c

12
TODO
View File

@ -1,4 +1,4 @@
BETA 8 TODO - Protocol changes as discussed in the Redis group
- keys expire - keys expire
- sunion ssub - sunion ssub
- write integers in a special way on disk (and on memory?) - write integers in a special way on disk (and on memory?)
@ -6,12 +6,4 @@ BETA 8 TODO
- network layer stresser in test in demo - network layer stresser in test in demo
- maxclients directive - maxclients directive
- check 'server.dirty' everywere - check 'server.dirty' everywere
- replication tests - replication automated tests
- command line client. If the last argument of a bulk command is missing get it from stdin. Example:
$ echo "bar" | redis-client SET foo
$ redis-client SET foo bar
$ redis-client GET foo
bar
$
- Make Redis aware of the memory it is using thanks to getrusage() and report this info with the INFO command.
- INFO command: clients, slave/master, requests/second in the last N seconds, memory usage, uptime, dirty, lastsave

File diff suppressed because one or more lines are too long

View File

@ -16,7 +16,7 @@
<div id="pagecontent"> <div id="pagecontent">
<div class="index"> <div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. --> <!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>ProtocolSpecification: Contents</b><br>&nbsp;&nbsp;<a href="#Protocol Specification">Protocol Specification</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Networking layer">Networking layer</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Simple INLINE commands">Simple INLINE commands</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Bulk commands">Bulk commands</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Bulk replies">Bulk replies</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Bulk reply error reporting">Bulk reply error reporting</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Multi-Bulk replies">Multi-Bulk replies</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Nil elements in Multi-Bulk replies">Nil elements in Multi-Bulk replies</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Multi-Bulk replies errors">Multi-Bulk replies errors</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Status code reply">Status code reply</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Integer reply">Integer reply</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Single line reply">Single line reply</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Multiple commands and pipelining">Multiple commands and pipelining</a> <b>ProtocolSpecification: Contents</b><br>&nbsp;&nbsp;<a href="#Protocol Specification">Protocol Specification</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Networking layer">Networking layer</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Simple INLINE commands">Simple INLINE commands</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Bulk commands">Bulk commands</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Bulk replies">Bulk replies</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Multi-Bulk replies">Multi-Bulk replies</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Nil elements in Multi-Bulk replies">Nil elements in Multi-Bulk replies</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Single line reply">Single line reply</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Integer reply">Integer reply</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Multiple commands and pipelining">Multiple commands and pipelining</a>
</div> </div>
<h1 class="wikiname">ProtocolSpecification</h1> <h1 class="wikiname">ProtocolSpecification</h1>
@ -35,14 +35,12 @@ terminated by &quot;\r\n&quot; (CRLF).<h2><a name="Simple INLINE commands">Simpl
server/client chat (the server chat starts with S:, the client chat with C:)<br/><br/><pre class="codeblock python" name="code"> server/client chat (the server chat starts with S:, the client chat with C:)<br/><br/><pre class="codeblock python" name="code">
C: PING C: PING
S: +PONG S: +PONG
</pre>An inline command is a CRLF-terminated string sent to the client. The server </pre>An inline command is a CRLF-terminated string sent to the client. The server can reply to commands in different ways:
usually replies to inline commands with a single line that can be a number <ul><li> With an error message (the first byte of the reply will be &quot;-&quot;)</li><li> With a single line reply (the first byte of the reply will be &quot;+)</li><li> With bulk data (the first byte of the reply will be &quot;$&quot;)</li><li> With multi-bulk data, a list of values (the first byte of the reply will be &quot;<code name="code" class="python">*</code>&quot;)</li><li> With an integer number (the first byte of the reply will be &quot;:&quot;)</li></ul>
or a return code.<br/><br/>When the server replies with a status code (that is a one line reply just indicating if the operation succeeded or not), if the first character of the The following is another example of an INLINE command returning an integer:<br/><br/><pre class="codeblock python python" name="code">
reply is a &quot;+&quot; then the command succeeded, if it is a &quot;-&quot; then the following
part of the string is an error.<br/><br/>The following is another example of an INLINE command returning an integer:<br/><br/><pre class="codeblock python python" name="code">
C: EXISTS somekey C: EXISTS somekey
S: 0 S: :0
</pre>Since 'somekey' does not exist the server returned '0'.<br/><br/>Note that the EXISTS command takes one argument. Arguments are separated </pre>Since 'somekey' does not exist the server returned ':0'.<br/><br/>Note that the EXISTS command takes one argument. Arguments are separated
simply by spaces.<h2><a name="Bulk commands">Bulk commands</a></h2>A bulk command is exactly like an inline command, but the last argument simply by spaces.<h2><a name="Bulk commands">Bulk commands</a></h2>A bulk command is exactly like an inline command, but the last argument
of the command must be a stream of bytes in order to send data to the server. of the command must be a stream of bytes in order to send data to the server.
the &quot;SET&quot; command is a bulk command, see the following example:<br/><br/><pre class="codeblock python python python" name="code"> the &quot;SET&quot; command is a bulk command, see the following example:<br/><br/><pre class="codeblock python python python" name="code">
@ -58,82 +56,60 @@ sent by the client in the above sample:<br/><br/><blockquote>&quot;SET mykey 6\r
<h2><a name="Bulk replies">Bulk replies</a></h2>The server may reply to an inline or bulk command with a bulk reply. See <h2><a name="Bulk replies">Bulk replies</a></h2>The server may reply to an inline or bulk command with a bulk reply. See
the following example:<br/><br/><pre class="codeblock python python python python" name="code"> the following example:<br/><br/><pre class="codeblock python python python python" name="code">
C: GET mykey C: GET mykey
S: 6 S: $6
S: foobar S: foobar
</pre>A bulk reply is very similar to the last argument of a bulk command. The </pre>A bulk reply is very similar to the last argument of a bulk command. The
server sends as the first line the number of bytes of the actual reply server sends as the first line a &quot;$&quot; byte followed by the number of bytes
followed by CRLF, then the bytes are sent followed by additional two bytes of the actual reply followed by CRLF, then the bytes are sent followed by
for the final CRLF. The exact sequence sent by the server is:<br/><br/><blockquote>&quot;6\r\nfoobar\r\n&quot;</blockquote> additional two bytes for the final CRLF. The exact sequence sent by the
server is:<br/><br/><blockquote>&quot;$6\r\nfoobar\r\n&quot;</blockquote>
If the requested value does not exist the bulk reply will use the special If the requested value does not exist the bulk reply will use the special
value 'nil' instead to send the line containing the number of bytes to read. value -1 as data length, example:<br/><br/><pre class="codeblock python python python python python" name="code">
This is an example:<br/><br/><pre class="codeblock python python python python python" name="code">
C: GET nonexistingkey C: GET nonexistingkey
S: nil S: $-1
</pre>The client library API should not return an empty string, but a nil object. </pre>The client library API should not return an empty string, but a nil object, when the requested object does not exist.
For example a Ruby library should return 'nil' while a C library should return For example a Ruby library should return 'nil' while a C library should return
NULL.<h2><a name="Bulk reply error reporting">Bulk reply error reporting</a></h2>Bulk replies can signal errors, for example trying to use GET against a list NULL, and so forth.<h2><a name="Multi-Bulk replies">Multi-Bulk replies</a></h2>Commands similar to LRANGE needs to return multiple values (every element
value is not permitted. Bulk replies use a negative bytes count in order to
signal an error. An error string of ABS(bytes_count) bytes will follow. See
the following example:<br/><br/><pre class="codeblock python python python python python python" name="code">
S: GET alistkey
S: -38
S: -ERR Requested element is not a string
</pre>-38 means: sorry your operation resulted in an error, but a 38 bytes string
that explains this error will follow. Client APIs should abort on this kind
of errors, for example a PHP client should call the die() function.<br/><br/>The following commands reply with a bulk reply: GET, KEYS, LINDEX, LPOP, RPOP<h2><a name="Multi-Bulk replies">Multi-Bulk replies</a></h2>Commands similar to LRANGE needs to return multiple values (every element
of the list is a value, and LRANGE needs to return more than a single element). This is accomplished using multiple bulk writes, of the list is a value, and LRANGE needs to return more than a single element). This is accomplished using multiple bulk writes,
prefixed by an initial line indicating how many bulk writes will follow. prefixed by an initial line indicating how many bulk writes will follow.
Example:<br/><br/><pre class="codeblock python python python python python python python" name="code"> The first byte of a multi bulk reply is always <code name="code" class="python">*</code>. Example:<br/><br/><pre class="codeblock python python python python python python" name="code">
C: LRANGE mylist 0 3 C: LRANGE mylist 0 3
S: 4 S: *4
S: 3 S: $3
S: foo S: foo
S: 3 S: $3
S: bar S: bar
S: 5 S: $5
S: Hello S: Hello
S: 5 S: $5
S: World S: World
</pre>The first line the server sent is &quot;4\r\n&quot; in order to specify that four bulk </pre>The first line the server sent is &quot;<b>4\r\n&quot; in order to specify that four bulk
write will follow. Then every bulk write is transmitted.<br/><br/>If the specified key does not exist instead of the number of elements in the write will follow. Then every bulk write is transmitted.<br/><br/>If the specified key does not exist instead of the number of elements in the
list, the special value 'nil' is sent. Example:<br/><br/><pre class="codeblock python python python python python python python python" name="code"> list, the special value -1 is sent as count. Example:<br/><br/><pre class="codeblock python python python python python python python" name="code">
C: LRANGE nokey 0 1 C: LRANGE nokey 0 1
S: nil S: *-1
</pre>A client library API SHOULD return a nil object and not an empty list when this </pre>A client library API SHOULD return a nil object and not an empty list when this
happens. This makes possible to distinguish between empty list and non existing ones.<h2><a name="Nil elements in Multi-Bulk replies">Nil elements in Multi-Bulk replies</a></h2>Single elements of a multi bulk reply may have -1 length, in order to signal that this elements are missing and not empty strings. This can happen with the SORT command when used with the GET <i>pattern</i> option when the specified key is missing. Example of a multi bulk reply containing an empty element:<br/><br/><pre class="codeblock python python python python python python python python python" name="code"> happens. This makes possible to distinguish between empty list and non existing ones.<h2><a name="Nil elements in Multi-Bulk replies">Nil elements in Multi-Bulk replies</a></h2>Single elements of a multi bulk reply may have -1 length, in order to signal that this elements are missing and not empty strings. This can happen with the SORT command when used with the GET <i>pattern</i> option when the specified key is missing. Example of a multi bulk reply containing an empty element:<br/><br/><pre class="codeblock python python python python python python python python" name="code">
S: 3 S: *3
S: 3 S: $3
S: foo S: foo
S: -1 S: $-1
S: 3 S: $3
S: bar S: bar
</pre>The second element is nul. The client library should return something like this:<br/><br/><pre class="codeblock python python python python python python python python python python" name="code"> </pre>The second element is nul. The client library should return something like this:<br/><br/><pre class="codeblock python python python python python python python python python" name="code">
[&quot;foo&quot;,nil,&quot;bar&quot;] [&quot;foo&quot;,nil,&quot;bar&quot;]
</pre><h2><a name="Multi-Bulk replies errors">Multi-Bulk replies errors</a></h2>Like bulk reply errors Multi-bulk reply errors are reported using a negative </pre><h2><a name="Single line reply">Single line reply</a></h2>As already seen a single line reply is in the form of a single line string
count. Example:<br/><br/><pre class="codeblock python python python python python python python python python python python" name="code"> starting with &quot;+&quot; terminated by &quot;\r\n&quot;. For example:<br/><br/><pre class="codeblock python python python python python python python python python python" name="code">
C: LRANGE stringkey 0 1
S: -38
S: -ERR Requested element is not a string
</pre>The following commands reply with a multi-bulk reply: LRANGE, LINTER<br/><br/>Check the Bulk replies errors section for more information.<h2><a name="Status code reply">Status code reply</a></h2>As already seen a status code reply is in the form of a single line string
terminated by &quot;\r\n&quot;. For example:<br/><br/><pre class="codeblock python python python python python python python python python python python python" name="code">
+OK +OK
</pre>and<br/><br/><pre class="codeblock python python python python python python python python python python python python python" name="code"> </pre>The client library should return everything after the &quot;+&quot;, that is, the string &quot;OK&quot; in the example.<br/><br/>The following commands reply with a status code reply:
-ERR no suck key PING, SET, SELECT, SAVE, BGSAVE, SHUTDOWN, RENAME, LPUSH, RPUSH, LSET, LTRIM<h2><a name="Integer reply">Integer reply</a></h2>This type of reply is just a CRLF terminated string representing an integer, prefixed by a &quot;:&quot; byte. For example &quot;:0\r\n&quot;, or &quot;:1000\r\n&quot; are integer replies.<br/><br/>With commands like INCR or LASTSAVE using the integer reply to actually return a value there is no special meaning for the returned integer. It is just an incremental number for INCR, a UNIX time for LASTSAVE and so on.<br/><br/>Some commands like EXISTS will return 1 for true and 0 for false.<br/><br/>Other commands like SADD, SREM and SETNX will return 1 if the operation was actually done, 0 otherwise.<br/><br/>The following commands will reply with an integer reply: SETNX, DEL, EXISTS, INCR, INCRBY, DECR, DECRBY, DBSIZE, LASTSAVE, RENAMENX, MOVE, LLEN, SADD, SREM, SISMEMBER, SCARD<h2><a name="Multiple commands and pipelining">Multiple commands and pipelining</a></h2>A client can use the same connection in order to issue multiple commands.
</pre>are two examples of status code replies. The first character of a status code reply is always &quot;+&quot; or &quot;-&quot;.<br/><br/>The following commands reply with a status code reply:
PING, SET, SELECT, SAVE, BGSAVE, SHUTDOWN, RENAME, LPUSH, RPUSH, LSET, LTRIM<h2><a name="Integer reply">Integer reply</a></h2>This type of reply is just a CRLF terminated string representing an integer. For example &quot;0\r\n&quot;, or &quot;1000\r\n&quot; are integer replies.<br/><br/>With commands like INCR or LASTSAVE using the integer reply to actually return a value there is no special meaning for the returned integer. It is just an incremental number for INCR, a UNIX time for LASTSAVE and so on.<br/><br/>Some commands like EXISTS will return 1 for true and 0 for false.<br/><br/>Other commands like SADD, SREM and SETNX will return 1 if the operation was actually done, 0 otherwise, and <b>a negative value</b> if the operation is invalid (for example SADD against a non-set value), accordingly to this table:
<pre class="codeblock python python python python python python python python python python python python python python" name="code">
-1 no such key
-2 operation against the a key holding a value of the wrong type
-3 source and destiantion objects/dbs are the same
-4 argument out of range
</pre>
In all this cases it is mandatory that the client raises an error instead to pass the negative value to the caller. Please check the commands documentation for the exact behaviour.<br/><br/>The following commands will reply with an integer reply: SETNX, DEL, EXISTS, INCR, INCRBY, DECR, DECRBY, DBSIZE, LASTSAVE, RENAMENX, MOVE, LLEN, SADD, SREM, SISMEMBER, SCARD<br/><br/>The commands that will never return a negative integer (commands that can't fail) are: INCR, DECR, INCRBY, DECRBY, LASTSAVE, EXISTS, SETNX, DEL, DBSIZE.<h2><a name="Single line reply">Single line reply</a></h2>This replies are just single line strings terminated by CRLF. Only two commands reply in this way currently, RANDOMKEY and TYPE.<h2><a name="Multiple commands and pipelining">Multiple commands and pipelining</a></h2>A client can use the same connection in order to issue multiple commands.
Pipelining is supported so multiple commands can be sent with a single Pipelining is supported so multiple commands can be sent with a single
write operation by the client, it is not needed to read the server reply write operation by the client, it is not needed to read the server reply
in order to issue the next command. All the replies can be read at the end.<br/><br/>Usually Redis server and client will have a very fast link so this is not in order to issue the next command. All the replies can be read at the end.<br/><br/>Usually Redis server and client will have a very fast link so this is not
very important to support this feature in a client implementation, still very important to support this feature in a client implementation, still
if an application needs to issue a very large number of commands in short if an application needs to issue a very large number of commands in short
time to use pipelining can be much faster.<br/><br/> time to use pipelining can be much faster.
</b>
</div> </div>
</div> </div>

View File

@ -40,11 +40,6 @@
#define REDIS_CMD_INLINE 1 #define REDIS_CMD_INLINE 1
#define REDIS_CMD_BULK 2 #define REDIS_CMD_BULK 2
#define REDIS_CMD_INTREPLY 4
#define REDIS_CMD_RETCODEREPLY 8
#define REDIS_CMD_BULKREPLY 16
#define REDIS_CMD_MULTIBULKREPLY 32
#define REDIS_CMD_SINGLELINEREPLY 64
#define REDIS_NOTUSED(V) ((void) V) #define REDIS_NOTUSED(V) ((void) V)
@ -60,54 +55,56 @@ struct redisCommand {
}; };
static struct redisCommand cmdTable[] = { static struct redisCommand cmdTable[] = {
{"get",2,REDIS_CMD_INLINE|REDIS_CMD_BULKREPLY}, {"get",2,REDIS_CMD_INLINE},
{"set",3,REDIS_CMD_BULK|REDIS_CMD_RETCODEREPLY}, {"set",3,REDIS_CMD_BULK},
{"setnx",3,REDIS_CMD_BULK|REDIS_CMD_INTREPLY}, {"setnx",3,REDIS_CMD_BULK},
{"del",2,REDIS_CMD_INLINE|REDIS_CMD_INTREPLY}, {"del",2,REDIS_CMD_INLINE},
{"exists",2,REDIS_CMD_INLINE|REDIS_CMD_INTREPLY}, {"exists",2,REDIS_CMD_INLINE},
{"incr",2,REDIS_CMD_INLINE|REDIS_CMD_INTREPLY}, {"incr",2,REDIS_CMD_INLINE},
{"decr",2,REDIS_CMD_INLINE|REDIS_CMD_INTREPLY}, {"decr",2,REDIS_CMD_INLINE},
{"rpush",3,REDIS_CMD_BULK|REDIS_CMD_RETCODEREPLY}, {"rpush",3,REDIS_CMD_BULK},
{"lpush",3,REDIS_CMD_BULK|REDIS_CMD_RETCODEREPLY}, {"lpush",3,REDIS_CMD_BULK},
{"rpop",2,REDIS_CMD_INLINE|REDIS_CMD_BULKREPLY}, {"rpop",2,REDIS_CMD_INLINE},
{"lpop",2,REDIS_CMD_INLINE|REDIS_CMD_BULKREPLY}, {"lpop",2,REDIS_CMD_INLINE},
{"llen",2,REDIS_CMD_INLINE|REDIS_CMD_INTREPLY}, {"llen",2,REDIS_CMD_INLINE},
{"lindex",3,REDIS_CMD_INLINE|REDIS_CMD_BULKREPLY}, {"lindex",3,REDIS_CMD_INLINE},
{"lset",4,REDIS_CMD_BULK|REDIS_CMD_RETCODEREPLY}, {"lset",4,REDIS_CMD_BULK},
{"lrange",4,REDIS_CMD_INLINE|REDIS_CMD_MULTIBULKREPLY}, {"lrange",4,REDIS_CMD_INLINE},
{"ltrim",4,REDIS_CMD_INLINE|REDIS_CMD_RETCODEREPLY}, {"ltrim",4,REDIS_CMD_INLINE},
{"lrem",4,REDIS_CMD_BULK|REDIS_CMD_INTREPLY}, {"lrem",4,REDIS_CMD_BULK},
{"sadd",3,REDIS_CMD_BULK|REDIS_CMD_INTREPLY}, {"sadd",3,REDIS_CMD_BULK},
{"srem",3,REDIS_CMD_BULK|REDIS_CMD_INTREPLY}, {"srem",3,REDIS_CMD_BULK},
{"sismember",3,REDIS_CMD_BULK|REDIS_CMD_INTREPLY}, {"sismember",3,REDIS_CMD_BULK},
{"scard",2,REDIS_CMD_INLINE|REDIS_CMD_INTREPLY}, {"scard",2,REDIS_CMD_INLINE},
{"sinter",-2,REDIS_CMD_INLINE|REDIS_CMD_MULTIBULKREPLY}, {"sinter",-2,REDIS_CMD_INLINE},
{"sinterstore",-3,REDIS_CMD_INLINE|REDIS_CMD_RETCODEREPLY}, {"sinterstore",-3,REDIS_CMD_INLINE},
{"smembers",2,REDIS_CMD_INLINE|REDIS_CMD_MULTIBULKREPLY}, {"smembers",2,REDIS_CMD_INLINE},
{"incrby",3,REDIS_CMD_INLINE|REDIS_CMD_INTREPLY}, {"incrby",3,REDIS_CMD_INLINE},
{"decrby",3,REDIS_CMD_INLINE|REDIS_CMD_INTREPLY}, {"decrby",3,REDIS_CMD_INLINE},
{"randomkey",1,REDIS_CMD_INLINE|REDIS_CMD_SINGLELINEREPLY}, {"randomkey",1,REDIS_CMD_INLINE},
{"select",2,REDIS_CMD_INLINE|REDIS_CMD_RETCODEREPLY}, {"select",2,REDIS_CMD_INLINE},
{"move",3,REDIS_CMD_INLINE|REDIS_CMD_INTREPLY}, {"move",3,REDIS_CMD_INLINE},
{"rename",3,REDIS_CMD_INLINE|REDIS_CMD_RETCODEREPLY}, {"rename",3,REDIS_CMD_INLINE},
{"renamenx",3,REDIS_CMD_INLINE|REDIS_CMD_INTREPLY}, {"renamenx",3,REDIS_CMD_INLINE},
{"keys",2,REDIS_CMD_INLINE|REDIS_CMD_BULKREPLY}, {"keys",2,REDIS_CMD_INLINE},
{"dbsize",1,REDIS_CMD_INLINE|REDIS_CMD_INTREPLY}, {"dbsize",1,REDIS_CMD_INLINE},
{"ping",1,REDIS_CMD_INLINE|REDIS_CMD_RETCODEREPLY}, {"ping",1,REDIS_CMD_INLINE},
{"echo",2,REDIS_CMD_BULK|REDIS_CMD_BULKREPLY}, {"echo",2,REDIS_CMD_BULK},
{"save",1,REDIS_CMD_INLINE|REDIS_CMD_RETCODEREPLY}, {"save",1,REDIS_CMD_INLINE},
{"bgsave",1,REDIS_CMD_INLINE|REDIS_CMD_RETCODEREPLY}, {"bgsave",1,REDIS_CMD_INLINE},
{"shutdown",1,REDIS_CMD_INLINE|REDIS_CMD_RETCODEREPLY}, {"shutdown",1,REDIS_CMD_INLINE},
{"lastsave",1,REDIS_CMD_INLINE|REDIS_CMD_INTREPLY}, {"lastsave",1,REDIS_CMD_INLINE},
{"type",2,REDIS_CMD_INLINE|REDIS_CMD_SINGLELINEREPLY}, {"type",2,REDIS_CMD_INLINE},
{"flushdb",1,REDIS_CMD_INLINE|REDIS_CMD_RETCODEREPLY}, {"flushdb",1,REDIS_CMD_INLINE},
{"flushall",1,REDIS_CMD_INLINE|REDIS_CMD_RETCODEREPLY}, {"flushall",1,REDIS_CMD_INLINE},
{"sort",-2,REDIS_CMD_INLINE|REDIS_CMD_MULTIBULKREPLY}, {"sort",-2,REDIS_CMD_INLINE},
{"info",1,REDIS_CMD_INLINE|REDIS_CMD_BULKREPLY}, {"info",1,REDIS_CMD_INLINE},
{"mget",-2,REDIS_CMD_INLINE|REDIS_CMD_MULTIBULKREPLY}, {"mget",-2,REDIS_CMD_INLINE},
{NULL,0,0} {NULL,0,0}
}; };
static int cliReadReply(int fd);
static struct redisCommand *lookupCommand(char *name) { static struct redisCommand *lookupCommand(char *name) {
int j = 0; int j = 0;
while(cmdTable[j].name != NULL) { while(cmdTable[j].name != NULL) {
@ -135,11 +132,13 @@ static sds cliReadLine(int fd) {
while(1) { while(1) {
char c; char c;
ssize_t ret;
if (read(fd,&c,1) == -1) { ret = read(fd,&c,1);
if (ret == -1) {
sdsfree(line); sdsfree(line);
return NULL; return NULL;
} else if (c == '\n') { } else if ((ret == 0) || (c == '\n')) {
break; break;
} else { } else {
line = sdscatlen(line,&c,1); line = sdscatlen(line,&c,1);
@ -148,38 +147,26 @@ static sds cliReadLine(int fd) {
return sdstrim(line,"\r\n"); return sdstrim(line,"\r\n");
} }
static int cliReadInlineReply(int fd, int type) { static int cliReadSingleLineReply(int fd) {
sds reply = cliReadLine(fd); sds reply = cliReadLine(fd);
if (reply == NULL) return 1; if (reply == NULL) return 1;
printf("%s\n", reply); printf("%s\n", reply);
if (type == REDIS_CMD_SINGLELINEREPLY) return 0;
if (type == REDIS_CMD_INTREPLY) return atoi(reply) < 0;
if (type == REDIS_CMD_RETCODEREPLY) return reply[0] == '-';
return 0; return 0;
} }
static int cliReadBulkReply(int fd, int multibulk) { static int cliReadBulkReply(int fd) {
sds replylen = cliReadLine(fd); sds replylen = cliReadLine(fd);
char *reply, crlf[2]; char *reply, crlf[2];
int bulklen, error = 0; int bulklen;
if (replylen == NULL) return 1; if (replylen == NULL) return 1;
if (strcmp(replylen,"nil") == 0) {
sdsfree(replylen);
printf("(nil)\n");
return 0;
}
bulklen = atoi(replylen); bulklen = atoi(replylen);
if (multibulk && bulklen == -1) { if (bulklen == -1) {
sdsfree(replylen); sdsfree(replylen);
printf("(nil)"); printf("(nil)");
return 0; return 0;
} }
if (bulklen < 0) {
bulklen = -bulklen;
error = 1;
}
reply = zmalloc(bulklen); reply = zmalloc(bulklen);
anetRead(fd,reply,bulklen); anetRead(fd,reply,bulklen);
anetRead(fd,crlf,2); anetRead(fd,crlf,2);
@ -187,10 +174,10 @@ static int cliReadBulkReply(int fd, int multibulk) {
zfree(reply); zfree(reply);
return 1; return 1;
} }
if (!multibulk && isatty(fileno(stdout)) && reply[bulklen-1] != '\n') if (isatty(fileno(stdout)) && reply[bulklen-1] != '\n')
printf("\n"); printf("\n");
zfree(reply); zfree(reply);
return error; return 0;
} }
static int cliReadMultiBulkReply(int fd) { static int cliReadMultiBulkReply(int fd) {
@ -198,21 +185,45 @@ static int cliReadMultiBulkReply(int fd) {
int elements, c = 1; int elements, c = 1;
if (replylen == NULL) return 1; if (replylen == NULL) return 1;
if (strcmp(replylen,"nil") == 0) { elements = atoi(replylen);
if (elements == -1) {
sdsfree(replylen); sdsfree(replylen);
printf("(nil)\n"); printf("(nil)\n");
return 0; return 0;
} }
elements = atoi(replylen); if (elements == 0) {
printf("(empty list or set)\n");
}
while(elements--) { while(elements--) {
printf("%d. ", c); printf("%d. ", c);
if (cliReadBulkReply(fd,1)) return 1; if (cliReadReply(fd)) return 1;
printf("\n");
c++; c++;
} }
return 0; return 0;
} }
static int cliReadReply(int fd) {
char type;
if (anetRead(fd,&type,1) <= 0) exit(1);
switch(type) {
case '-':
printf("(error) ");
cliReadSingleLineReply(fd);
return 1;
case '+':
case ':':
return cliReadSingleLineReply(fd);
case '$':
return cliReadBulkReply(fd);
case '*':
return cliReadMultiBulkReply(fd);
default:
printf("protocol error, got '%c' as reply type byte\n", type);
return 1;
}
}
static int cliSendCommand(int argc, char **argv) { static int cliSendCommand(int argc, char **argv) {
struct redisCommand *rc = lookupCommand(argv[0]); struct redisCommand *rc = lookupCommand(argv[0]);
int fd, j, retval = 0; int fd, j, retval = 0;
@ -245,17 +256,7 @@ static int cliSendCommand(int argc, char **argv) {
cmd = sdscat(cmd,"\r\n"); cmd = sdscat(cmd,"\r\n");
} }
anetWrite(fd,cmd,sdslen(cmd)); anetWrite(fd,cmd,sdslen(cmd));
if (rc->flags & REDIS_CMD_INTREPLY) { retval = cliReadReply(fd);
retval = cliReadInlineReply(fd,REDIS_CMD_INTREPLY);
} else if (rc->flags & REDIS_CMD_RETCODEREPLY) {
retval = cliReadInlineReply(fd,REDIS_CMD_RETCODEREPLY);
} else if (rc->flags & REDIS_CMD_SINGLELINEREPLY) {
retval = cliReadInlineReply(fd,REDIS_CMD_SINGLELINEREPLY);
} else if (rc->flags & REDIS_CMD_BULKREPLY) {
retval = cliReadBulkReply(fd,0);
} else if (rc->flags & REDIS_CMD_MULTIBULKREPLY) {
retval = cliReadMultiBulkReply(fd);
}
if (retval) { if (retval) {
close(fd); close(fd);
return retval; return retval;

248
redis.c
View File

@ -27,7 +27,7 @@
* POSSIBILITY OF SUCH DAMAGE. * POSSIBILITY OF SUCH DAMAGE.
*/ */
#define REDIS_VERSION "0.07" #define REDIS_VERSION "0.08"
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@ -89,6 +89,7 @@
#define REDIS_CLOSE 1 /* This client connection should be closed ASAP */ #define REDIS_CLOSE 1 /* This client connection should be closed ASAP */
#define REDIS_SLAVE 2 /* This client is a slave server */ #define REDIS_SLAVE 2 /* This client is a slave server */
#define REDIS_MASTER 4 /* This client is a master server */ #define REDIS_MASTER 4 /* This client is a master server */
#define REDIS_MONITOR 8 /* This client is a slave monitor, see MONITOR */
/* Server replication state */ /* Server replication state */
#define REDIS_REPL_NONE 0 /* No active replication */ #define REDIS_REPL_NONE 0 /* No active replication */
@ -138,7 +139,7 @@ typedef struct redisClient {
list *reply; list *reply;
int sentlen; int sentlen;
time_t lastinteraction; /* time of the last interaction, used for timeout */ time_t lastinteraction; /* time of the last interaction, used for timeout */
int flags; /* REDIS_CLOSE | REDIS_SLAVE */ int flags; /* REDIS_CLOSE | REDIS_SLAVE | REDIS_MONITOR */
int slaveseldb; /* slave selected db, if this client is a slave */ int slaveseldb; /* slave selected db, if this client is a slave */
} redisClient; } redisClient;
@ -154,7 +155,7 @@ struct redisServer {
dict **dict; dict **dict;
long long dirty; /* changes to DB from the last save */ long long dirty; /* changes to DB from the last save */
list *clients; list *clients;
list *slaves; list *slaves, *monitors;
char neterr[ANET_ERR_LEN]; char neterr[ANET_ERR_LEN];
aeEventLoop *el; aeEventLoop *el;
int cronloops; /* number of times the cron function run */ int cronloops; /* number of times the cron function run */
@ -171,6 +172,7 @@ struct redisServer {
int maxidletime; int maxidletime;
int dbnum; int dbnum;
int daemonize; int daemonize;
char *pidfile;
int bgsaveinprogress; int bgsaveinprogress;
struct saveparam *saveparams; struct saveparam *saveparams;
int saveparamslen; int saveparamslen;
@ -212,10 +214,10 @@ typedef struct _redisSortOperation {
} redisSortOperation; } redisSortOperation;
struct sharedObjectsStruct { struct sharedObjectsStruct {
robj *crlf, *ok, *err, *zerobulk, *nil, *zero, *one, *pong, *space, robj *crlf, *ok, *err, *emptybulk, *czero, *cone, *pong, *space,
*minus1, *minus2, *minus3, *minus4, *colon, *minus1, *nullbulk, *nullmultibulk,
*wrongtypeerr, *nokeyerr, *wrongtypeerrbulk, *nokeyerrbulk, *emptymultibulk, *wrongtypeerr, *nokeyerr, *syntaxerr, *sameobjecterr,
*syntaxerr, *syntaxerrbulk, *outofrangeerr, *plus,
*select0, *select1, *select2, *select3, *select4, *select0, *select1, *select2, *select3, *select4,
*select5, *select6, *select7, *select8, *select9; *select5, *select6, *select7, *select8, *select9;
} shared; } shared;
@ -234,7 +236,7 @@ static void addReplySds(redisClient *c, sds s);
static void incrRefCount(robj *o); static void incrRefCount(robj *o);
static int saveDbBackground(char *filename); static int saveDbBackground(char *filename);
static robj *createStringObject(char *ptr, size_t len); static robj *createStringObject(char *ptr, size_t len);
static void replicationFeedSlaves(struct redisCommand *cmd, int dictid, robj **argv, int argc); static void replicationFeedSlaves(list *slaves, struct redisCommand *cmd, int dictid, robj **argv, int argc);
static int syncWithMaster(void); static int syncWithMaster(void);
static void pingCommand(redisClient *c); static void pingCommand(redisClient *c);
@ -282,6 +284,7 @@ static void sortCommand(redisClient *c);
static void lremCommand(redisClient *c); static void lremCommand(redisClient *c);
static void infoCommand(redisClient *c); static void infoCommand(redisClient *c);
static void mgetCommand(redisClient *c); static void mgetCommand(redisClient *c);
static void monitorCommand(redisClient *c);
/*================================= Globals ================================= */ /*================================= Globals ================================= */
@ -334,6 +337,7 @@ static struct redisCommand cmdTable[] = {
{"flushall",flushallCommand,1,REDIS_CMD_INLINE}, {"flushall",flushallCommand,1,REDIS_CMD_INLINE},
{"sort",sortCommand,-2,REDIS_CMD_INLINE}, {"sort",sortCommand,-2,REDIS_CMD_INLINE},
{"info",infoCommand,1,REDIS_CMD_INLINE}, {"info",infoCommand,1,REDIS_CMD_INLINE},
{"monitor",monitorCommand,1,REDIS_CMD_INLINE},
{NULL,NULL,0,0} {NULL,NULL,0,0}
}; };
@ -656,29 +660,28 @@ static void createSharedObjects(void) {
shared.crlf = createObject(REDIS_STRING,sdsnew("\r\n")); shared.crlf = createObject(REDIS_STRING,sdsnew("\r\n"));
shared.ok = createObject(REDIS_STRING,sdsnew("+OK\r\n")); shared.ok = createObject(REDIS_STRING,sdsnew("+OK\r\n"));
shared.err = createObject(REDIS_STRING,sdsnew("-ERR\r\n")); shared.err = createObject(REDIS_STRING,sdsnew("-ERR\r\n"));
shared.zerobulk = createObject(REDIS_STRING,sdsnew("0\r\n\r\n")); shared.emptybulk = createObject(REDIS_STRING,sdsnew("$0\r\n\r\n"));
shared.nil = createObject(REDIS_STRING,sdsnew("nil\r\n")); shared.czero = createObject(REDIS_STRING,sdsnew(":0\r\n"));
shared.zero = createObject(REDIS_STRING,sdsnew("0\r\n")); shared.cone = createObject(REDIS_STRING,sdsnew(":1\r\n"));
shared.one = createObject(REDIS_STRING,sdsnew("1\r\n")); shared.nullbulk = createObject(REDIS_STRING,sdsnew("$-1\r\n"));
shared.nullmultibulk = createObject(REDIS_STRING,sdsnew("*-1\r\n"));
shared.emptymultibulk = createObject(REDIS_STRING,sdsnew("*0\r\n"));
/* no such key */ /* no such key */
shared.minus1 = createObject(REDIS_STRING,sdsnew("-1\r\n")); shared.minus1 = createObject(REDIS_STRING,sdsnew("-1\r\n"));
/* operation against key holding a value of the wrong type */
shared.minus2 = createObject(REDIS_STRING,sdsnew("-2\r\n"));
/* src and dest objects are the same */
shared.minus3 = createObject(REDIS_STRING,sdsnew("-3\r\n"));
/* out of range argument */
shared.minus4 = createObject(REDIS_STRING,sdsnew("-4\r\n"));
shared.pong = createObject(REDIS_STRING,sdsnew("+PONG\r\n")); shared.pong = createObject(REDIS_STRING,sdsnew("+PONG\r\n"));
shared.wrongtypeerr = createObject(REDIS_STRING,sdsnew( shared.wrongtypeerr = createObject(REDIS_STRING,sdsnew(
"-ERR Operation against a key holding the wrong kind of value\r\n")); "-ERR Operation against a key holding the wrong kind of value\r\n"));
shared.wrongtypeerrbulk = createObject(REDIS_STRING,sdscatprintf(sdsempty(),"%d\r\n%s",-sdslen(shared.wrongtypeerr->ptr)+2,shared.wrongtypeerr->ptr));
shared.nokeyerr = createObject(REDIS_STRING,sdsnew( shared.nokeyerr = createObject(REDIS_STRING,sdsnew(
"-ERR no such key\r\n")); "-ERR no such key\r\n"));
shared.nokeyerrbulk = createObject(REDIS_STRING,sdscatprintf(sdsempty(),"%d\r\n%s",-sdslen(shared.nokeyerr->ptr)+2,shared.nokeyerr->ptr));
shared.syntaxerr = createObject(REDIS_STRING,sdsnew( shared.syntaxerr = createObject(REDIS_STRING,sdsnew(
"-ERR syntax error\r\n")); "-ERR syntax error\r\n"));
shared.syntaxerrbulk = createObject(REDIS_STRING,sdscatprintf(sdsempty(),"%d\r\n%s",-sdslen(shared.syntaxerr->ptr)+2,shared.syntaxerr->ptr)); shared.sameobjecterr = createObject(REDIS_STRING,sdsnew(
"-ERR source and destination objects are the same\r\n"));
shared.outofrangeerr = createObject(REDIS_STRING,sdsnew(
"-ERR index out of range\r\n"));
shared.space = createObject(REDIS_STRING,sdsnew(" ")); shared.space = createObject(REDIS_STRING,sdsnew(" "));
shared.colon = createObject(REDIS_STRING,sdsnew(":"));
shared.plus = createObject(REDIS_STRING,sdsnew("+"));
shared.select0 = createStringObject("select 0\r\n",10); shared.select0 = createStringObject("select 0\r\n",10);
shared.select1 = createStringObject("select 1\r\n",10); shared.select1 = createStringObject("select 1\r\n",10);
shared.select2 = createStringObject("select 2\r\n",10); shared.select2 = createStringObject("select 2\r\n",10);
@ -715,6 +718,7 @@ static void initServerConfig() {
server.bindaddr = NULL; server.bindaddr = NULL;
server.glueoutputbuf = 1; server.glueoutputbuf = 1;
server.daemonize = 0; server.daemonize = 0;
server.pidfile = "/var/run/redis.pid";
server.dbfilename = "dump.rdb"; server.dbfilename = "dump.rdb";
ResetServerSaveParams(); ResetServerSaveParams();
@ -737,11 +741,12 @@ static void initServer() {
server.clients = listCreate(); server.clients = listCreate();
server.slaves = listCreate(); server.slaves = listCreate();
server.monitors = listCreate();
server.objfreelist = listCreate(); server.objfreelist = listCreate();
createSharedObjects(); createSharedObjects();
server.el = aeCreateEventLoop(); server.el = aeCreateEventLoop();
server.dict = zmalloc(sizeof(dict*)*server.dbnum); server.dict = zmalloc(sizeof(dict*)*server.dbnum);
if (!server.dict || !server.clients || !server.slaves || !server.el || !server.objfreelist) if (!server.dict || !server.clients || !server.slaves || !server.monitors || !server.el || !server.objfreelist)
oom("server initialization"); /* Fatal OOM */ oom("server initialization"); /* Fatal OOM */
server.fd = anetTcpServer(server.neterr, server.port, server.bindaddr); server.fd = anetTcpServer(server.neterr, server.port, server.bindaddr);
if (server.fd == -1) { if (server.fd == -1) {
@ -878,6 +883,8 @@ static void loadServerConfig(char *filename) {
else { else {
err = "argument must be 'yes' or 'no'"; goto loaderr; err = "argument must be 'yes' or 'no'"; goto loaderr;
} }
} else if (!strcmp(argv[0],"pidfile") && argc == 2) {
server.pidfile = zstrdup(argv[1]);
} else { } else {
err = "Bad directive or wrong number of arguments"; goto loaderr; err = "Bad directive or wrong number of arguments"; goto loaderr;
} }
@ -918,9 +925,10 @@ static void freeClient(redisClient *c) {
assert(ln != NULL); assert(ln != NULL);
listDelNode(server.clients,ln); listDelNode(server.clients,ln);
if (c->flags & REDIS_SLAVE) { if (c->flags & REDIS_SLAVE) {
ln = listSearchKey(server.slaves,c); list *l = (c->flags & REDIS_MONITOR) ? server.monitors : server.slaves;
ln = listSearchKey(l,c);
assert(ln != NULL); assert(ln != NULL);
listDelNode(server.slaves,ln); listDelNode(l,ln);
} }
if (c->flags & REDIS_MASTER) { if (c->flags & REDIS_MASTER) {
server.master = NULL; server.master = NULL;
@ -1079,7 +1087,9 @@ static int processCommand(redisClient *c) {
dirty = server.dirty; dirty = server.dirty;
cmd->proc(c); cmd->proc(c);
if (server.dirty-dirty != 0 && listLength(server.slaves)) if (server.dirty-dirty != 0 && listLength(server.slaves))
replicationFeedSlaves(cmd,c->dictid,c->argv,c->argc); replicationFeedSlaves(server.slaves,cmd,c->dictid,c->argv,c->argc);
if (listLength(server.monitors))
replicationFeedSlaves(server.monitors,cmd,c->dictid,c->argv,c->argc);
server.stat_numcommands++; server.stat_numcommands++;
/* Prepare the client for the next command */ /* Prepare the client for the next command */
@ -1091,8 +1101,8 @@ static int processCommand(redisClient *c) {
return 1; return 1;
} }
static void replicationFeedSlaves(struct redisCommand *cmd, int dictid, robj **argv, int argc) { static void replicationFeedSlaves(list *slaves, struct redisCommand *cmd, int dictid, robj **argv, int argc) {
listNode *ln = server.slaves->head; listNode *ln = slaves->head;
robj *outv[REDIS_MAX_ARGS*4]; /* enough room for args, spaces, newlines */ robj *outv[REDIS_MAX_ARGS*4]; /* enough room for args, spaces, newlines */
int outc = 0, j; int outc = 0, j;
@ -1643,7 +1653,7 @@ static void pingCommand(redisClient *c) {
} }
static void echoCommand(redisClient *c) { static void echoCommand(redisClient *c) {
addReplySds(c,sdscatprintf(sdsempty(),"%d\r\n", addReplySds(c,sdscatprintf(sdsempty(),"$%d\r\n",
(int)sdslen(c->argv[1]->ptr))); (int)sdslen(c->argv[1]->ptr)));
addReply(c,c->argv[1]); addReply(c,c->argv[1]);
addReply(c,shared.crlf); addReply(c,shared.crlf);
@ -1660,7 +1670,7 @@ static void setGenericCommand(redisClient *c, int nx) {
dictReplace(c->dict,c->argv[1],c->argv[2]); dictReplace(c->dict,c->argv[1],c->argv[2]);
incrRefCount(c->argv[2]); incrRefCount(c->argv[2]);
} else { } else {
addReply(c,shared.zero); addReply(c,shared.czero);
return; return;
} }
} else { } else {
@ -1668,7 +1678,7 @@ static void setGenericCommand(redisClient *c, int nx) {
incrRefCount(c->argv[2]); incrRefCount(c->argv[2]);
} }
server.dirty++; server.dirty++;
addReply(c, nx ? shared.one : shared.ok); addReply(c, nx ? shared.cone : shared.ok);
} }
static void setCommand(redisClient *c) { static void setCommand(redisClient *c) {
@ -1684,14 +1694,14 @@ static void getCommand(redisClient *c) {
de = dictFind(c->dict,c->argv[1]); de = dictFind(c->dict,c->argv[1]);
if (de == NULL) { if (de == NULL) {
addReply(c,shared.nil); addReply(c,shared.nullbulk);
} else { } else {
robj *o = dictGetEntryVal(de); robj *o = dictGetEntryVal(de);
if (o->type != REDIS_STRING) { if (o->type != REDIS_STRING) {
addReply(c,shared.wrongtypeerrbulk); addReply(c,shared.wrongtypeerr);
} else { } else {
addReplySds(c,sdscatprintf(sdsempty(),"%d\r\n",(int)sdslen(o->ptr))); addReplySds(c,sdscatprintf(sdsempty(),"$%d\r\n",(int)sdslen(o->ptr)));
addReply(c,o); addReply(c,o);
addReply(c,shared.crlf); addReply(c,shared.crlf);
} }
@ -1702,18 +1712,18 @@ static void mgetCommand(redisClient *c) {
dictEntry *de; dictEntry *de;
int j; int j;
addReplySds(c,sdscatprintf(sdsempty(),"%d\r\n",c->argc-1)); addReplySds(c,sdscatprintf(sdsempty(),"*%d\r\n",c->argc-1));
for (j = 1; j < c->argc; j++) { for (j = 1; j < c->argc; j++) {
de = dictFind(c->dict,c->argv[j]); de = dictFind(c->dict,c->argv[j]);
if (de == NULL) { if (de == NULL) {
addReply(c,shared.minus1); addReply(c,shared.nullbulk);
} else { } else {
robj *o = dictGetEntryVal(de); robj *o = dictGetEntryVal(de);
if (o->type != REDIS_STRING) { if (o->type != REDIS_STRING) {
addReply(c,shared.minus1); addReply(c,shared.nullbulk);
} else { } else {
addReplySds(c,sdscatprintf(sdsempty(),"%d\r\n",(int)sdslen(o->ptr))); addReplySds(c,sdscatprintf(sdsempty(),"$%d\r\n",(int)sdslen(o->ptr)));
addReply(c,o); addReply(c,o);
addReply(c,shared.crlf); addReply(c,shared.crlf);
} }
@ -1751,6 +1761,7 @@ static void incrDecrCommand(redisClient *c, int incr) {
incrRefCount(c->argv[1]); incrRefCount(c->argv[1]);
} }
server.dirty++; server.dirty++;
addReply(c,shared.colon);
addReply(c,o); addReply(c,o);
addReply(c,shared.crlf); addReply(c,shared.crlf);
} }
@ -1778,9 +1789,9 @@ static void decrbyCommand(redisClient *c) {
static void delCommand(redisClient *c) { static void delCommand(redisClient *c) {
if (dictDelete(c->dict,c->argv[1]) == DICT_OK) { if (dictDelete(c->dict,c->argv[1]) == DICT_OK) {
server.dirty++; server.dirty++;
addReply(c,shared.one); addReply(c,shared.cone);
} else { } else {
addReply(c,shared.zero); addReply(c,shared.czero);
} }
} }
@ -1789,9 +1800,9 @@ static void existsCommand(redisClient *c) {
de = dictFind(c->dict,c->argv[1]); de = dictFind(c->dict,c->argv[1]);
if (de == NULL) if (de == NULL)
addReply(c,shared.zero); addReply(c,shared.czero);
else else
addReply(c,shared.one); addReply(c,shared.cone);
} }
static void selectCommand(redisClient *c) { static void selectCommand(redisClient *c) {
@ -1811,6 +1822,7 @@ static void randomkeyCommand(redisClient *c) {
if (de == NULL) { if (de == NULL) {
addReply(c,shared.crlf); addReply(c,shared.crlf);
} else { } else {
addReply(c,shared.plus);
addReply(c,dictGetEntryKey(de)); addReply(c,dictGetEntryKey(de));
addReply(c,shared.crlf); addReply(c,shared.crlf);
} }
@ -1841,18 +1853,18 @@ static void keysCommand(redisClient *c) {
} }
} }
dictReleaseIterator(di); dictReleaseIterator(di);
lenobj->ptr = sdscatprintf(sdsempty(),"%lu\r\n",keyslen+(numkeys ? (numkeys-1) : 0)); lenobj->ptr = sdscatprintf(sdsempty(),"$%lu\r\n",keyslen+(numkeys ? (numkeys-1) : 0));
addReply(c,shared.crlf); addReply(c,shared.crlf);
} }
static void dbsizeCommand(redisClient *c) { static void dbsizeCommand(redisClient *c) {
addReplySds(c, addReplySds(c,
sdscatprintf(sdsempty(),"%lu\r\n",dictGetHashTableUsed(c->dict))); sdscatprintf(sdsempty(),":%lu\r\n",dictGetHashTableUsed(c->dict)));
} }
static void lastsaveCommand(redisClient *c) { static void lastsaveCommand(redisClient *c) {
addReplySds(c, addReplySds(c,
sdscatprintf(sdsempty(),"%lu\r\n",server.lastsave)); sdscatprintf(sdsempty(),":%lu\r\n",server.lastsave));
} }
static void typeCommand(redisClient *c) { static void typeCommand(redisClient *c) {
@ -1861,14 +1873,14 @@ static void typeCommand(redisClient *c) {
de = dictFind(c->dict,c->argv[1]); de = dictFind(c->dict,c->argv[1]);
if (de == NULL) { if (de == NULL) {
type = "none"; type = "+none";
} else { } else {
robj *o = dictGetEntryVal(de); robj *o = dictGetEntryVal(de);
switch(o->type) { switch(o->type) {
case REDIS_STRING: type = "string"; break; case REDIS_STRING: type = "+string"; break;
case REDIS_LIST: type = "list"; break; case REDIS_LIST: type = "+list"; break;
case REDIS_SET: type = "set"; break; case REDIS_SET: type = "+set"; break;
default: type = "unknown"; break; default: type = "unknown"; break;
} }
} }
@ -1899,6 +1911,9 @@ static void bgsaveCommand(redisClient *c) {
static void shutdownCommand(redisClient *c) { static void shutdownCommand(redisClient *c) {
redisLog(REDIS_WARNING,"User requested shutdown, saving DB..."); redisLog(REDIS_WARNING,"User requested shutdown, saving DB...");
if (saveDb(server.dbfilename) == REDIS_OK) { if (saveDb(server.dbfilename) == REDIS_OK) {
if (server.daemonize) {
unlink(server.pidfile);
}
redisLog(REDIS_WARNING,"Server exit now, bye bye..."); redisLog(REDIS_WARNING,"Server exit now, bye bye...");
exit(1); exit(1);
} else { } else {
@ -1913,19 +1928,13 @@ static void renameGenericCommand(redisClient *c, int nx) {
/* To use the same key as src and dst is probably an error */ /* To use the same key as src and dst is probably an error */
if (sdscmp(c->argv[1]->ptr,c->argv[2]->ptr) == 0) { if (sdscmp(c->argv[1]->ptr,c->argv[2]->ptr) == 0) {
if (nx) addReply(c,shared.sameobjecterr);
addReply(c,shared.minus3);
else
addReplySds(c,sdsnew("-ERR src and dest key are the same\r\n"));
return; return;
} }
de = dictFind(c->dict,c->argv[1]); de = dictFind(c->dict,c->argv[1]);
if (de == NULL) { if (de == NULL) {
if (nx) addReply(c,shared.nokeyerr);
addReply(c,shared.minus1);
else
addReply(c,shared.nokeyerr);
return; return;
} }
o = dictGetEntryVal(de); o = dictGetEntryVal(de);
@ -1933,7 +1942,7 @@ static void renameGenericCommand(redisClient *c, int nx) {
if (dictAdd(c->dict,c->argv[2],o) == DICT_ERR) { if (dictAdd(c->dict,c->argv[2],o) == DICT_ERR) {
if (nx) { if (nx) {
decrRefCount(o); decrRefCount(o);
addReply(c,shared.zero); addReply(c,shared.czero);
return; return;
} }
dictReplace(c->dict,c->argv[2],o); dictReplace(c->dict,c->argv[2],o);
@ -1942,7 +1951,7 @@ static void renameGenericCommand(redisClient *c, int nx) {
} }
dictDelete(c->dict,c->argv[1]); dictDelete(c->dict,c->argv[1]);
server.dirty++; server.dirty++;
addReply(c,nx ? shared.one : shared.ok); addReply(c,nx ? shared.cone : shared.ok);
} }
static void renameCommand(redisClient *c) { static void renameCommand(redisClient *c) {
@ -1963,7 +1972,7 @@ static void moveCommand(redisClient *c) {
src = c->dict; src = c->dict;
srcid = c->dictid; srcid = c->dictid;
if (selectDb(c,atoi(c->argv[2]->ptr)) == REDIS_ERR) { if (selectDb(c,atoi(c->argv[2]->ptr)) == REDIS_ERR) {
addReply(c,shared.minus4); addReply(c,shared.outofrangeerr);
return; return;
} }
dst = c->dict; dst = c->dict;
@ -1973,14 +1982,14 @@ static void moveCommand(redisClient *c) {
/* If the user is moving using as target the same /* If the user is moving using as target the same
* DB as the source DB it is probably an error. */ * DB as the source DB it is probably an error. */
if (src == dst) { if (src == dst) {
addReply(c,shared.minus3); addReply(c,shared.sameobjecterr);
return; return;
} }
/* Check if the element exists and get a reference */ /* Check if the element exists and get a reference */
de = dictFind(c->dict,c->argv[1]); de = dictFind(c->dict,c->argv[1]);
if (!de) { if (!de) {
addReply(c,shared.zero); addReply(c,shared.czero);
return; return;
} }
@ -1988,7 +1997,7 @@ static void moveCommand(redisClient *c) {
key = dictGetEntryKey(de); key = dictGetEntryKey(de);
o = dictGetEntryVal(de); o = dictGetEntryVal(de);
if (dictAdd(dst,key,o) == DICT_ERR) { if (dictAdd(dst,key,o) == DICT_ERR) {
addReply(c,shared.zero); addReply(c,shared.czero);
return; return;
} }
incrRefCount(key); incrRefCount(key);
@ -1997,7 +2006,7 @@ static void moveCommand(redisClient *c) {
/* OK! key moved, free the entry in the source DB */ /* OK! key moved, free the entry in the source DB */
dictDelete(src,c->argv[1]); dictDelete(src,c->argv[1]);
server.dirty++; server.dirty++;
addReply(c,shared.one); addReply(c,shared.cone);
} }
/* =================================== Lists ================================ */ /* =================================== Lists ================================ */
@ -2050,15 +2059,15 @@ static void llenCommand(redisClient *c) {
de = dictFind(c->dict,c->argv[1]); de = dictFind(c->dict,c->argv[1]);
if (de == NULL) { if (de == NULL) {
addReply(c,shared.zero); addReply(c,shared.czero);
return; return;
} else { } else {
robj *o = dictGetEntryVal(de); robj *o = dictGetEntryVal(de);
if (o->type != REDIS_LIST) { if (o->type != REDIS_LIST) {
addReply(c,shared.minus2); addReply(c,shared.wrongtypeerr);
} else { } else {
l = o->ptr; l = o->ptr;
addReplySds(c,sdscatprintf(sdsempty(),"%d\r\n",listLength(l))); addReplySds(c,sdscatprintf(sdsempty(),":%d\r\n",listLength(l)));
} }
} }
} }
@ -2069,22 +2078,22 @@ static void lindexCommand(redisClient *c) {
de = dictFind(c->dict,c->argv[1]); de = dictFind(c->dict,c->argv[1]);
if (de == NULL) { if (de == NULL) {
addReply(c,shared.nil); addReply(c,shared.nullbulk);
} else { } else {
robj *o = dictGetEntryVal(de); robj *o = dictGetEntryVal(de);
if (o->type != REDIS_LIST) { if (o->type != REDIS_LIST) {
addReply(c,shared.wrongtypeerrbulk); addReply(c,shared.wrongtypeerr);
} else { } else {
list *list = o->ptr; list *list = o->ptr;
listNode *ln; listNode *ln;
ln = listIndex(list, index); ln = listIndex(list, index);
if (ln == NULL) { if (ln == NULL) {
addReply(c,shared.nil); addReply(c,shared.nullbulk);
} else { } else {
robj *ele = listNodeValue(ln); robj *ele = listNodeValue(ln);
addReplySds(c,sdscatprintf(sdsempty(),"%d\r\n",(int)sdslen(ele->ptr))); addReplySds(c,sdscatprintf(sdsempty(),"$%d\r\n",(int)sdslen(ele->ptr)));
addReply(c,ele); addReply(c,ele);
addReply(c,shared.crlf); addReply(c,shared.crlf);
} }
@ -2110,7 +2119,7 @@ static void lsetCommand(redisClient *c) {
ln = listIndex(list, index); ln = listIndex(list, index);
if (ln == NULL) { if (ln == NULL) {
addReplySds(c,sdsnew("-ERR index out of range\r\n")); addReply(c,shared.outofrangeerr);
} else { } else {
robj *ele = listNodeValue(ln); robj *ele = listNodeValue(ln);
@ -2129,12 +2138,12 @@ static void popGenericCommand(redisClient *c, int where) {
de = dictFind(c->dict,c->argv[1]); de = dictFind(c->dict,c->argv[1]);
if (de == NULL) { if (de == NULL) {
addReply(c,shared.nil); addReply(c,shared.nullbulk);
} else { } else {
robj *o = dictGetEntryVal(de); robj *o = dictGetEntryVal(de);
if (o->type != REDIS_LIST) { if (o->type != REDIS_LIST) {
addReply(c,shared.wrongtypeerrbulk); addReply(c,shared.wrongtypeerr);
} else { } else {
list *list = o->ptr; list *list = o->ptr;
listNode *ln; listNode *ln;
@ -2145,10 +2154,10 @@ static void popGenericCommand(redisClient *c, int where) {
ln = listLast(list); ln = listLast(list);
if (ln == NULL) { if (ln == NULL) {
addReply(c,shared.nil); addReply(c,shared.nullbulk);
} else { } else {
robj *ele = listNodeValue(ln); robj *ele = listNodeValue(ln);
addReplySds(c,sdscatprintf(sdsempty(),"%d\r\n",(int)sdslen(ele->ptr))); addReplySds(c,sdscatprintf(sdsempty(),"$%d\r\n",(int)sdslen(ele->ptr)));
addReply(c,ele); addReply(c,ele);
addReply(c,shared.crlf); addReply(c,shared.crlf);
listDelNode(list,ln); listDelNode(list,ln);
@ -2173,12 +2182,12 @@ static void lrangeCommand(redisClient *c) {
de = dictFind(c->dict,c->argv[1]); de = dictFind(c->dict,c->argv[1]);
if (de == NULL) { if (de == NULL) {
addReply(c,shared.nil); addReply(c,shared.nullmultibulk);
} else { } else {
robj *o = dictGetEntryVal(de); robj *o = dictGetEntryVal(de);
if (o->type != REDIS_LIST) { if (o->type != REDIS_LIST) {
addReply(c,shared.wrongtypeerrbulk); addReply(c,shared.wrongtypeerr);
} else { } else {
list *list = o->ptr; list *list = o->ptr;
listNode *ln; listNode *ln;
@ -2195,7 +2204,7 @@ static void lrangeCommand(redisClient *c) {
/* indexes sanity checks */ /* indexes sanity checks */
if (start > end || start >= llen) { if (start > end || start >= llen) {
/* Out of range start or start > end result in empty list */ /* Out of range start or start > end result in empty list */
addReply(c,shared.zero); addReply(c,shared.emptymultibulk);
return; return;
} }
if (end >= llen) end = llen-1; if (end >= llen) end = llen-1;
@ -2203,10 +2212,10 @@ static void lrangeCommand(redisClient *c) {
/* Return the result in form of a multi-bulk reply */ /* Return the result in form of a multi-bulk reply */
ln = listIndex(list, start); ln = listIndex(list, start);
addReplySds(c,sdscatprintf(sdsempty(),"%d\r\n",rangelen)); addReplySds(c,sdscatprintf(sdsempty(),"*%d\r\n",rangelen));
for (j = 0; j < rangelen; j++) { for (j = 0; j < rangelen; j++) {
ele = listNodeValue(ln); ele = listNodeValue(ln);
addReplySds(c,sdscatprintf(sdsempty(),"%d\r\n",(int)sdslen(ele->ptr))); addReplySds(c,sdscatprintf(sdsempty(),"$%d\r\n",(int)sdslen(ele->ptr)));
addReply(c,ele); addReply(c,ele);
addReply(c,shared.crlf); addReply(c,shared.crlf);
ln = ln->next; ln = ln->next;
@ -2276,7 +2285,7 @@ static void lremCommand(redisClient *c) {
robj *o = dictGetEntryVal(de); robj *o = dictGetEntryVal(de);
if (o->type != REDIS_LIST) { if (o->type != REDIS_LIST) {
addReply(c,shared.minus2); addReply(c,shared.wrongtypeerr);
} else { } else {
list *list = o->ptr; list *list = o->ptr;
listNode *ln, *next; listNode *ln, *next;
@ -2300,7 +2309,7 @@ static void lremCommand(redisClient *c) {
} }
ln = next; ln = next;
} }
addReplySds(c,sdscatprintf(sdsempty(),"%d\r\n",removed)); addReplySds(c,sdscatprintf(sdsempty(),":%d\r\n",removed));
} }
} }
} }
@ -2319,16 +2328,16 @@ static void saddCommand(redisClient *c) {
} else { } else {
set = dictGetEntryVal(de); set = dictGetEntryVal(de);
if (set->type != REDIS_SET) { if (set->type != REDIS_SET) {
addReply(c,shared.minus2); addReply(c,shared.wrongtypeerr);
return; return;
} }
} }
if (dictAdd(set->ptr,c->argv[2],NULL) == DICT_OK) { if (dictAdd(set->ptr,c->argv[2],NULL) == DICT_OK) {
incrRefCount(c->argv[2]); incrRefCount(c->argv[2]);
server.dirty++; server.dirty++;
addReply(c,shared.one); addReply(c,shared.cone);
} else { } else {
addReply(c,shared.zero); addReply(c,shared.czero);
} }
} }
@ -2337,20 +2346,20 @@ static void sremCommand(redisClient *c) {
de = dictFind(c->dict,c->argv[1]); de = dictFind(c->dict,c->argv[1]);
if (de == NULL) { if (de == NULL) {
addReply(c,shared.zero); addReply(c,shared.czero);
} else { } else {
robj *set; robj *set;
set = dictGetEntryVal(de); set = dictGetEntryVal(de);
if (set->type != REDIS_SET) { if (set->type != REDIS_SET) {
addReply(c,shared.minus2); addReply(c,shared.wrongtypeerr);
return; return;
} }
if (dictDelete(set->ptr,c->argv[2]) == DICT_OK) { if (dictDelete(set->ptr,c->argv[2]) == DICT_OK) {
server.dirty++; server.dirty++;
addReply(c,shared.one); addReply(c,shared.cone);
} else { } else {
addReply(c,shared.zero); addReply(c,shared.czero);
} }
} }
} }
@ -2360,19 +2369,19 @@ static void sismemberCommand(redisClient *c) {
de = dictFind(c->dict,c->argv[1]); de = dictFind(c->dict,c->argv[1]);
if (de == NULL) { if (de == NULL) {
addReply(c,shared.zero); addReply(c,shared.czero);
} else { } else {
robj *set; robj *set;
set = dictGetEntryVal(de); set = dictGetEntryVal(de);
if (set->type != REDIS_SET) { if (set->type != REDIS_SET) {
addReply(c,shared.minus2); addReply(c,shared.wrongtypeerr);
return; return;
} }
if (dictFind(set->ptr,c->argv[2])) if (dictFind(set->ptr,c->argv[2]))
addReply(c,shared.one); addReply(c,shared.cone);
else else
addReply(c,shared.zero); addReply(c,shared.czero);
} }
} }
@ -2382,15 +2391,15 @@ static void scardCommand(redisClient *c) {
de = dictFind(c->dict,c->argv[1]); de = dictFind(c->dict,c->argv[1]);
if (de == NULL) { if (de == NULL) {
addReply(c,shared.zero); addReply(c,shared.czero);
return; return;
} else { } else {
robj *o = dictGetEntryVal(de); robj *o = dictGetEntryVal(de);
if (o->type != REDIS_SET) { if (o->type != REDIS_SET) {
addReply(c,shared.minus2); addReply(c,shared.wrongtypeerr);
} else { } else {
s = o->ptr; s = o->ptr;
addReplySds(c,sdscatprintf(sdsempty(),"%d\r\n", addReplySds(c,sdscatprintf(sdsempty(),":%d\r\n",
dictGetHashTableUsed(s))); dictGetHashTableUsed(s)));
} }
} }
@ -2417,13 +2426,13 @@ static void sinterGenericCommand(redisClient *c, robj **setskeys, int setsnum, r
de = dictFind(c->dict,setskeys[j]); de = dictFind(c->dict,setskeys[j]);
if (!de) { if (!de) {
zfree(dv); zfree(dv);
addReply(c,dstkey ? shared.nokeyerr : shared.nil); addReply(c,shared.nokeyerr);
return; return;
} }
setobj = dictGetEntryVal(de); setobj = dictGetEntryVal(de);
if (setobj->type != REDIS_SET) { if (setobj->type != REDIS_SET) {
zfree(dv); zfree(dv);
addReply(c,dstkey ? shared.wrongtypeerr : shared.wrongtypeerrbulk); addReply(c,shared.wrongtypeerr);
return; return;
} }
dv[j] = setobj->ptr; dv[j] = setobj->ptr;
@ -2465,7 +2474,7 @@ static void sinterGenericCommand(redisClient *c, robj **setskeys, int setsnum, r
continue; /* at least one set does not contain the member */ continue; /* at least one set does not contain the member */
ele = dictGetEntryKey(de); ele = dictGetEntryKey(de);
if (!dstkey) { if (!dstkey) {
addReplySds(c,sdscatprintf(sdsempty(),"%d\r\n",sdslen(ele->ptr))); addReplySds(c,sdscatprintf(sdsempty(),"$%d\r\n",sdslen(ele->ptr)));
addReply(c,ele); addReply(c,ele);
addReply(c,shared.crlf); addReply(c,shared.crlf);
cardinality++; cardinality++;
@ -2477,7 +2486,7 @@ static void sinterGenericCommand(redisClient *c, robj **setskeys, int setsnum, r
dictReleaseIterator(di); dictReleaseIterator(di);
if (!dstkey) if (!dstkey)
lenobj->ptr = sdscatprintf(sdsempty(),"%d\r\n",cardinality); lenobj->ptr = sdscatprintf(sdsempty(),"*%d\r\n",cardinality);
else else
addReply(c,shared.ok); addReply(c,shared.ok);
zfree(dv); zfree(dv);
@ -2607,19 +2616,19 @@ static void sortCommand(redisClient *c) {
/* Lookup the key to sort. It must be of the right types */ /* Lookup the key to sort. It must be of the right types */
de = dictFind(c->dict,c->argv[1]); de = dictFind(c->dict,c->argv[1]);
if (de == NULL) { if (de == NULL) {
addReply(c,shared.nokeyerrbulk); addReply(c,shared.nokeyerr);
return; return;
} }
sortval = dictGetEntryVal(de); sortval = dictGetEntryVal(de);
if (sortval->type != REDIS_SET && sortval->type != REDIS_LIST) { if (sortval->type != REDIS_SET && sortval->type != REDIS_LIST) {
addReply(c,shared.wrongtypeerrbulk); addReply(c,shared.wrongtypeerr);
return; return;
} }
/* Create a list of operations to perform for every sorted element. /* Create a list of operations to perform for every sorted element.
* Operations can be GET/DEL/INCR/DECR */ * Operations can be GET/DEL/INCR/DECR */
operations = listCreate(); operations = listCreate();
listSetFreeMethod(operations,free); listSetFreeMethod(operations,zfree);
j = 2; j = 2;
/* Now we need to protect sortval incrementing its count, in the future /* Now we need to protect sortval incrementing its count, in the future
@ -2666,7 +2675,7 @@ static void sortCommand(redisClient *c) {
} else { } else {
decrRefCount(sortval); decrRefCount(sortval);
listRelease(operations); listRelease(operations);
addReply(c,shared.syntaxerrbulk); addReply(c,shared.syntaxerr);
return; return;
} }
j++; j++;
@ -2747,11 +2756,11 @@ static void sortCommand(redisClient *c) {
/* Send command output to the output buffer, performing the specified /* Send command output to the output buffer, performing the specified
* GET/DEL/INCR/DECR operations if any. */ * GET/DEL/INCR/DECR operations if any. */
outputlen = getop ? getop*(end-start+1) : end-start+1; outputlen = getop ? getop*(end-start+1) : end-start+1;
addReplySds(c,sdscatprintf(sdsempty(),"%d\r\n",outputlen)); addReplySds(c,sdscatprintf(sdsempty(),"*%d\r\n",outputlen));
for (j = start; j <= end; j++) { for (j = start; j <= end; j++) {
listNode *ln = operations->head; listNode *ln = operations->head;
if (!getop) { if (!getop) {
addReplySds(c,sdscatprintf(sdsempty(),"%d\r\n", addReplySds(c,sdscatprintf(sdsempty(),"$%d\r\n",
sdslen(vector[j].obj->ptr))); sdslen(vector[j].obj->ptr)));
addReply(c,vector[j].obj); addReply(c,vector[j].obj);
addReply(c,shared.crlf); addReply(c,shared.crlf);
@ -2763,9 +2772,9 @@ static void sortCommand(redisClient *c) {
if (sop->type == REDIS_SORT_GET) { if (sop->type == REDIS_SORT_GET) {
if (!val || val->type != REDIS_STRING) { if (!val || val->type != REDIS_STRING) {
addReply(c,shared.minus1); addReply(c,shared.nullbulk);
} else { } else {
addReplySds(c,sdscatprintf(sdsempty(),"%d\r\n", addReplySds(c,sdscatprintf(sdsempty(),"$%d\r\n",
sdslen(val->ptr))); sdslen(val->ptr)));
addReply(c,val); addReply(c,val);
addReply(c,shared.crlf); addReply(c,shared.crlf);
@ -2813,7 +2822,7 @@ static void infoCommand(redisClient *c) {
uptime, uptime,
uptime/(3600*24) uptime/(3600*24)
); );
addReplySds(c,sdscatprintf(sdsempty(),"%d\r\n",sdslen(info))); addReplySds(c,sdscatprintf(sdsempty(),"$%d\r\n",sdslen(info)));
addReplySds(c,info); addReplySds(c,info);
addReply(c,shared.crlf); addReply(c,shared.crlf);
} }
@ -2905,6 +2914,9 @@ static void syncCommand(redisClient *c) {
time_t start = time(NULL); time_t start = time(NULL);
char sizebuf[32]; char sizebuf[32];
/* ignore SYNC if aleady slave or in monitor mode */
if (c->flags & REDIS_SLAVE) return;
redisLog(REDIS_NOTICE,"Slave ask for syncronization"); redisLog(REDIS_NOTICE,"Slave ask for syncronization");
if (flushClientOutput(c) == REDIS_ERR || saveDb(server.dbfilename) != REDIS_OK) if (flushClientOutput(c) == REDIS_ERR || saveDb(server.dbfilename) != REDIS_OK)
goto closeconn; goto closeconn;
@ -2913,7 +2925,7 @@ static void syncCommand(redisClient *c) {
if (fd == -1 || fstat(fd,&sb) == -1) goto closeconn; if (fd == -1 || fstat(fd,&sb) == -1) goto closeconn;
len = sb.st_size; len = sb.st_size;
snprintf(sizebuf,32,"%d\r\n",len); snprintf(sizebuf,32,"$%d\r\n",len);
if (syncWrite(c->fd,sizebuf,strlen(sizebuf),5) == -1) goto closeconn; if (syncWrite(c->fd,sizebuf,strlen(sizebuf),5) == -1) goto closeconn;
while(len) { while(len) {
char buf[1024]; char buf[1024];
@ -2965,7 +2977,7 @@ static int syncWithMaster(void) {
strerror(errno)); strerror(errno));
return REDIS_ERR; return REDIS_ERR;
} }
dumpsize = atoi(buf); dumpsize = atoi(buf+1);
redisLog(REDIS_NOTICE,"Receiving %d bytes data dump from MASTER",dumpsize); redisLog(REDIS_NOTICE,"Receiving %d bytes data dump from MASTER",dumpsize);
/* Read the bulk write data on a temp file */ /* Read the bulk write data on a temp file */
snprintf(tmpfile,256,"temp-%d.%ld.rdb",(int)time(NULL),(long int)random()); snprintf(tmpfile,256,"temp-%d.%ld.rdb",(int)time(NULL),(long int)random());
@ -3014,6 +3026,16 @@ static int syncWithMaster(void) {
return REDIS_OK; return REDIS_OK;
} }
static void monitorCommand(redisClient *c) {
/* ignore MONITOR if aleady slave or in monitor mode */
if (c->flags & REDIS_SLAVE) return;
c->flags |= (REDIS_SLAVE|REDIS_MONITOR);
c->slaveseldb = 0;
if (!listAddNodeTail(server.monitors,c)) oom("listAddNodeTail");
addReply(c,shared.ok);
}
/* =================================== Main! ================================ */ /* =================================== Main! ================================ */
static void daemonize(void) { static void daemonize(void) {
@ -3033,7 +3055,7 @@ static void daemonize(void) {
if (fd > STDERR_FILENO) close(fd); if (fd > STDERR_FILENO) close(fd);
} }
/* Try to write the pid file */ /* Try to write the pid file */
fp = fopen("/var/run/redis.pid","w"); fp = fopen(server.pidfile,"w");
if (fp) { if (fp) {
fprintf(fp,"%d\n",getpid()); fprintf(fp,"%d\n",getpid());
fclose(fp); fclose(fp);
@ -3056,7 +3078,7 @@ int main(int argc, char **argv) {
redisLog(REDIS_NOTICE,"DB loaded from disk"); redisLog(REDIS_NOTICE,"DB loaded from disk");
if (aeCreateFileEvent(server.el, server.fd, AE_READABLE, if (aeCreateFileEvent(server.el, server.fd, AE_READABLE,
acceptHandler, NULL, NULL) == AE_ERR) oom("creating file event"); acceptHandler, NULL, NULL) == AE_ERR) oom("creating file event");
redisLog(REDIS_NOTICE,"The server is now ready to accept connections"); redisLog(REDIS_NOTICE,"The server is now ready to accept connections on port %d", server.port);
aeMain(server.el); aeMain(server.el);
aeDeleteEventLoop(server.el); aeDeleteEventLoop(server.el);
return 0; return 0;

View File

@ -4,6 +4,10 @@
# Note that Redis will write a pid file in /var/run/redis.pid when daemonized. # Note that Redis will write a pid file in /var/run/redis.pid when daemonized.
daemonize no daemonize no
# When run as a daemon, Redis write a pid file in /var/run/redis.pid by default.
# You can specify a custom pid file location here.
pidfile /var/run/redis.pid
# Accept connections on the specified port, default is 6379 # Accept connections on the specified port, default is 6379
port 6379 port 6379

View File

@ -124,16 +124,16 @@ proc main {server port} {
puts -nonewline $fd "SET k1 4\r\nxyzk\r\nGET k1\r\nPING\r\n" puts -nonewline $fd "SET k1 4\r\nxyzk\r\nGET k1\r\nPING\r\n"
flush $fd flush $fd
set res {} set res {}
append res [string match +OK* [redis_read_retcode $fd]] append res [string match OK* [redis_read_reply $fd]]
append res [redis_bulk_read $fd] append res [redis_read_reply $fd]
append res [string match +PONG* [redis_read_retcode $fd]] append res [string match PONG* [redis_read_reply $fd]]
format $res format $res
} {1xyzk1} } {1xyzk1}
test {Non existing command} { test {Non existing command} {
puts -nonewline $fd "foo\r\n" puts -nonewline $fd "foo\r\n"
flush $fd flush $fd
string match -ERR* [redis_read_retcode $fd] string match ERR* [redis_read_reply $fd]
} {1} } {1}
test {Basic LPUSH, RPUSH, LLENGTH, LINDEX} { test {Basic LPUSH, RPUSH, LLENGTH, LINDEX} {
@ -181,19 +181,19 @@ proc main {server port} {
redis_del $fd mylist redis_del $fd mylist
redis_set $fd mylist foobar redis_set $fd mylist foobar
redis_llen $fd mylist redis_llen $fd mylist
} {-2} } {ERR*}
test {LINDEX against non-list value error} { test {LINDEX against non-list value error} {
redis_lindex $fd mylist 0 redis_lindex $fd mylist 0
} {*ERROR*} } {ERR*}
test {LPUSH against non-list value error} { test {LPUSH against non-list value error} {
redis_lpush $fd mylist 0 redis_lpush $fd mylist 0
} {-ERR*} } {ERR*}
test {RPUSH against non-list value error} { test {RPUSH against non-list value error} {
redis_rpush $fd mylist 0 redis_rpush $fd mylist 0
} {-ERR*} } {ERR*}
test {RENAME basic usage} { test {RENAME basic usage} {
redis_set $fd mykey hello redis_set $fd mykey hello
@ -236,11 +236,11 @@ proc main {server port} {
test {RENAME against non existing source key} { test {RENAME against non existing source key} {
redis_rename $fd nokey foobar redis_rename $fd nokey foobar
} {-ERR*} } {ERR*}
test {RENAME where source and dest key is the same} { test {RENAME where source and dest key is the same} {
redis_rename $fd mykey mykey redis_rename $fd mykey mykey
} {-ERR*} } {ERR*}
test {DEL all keys again (DB 0)} { test {DEL all keys again (DB 0)} {
foreach key [redis_keys $fd *] { foreach key [redis_keys $fd *] {
@ -309,7 +309,7 @@ proc main {server port} {
test {LPOP against non list value} { test {LPOP against non list value} {
redis_set $fd notalist foo redis_set $fd notalist foo
redis_lpop $fd notalist redis_lpop $fd notalist
} {*ERROR*against*} } {ERR*kind*}
test {Mass LPUSH/LPOP} { test {Mass LPUSH/LPOP} {
set sum 0 set sum 0
@ -363,16 +363,16 @@ proc main {server port} {
test {LSET out of range index} { test {LSET out of range index} {
redis_lset $fd mylist 10 foo redis_lset $fd mylist 10 foo
} {-ERR*range*} } {ERR*range*}
test {LSET against non existing key} { test {LSET against non existing key} {
redis_lset $fd nosuchkey 10 foo redis_lset $fd nosuchkey 10 foo
} {-ERR*key*} } {ERR*key*}
test {LSET against non list value} { test {LSET against non list value} {
redis_set $fd nolist foobar redis_set $fd nolist foobar
redis_lset $fd nolist 0 foo redis_lset $fd nolist 0 foo
} {-ERR*value*} } {ERR*value*}
test {SADD, SCARD, SISMEMBER, SMEMBERS basics} { test {SADD, SCARD, SISMEMBER, SMEMBERS basics} {
redis_sadd $fd myset foo redis_sadd $fd myset foo
@ -391,7 +391,7 @@ proc main {server port} {
test {SADD against non set} { test {SADD against non set} {
redis_sadd $fd mylist foo redis_sadd $fd mylist foo
} {-2} } {ERR*kind*}
test {SREM basics} { test {SREM basics} {
redis_sadd $fd myset ciao redis_sadd $fd myset ciao
@ -431,7 +431,7 @@ proc main {server port} {
redis_set $fd myemptykey {} redis_set $fd myemptykey {}
redis_set $fd mynormalkey {blablablba} redis_set $fd mynormalkey {blablablba}
redis_save $fd redis_save $fd
} {+OK} } {OK}
test {Create a random list} { test {Create a random list} {
set tosort {} set tosort {}
@ -606,225 +606,255 @@ proc redis_readnl {fd len} {
return $buf return $buf
} }
proc redis_bulk_read {fd {multi 0}} { proc redis_bulk_read {fd} {
set count [redis_read_integer $fd] set count [redis_read_line $fd]
if {$count eq {nil}} return {} if {$count == -1} return {}
if {$multi && $count == -1} return {} set buf [redis_readnl $fd $count]
set len [expr {abs($count)}]
set buf [redis_readnl $fd $len]
if {$count < 0} {return "***ERROR*** $buf"}
return $buf return $buf
} }
proc redis_multi_bulk_read fd { proc redis_multi_bulk_read fd {
set count [redis_read_integer $fd] set count [redis_read_line $fd]
if {$count eq {nil}} return {} if {$count == -1} return {}
if {$count < 0} {
set len [expr {abs($count)}]
set buf [redis_readnl $fd $len]
return "***ERROR*** $buf"
}
set l {} set l {}
for {set i 0} {$i < $count} {incr i} { for {set i 0} {$i < $count} {incr i} {
lappend l [redis_bulk_read $fd 1] lappend l [redis_read_reply $fd]
} }
return $l return $l
} }
proc redis_read_retcode fd { proc redis_read_line fd {
set retcode [string trim [gets $fd]] string trim [gets $fd]
# puts "S: $retcode"
return $retcode
} }
proc redis_read_integer fd { proc redis_read_reply fd {
string trim [gets $fd] set type [read $fd 1]
if {$type eq {:}} {
redis_read_line $fd
} elseif {$type eq {-}} {
redis_read_line $fd
} elseif {$type eq {+}} {
redis_read_line $fd
} elseif {$type eq {$}} {
redis_bulk_read $fd
} elseif {$type eq {*}} {
redis_multi_bulk_read $fd
} else {
error "Bad protocol: $type as initial reply byte"
}
} }
### Actual API ### ### Actual API ###
proc redis_set {fd key val} { proc redis_set {fd key val} {
redis_writenl $fd "set $key [string length $val]\r\n$val" redis_writenl $fd "set $key [string length $val]\r\n$val"
redis_read_retcode $fd redis_read_reply $fd
} }
proc redis_setnx {fd key val} { proc redis_setnx {fd key val} {
redis_writenl $fd "setnx $key [string length $val]\r\n$val" redis_writenl $fd "setnx $key [string length $val]\r\n$val"
redis_read_integer $fd redis_read_reply $fd
} }
proc redis_get {fd key} { proc redis_get {fd key} {
redis_writenl $fd "get $key" redis_writenl $fd "get $key"
redis_bulk_read $fd redis_read_reply $fd
} }
proc redis_select {fd id} { proc redis_select {fd id} {
redis_writenl $fd "select $id" redis_writenl $fd "select $id"
redis_read_retcode $fd redis_read_reply $fd
} }
proc redis_move {fd key id} { proc redis_move {fd key id} {
redis_writenl $fd "move $key $id" redis_writenl $fd "move $key $id"
redis_read_integer $fd redis_read_reply $fd
} }
proc redis_del {fd key} { proc redis_del {fd key} {
redis_writenl $fd "del $key" redis_writenl $fd "del $key"
redis_read_integer $fd redis_read_reply $fd
} }
proc redis_keys {fd pattern} { proc redis_keys {fd pattern} {
redis_writenl $fd "keys $pattern" redis_writenl $fd "keys $pattern"
split [redis_bulk_read $fd] split [redis_read_reply $fd]
} }
proc redis_dbsize {fd} { proc redis_dbsize {fd} {
redis_writenl $fd "dbsize" redis_writenl $fd "dbsize"
redis_read_integer $fd redis_read_reply $fd
} }
proc redis_incr {fd key} { proc redis_incr {fd key} {
redis_writenl $fd "incr $key" redis_writenl $fd "incr $key"
redis_read_integer $fd redis_read_reply $fd
} }
proc redis_decr {fd key} { proc redis_decr {fd key} {
redis_writenl $fd "decr $key" redis_writenl $fd "decr $key"
redis_read_integer $fd redis_read_reply $fd
} }
proc redis_exists {fd key} { proc redis_exists {fd key} {
redis_writenl $fd "exists $key" redis_writenl $fd "exists $key"
redis_read_integer $fd redis_read_reply $fd
} }
proc redis_lpush {fd key val} { proc redis_lpush {fd key val} {
redis_writenl $fd "lpush $key [string length $val]\r\n$val" redis_writenl $fd "lpush $key [string length $val]\r\n$val"
redis_read_retcode $fd redis_read_reply $fd
} }
proc redis_rpush {fd key val} { proc redis_rpush {fd key val} {
redis_writenl $fd "rpush $key [string length $val]\r\n$val" redis_writenl $fd "rpush $key [string length $val]\r\n$val"
redis_read_retcode $fd redis_read_reply $fd
} }
proc redis_llen {fd key} { proc redis_llen {fd key} {
redis_writenl $fd "llen $key" redis_writenl $fd "llen $key"
redis_read_integer $fd redis_read_reply $fd
} }
proc redis_scard {fd key} { proc redis_scard {fd key} {
redis_writenl $fd "scard $key" redis_writenl $fd "scard $key"
redis_read_integer $fd redis_read_reply $fd
} }
proc redis_lindex {fd key index} { proc redis_lindex {fd key index} {
redis_writenl $fd "lindex $key $index" redis_writenl $fd "lindex $key $index"
redis_bulk_read $fd redis_read_reply $fd
} }
proc redis_lrange {fd key first last} { proc redis_lrange {fd key first last} {
redis_writenl $fd "lrange $key $first $last" redis_writenl $fd "lrange $key $first $last"
redis_multi_bulk_read $fd redis_read_reply $fd
} }
proc redis_mget {fd args} { proc redis_mget {fd args} {
redis_writenl $fd "mget [join $args]" redis_writenl $fd "mget [join $args]"
redis_multi_bulk_read $fd redis_read_reply $fd
} }
proc redis_sort {fd key {params {}}} { proc redis_sort {fd key {params {}}} {
redis_writenl $fd "sort $key $params" redis_writenl $fd "sort $key $params"
redis_multi_bulk_read $fd redis_read_reply $fd
} }
proc redis_ltrim {fd key first last} { proc redis_ltrim {fd key first last} {
redis_writenl $fd "ltrim $key $first $last" redis_writenl $fd "ltrim $key $first $last"
redis_read_retcode $fd redis_read_reply $fd
} }
proc redis_rename {fd key1 key2} { proc redis_rename {fd key1 key2} {
redis_writenl $fd "rename $key1 $key2" redis_writenl $fd "rename $key1 $key2"
redis_read_retcode $fd redis_read_reply $fd
} }
proc redis_renamenx {fd key1 key2} { proc redis_renamenx {fd key1 key2} {
redis_writenl $fd "renamenx $key1 $key2" redis_writenl $fd "renamenx $key1 $key2"
redis_read_integer $fd redis_read_reply $fd
} }
proc redis_lpop {fd key} { proc redis_lpop {fd key} {
redis_writenl $fd "lpop $key" redis_writenl $fd "lpop $key"
redis_bulk_read $fd redis_read_reply $fd
} }
proc redis_rpop {fd key} { proc redis_rpop {fd key} {
redis_writenl $fd "rpop $key" redis_writenl $fd "rpop $key"
redis_bulk_read $fd redis_read_reply $fd
} }
proc redis_lset {fd key index val} { proc redis_lset {fd key index val} {
redis_writenl $fd "lset $key $index [string length $val]\r\n$val" redis_writenl $fd "lset $key $index [string length $val]\r\n$val"
redis_read_retcode $fd redis_read_reply $fd
} }
proc redis_sadd {fd key val} { proc redis_sadd {fd key val} {
redis_writenl $fd "sadd $key [string length $val]\r\n$val" redis_writenl $fd "sadd $key [string length $val]\r\n$val"
redis_read_integer $fd redis_read_reply $fd
} }
proc redis_srem {fd key val} { proc redis_srem {fd key val} {
redis_writenl $fd "srem $key [string length $val]\r\n$val" redis_writenl $fd "srem $key [string length $val]\r\n$val"
redis_read_integer $fd redis_read_reply $fd
} }
proc redis_sismember {fd key val} { proc redis_sismember {fd key val} {
redis_writenl $fd "sismember $key [string length $val]\r\n$val" redis_writenl $fd "sismember $key [string length $val]\r\n$val"
redis_read_integer $fd redis_read_reply $fd
} }
proc redis_sinter {fd args} { proc redis_sinter {fd args} {
redis_writenl $fd "sinter [join $args]\r\n" redis_writenl $fd "sinter [join $args]"
redis_multi_bulk_read $fd redis_read_reply $fd
} }
proc redis_sinterstore {fd args} { proc redis_sinterstore {fd args} {
redis_writenl $fd "sinterstore [join $args]\r\n" redis_writenl $fd "sinterstore [join $args]"
redis_read_retcode $fd redis_read_reply $fd
} }
proc redis_smembers {fd key} { proc redis_smembers {fd key} {
redis_writenl $fd "smembers $key\r\n" redis_writenl $fd "smembers $key"
redis_multi_bulk_read $fd redis_read_reply $fd
} }
proc redis_echo {fd str} { proc redis_echo {fd str} {
redis_writenl $fd "echo [string length $str]\r\n$str\r\n" redis_writenl $fd "echo [string length $str]\r\n$str"
redis_writenl $fd "smembers $key\r\n" redis_read_reply $fd
} }
proc redis_save {fd} { proc redis_save {fd} {
redis_writenl $fd "save\r\n" redis_writenl $fd "save"
redis_read_retcode $fd redis_read_reply $fd
} }
proc redis_flushall {fd} { proc redis_flushall {fd} {
redis_writenl $fd "flushall\r\n" redis_writenl $fd "flushall"
redis_read_retcode $fd redis_read_reply $fd
} }
proc redis_flushdb {fd} { proc redis_flushdb {fd} {
redis_writenl $fd "flushdb\r\n" redis_writenl $fd "flushdb"
redis_read_retcode $fd redis_read_reply $fd
} }
proc redis_lrem {fd key count val} { proc redis_lrem {fd key count val} {
redis_writenl $fd "lrem $key $count [string length $val]\r\n$val" redis_writenl $fd "lrem $key $count [string length $val]\r\n$val"
redis_read_integer $fd redis_read_reply $fd
}
proc stress {} {
set fd [socket 127.0.0.1 6379]
fconfigure $fd -translation binary
redis_flushall $fd
while 1 {
set randkey [expr int(rand()*10000)]
set randval [expr int(rand()*10000)]
set randidx0 [expr int(rand()*10)]
set randidx1 [expr int(rand()*10)]
set cmd [expr int(rand()*10)]
if {$cmd == 0} {redis_set $fd $randkey $randval}
if {$cmd == 1} {redis_get $fd $randkey}
if {$cmd == 2} {redis_incr $fd $randkey}
if {$cmd == 3} {redis_lpush $fd $randkey $randval}
if {$cmd == 4} {redis_rpop $fd $randkey}
if {$cmd == 5} {redis_del $fd $randkey}
if {$cmd == 6} {redis_lrange $fd $randkey $randidx0 $randidx1}
if {$cmd == 7} {redis_ltrim $fd $randkey $randidx0 $randidx1}
if {$cmd == 8} {redis_lindex $fd $randkey $randidx0}
if {$cmd == 9} {redis_lset $fd $randkey $randidx0 $randval}
flush stdout
}
close $fd
} }
if {[llength $argv] == 0} { if {[llength $argv] == 0} {
main 127.0.0.1 6379 main 127.0.0.1 6379
} elseif {[llength $argv] == 1 && [lindex $argv 0] eq {stress}} {
stress
} else { } else {
main [lindex $argv 0] [lindex $argv 1] main [lindex $argv 0] [lindex $argv 1]
} }

View File

@ -33,7 +33,7 @@
void *zmalloc(size_t size); void *zmalloc(size_t size);
void *zrealloc(void *ptr, size_t size); void *zrealloc(void *ptr, size_t size);
void *zfree(void *ptr); void zfree(void *ptr);
char *zstrdup(const char *s); char *zstrdup(const char *s);
size_t zmalloc_used_memory(void); size_t zmalloc_used_memory(void);