hiredis 0.1.3 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -13,7 +13,7 @@ ID ivar_hiredis_error;
13
13
  * Note that the parent should always be of type T_ARRAY. */
14
14
  static void *tryParentize(const redisReadTask *task, VALUE v) {
15
15
  if (task && task->parent != NULL) {
16
- VALUE parent = (VALUE)task->parent;
16
+ VALUE parent = (VALUE)task->parent->obj;
17
17
  assert(TYPE(parent) == T_ARRAY);
18
18
  rb_ary_store(parent,task->idx,v);
19
19
  }
@@ -1,3 +1,3 @@
1
1
  module Hiredis
2
- VERSION = "0.1.3"
2
+ VERSION = "0.1.4"
3
3
  end
@@ -6,7 +6,7 @@ OBJ = net.o hiredis.o sds.o async.o
6
6
  BINS = hiredis-example hiredis-test
7
7
 
8
8
  uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
9
- OPTIMIZATION?=-O2
9
+ OPTIMIZATION?=-O3
10
10
  ifeq ($(uname_S),SunOS)
11
11
  CFLAGS?= -std=c99 -pedantic $(OPTIMIZATION) -fPIC -Wall -W -D__EXTENSIONS__ -D_XPG6
12
12
  CCLINK?= -ldl -lnsl -lsocket -lm -lpthread
@@ -31,7 +31,7 @@ else
31
31
  STLIB_MAKE_CMD?=ar rcs ${STLIBNAME} ${OBJ}
32
32
  endif
33
33
  CCOPT= $(CFLAGS) $(CCLINK) $(ARCH) $(PROF)
34
- DEBUG?= -g -ggdb
34
+ DEBUG?= -g -ggdb
35
35
 
36
36
  PREFIX?= /usr/local
37
37
  INSTALL_INC= $(PREFIX)/include/hiredis
@@ -43,8 +43,6 @@ all: ${DYLIBNAME} ${BINS}
43
43
  # Deps (use make dep to generate this)
44
44
  net.o: net.c fmacros.h net.h
45
45
  async.o: async.c async.h hiredis.h sds.h util.h
46
- example-libev.o: example-libev.c hiredis.h async.h adapters/libev.h
47
- example-libevent.o: example-libevent.c hiredis.h async.h adapters/libevent.h
48
46
  example.o: example.c hiredis.h
49
47
  hiredis.o: hiredis.c hiredis.h net.h sds.h util.h
50
48
  sds.o: sds.c sds.h
@@ -60,12 +58,21 @@ dynamic: ${DYLIBNAME}
60
58
  static: ${STLIBNAME}
61
59
 
62
60
  # Binaries:
63
- hiredis-example-libevent: example-libevent.o ${DYLIBNAME}
61
+ hiredis-example-libevent: example-libevent.c adapters/libevent.h ${DYLIBNAME}
64
62
  $(CC) -o $@ $(CCOPT) $(DEBUG) -L. -lhiredis -levent -Wl,-rpath,. example-libevent.c
65
63
 
66
- hiredis-example-libev: example-libev.o ${DYLIBNAME}
64
+ hiredis-example-libev: example-libev.c adapters/libev.h ${DYLIBNAME}
67
65
  $(CC) -o $@ $(CCOPT) $(DEBUG) -L. -lhiredis -lev -Wl,-rpath,. example-libev.c
68
66
 
67
+ ifndef AE_DIR
68
+ hiredis-example-ae:
69
+ @echo "Please specify AE_DIR (e.g. <redis repository>/src)"
70
+ @false
71
+ else
72
+ hiredis-example-ae: example-ae.c adapters/ae.h ${DYLIBNAME}
73
+ $(CC) -o $@ $(CCOPT) $(DEBUG) -I$(AE_DIR) -L. -lhiredis -Wl,-rpath,. example-ae.c $(AE_DIR)/ae.o $(AE_DIR)/zmalloc.o
74
+ endif
75
+
69
76
  hiredis-%: %.o ${DYLIBNAME}
70
77
  $(CC) -o $@ $(CCOPT) $(DEBUG) -L. -lhiredis -Wl,-rpath,. $<
71
78
 
@@ -38,8 +38,18 @@ void __redisAppendCommand(redisContext *c, char *cmd, size_t len);
38
38
 
39
39
  static redisAsyncContext *redisAsyncInitialize(redisContext *c) {
40
40
  redisAsyncContext *ac = realloc(c,sizeof(redisAsyncContext));
41
- /* Set all bytes in the async part of the context to 0 */
42
- memset(ac+sizeof(redisContext),0,sizeof(redisAsyncContext)-sizeof(redisContext));
41
+ ac->err = 0;
42
+ ac->errstr = NULL;
43
+ ac->data = NULL;
44
+ ac->_adapter_data = NULL;
45
+ ac->evAddRead = NULL;
46
+ ac->evDelRead = NULL;
47
+ ac->evAddWrite = NULL;
48
+ ac->evDelWrite = NULL;
49
+ ac->evCleanup = NULL;
50
+ ac->onDisconnect = NULL;
51
+ ac->replies.head = NULL;
52
+ ac->replies.tail = NULL;
43
53
  return ac;
44
54
  }
45
55
 
@@ -153,7 +163,7 @@ static void __redisAsyncDisconnect(redisAsyncContext *ac) {
153
163
  }
154
164
 
155
165
  /* Signal event lib to clean up */
156
- if (ac->evCleanup) ac->evCleanup(ac->data);
166
+ if (ac->evCleanup) ac->evCleanup(ac->_adapter_data);
157
167
 
158
168
  /* Execute callback with proper status */
159
169
  if (ac->onDisconnect) ac->onDisconnect(ac,status);
@@ -206,7 +216,7 @@ void redisAsyncHandleRead(redisAsyncContext *ac) {
206
216
  __redisAsyncDisconnect(ac);
207
217
  } else {
208
218
  /* Always re-schedule reads */
209
- if (ac->evAddRead) ac->evAddRead(ac->data);
219
+ if (ac->evAddRead) ac->evAddRead(ac->_adapter_data);
210
220
  redisProcessCallbacks(ac);
211
221
  }
212
222
  }
