├── LICENSE ├── Makefile ├── README.md └── src ├── .gitignore ├── client.cc ├── exec.cc ├── libuv-1.10.1.tar.gz ├── match.cc ├── rocksdb-4.13.tar.gz ├── server.cc ├── server.h └── util.cc /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Josh Baker 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: rocksdb libuv 2 | @g++ -O2 -std=c++11 $(FLAGS) \ 3 | -DROCKSDB_VERSION="\"4.13"\" \ 4 | -DSERVER_VERSION="\"0.1.0"\" \ 5 | -DLIBUV_VERSION="\"1.10.1"\" \ 6 | -Isrc/rocksdb-4.13/include/ \ 7 | -Isrc/libuv-1.10.1/build/include/ \ 8 | -pthread \ 9 | -o rocksdb-server \ 10 | src/server.cc src/client.cc src/exec.cc src/match.cc src/util.cc \ 11 | src/rocksdb-4.13/librocksdb.a \ 12 | src/rocksdb-4.13/libbz2.a \ 13 | src/rocksdb-4.13/libz.a \ 14 | src/rocksdb-4.13/libsnappy.a \ 15 | src/libuv-1.10.1/build/lib/libuv.a 16 | clean: 17 | rm -f rocksdb-server 18 | rm -rf src/libuv-1.10.1/ 19 | rm -rf src/rocksdb-4.13/ 20 | install: all 21 | cp rocksdb-server /usr/local/bin 22 | uninstall: 23 | rm -f /usr/local/bin/rocksdb-server 24 | 25 | # libuv 26 | libuv: src/libuv-1.10.1/build/lib/libuv.a 27 | src/libuv-1.10.1/build/lib/libuv.a: 28 | cd src && tar xf libuv-1.10.1.tar.gz 29 | cd src/libuv-1.10.1 && sh autogen.sh 30 | mkdir -p src/libuv-1.10.1/build 31 | cd src/libuv-1.10.1/build && ../configure --prefix=$$(pwd) 32 | make -C src/libuv-1.10.1/build install 33 | 34 | 35 | # rocksdb 36 | rocksdb: src/rocksdb-4.13 \ 37 | src/rocksdb-4.13/librocksdb.a \ 38 | src/rocksdb-4.13/libz.a \ 39 | src/rocksdb-4.13/libbz2.a \ 40 | src/rocksdb-4.13/libsnappy.a 41 | src/rocksdb-4.13: 42 | cd src && tar xf rocksdb-4.13.tar.gz 43 | src/rocksdb-4.13/librocksdb.a: 44 | DEBUG_LEVEL=0 make -C src/rocksdb-4.13 static_lib 45 | src/rocksdb-4.13/libz.a: 46 | DEBUG_LEVEL=0 make -C src/rocksdb-4.13 libz.a 47 | src/rocksdb-4.13/libbz2.a: 48 | DEBUG_LEVEL=0 make -C src/rocksdb-4.13 libbz2.a 49 | src/rocksdb-4.13/libsnappy.a: 50 | DEBUG_LEVEL=0 make -C src/rocksdb-4.13 libsnappy.a 51 | 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RocksDB-Server 2 | [Fast](#benchmarks) and simple [Redis](https://redis.io/) clone written in C using [RocksDB](http://rocksdb.org/) as a backend. 3 | 4 | ## Supported commands 5 | 6 | ``` 7 | SET key value 8 | GET key 9 | DEL key 10 | KEYS * 11 | SCAN cursor [MATCH pattern] [COUNT count] 12 | FLUSHDB 13 | ``` 14 | 15 | Any [Redis client](https://redis.io/clients) should work. 16 | 17 | ## Building 18 | 19 | Tested on Mac and Linux (Ubuntu), though should work on other platforms. 20 | Please let me know if you run into build problems. 21 | 22 | Requires `libtool` and `automake`. 23 | 24 | Ubuntu users: 25 | ``` 26 | $ apt-get install build-esstential libtool automake 27 | ``` 28 | 29 | To build everything simply: 30 | 31 | ``` 32 | $ make 33 | ``` 34 | 35 | ## Running 36 | 37 | ``` 38 | usage: ./rocksdb-server [-d data_path] [-p tcp_port] [--sync] [--inmem] 39 | ``` 40 | - `-d` -- The database path. Default `./data/` 41 | - `-p` -- TCP server port. Default 5555. 42 | - `--inmem` -- The active dataset is stored in memory. 43 | - `--sync` -- Execute fsync after every SET. More durable, but much slower. 44 | 45 | ## Benchmarks 46 | 47 | **Redis** 48 | 49 | ``` 50 | $ redis-server 51 | ``` 52 | ``` 53 | $ redis-benchmark -p 6379 -t set,get -n 10000000 -q -P 256 -c 256 54 | SET: 947867.38 requests per second 55 | GET: 1394700.12 requests per second 56 | ``` 57 | 58 | **RocksDB** 59 | 60 | ``` 61 | $ rocksdb-server 62 | ``` 63 | ``` 64 | $ redis-benchmark -p 5555 -t set,get -n 10000000 -q -P 256 -c 256 65 | SET: 419815.28 requests per second 66 | GET: 2132196.00 requests per second 67 | ``` 68 | 69 | *Running on a MacBook Pro 15" 2.8 GHz Intel Core i7 using Go 1.7* 70 | 71 | 72 | ## Contact 73 | Josh Baker [@tidwall](http://twitter.com/tidwall) 74 | 75 | ## License 76 | RocksDB-Server source code is available under the MIT [License](/LICENSE). 77 | -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | libuv-1.10.1/ 2 | rocksdb-*/ 3 | 4 | -------------------------------------------------------------------------------- /src/client.cc: -------------------------------------------------------------------------------- 1 | #include "server.h" 2 | 3 | client *client_new(){ 4 | client *c = (client*)calloc(1, sizeof(client)); 5 | if (!c){ 6 | err(1, "malloc"); 7 | } 8 | c->worker.data = c; // self reference 9 | return c; 10 | } 11 | 12 | void client_free(client *c){ 13 | if (!c){ 14 | return; 15 | } 16 | if (c->buf){ 17 | free(c->buf); 18 | } 19 | if (c->args){ 20 | free(c->args); 21 | } 22 | if (c->args_size){ 23 | free(c->args_size); 24 | } 25 | if (c->output){ 26 | free(c->output); 27 | } 28 | if (c->tmp_err){ 29 | free(c->tmp_err); 30 | } 31 | free(c); 32 | } 33 | 34 | void on_close(uv_handle_t *stream){ 35 | client_free((client*)stream); 36 | } 37 | 38 | void client_close(client *c){ 39 | uv_close((uv_handle_t *)&c->tcp, on_close); 40 | } 41 | inline void client_output_require(client *c, size_t siz){ 42 | if (c->output_cap < siz){ 43 | while (c->output_cap < siz){ 44 | if (c->output_cap == 0){ 45 | c->output_cap = 1; 46 | }else{ 47 | c->output_cap *= 2; 48 | } 49 | } 50 | c->output = (char*)realloc(c->output, c->output_cap); 51 | if (!c->output){ 52 | err(1, "malloc"); 53 | } 54 | } 55 | } 56 | void client_write(client *c, const char *data, int n){ 57 | client_output_require(c, c->output_len+n); 58 | memcpy(c->output+c->output_len, data, n); 59 | c->output_len+=n; 60 | } 61 | 62 | void client_clear(client *c){ 63 | c->output_len = 0; 64 | c->output_offset = 0; 65 | } 66 | 67 | void client_write_byte(client *c, char b){ 68 | client_output_require(c, c->output_len+1); 69 | c->output[c->output_len++] = b; 70 | } 71 | 72 | void client_write_bulk(client *c, const char *data, int n){ 73 | char h[32]; 74 | sprintf(h, "$%d\r\n", n); 75 | client_write(c, h, strlen(h)); 76 | client_write(c, data, n); 77 | client_write_byte(c, '\r'); 78 | client_write_byte(c, '\n'); 79 | } 80 | 81 | void client_write_multibulk(client *c, int n){ 82 | char h[32]; 83 | sprintf(h, "*%d\r\n", n); 84 | client_write(c, h, strlen(h)); 85 | } 86 | 87 | void client_write_int(client *c, int n){ 88 | char h[32]; 89 | sprintf(h, ":%d\r\n", n); 90 | client_write(c, h, strlen(h)); 91 | } 92 | 93 | void client_write_error(client *c, error err){ 94 | client_write(c, "-ERR ", 5); 95 | client_write(c, err, strlen(err)); 96 | client_write_byte(c, '\r'); 97 | client_write_byte(c, '\n'); 98 | } 99 | 100 | 101 | void client_flush_offset(client *c, int offset){ 102 | if (c->output_len-offset <= 0){ 103 | return; 104 | } 105 | uv_buf_t buf = {0}; 106 | buf.base = c->output+offset; 107 | buf.len = c->output_len-offset; 108 | uv_write(&c->req, (uv_stream_t *)&c->tcp, &buf, 1, NULL); 109 | c->output_len = 0; 110 | } 111 | 112 | 113 | void client_flush(client *c){ 114 | client_flush_offset(c, 0); 115 | } 116 | 117 | void client_err_alloc(client *c, int n){ 118 | if (c->tmp_err){ 119 | free(c->tmp_err); 120 | } 121 | c->tmp_err = (char*)malloc(n); 122 | if (!c->tmp_err){ 123 | err(1, "malloc"); 124 | } 125 | memset(c->tmp_err, 0, n); 126 | } 127 | 128 | error client_err_expected_got(client *c, char c1, char c2){ 129 | client_err_alloc(c, 64); 130 | sprintf(c->tmp_err, "Protocol error: expected '%c', got '%c'", c1, c2); 131 | return c->tmp_err; 132 | } 133 | 134 | error client_err_unknown_command(client *c, const char *name, int count){ 135 | client_err_alloc(c, count+64); 136 | c->tmp_err[0] = 0; 137 | strcat(c->tmp_err, "unknown command '"); 138 | strncat(c->tmp_err, name, count); 139 | strcat(c->tmp_err, "'"); 140 | return c->tmp_err; 141 | } 142 | void client_append_arg(client *c, const char *data, int nbyte){ 143 | if (c->args_cap==c->args_len){ 144 | if (c->args_cap==0){ 145 | c->args_cap=1; 146 | }else{ 147 | c->args_cap*=2; 148 | } 149 | c->args = (const char**)realloc(c->args, c->args_cap*sizeof(const char *)); 150 | if (!c->args){ 151 | err(1, "malloc"); 152 | } 153 | c->args_size = (int*)realloc(c->args_size, c->args_cap*sizeof(int)); 154 | if (!c->args_size){ 155 | err(1, "malloc"); 156 | } 157 | } 158 | c->args[c->args_len] = data; 159 | c->args_size[c->args_len] = nbyte; 160 | c->args_len++; 161 | } 162 | 163 | error client_parse_telnet_command(client *c){ 164 | size_t i = c->buf_idx; 165 | size_t z = c->buf_len+c->buf_idx; 166 | if (i >= z){ 167 | return ERR_INCOMPLETE; 168 | } 169 | c->args_len = 0; 170 | size_t s = i; 171 | bool first = true; 172 | for (;ibuf[i]=='\'' || c->buf[i]=='\"'){ 174 | if (!first){ 175 | return "Protocol error: unbalanced quotes in request"; 176 | } 177 | char b = c->buf[i]; 178 | i++; 179 | s = i; 180 | for (;ibuf[i] == b){ 182 | if (i+1>=z||c->buf[i+1]==' '||c->buf[i+1]=='\r'||c->buf[i+1]=='\n'){ 183 | client_append_arg(c, c->buf+s, i-s); 184 | i--; 185 | }else{ 186 | return "Protocol error: unbalanced quotes in request"; 187 | } 188 | break; 189 | } 190 | } 191 | i++; 192 | continue; 193 | } 194 | if (c->buf[i] == '\n'){ 195 | if (!first){ 196 | size_t e; 197 | if (i>s && c->buf[i-1] == '\r'){ 198 | e = i-1; 199 | }else{ 200 | e = i; 201 | } 202 | client_append_arg(c, c->buf+s, e-s); 203 | } 204 | i++; 205 | c->buf_len -= i-c->buf_idx; 206 | if (c->buf_len == 0){ 207 | c->buf_idx = 0; 208 | }else{ 209 | c->buf_idx = i; 210 | } 211 | return NULL; 212 | } 213 | if (c->buf[i] == ' '){ 214 | if (!first){ 215 | client_append_arg(c, c->buf+s, i-s); 216 | first = true; 217 | } 218 | }else{ 219 | if (first){ 220 | s = i; 221 | first = false; 222 | } 223 | } 224 | } 225 | return ERR_INCOMPLETE; 226 | } 227 | 228 | error client_read_command(client *c){ 229 | c->args_len = 0; 230 | size_t i = c->buf_idx; 231 | size_t z = c->buf_idx+c->buf_len; 232 | if (i >= z){ 233 | return ERR_INCOMPLETE; 234 | } 235 | if (c->buf[i] != '*'){ 236 | return client_parse_telnet_command(c); 237 | } 238 | i++; 239 | int args_len = 0; 240 | size_t s = i; 241 | for (;i < z;i++){ 242 | if (c->buf[i]=='\n'){ 243 | if (c->buf[i-1] !='\r'){ 244 | return "Protocol error: invalid multibulk length"; 245 | } 246 | c->buf[i-1] = 0; 247 | args_len = atoi(c->buf+s); 248 | c->buf[i-1] = '\r'; 249 | if (args_len <= 0){ 250 | if (args_len < 0 || i-s != 2){ 251 | return "Protocol error: invalid multibulk length"; 252 | } 253 | } 254 | i++; 255 | break; 256 | } 257 | } 258 | if (i >= z){ 259 | return ERR_INCOMPLETE; 260 | } 261 | for (int j=0;j= z){ 263 | return ERR_INCOMPLETE; 264 | } 265 | if (c->buf[i] != '$'){ 266 | return client_err_expected_got(c, '$', c->buf[i]); 267 | } 268 | i++; 269 | int nsiz = 0; 270 | size_t s = i; 271 | for (;i < z;i++){ 272 | if (c->buf[i]=='\n'){ 273 | if (c->buf[i-1] !='\r'){ 274 | return "Protocol error: invalid bulk length"; 275 | } 276 | c->buf[i-1] = 0; 277 | nsiz = atoi(c->buf+s); 278 | c->buf[i-1] = '\r'; 279 | if (nsiz <= 0){ 280 | if (nsiz < 0 || i-s != 2){ 281 | return "Protocol error: invalid bulk length"; 282 | } 283 | } 284 | i++; 285 | if (z-i < nsiz+2){ 286 | return ERR_INCOMPLETE; 287 | } 288 | s = i; 289 | if (c->buf[s+nsiz] != '\r'){ 290 | return "Protocol error: invalid bulk data"; 291 | } 292 | if (c->buf[s+nsiz+1] != '\n'){ 293 | return "Protocol error: invalid bulk data"; 294 | } 295 | client_append_arg(c, c->buf+s, nsiz); 296 | i += nsiz+2; 297 | break; 298 | } 299 | } 300 | } 301 | c->buf_len -= i-c->buf_idx; 302 | if (c->buf_len == 0){ 303 | c->buf_idx = 0; 304 | }else{ 305 | c->buf_idx = i; 306 | } 307 | return NULL; 308 | } 309 | 310 | void client_print_args(client *c){ 311 | printf("args[%d]:", c->args_len); 312 | for (int i=0;iargs_len;i++){ 313 | printf(" ["); 314 | for (int j=0;jargs_size[i];j++){ 315 | printf("%c", c->args[i][j]); 316 | } 317 | printf("]"); 318 | } 319 | printf("\n"); 320 | } 321 | 322 | bool client_exec_commands(client *c){ 323 | for (;;){ 324 | error err = client_read_command(c); 325 | if (err != NULL){ 326 | if ((char*)err == (char*)ERR_INCOMPLETE){ 327 | return true; 328 | } 329 | client_write_error(c, err); 330 | return false; 331 | } 332 | err = exec_command(c); 333 | if (err != NULL){ 334 | if (err == ERR_QUIT){ 335 | return false; 336 | } 337 | client_write_error(c, err); 338 | return true; 339 | } 340 | } 341 | return true; 342 | } 343 | -------------------------------------------------------------------------------- /src/exec.cc: -------------------------------------------------------------------------------- 1 | #include "server.h" 2 | 3 | static bool islstr(client *c, int arg_idx, const char *str){ 4 | int i = 0; 5 | for (;iargs_size[arg_idx];i++){ 6 | if (c->args[arg_idx][i] != str[i] && c->args[arg_idx][i] != str[i]-32){ 7 | return false; 8 | } 9 | } 10 | return !str[i]; 11 | } 12 | 13 | static bool iscmd(client *c, const char *cmd){ 14 | return islstr(c, 0, cmd); 15 | } 16 | 17 | error exec_set(client *c){ 18 | const char **argv = c->args; 19 | int *argl = c->args_size; 20 | int argc = c->args_len; 21 | if (argc!=3){ 22 | return "wrong number of arguments for 'set' command"; 23 | } 24 | std::string key(argv[1], argl[1]); 25 | std::string value(argv[2], argl[2]); 26 | rocksdb::WriteOptions write_options; 27 | write_options.sync = !nosync; 28 | rocksdb::Status s = db->Put(write_options, key, value); 29 | if (!s.ok()){ 30 | err(1, "%s", s.ToString().c_str()); 31 | } 32 | client_write(c, "+OK\r\n", 5); 33 | return NULL; 34 | } 35 | 36 | error exec_get(client *c){ 37 | const char **argv = c->args; 38 | int *argl = c->args_size; 39 | int argc = c->args_len; 40 | if (argc!=2){ 41 | return "wrong number of arguments for 'get' command"; 42 | } 43 | std::string key(argv[1], argl[1]); 44 | std::string value; 45 | rocksdb::Status s = db->Get(rocksdb::ReadOptions(), key, &value); 46 | if (!s.ok()){ 47 | if (s.IsNotFound()){ 48 | client_write(c, "$-1\r\n", 5); 49 | return NULL; 50 | } 51 | err(1, "%s", s.ToString().c_str()); 52 | } 53 | client_write_bulk(c, value.data(), value.size()); 54 | return NULL; 55 | } 56 | 57 | error exec_del(client *c){ 58 | const char **argv = c->args; 59 | int *argl = c->args_size; 60 | int argc = c->args_len; 61 | if (argc!=2){ 62 | return "wrong number of arguments for 'del' command"; 63 | } 64 | std::string key(argv[1], argl[1]); 65 | std::string value; 66 | rocksdb::Status s = db->Get(rocksdb::ReadOptions(), key, &value); 67 | if (!s.ok()){ 68 | if (s.IsNotFound()){ 69 | client_write(c, ":0\r\n", 4); 70 | return NULL; 71 | } 72 | err(1, "%s", s.ToString().c_str()); 73 | } 74 | rocksdb::WriteOptions write_options; 75 | write_options.sync = !nosync; 76 | s = db->Delete(write_options, key); 77 | if (!s.ok()){ 78 | err(1, "%s", s.ToString().c_str()); 79 | } 80 | client_write(c, ":1\r\n", 4); 81 | return NULL; 82 | } 83 | 84 | error exec_quit(client *c){ 85 | client_write(c, "+OK\r\n", 5); 86 | return ERR_QUIT; 87 | } 88 | 89 | error exec_flushdb(client *c){ 90 | if (c->args_len!=1){ 91 | return "wrong number of arguments for 'flushdb' command"; 92 | } 93 | flushdb(); 94 | client_write(c, "+OK\r\n", 5); 95 | return NULL; 96 | } 97 | 98 | static error exec_scan_keys(client *c, 99 | bool scan, 100 | const char *pat, int pat_len, 101 | int cursor, int count 102 | ){ 103 | if (count < 0){ 104 | count = 10; 105 | } 106 | if (cursor < 0){ 107 | cursor = 0; 108 | } 109 | 110 | char *start = NULL; 111 | char *end = NULL; 112 | int start_len = 0; 113 | int end_len = 0; 114 | int star = pattern_limits(pat, pat_len, &start, &start_len, &end, &end_len); 115 | std::string prefix(start, start_len); 116 | std::string postfix(end, end_len); 117 | 118 | // to avoid double-buffering, prewrite some bytes and then we'll go back 119 | // and fill it in with correctness. 120 | const int filler = 128; 121 | for (int i=0;iNewIterator(rocksdb::ReadOptions()); 128 | if (star){ 129 | it->SeekToFirst(); 130 | }else{ 131 | it->Seek(prefix); 132 | } 133 | for (; it->Valid(); it->Next()) { 134 | rocksdb::Slice key = it->key(); 135 | if (stringmatchlen(pat, pat_len, key.data(), key.size(), 1)){ 136 | if (!star){ 137 | int res = key.compare(postfix); 138 | if (res>=0){ 139 | break; 140 | } 141 | } 142 | if (i >= cursor){ 143 | if (scan&&total==count){ 144 | ncursor = i; 145 | break; 146 | } 147 | client_write_bulk(c, key.data(), key.size()); 148 | total++; 149 | } 150 | i++; 151 | } 152 | } 153 | if (start){ 154 | free(start); 155 | } 156 | if (end){ 157 | free(end); 158 | } 159 | 160 | rocksdb::Status s = it->status(); 161 | if (!s.ok()){ 162 | err(1, "%s", s.ToString().c_str()); 163 | } 164 | delete it; 165 | 166 | // fill in the header and write from offset. 167 | char nb[filler]; 168 | if (scan){ 169 | char cursor_s[32]; 170 | sprintf(cursor_s, "%d", ncursor); 171 | sprintf(nb, "*2\r\n$%zu\r\n%s\r\n*%d\r\n", strlen(cursor_s), cursor_s, total); 172 | }else{ 173 | sprintf(nb, "*%d\r\n", total); 174 | } 175 | int nbn = strlen(nb); 176 | memcpy(c->output+filler-nbn, nb, nbn); 177 | c->output_offset = filler-nbn; 178 | 179 | return NULL; 180 | } 181 | 182 | error exec_keys(client *c){ 183 | const char **argv = c->args; 184 | int *argl = c->args_size; 185 | int argc = c->args_len; 186 | if (argc!=2){ 187 | return "wrong number of arguments for 'keys' command"; 188 | } 189 | return exec_scan_keys(c, false, argv[1], argl[1], -1, -1); 190 | } 191 | error exec_scan(client *c){ 192 | const char **argv = c->args; 193 | int *argl = c->args_size; 194 | int argc = c->args_len; 195 | if (argc<2){ 196 | return "wrong number of arguments for 'scan' command"; 197 | } 198 | int cursor = atop(argv[1], argl[1]); 199 | if (cursor < 0){ 200 | return "invalid cursor"; 201 | } 202 | int count = -1; 203 | const char *pat = "*"; 204 | int pat_len = 1; 205 | 206 | for (int i=2;iargs_len==0||(c->args_len==1&&c->args_size[0]==0)){ 233 | return NULL; 234 | } 235 | if (iscmd(c, "set")){ 236 | return exec_set(c); 237 | }else if (iscmd(c, "get")){ 238 | return exec_get(c); 239 | }else if (iscmd(c, "del")){ 240 | return exec_del(c); 241 | }else if (iscmd(c, "quit")){ 242 | return exec_quit(c); 243 | }else if (iscmd(c, "keys")){ 244 | return exec_keys(c); 245 | }else if (iscmd(c, "scan")){ 246 | return exec_scan(c); 247 | }else if (iscmd(c, "flushdb")){ 248 | return exec_flushdb(c); 249 | } 250 | return client_err_unknown_command(c, c->args[0], c->args_size[0]); 251 | } 252 | 253 | -------------------------------------------------------------------------------- /src/libuv-1.10.1.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tidwall/rocksdb-server/7d21b1dcf533eda05a9af4bf8f018469b09dc580/src/libuv-1.10.1.tar.gz -------------------------------------------------------------------------------- /src/match.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2009-2012, Salvatore Sanfilippo 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 31 | #include 32 | 33 | /* Glob-style pattern matching. */ 34 | int stringmatchlen(const char *pattern, int patternLen, 35 | const char *string, int stringLen, int nocase) 36 | { 37 | while(patternLen) { 38 | switch(pattern[0]) { 39 | case '*': 40 | while (pattern[1] == '*') { 41 | pattern++; 42 | patternLen--; 43 | } 44 | if (patternLen == 1) 45 | return 1; /* match */ 46 | while(stringLen) { 47 | if (stringmatchlen(pattern+1, patternLen-1, 48 | string, stringLen, nocase)) 49 | return 1; /* match */ 50 | string++; 51 | stringLen--; 52 | } 53 | return 0; /* no match */ 54 | break; 55 | case '?': 56 | if (stringLen == 0) 57 | return 0; /* no match */ 58 | string++; 59 | stringLen--; 60 | break; 61 | case '[': 62 | { 63 | int _not, match; 64 | 65 | pattern++; 66 | patternLen--; 67 | _not = pattern[0] == '^'; 68 | if (_not) { 69 | pattern++; 70 | patternLen--; 71 | } 72 | match = 0; 73 | while(1) { 74 | if (pattern[0] == '\\') { 75 | pattern++; 76 | patternLen--; 77 | if (pattern[0] == string[0]) 78 | match = 1; 79 | } else if (pattern[0] == ']') { 80 | break; 81 | } else if (patternLen == 0) { 82 | pattern--; 83 | patternLen++; 84 | break; 85 | } else if (pattern[1] == '-' && patternLen >= 3) { 86 | int start = pattern[0]; 87 | int end = pattern[2]; 88 | int c = string[0]; 89 | if (start > end) { 90 | int t = start; 91 | start = end; 92 | end = t; 93 | } 94 | if (nocase) { 95 | start = tolower(start); 96 | end = tolower(end); 97 | c = tolower(c); 98 | } 99 | pattern += 2; 100 | patternLen -= 2; 101 | if (c >= start && c <= end) 102 | match = 1; 103 | } else { 104 | if (!nocase) { 105 | if (pattern[0] == string[0]) 106 | match = 1; 107 | } else { 108 | if (tolower((int)pattern[0]) == tolower((int)string[0])) 109 | match = 1; 110 | } 111 | } 112 | pattern++; 113 | patternLen--; 114 | } 115 | if (_not) 116 | match = !match; 117 | if (!match) 118 | return 0; /* no match */ 119 | string++; 120 | stringLen--; 121 | break; 122 | } 123 | case '\\': 124 | if (patternLen >= 2) { 125 | pattern++; 126 | patternLen--; 127 | } 128 | /* fall through */ 129 | default: 130 | if (!nocase) { 131 | if (pattern[0] != string[0]) 132 | return 0; /* no match */ 133 | } else { 134 | if (tolower((int)pattern[0]) != tolower((int)string[0])) 135 | return 0; /* no match */ 136 | } 137 | string++; 138 | stringLen--; 139 | break; 140 | } 141 | pattern++; 142 | patternLen--; 143 | if (stringLen == 0) { 144 | while(*pattern == '*') { 145 | pattern++; 146 | patternLen--; 147 | } 148 | break; 149 | } 150 | } 151 | if (patternLen == 0 && stringLen == 0) 152 | return 1; 153 | return 0; 154 | } 155 | 156 | int stringmatch(const char *pattern, const char *string, int nocase) { 157 | return stringmatchlen(pattern,strlen(pattern),string,strlen(string),nocase); 158 | } 159 | 160 | -------------------------------------------------------------------------------- /src/rocksdb-4.13.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tidwall/rocksdb-server/7d21b1dcf533eda05a9af4bf8f018469b09dc580/src/rocksdb-4.13.tar.gz -------------------------------------------------------------------------------- /src/server.cc: -------------------------------------------------------------------------------- 1 | #include "server.h" 2 | 3 | rocksdb::DB* db = NULL; 4 | bool nosync = true; 5 | int nprocs = 1; 6 | uv_loop_t *loop = NULL; 7 | bool inmem = false; 8 | const char *dir = "data"; 9 | 10 | const char *ERR_INCOMPLETE = "incomplete"; 11 | const char *ERR_QUIT = "quit"; 12 | 13 | void get_buffer(uv_handle_t *handle, size_t size, uv_buf_t *buf){ 14 | client *c = (client*)handle; 15 | if (c->buf_cap-c->buf_idx-c->buf_len < size){ 16 | while (c->buf_cap-c->buf_idx-c->buf_len < size){ 17 | if (c->buf_cap==0){ 18 | c->buf_cap=1; 19 | }else{ 20 | c->buf_cap*=2; 21 | } 22 | } 23 | c->buf = (char*)realloc(c->buf, c->buf_cap); 24 | if (!c->buf){ 25 | err(1, "malloc"); 26 | } 27 | } 28 | buf->base = c->buf+c->buf_idx+c->buf_len; 29 | buf->len = size; 30 | } 31 | 32 | void on_read(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf){ 33 | client *c = (client*)stream; 34 | if (nread < 0) { 35 | client_close(c); 36 | return; 37 | } 38 | c->buf_len += nread; 39 | client_clear(c); 40 | bool keep_alive = client_exec_commands(c); 41 | client_flush_offset(c, c->output_offset); 42 | if (!keep_alive){ 43 | client_close(c); 44 | } 45 | } 46 | 47 | void on_accept_work(uv_work_t *worker) { 48 | client *c = (client*)worker->data; 49 | uv_tcp_init(loop, &c->tcp); 50 | if (uv_accept(c->server, (uv_stream_t *)&c->tcp) == 0) { 51 | if (uv_read_start((uv_stream_t *)&c->tcp, get_buffer, on_read)){ 52 | c->must_close = 1; 53 | } 54 | // read has started 55 | }else{ 56 | c->must_close = 1; 57 | } 58 | } 59 | 60 | void on_accept_work_done(uv_work_t *worker, int status) { 61 | client *c = (client*)worker->data; 62 | if (c->must_close){ 63 | client_close(c); 64 | } 65 | } 66 | 67 | void on_accept(uv_stream_t *server, int status) { 68 | if (status == -1) { 69 | return; 70 | } 71 | client *c = client_new(); 72 | c->server = server; 73 | if (0){ 74 | // future thread-pool stuff, perhaps rip this out 75 | uv_queue_work(loop, &c->worker, on_accept_work, on_accept_work_done); 76 | }else{ 77 | on_accept_work(&c->worker); 78 | on_accept_work_done(&c->worker, 0); 79 | } 80 | } 81 | 82 | void log(char c, const char *format, ...){ 83 | time_t rawtime; 84 | struct tm *info; 85 | char tbuf[32]; 86 | struct timespec spec; 87 | clock_gettime(CLOCK_REALTIME, &spec); 88 | rawtime = spec.tv_sec; 89 | info = localtime( &rawtime ); 90 | strftime(tbuf,sizeof(tbuf),"%d %b %H:%M:%S", info); 91 | char buffer[512]; 92 | va_list args; 93 | va_start(args, format); 94 | vsnprintf(buffer,sizeof(buffer),format, args); 95 | char cc[32]; 96 | if (isatty(1)){ 97 | switch (c) { 98 | default: cc[0] = c; cc[1] = 0; 99 | case '.': strcpy(cc, "\x1b[35m.\x1b[0m"); break; 100 | case '*': strcpy(cc, "\x1b[1m*\x1b[0m"); break; 101 | case '#': strcpy(cc, "\x1b[33m#\x1b[0m"); break; 102 | } 103 | }else{ 104 | cc[0] = c; cc[1] = 0; 105 | } 106 | fprintf(stderr, "%d:M %s.%d %s %s\n", getpid(), tbuf, ((int)(spec.tv_nsec/1.0e6))%1000, cc, buffer); 107 | va_end(args); 108 | } 109 | 110 | void opendb(){ 111 | rocksdb::Options options; 112 | options.create_if_missing = true; 113 | if (inmem){ 114 | options.env = rocksdb::NewMemEnv(rocksdb::Env::Default()); 115 | } 116 | rocksdb::Status s = rocksdb::DB::Open(options, dir, &db); 117 | if (!s.ok()){ 118 | err(1, "%s", s.ToString().c_str()); 119 | } 120 | } 121 | 122 | void flushdb(){ 123 | delete db; 124 | if (remove_directory(dir, false)){ 125 | err(1, "remove_directory"); 126 | } 127 | opendb(); 128 | } 129 | 130 | int main(int argc, char **argv) { 131 | int tcp_port = 5555; 132 | bool tcp_port_provided = false; 133 | for (int i=1;i 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | extern rocksdb::DB* db; 12 | extern bool nosync; 13 | extern int nprocs; 14 | extern uv_loop_t *loop; 15 | 16 | extern const char *ERR_INCOMPLETE; 17 | extern const char *ERR_QUIT; 18 | 19 | void log(char c, const char *format, ...); 20 | 21 | int stringmatchlen(const char *pattern, int patternLen, 22 | const char *string, int stringLen, int nocase); 23 | int pattern_limits(const char *pattern, int patternLen, 24 | char **start, int *startLen, char **end, int *endLen); 25 | void flushdb(); 26 | int remove_directory(const char *path, int remove_parent); 27 | 28 | // atoul returns a positive integer. invalid or negative integers return -1. 29 | int atop(const char* str, int len); 30 | 31 | typedef const char *error; 32 | 33 | typedef struct client_t { 34 | uv_tcp_t tcp; // should always be the first element. 35 | uv_write_t req; 36 | uv_work_t worker; 37 | uv_stream_t *server; 38 | int must_close; 39 | char *buf; 40 | int buf_cap; 41 | int buf_len; 42 | int buf_idx; 43 | const char **args; 44 | int args_cap; 45 | int args_len; 46 | int *args_size; 47 | char *tmp_err; 48 | char *output; 49 | int output_len; 50 | int output_cap; 51 | int output_offset; 52 | } client; 53 | 54 | client *client_new(); 55 | void client_free(client *c); 56 | void client_close(client *c); 57 | 58 | void client_write(client *c, const char *data, int n); 59 | void client_clear(client *c); 60 | void client_write_byte(client *c, char b); 61 | void client_write_bulk(client *c, const char *data, int n); 62 | void client_write_multibulk(client *c, int n); 63 | void client_write_int(client *c, int n); 64 | void client_write_error(client *c, error err); 65 | void client_flush_offset(client *c, int offset); 66 | void client_flush(client *c); 67 | error client_err_expected_got(client *c, char c1, char c2); 68 | error client_err_unknown_command(client *c, const char *name, int count); 69 | 70 | bool client_exec_commands(client *c); 71 | 72 | error exec_command(client *c); 73 | 74 | 75 | #endif // SERVER_H 76 | -------------------------------------------------------------------------------- /src/util.cc: -------------------------------------------------------------------------------- 1 | #include "server.h" 2 | 3 | int remove_directory(const char *path, int remove_parent){ 4 | DIR *d = opendir(path); 5 | size_t path_len = strlen(path); 6 | int r = -1; 7 | if (d){ 8 | struct dirent *p; 9 | r = 0; 10 | while (!r && (p=readdir(d))){ 11 | int r2 = -1; 12 | char *buf; 13 | size_t len; 14 | if (!strcmp(p->d_name, ".") || !strcmp(p->d_name, "..")){ 15 | continue; 16 | } 17 | len = path_len + strlen(p->d_name) + 2; 18 | buf = (char*)malloc(len); 19 | if (!buf){ 20 | err(1, "malloc"); 21 | } 22 | struct stat statbuf; 23 | snprintf(buf, len, "%s/%s", path, p->d_name); 24 | if (!stat(buf, &statbuf)){ 25 | if (S_ISDIR(statbuf.st_mode)){ 26 | r2 = remove_directory(buf, 1); 27 | }else{ 28 | r2 = unlink(buf); 29 | } 30 | } 31 | free(buf); 32 | r = r2; 33 | } 34 | closedir(d); 35 | } 36 | if (!r&&remove_parent){ 37 | r = rmdir(path); 38 | } 39 | return r; 40 | } 41 | 42 | int pattern_limits(const char *pattern, int pattern_len, char **start, int *start_len, char **end, int *end_len) { 43 | int n = pattern_len; 44 | if (pattern_len==1&&pattern[0]=='*'){ 45 | *start = NULL; 46 | *start_len = 0; 47 | *end = NULL; 48 | *end_len = 0; 49 | return 1; 50 | } 51 | *start = (char*)calloc(1, n*2); 52 | *end = (char*)calloc(1, n*2); 53 | if (!*start||!*end){ 54 | err(1, "malloc"); 55 | } 56 | int i = 0; 57 | int j = 0; 58 | int star = 1; 59 | for (;i '9'){ 93 | return -1; 94 | } 95 | ret = ret * 10 + (str[i] - '0'); 96 | } 97 | return ret; 98 | } 99 | --------------------------------------------------------------------------------