@@ -220,13 +230,13 @@ void redisAsyncHandleWrite(redisAsyncContext *ac) {
220
230
  } else {
221
231
  /* Continue writing when not done, stop writing otherwise */
222
232
  if (!done) {
223
- if (ac->evAddWrite) ac->evAddWrite(ac->data);
233
+ if (ac->evAddWrite) ac->evAddWrite(ac->_adapter_data);
224
234
  } else {
225
- if (ac->evDelWrite) ac->evDelWrite(ac->data);
235
+ if (ac->evDelWrite) ac->evDelWrite(ac->_adapter_data);
226
236
  }
227
237
 
228
238
  /* Always schedule reads when something was written */
229
- if (ac->evAddRead) ac->evAddRead(ac->data);
239
+ if (ac->evAddRead) ac->evAddRead(ac->_adapter_data);
230
240
  }
231
241
  }
232
242
 
@@ -249,7 +259,7 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void
249
259
  __redisPushCallback(&ac->replies,&cb);
250
260
 
251
261
  /* Always schedule a write when the write buffer is non-empty */
252
- if (ac->evAddWrite) ac->evAddWrite(ac->data);
262
+ if (ac->evAddWrite) ac->evAddWrite(ac->_adapter_data);
253
263
 
254
264
  return REDIS_OK;
255
265
  }
@@ -31,6 +31,10 @@
31
31
  #define __HIREDIS_ASYNC_H
32
32
  #include "hiredis.h"
33
33
 
34
+ #ifdef __cplusplus
35
+ extern "C" {
36
+ #endif
37
+
34
38
  struct redisAsyncContext; /* need forward declaration of redisAsyncContext */
35
39
 
36
40
  /* Reply callback prototype and container */
@@ -58,6 +62,12 @@ typedef struct redisAsyncContext {
58
62
  int err;
59
63
  char *errstr;
60
64
 
65
+ /* Not used by hiredis */
66
+ void *data;
67
+
68
+ /* Used by the different event lib adapters to store their private data */
69
+ void *_adapter_data;
70
+
61
71
  /* Called when the library expects to start reading/writing.
62
72
  * The supplied functions should be idempotent. */
63
73
  void (*evAddRead)(void *privdata);
@@ -65,7 +75,6 @@ typedef struct redisAsyncContext {
65
75
  void (*evAddWrite)(void *privdata);
66
76
  void (*evDelWrite)(void *privdata);
67
77
  void (*evCleanup)(void *privdata);
68
- void *data;
69
78
 
70
79
  /* Called when either the connection is terminated due to an error or per
71
80
  * user request. The status is set accordingly (REDIS_OK, REDIS_ERR). */
@@ -91,4 +100,8 @@ int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdat
91
100
  int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...);
92
101
  int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen);
93
102
 
103
+ #ifdef __cplusplus
104
+ }
105
+ #endif
106
+
94
107
  #endif
@@ -32,6 +32,7 @@
32
32
  #include <unistd.h>
33
33
  #include <assert.h>
34
34
  #include <errno.h>
35
+ #include <ctype.h>
35
36
 
36
37
  #include "hiredis.h"
37
38
  #include "net.h"
@@ -44,10 +45,12 @@ typedef struct redisReader {
44
45
  void *reply; /* holds temporary reply */
45
46
 
46
47
  sds buf; /* read buffer */
47
- unsigned int pos; /* buffer cursor */
48
+ size_t pos; /* buffer cursor */
49
+ size_t len; /* buffer length */
48
50
 
49
51
  redisReadTask rstack[3]; /* stack of read tasks */
50
52
  int ridx; /* index of stack */
53
+ void *privdata; /* user-settable arbitrary field */
51
54
  } redisReader;
52
55
 
53
56
  static redisReply *createReplyObject(int type);
@@ -68,7 +71,7 @@ static redisReplyObjectFunctions defaultFunctions = {
68
71
 
69
72
  /* Create a reply object */
70
73
  static redisReply *createReplyObject(int type) {
71
- redisReply *r = calloc(sizeof(*r),1);
74
+ redisReply *r = malloc(sizeof(*r));
72
75
 
73
76
  if (!r) redisOOM();
74
77
  r->type = type;
@@ -88,9 +91,10 @@ void freeReplyObject(void *reply) {
88
91
  if (r->element[j]) freeReplyObject(r->element[j]);
89
92
  free(r->element);
90
93
  break;
91
- default:
92
- if (r->str != NULL)
93
- free(r->str);
94
+ case REDIS_REPLY_ERROR:
95
+ case REDIS_REPLY_STATUS:
96
+ case REDIS_REPLY_STRING:
97
+ free(r->str);
94
98
  break;
95
99
  }
96
100
  free(r);
@@ -111,7 +115,7 @@ static void *createStringObject(const redisReadTask *task, char *str, size_t len
111
115
  r->len = len;
112
116
 
113
117
  if (task->parent) {
114
- redisReply *parent = task->parent;
118
+ redisReply *parent = task->parent->obj;
115
119
  assert(parent->type == REDIS_REPLY_ARRAY);
116
120
  parent->element[task->idx] = r;
117
121
  }
@@ -124,7 +128,7 @@ static void *createArrayObject(const redisReadTask *task, int elements) {
124
128
  if ((r->element = calloc(sizeof(redisReply*),elements)) == NULL)
125
129
  redisOOM();
126
130
  if (task->parent) {
127
- redisReply *parent = task->parent;
131
+ redisReply *parent = task->parent->obj;
128
132
  assert(parent->type == REDIS_REPLY_ARRAY);
129
133
  parent->element[task->idx] = r;
130
134
  }
@@ -135,7 +139,7 @@ static void *createIntegerObject(const redisReadTask *task, long long value) {
135
139
  redisReply *r = createReplyObject(REDIS_REPLY_INTEGER);
136
140
  r->integer = value;
137
141
  if (task->parent) {
138
- redisReply *parent = task->parent;
142
+ redisReply *parent = task->parent->obj;
139
143
  assert(parent->type == REDIS_REPLY_ARRAY);
140
144
  parent->element[task->idx] = r;
141
145
  }
@@ -145,7 +149,7 @@ static void *createIntegerObject(const redisReadTask *task, long long value) {
145
149
  static void *createNilObject(const redisReadTask *task) {
146
150
  redisReply *r = createReplyObject(REDIS_REPLY_NIL);
147
151
  if (task->parent) {
148
- redisReply *parent = task->parent;
152
+ redisReply *parent = task->parent->obj;
149
153
  assert(parent->type == REDIS_REPLY_ARRAY);
150
154
  parent->element[task->idx] = r;
151
155
  }
@@ -154,7 +158,7 @@ static void *createNilObject(const redisReadTask *task) {
154
158
 
155
159
  static char *readBytes(redisReader *r, unsigned int bytes) {
156
160
  char *p;
157
- if (sdslen(r->buf)-r->pos >= bytes) {
161
+ if (r->len-r->pos >= bytes) {
158
162
  p = r->buf+r->pos;
159
163
  r->pos += bytes;
160
164
  return p;
@@ -162,20 +166,60 @@ static char *readBytes(redisReader *r, unsigned int bytes) {
162
166
  return NULL;
163
167
  }
164
168
 
165
- static char *seekNewline(char *s) {
166
- /* Find pointer to \r\n without strstr */
167
- while (s != NULL) {
168
- s = strchr(s,'\r');
169
- if (s != NULL) {
170
- if (s[1] == '\n')
171
- break;
172
- else
173
- s++;
169
+ /* Find pointer to \r\n. */
170
+ static char *seekNewline(char *s, size_t len) {
171
+ int pos = 0;
172
+ int _len = len-1;
173
+
174
+ /* Position should be < len-1 because the character at "pos" should be
175
+ * followed by a \n. Note that strchr cannot be used because it doesn't
176
+ * allow to search a limited length and the buffer that is being searched
177
+ * might not have a trailing NULL character. */
178
+ while (pos < _len) {
179
+ while(pos < _len && s[pos] != '\r') pos++;
180
+ if (s[pos] != '\r') {
181
+ /* Not found. */
182
+ return NULL;
174
183
  } else {
175
- break;
184
+ if (s[pos+1] == '\n') {
185
+ /* Found. */
186
+ return s+pos;
187
+ } else {
188
+ /* Continue searching. */
189
+ pos++;
190
+ }
176
191
  }
177
192
  }
178
- return s;
193
+ return NULL;
194
+ }
195
+
196
+ /* Read a long long value starting at *s, under the assumption that it will be
197
+ * terminated by \r\n. Ambiguously returns -1 for unexpected input. */
198
+ static long long readLongLong(char *s) {
199
+ long long v = 0;
200
+ int dec, mult = 1;
201
+ char c;
202
+
203
+ if (*s == '-') {
204
+ mult = -1;
205
+ s++;
206
+ } else if (*s == '+') {
207
+ mult = 1;
208
+ s++;
209
+ }
210
+
211
+ while ((c = *(s++)) != '\r') {
212
+ dec = c - '0';
213
+ if (dec >= 0 && dec < 10) {
214
+ v *= 10;
215
+ v += dec;
216
+ } else {
217
+ /* Should not happen... */
218
+ return -1;
219
+ }
220
+ }
221
+
222
+ return mult*v;
179
223
  }
180
224
 
181
225
  static char *readLine(redisReader *r, int *_len) {
@@ -183,7 +227,7 @@ static char *readLine(redisReader *r, int *_len) {
183
227
  int len;
184
228
 
185
229
  p = r->buf+r->pos;
186
- s = seekNewline(p);
230
+ s = seekNewline(p,(r->len-r->pos));
187
231
  if (s != NULL) {
188
232
  len = s-(r->buf+r->pos);
189
233
  r->pos += len+2; /* skip \r\n */
@@ -227,7 +271,7 @@ static int processLineItem(redisReader *r) {
227
271
  if ((p = readLine(r,&len)) != NULL) {
228
272
  if (r->fn) {
229
273
  if (cur->type == REDIS_REPLY_INTEGER) {
230
- obj = r->fn->createInteger(cur,strtoll(p,NULL,10));
274
+ obj = r->fn->createInteger(cur,readLongLong(p));
231
275
  } else {
232
276
  obj = r->fn->createString(cur,p,len);
233
277
  }
@@ -235,9 +279,8 @@ static int processLineItem(redisReader *r) {
235
279
  obj = (void*)(size_t)(cur->type);
236
280
  }
237
281
 
238
- /* If there is no root yet, register this object as root. */
239
- if (r->reply == NULL)
240
- r->reply = obj;
282
+ /* Set reply if this is the root object. */
283
+ if (r->ridx == 0) r->reply = obj;
241
284
  moveToNextTask(r);
242
285
  return 0;
243
286
  }
@@ -250,32 +293,36 @@ static int processBulkItem(redisReader *r) {
250
293
  char *p, *s;
251
294
  long len;
252
295
  unsigned long bytelen;
296
+ int success = 0;
253
297
 
254
298
  p = r->buf+r->pos;
255
- s = seekNewline(p);
299
+ s = seekNewline(p,r->len-r->pos);
256
300
  if (s != NULL) {
257
301
  p = r->buf+r->pos;
258
302
  bytelen = s-(r->buf+r->pos)+2; /* include \r\n */
259
- len = strtol(p,NULL,10);
303
+ len = readLongLong(p);
260
304
 
261
305
  if (len < 0) {
262
306
  /* The nil object can always be created. */
263
307
  obj = r->fn ? r->fn->createNil(cur) :
264
308
  (void*)REDIS_REPLY_NIL;
309
+ success = 1;
265
310
  } else {
266
311
  /* Only continue when the buffer contains the entire bulk item. */
267
312
  bytelen += len+2; /* include \r\n */
268
- if (r->pos+bytelen <= sdslen(r->buf)) {
313
+ if (r->pos+bytelen <= r->len) {
269
314
  obj = r->fn ? r->fn->createString(cur,s+2,len) :
270
315
  (void*)REDIS_REPLY_STRING;
316
+ success = 1;
271
317
  }
272
318
  }
273
319
 
274
320
  /* Proceed when obj was created. */
275
- if (obj != NULL) {
321
+ if (success) {
276
322
  r->pos += bytelen;
277
- if (r->reply == NULL)
278
- r->reply = obj;
323
+
324
+ /* Set reply if this is the root object. */
325
+ if (r->ridx == 0) r->reply = obj;
279
326
  moveToNextTask(r);
280
327
  return 0;
281
328
  }
@@ -288,9 +335,19 @@ static int processMultiBulkItem(redisReader *r) {
288
335
  void *obj;
289
336
  char *p;
290
337
  long elements;
338
+ int root = 0;
339
+
340
+ /* Set error for nested multi bulks with depth > 1 */
341
+ if (r->ridx == 2) {
342
+ redisSetReplyReaderError(r,sdscatprintf(sdsempty(),
343
+ "No support for nested multi bulk replies with depth > 1"));
344
+ return -1;
345
+ }
291
346
 
292
347
  if ((p = readLine(r,NULL)) != NULL) {
293
- elements = strtol(p,NULL,10);
348
+ elements = readLongLong(p);
349
+ root = (r->ridx == 0);
350
+
294
351
  if (elements == -1) {
295
352
  obj = r->fn ? r->fn->createNil(cur) :
296
353
  (void*)REDIS_REPLY_NIL;
@@ -302,19 +359,21 @@ static int processMultiBulkItem(redisReader *r) {
302
359
  /* Modify task stack when there are more than 0 elements. */
303
360
  if (elements > 0) {
304
361
  cur->elements = elements;
362
+ cur->obj = obj;
305
363
  r->ridx++;
306
364
  r->rstack[r->ridx].type = -1;
307
365
  r->rstack[r->ridx].elements = -1;
308
- r->rstack[r->ridx].parent = obj;
309
366
  r->rstack[r->ridx].idx = 0;
367
+ r->rstack[r->ridx].obj = NULL;
368
+ r->rstack[r->ridx].parent = cur;
369
+ r->rstack[r->ridx].privdata = r->privdata;
310
370
  } else {
311
371
  moveToNextTask(r);
312
372
  }
313
373
  }
314
374
 
315
- /* Object was created, so we can always continue. */
316
- if (r->reply == NULL)
317
- r->reply = obj;
375
+ /* Set reply if this is the root object. */
376
+ if (root) r->reply = obj;
318
377
  return 0;
319
378
  }
320
379
  return -1;
@@ -347,7 +406,7 @@ static int processItem(redisReader *r) {
347
406
  default:
348
407
  byte = sdscatrepr(sdsempty(),p,1);
349
408
  redisSetReplyReaderError(r,sdscatprintf(sdsempty(),
350
- "protocol error, got %s as reply type byte", byte));
409
+ "Protocol error, got %s as reply type byte", byte));
351
410
  sdsfree(byte);
352
411
  return -1;
353
412
  }
@@ -368,8 +427,7 @@ static int processItem(redisReader *r) {
368
427
  case REDIS_REPLY_ARRAY:
369
428
  return processMultiBulkItem(r);
370
429
  default:
371
- redisSetReplyReaderError(r,sdscatprintf(sdsempty(),
372
- "unknown item type '%d'", cur->type));
430
+ assert(NULL);
373
431
  return -1;
374
432
  }
375
433
  }
@@ -394,6 +452,17 @@ int redisReplyReaderSetReplyObjectFunctions(void *reader, redisReplyObjectFuncti
394
452
  return REDIS_ERR;
395
453
  }
396
454
 
455
+ /* Set the private data field that is used in the read tasks. This argument can
456
+ * be used to curry arbitrary data to the custom reply object functions. */
457
+ int redisReplyReaderSetPrivdata(void *reader, void *privdata) {
458
+ redisReader *r = reader;
459
+ if (r->reply == NULL) {
460
+ r->privdata = privdata;
461
+ return REDIS_OK;
462
+ }
463
+ return REDIS_ERR;
464
+ }
465
+
397
466
  /* External libraries wrapping hiredis might need access to the temporary
398
467
  * variable while the reply is built up. When the reader contains an
399
468
  * object in between receiving some bytes to parse, this object might
@@ -437,8 +506,10 @@ void redisReplyReaderFeed(void *reader, char *buf, size_t len) {
437
506
  redisReader *r = reader;
438
507
 
439
508
  /* Copy the provided buffer. */
440
- if (buf != NULL && len >= 1)
509
+ if (buf != NULL && len >= 1) {
441
510
  r->buf = sdscatlen(r->buf,buf,len);
511
+ r->len = sdslen(r->buf);
512
+ }
442
513
  }
443
514
 
444
515
  int redisReplyReaderGetReply(void *reader, void **reply) {
@@ -446,15 +517,17 @@ int redisReplyReaderGetReply(void *reader, void **reply) {
446
517
  if (reply != NULL) *reply = NULL;
447
518
 
448
519
  /* When the buffer is empty, there will never be a reply. */
449
- if (sdslen(r->buf) == 0)
520
+ if (r->len == 0)
450
521
  return REDIS_OK;
451
522
 
452
523
  /* Set first item to process when the stack is empty. */
453
524
  if (r->ridx == -1) {
454
525
  r->rstack[0].type = -1;
455
526
  r->rstack[0].elements = -1;
456
- r->rstack[0].parent = NULL;
457
527
  r->rstack[0].idx = -1;
528
+ r->rstack[0].obj = NULL;
529
+ r->rstack[0].parent = NULL;
530
+ r->rstack[0].privdata = r->privdata;
458
531
  r->ridx = 0;
459
532
  }
460
533
 
@@ -465,14 +538,15 @@ int redisReplyReaderGetReply(void *reader, void **reply) {
465
538
 
466
539
  /* Discard the consumed part of the buffer. */
467
540
  if (r->pos > 0) {
468
- if (r->pos == sdslen(r->buf)) {
541
+ if (r->pos == r->len) {
469
542
  /* sdsrange has a quirck on this edge case. */
470
543
  sdsfree(r->buf);
471
544
  r->buf = sdsempty();
472
545
  } else {
473
- r->buf = sdsrange(r->buf,r->pos,sdslen(r->buf));
546
+ r->buf = sdsrange(r->buf,r->pos,r->len);
474
547
  }
475
548
  r->pos = 0;
549
+ r->len = sdslen(r->buf);
476
550
  }
477
551
 
478
552
  /* Emit a reply when there is one. */
@@ -481,7 +555,7 @@ int redisReplyReaderGetReply(void *reader, void **reply) {
481
555
  r->reply = NULL;
482
556
 
483
557
  /* Destroy the buffer when it is empty and is quite large. */
484
- if (sdslen(r->buf) == 0 && sdsavail(r->buf) > 16*1024) {
558
+ if (r->len == 0 && sdsavail(r->buf) > 16*1024) {
485
559
  sdsfree(r->buf);
486
560
  r->buf = sdsempty();
487
561
  r->pos = 0;
@@ -525,6 +599,7 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
525
599
  char *cmd = NULL; /* final command */
526
600
  int pos; /* position in final command */
527
601
  sds current; /* current argument */
602
+ int interpolated = 0; /* did we do interpolation on an argument? */
528
603
  char **argv = NULL;
529
604
  int argc = 0, j;
530
605
  int totlen = 0;
@@ -541,6 +616,7 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
541
616
  if (sdslen(current) != 0) {
542
617
  addArgument(current, &argv, &argc, &totlen);
543
618
  current = sdsempty();
619
+ interpolated = 0;
544
620
  }
545
621
  } else {
546
622
  current = sdscatlen(current,c,1);
@@ -549,16 +625,74 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
549
625
  switch(c[1]) {
550
626
  case 's':
551
627
  arg = va_arg(ap,char*);
552
- current = sdscat(current,arg);
628
+ size = strlen(arg);
629
+ if (size > 0)
630
+ current = sdscatlen(current,arg,size);
631
+ interpolated = 1;
553
632
  break;
554
633
  case 'b':
555
634
  arg = va_arg(ap,char*);
556
635
  size = va_arg(ap,size_t);
557
- current = sdscatlen(current,arg,size);
636
+ if (size > 0)
637
+ current = sdscatlen(current,arg,size);
638
+ interpolated = 1;
558
639
  break;
559
640
  case '%':
560
- cmd = sdscat(cmd,"%");
641
+ current = sdscat(current,"%");
561
642
  break;
643
+ default:
644
+ /* Try to detect printf format */
645
+ {
646
+ char _format[16];
647
+ const char *_p = c+1;
648
+ size_t _l = 0;
649
+ va_list _cpy;
650
+
651
+ /* Flags */
652
+ if (*_p != '\0' && *_p == '#') _p++;
653
+ if (*_p != '\0' && *_p == '0') _p++;
654
+ if (*_p != '\0' && *_p == '-') _p++;
655
+ if (*_p != '\0' && *_p == ' ') _p++;
656
+ if (*_p != '\0' && *_p == '+') _p++;
657
+
658
+ /* Field width */
659
+ while (*_p != '\0' && isdigit(*_p)) _p++;
660
+
661
+ /* Precision */
662
+ if (*_p == '.') {
663
+ _p++;
664
+ while (*_p != '\0' && isdigit(*_p)) _p++;
665
+ }
666
+
667
+ /* Modifiers */
668
+ if (*_p != '\0') {
669
+ if (*_p == 'h' || *_p == 'l') {
670
+ /* Allow a single repetition for these modifiers */
671
+ if (_p[0] == _p[1]) _p++;
672
+ _p++;
673
+ }
674
+ }
675
+
676
+ /* Conversion specifier */
677
+ if (*_p != '\0' && strchr("diouxXeEfFgGaA",*_p) != NULL) {
678
+ _l = (_p+1)-c;
679
+ if (_l < sizeof(_format)-2) {
680
+ memcpy(_format,c,_l);
681
+ _format[_l] = '\0';
682
+ va_copy(_cpy,ap);
683
+ current = sdscatvprintf(current,_format,_cpy);
684
+ interpolated = 1;
685
+ va_end(_cpy);
686
+
687
+ /* Update current position (note: outer blocks
688
+ * increment c twice so compensate here) */
689
+ c = _p-1;
690
+ }
691
+ }
692
+
693
+ /* Consume and discard vararg */
694
+ va_arg(ap,void);
695
+ }
562
696
  }
563
697
  c++;
564
698
  }
@@ -566,7 +700,7 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
566
700
  }
567
701
 
568
702
  /* Add the last argument if needed */
569
- if (sdslen(current) != 0) {
703
+ if (interpolated || sdslen(current) != 0) {
570
704
  addArgument(current, &argv, &argc, &totlen);
571
705
  } else {
572
706
  sdsfree(current);
@@ -691,7 +825,6 @@ void redisFree(redisContext *c) {
691
825
  redisContext *redisConnect(const char *ip, int port) {
692
826
  redisContext *c = redisContextInit();
693
827
  c->flags |= REDIS_BLOCK;
694
- c->flags |= REDIS_CONNECTED;
695
828
  redisContextConnectTcp(c,ip,port);
696
829
  return c;
697
830
  }
@@ -699,7 +832,6 @@ redisContext *redisConnect(const char *ip, int port) {
699
832
  redisContext *redisConnectNonBlock(const char *ip, int port) {
700
833
  redisContext *c = redisContextInit();
701
834
  c->flags &= ~REDIS_BLOCK;
702
- c->flags |= REDIS_CONNECTED;
703
835
  redisContextConnectTcp(c,ip,port);
704
836
  return c;
705
837
  }
@@ -707,7 +839,6 @@ redisContext *redisConnectNonBlock(const char *ip, int port) {
707
839
  redisContext *redisConnectUnix(const char *path) {
708
840
  redisContext *c = redisContextInit();
709
841
  c->flags |= REDIS_BLOCK;
710
- c->flags |= REDIS_CONNECTED;
711
842
  redisContextConnectUnix(c,path);
712
843
  return c;
713
844
  }
@@ -715,7 +846,6 @@ redisContext *redisConnectUnix(const char *path) {
715
846
  redisContext *redisConnectUnixNonBlock(const char *path) {
716
847
  redisContext *c = redisContextInit();
717
848
  c->flags &= ~REDIS_BLOCK;
718
- c->flags |= REDIS_CONNECTED;
719
849
  redisContextConnectUnix(c,path);
720
850
  return c;
721
851
  }
@@ -34,7 +34,7 @@
34
34
 
35
35
  #define HIREDIS_MAJOR 0
36
36
  #define HIREDIS_MINOR 9
37
- #define HIREDIS_PATCH 0
37
+ #define HIREDIS_PATCH 1
38
38
 
39
39
  #define REDIS_ERR -1
40
40
  #define REDIS_OK 0
@@ -69,6 +69,10 @@
69
69
  #define REDIS_REPLY_NIL 4
70
70
  #define REDIS_REPLY_STATUS 5
71
71
 
72
+ #ifdef __cplusplus
73
+ extern "C" {
74
+ #endif
75
+
72
76
  /* This is the reply object returned by redisCommand() */
73
77
  typedef struct redisReply {
74
78
  int type; /* REDIS_REPLY_* */
@@ -82,8 +86,10 @@ typedef struct redisReply {
82
86
  typedef struct redisReadTask {
83
87
  int type;
84
88
  int elements; /* number of elements in multibulk container */
85
- void *parent; /* optional pointer to parent object */
86
89
  int idx; /* index in parent (array) object */
90
+ void *obj; /* holds user-generated value for a read task */
91
+ struct redisReadTask *parent; /* parent task */
92
+ void *privdata; /* user-settable arbitrary field */
87
93
  } redisReadTask;
88
94
 
89
95
  typedef struct redisReplyObjectFunctions {
@@ -112,6 +118,7 @@ typedef struct redisContext {
112
118
  void freeReplyObject(void *reply);
113
119
  void *redisReplyReaderCreate();
114
120
  int redisReplyReaderSetReplyObjectFunctions(void *reader, redisReplyObjectFunctions *fn);
121
+ int redisReplyReaderSetPrivdata(void *reader, void *privdata);
115
122
  void *redisReplyReaderGetObject(void *reader);
116
123
  char *redisReplyReaderGetError(void *reader);
117
124
  void redisReplyReaderFree(void *ptr);
@@ -154,4 +161,8 @@ void *redisvCommand(redisContext *c, const char *format, va_list ap);
154
161
  void *redisCommand(redisContext *c, const char *format, ...);
155
162
  void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);
156
163
 
164
+ #ifdef __cplusplus
165
+ }
166
+ #endif
167
+
157
168
  #endif
@@ -114,7 +114,7 @@ int redisContextConnectTcp(redisContext *c, const char *addr, int port) {
114
114
  he = gethostbyname(addr);
115
115
  if (he == NULL) {
116
116
  __redisSetError(c,REDIS_ERR_OTHER,
117
- sdscatprintf(sdsempty(),"can't resolve: %s",addr));
117
+ sdscatprintf(sdsempty(),"Can't resolve: %s",addr));
118
118
  close(s);
119
119
  return REDIS_ERR;
120
120
  }
@@ -137,6 +137,7 @@ int redisContextConnectTcp(redisContext *c, const char *addr, int port) {
137
137
  }
138
138
 
139
139
  c->fd = s;
140
+ c->flags |= REDIS_CONNECTED;
140
141
  return REDIS_OK;
141
142
  }
142
143
 
@@ -163,5 +164,6 @@ int redisContextConnectUnix(redisContext *c, const char *path) {
163
164
  }
164
165
 
165
166
  c->fd = s;
167
+ c->flags |= REDIS_CONNECTED;
166
168
  return REDIS_OK;
167
169
  }
@@ -5,6 +5,7 @@
5
5
  #include <sys/time.h>
6
6
  #include <assert.h>
7
7
  #include <unistd.h>
8
+ #include <signal.h>
8
9
 
9
10
  #include "hiredis.h"
10
11
 
@@ -46,17 +47,59 @@ static void test_format_commands() {
46
47
  len == 4+4+(3+2)+4+(3+2)+4+(3+2));
47
48
  free(cmd);
48
49
 
50
+ test("Format command with %%s and an empty string: ");
51
+ len = redisFormatCommand(&cmd,"SET %s %s","foo","");
52
+ test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$0\r\n\r\n",len) == 0 &&
53
+ len == 4+4+(3+2)+4+(3+2)+4+(0+2));
54
+ free(cmd);
55
+
49
56
  test("Format command with %%b string interpolation: ");
50
57
  len = redisFormatCommand(&cmd,"SET %b %b","foo",3,"b\0r",3);
51
58
  test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nb\0r\r\n",len) == 0 &&
52
59
  len == 4+4+(3+2)+4+(3+2)+4+(3+2));
53
60
  free(cmd);
54
61
 
62
+ test("Format command with %%b and an empty string: ");
63
+ len = redisFormatCommand(&cmd,"SET %b %b","foo",3,"",0);
64
+ test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$0\r\n\r\n",len) == 0 &&
65
+ len == 4+4+(3+2)+4+(3+2)+4+(0+2));
66
+ free(cmd);
67
+
68
+ test("Format command with literal %%: ");
69
+ len = redisFormatCommand(&cmd,"SET %% %%");
70
+ test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$1\r\n%\r\n$1\r\n%\r\n",len) == 0 &&
71
+ len == 4+4+(3+2)+4+(1+2)+4+(1+2));
72
+ free(cmd);
73
+
74
+ test("Format command with printf-delegation (long long): ");
75
+ len = redisFormatCommand(&cmd,"key:%08lld",1234ll);
76
+ test_cond(strncmp(cmd,"*1\r\n$12\r\nkey:00001234\r\n",len) == 0 &&
77
+ len == 4+5+(12+2));
78
+ free(cmd);
79
+
80
+ test("Format command with printf-delegation (float): ");
81
+ len = redisFormatCommand(&cmd,"v:%06.1f",12.34f);
82
+ test_cond(strncmp(cmd,"*1\r\n$8\r\nv:0012.3\r\n",len) == 0 &&
83
+ len == 4+4+(8+2));
84
+ free(cmd);
85
+
86
+ test("Format command with printf-delegation and extra interpolation: ");
87
+ len = redisFormatCommand(&cmd,"key:%d %b",1234,"foo",3);
88
+ test_cond(strncmp(cmd,"*2\r\n$8\r\nkey:1234\r\n$3\r\nfoo\r\n",len) == 0 &&
89
+ len == 4+4+(8+2)+4+(3+2));
90
+ free(cmd);
91
+
92
+ test("Format command with wrong printf format and extra interpolation: ");
93
+ len = redisFormatCommand(&cmd,"key:%08p %b",1234,"foo",3);
94
+ test_cond(strncmp(cmd,"*2\r\n$6\r\nkey:8p\r\n$3\r\nfoo\r\n",len) == 0 &&
95
+ len == 4+4+(6+2)+4+(3+2));
96
+ free(cmd);
97
+
55
98
  const char *argv[3];
56
99
  argv[0] = "SET";
57
- argv[1] = "foo";
100
+ argv[1] = "foo\0xxx";
58
101
  argv[2] = "bar";
59
- size_t lens[3] = { 3, 3, 3 };
102
+ size_t lens[3] = { 3, 7, 3 };
60
103
  int argc = 3;
61
104
 
62
105
  test("Format command by passing argc/argv without lengths: ");
@@ -67,38 +110,29 @@ static void test_format_commands() {
67
110
 
68
111
  test("Format command by passing argc/argv with lengths: ");
69
112
  len = redisFormatCommandArgv(&cmd,argc,argv,lens);
70
- test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 &&
71
- len == 4+4+(3+2)+4+(3+2)+4+(3+2));
113
+ test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$7\r\nfoo\0xxx\r\n$3\r\nbar\r\n",len) == 0 &&
114
+ len == 4+4+(3+2)+4+(7+2)+4+(3+2));
72
115
  free(cmd);
73
116
  }
74
117
 
75
118
  static void test_blocking_connection() {
76
119
  redisContext *c;
77
120
  redisReply *reply;
121
+ int major, minor;
78
122
 
79
- __connect(&c);
80
- test("Returns I/O error when the connection is lost: ");
81
- reply = redisCommand(c,"QUIT");
82
- test_cond(strcasecmp(reply->str,"OK") == 0 && redisCommand(c,"PING") == NULL);
83
-
84
- /* Two conditions may happen, depending on the type of connection.
85
- * When connected via TCP, the socket will not yet be aware of the closed
86
- * connection and the write(2) call will succeed, but the read(2) will
87
- * result in an EOF. When connected via Unix sockets, the socket will be
88
- * immediately aware that it was closed and fail on the write(2) call. */
89
- if (use_unix) {
90
- fprintf(stderr,"Error: %s\n", c->errstr);
91
- assert(c->err == REDIS_ERR_IO &&
92
- strcmp(c->errstr,"Broken pipe") == 0);
93
- } else {
94
- fprintf(stderr,"Error: %s\n", c->errstr);
95
- assert(c->err == REDIS_ERR_EOF &&
96
- strcmp(c->errstr,"Server closed the connection") == 0);
97
- }
98
- freeReplyObject(reply);
123
+ test("Returns error when host cannot be resolved: ");
124
+ c = redisConnect((char*)"idontexist.local", 6379);
125
+ test_cond(c->err == REDIS_ERR_OTHER &&
126
+ strcmp(c->errstr,"Can't resolve: idontexist.local") == 0);
99
127
  redisFree(c);
100
128
 
101
- __connect(&c); /* reconnect */
129
+ test("Returns error when the port is not open: ");
130
+ c = redisConnect((char*)"localhost", 56380);
131
+ test_cond(c->err == REDIS_ERR_IO &&
132
+ strcmp(c->errstr,"Connection refused") == 0);
133
+ redisFree(c);
134
+
135
+ __connect(&c);
102
136
  test("Is able to deliver commands: ");
103
137
  reply = redisCommand(c,"PING");
104
138
  test_cond(reply->type == REDIS_REPLY_STATUS &&
@@ -111,12 +145,9 @@ static void test_blocking_connection() {
111
145
 
112
146
  /* Make sure the DB is emtpy */
113
147
  reply = redisCommand(c,"DBSIZE");
114
- if (reply->type != REDIS_REPLY_INTEGER ||
115
- reply->integer != 0) {
116
- printf("Sorry DB 9 is not empty, test can not continue\n");
148
+ if (reply->type != REDIS_REPLY_INTEGER || reply->integer != 0) {
149
+ printf("Database #9 is not empty, test can not continue\n");
117
150
  exit(1);
118
- } else {
119
- printf("DB 9 is empty... test can continue\n");
120
151
  }
121
152
  freeReplyObject(reply);
122
153
 
@@ -182,6 +213,43 @@ static void test_blocking_connection() {
182
213
  reply->element[1]->type == REDIS_REPLY_STATUS &&
183
214
  strcasecmp(reply->element[1]->str,"pong") == 0);
184
215
  freeReplyObject(reply);
216
+
217
+ {
218
+ /* Find out Redis version to determine the path for the next test */
219
+ const char *field = "redis_version:";
220
+ char *p, *eptr;
221
+
222
+ reply = redisCommand(c,"INFO");
223
+ p = strstr(reply->str,field);
224
+ major = strtol(p+strlen(field),&eptr,10);
225
+ p = eptr+1; /* char next to the first "." */
226
+ minor = strtol(p,&eptr,10);
227
+ freeReplyObject(reply);
228
+ }
229
+
230
+ test("Returns I/O error when the connection is lost: ");
231
+ reply = redisCommand(c,"QUIT");
232
+ if (major >= 2 && minor > 0) {
233
+ /* > 2.0 returns OK on QUIT and read() should be issued once more
234
+ * to know the descriptor is at EOF. */
235
+ test_cond(strcasecmp(reply->str,"OK") == 0 &&
236
+ redisGetReply(c,(void**)&reply) == REDIS_ERR);
237
+ freeReplyObject(reply);
238
+ } else {
239
+ test_cond(reply == NULL);
240
+ }
241
+
242
+ /* On 2.0, QUIT will cause the connection to be closed immediately and
243
+ * the read(2) for the reply on QUIT will set the error to EOF.
244
+ * On >2.0, QUIT will return with OK and another read(2) needed to be
245
+ * issued to find out the socket was closed by the server. In both
246
+ * conditions, the error will be set to EOF. */
247
+ assert(c->err == REDIS_ERR_EOF &&
248
+ strcmp(c->errstr,"Server closed the connection") == 0);
249
+
250
+ /* Clean up context and reconnect again */
251
+ redisFree(c);
252
+ __connect(&c);
185
253
  }
186
254
 
187
255
  static void test_reply_reader() {
@@ -196,7 +264,7 @@ static void test_reply_reader() {
196
264
  ret = redisReplyReaderGetReply(reader,NULL);
197
265
  err = redisReplyReaderGetError(reader);
198
266
  test_cond(ret == REDIS_ERR &&
199
- strcasecmp(err,"protocol error, got \"@\" as reply type byte") == 0);
267
+ strcasecmp(err,"Protocol error, got \"@\" as reply type byte") == 0);
200
268
  redisReplyReaderFree(reader);
201
269
 
202
270
  /* when the reply already contains multiple items, they must be free'd
@@ -209,7 +277,18 @@ static void test_reply_reader() {
209
277
  ret = redisReplyReaderGetReply(reader,NULL);
210
278
  err = redisReplyReaderGetError(reader);
211
279
  test_cond(ret == REDIS_ERR &&
212
- strcasecmp(err,"protocol error, got \"@\" as reply type byte") == 0);
280
+ strcasecmp(err,"Protocol error, got \"@\" as reply type byte") == 0);
281
+ redisReplyReaderFree(reader);
282
+
283
+ test("Set error on nested multi bulks with depth > 1: ");
284
+ reader = redisReplyReaderCreate();
285
+ redisReplyReaderFeed(reader,(char*)"*1\r\n",4);
286
+ redisReplyReaderFeed(reader,(char*)"*1\r\n",4);
287
+ redisReplyReaderFeed(reader,(char*)"*1\r\n",4);
288
+ ret = redisReplyReaderGetReply(reader,NULL);
289
+ err = redisReplyReaderGetError(reader);
290
+ test_cond(ret == REDIS_ERR &&
291
+ strncasecmp(err,"No support for",14) == 0);
213
292
  redisReplyReaderFree(reader);
214
293
 
215
294
  test("Works with NULL functions for reply: ");
metadata CHANGED
@@ -1,12 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hiredis
3
3
  version: !ruby/object:Gem::Version
4
+ hash: 19
4
5
  prerelease: false
5
6
  segments:
6
7
  - 0
7
8
  - 1
8
- - 3
9
- version: 0.1.3
9
+ - 4
10
+ version: 0.1.4
10
11
  platform: ruby
11
12
  authors:
12
13
  - Pieter Noordhuis
@@ -14,7 +15,7 @@ autorequire:
14
15
  bindir: bin
15
16
  cert_chain: []
16
17
 
17
- date: 2010-11-06 00:00:00 -03:00
18
+ date: 2010-12-06 00:00:00 +01:00
18
19
  default_executable:
19
20
  dependencies:
20
21
  - !ruby/object:Gem::Dependency
@@ -25,6 +26,7 @@ dependencies:
25
26
  requirements:
26
27
  - - ~>
27
28
  - !ruby/object:Gem::Version
29
+ hash: 1
28
30
  segments:
29
31
  - 0
30
32
  - 7
@@ -40,6 +42,7 @@ dependencies:
40
42
  requirements:
41
43
  - - ~>
42
44
  - !ruby/object:Gem::Version
45
+ hash: 9
43
46
  segments:
44
47
  - 2
45
48
  - 1
@@ -94,6 +97,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
94
97
  requirements:
95
98
  - - ">="
96
99
  - !ruby/object:Gem::Version
100
+ hash: 3
97
101
  segments:
98
102
  - 0
99
103
  version: "0"
@@ -102,6 +106,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
102
106
  requirements:
103
107
  - - ">="
104
108
  - !ruby/object:Gem::Version
109
+ hash: 3
105
110
  segments:
106
111
  - 0
107
112
  version: "0"