]>
Commit | Line | Data |
---|---|---|
1 | #include "redis.h" | |
2 | #include "sha1.h" /* SHA1 is used for DEBUG DIGEST */ | |
3 | ||
4 | #include <arpa/inet.h> | |
5 | ||
6 | /* ================================= Debugging ============================== */ | |
7 | ||
8 | /* Compute the sha1 of string at 's' with 'len' bytes long. | |
9 | * The SHA1 is then xored againt the string pointed by digest. | |
10 | * Since xor is commutative, this operation is used in order to | |
11 | * "add" digests relative to unordered elements. | |
12 | * | |
13 | * So digest(a,b,c,d) will be the same of digest(b,a,c,d) */ | |
14 | void xorDigest(unsigned char *digest, void *ptr, size_t len) { | |
15 | SHA1_CTX ctx; | |
16 | unsigned char hash[20], *s = ptr; | |
17 | int j; | |
18 | ||
19 | SHA1Init(&ctx); | |
20 | SHA1Update(&ctx,s,len); | |
21 | SHA1Final(hash,&ctx); | |
22 | ||
23 | for (j = 0; j < 20; j++) | |
24 | digest[j] ^= hash[j]; | |
25 | } | |
26 | ||
27 | void xorObjectDigest(unsigned char *digest, robj *o) { | |
28 | o = getDecodedObject(o); | |
29 | xorDigest(digest,o->ptr,sdslen(o->ptr)); | |
30 | decrRefCount(o); | |
31 | } | |
32 | ||
33 | /* This function instead of just computing the SHA1 and xoring it | |
34 | * against diget, also perform the digest of "digest" itself and | |
35 | * replace the old value with the new one. | |
36 | * | |
37 | * So the final digest will be: | |
38 | * | |
39 | * digest = SHA1(digest xor SHA1(data)) | |
40 | * | |
41 | * This function is used every time we want to preserve the order so | |
42 | * that digest(a,b,c,d) will be different than digest(b,c,d,a) | |
43 | * | |
44 | * Also note that mixdigest("foo") followed by mixdigest("bar") | |
45 | * will lead to a different digest compared to "fo", "obar". | |
46 | */ | |
47 | void mixDigest(unsigned char *digest, void *ptr, size_t len) { | |
48 | SHA1_CTX ctx; | |
49 | char *s = ptr; | |
50 | ||
51 | xorDigest(digest,s,len); | |
52 | SHA1Init(&ctx); | |
53 | SHA1Update(&ctx,digest,20); | |
54 | SHA1Final(digest,&ctx); | |
55 | } | |
56 | ||
57 | void mixObjectDigest(unsigned char *digest, robj *o) { | |
58 | o = getDecodedObject(o); | |
59 | mixDigest(digest,o->ptr,sdslen(o->ptr)); | |
60 | decrRefCount(o); | |
61 | } | |
62 | ||
63 | /* Compute the dataset digest. Since keys, sets elements, hashes elements | |
64 | * are not ordered, we use a trick: every aggregate digest is the xor | |
65 | * of the digests of their elements. This way the order will not change | |
66 | * the result. For list instead we use a feedback entering the output digest | |
67 | * as input in order to ensure that a different ordered list will result in | |
68 | * a different digest. */ | |
69 | void computeDatasetDigest(unsigned char *final) { | |
70 | unsigned char digest[20]; | |
71 | char buf[128]; | |
72 | dictIterator *di = NULL; | |
73 | dictEntry *de; | |
74 | int j; | |
75 | uint32_t aux; | |
76 | ||
77 | memset(final,0,20); /* Start with a clean result */ | |
78 | ||
79 | for (j = 0; j < server.dbnum; j++) { | |
80 | redisDb *db = server.db+j; | |
81 | ||
82 | if (dictSize(db->dict) == 0) continue; | |
83 | di = dictGetIterator(db->dict); | |
84 | ||
85 | /* hash the DB id, so the same dataset moved in a different | |
86 | * DB will lead to a different digest */ | |
87 | aux = htonl(j); | |
88 | mixDigest(final,&aux,sizeof(aux)); | |
89 | ||
90 | /* Iterate this DB writing every entry */ | |
91 | while((de = dictNext(di)) != NULL) { | |
92 | sds key; | |
93 | robj *keyobj, *o; | |
94 | long long expiretime; | |
95 | ||
96 | memset(digest,0,20); /* This key-val digest */ | |
97 | key = dictGetKey(de); | |
98 | keyobj = createStringObject(key,sdslen(key)); | |
99 | ||
100 | mixDigest(digest,key,sdslen(key)); | |
101 | ||
102 | /* Make sure the key is loaded if VM is active */ | |
103 | o = dictGetVal(de); | |
104 | ||
105 | aux = htonl(o->type); | |
106 | mixDigest(digest,&aux,sizeof(aux)); | |
107 | expiretime = getExpire(db,keyobj); | |
108 | ||
109 | /* Save the key and associated value */ | |
110 | if (o->type == REDIS_STRING) { | |
111 | mixObjectDigest(digest,o); | |
112 | } else if (o->type == REDIS_LIST) { | |
113 | listTypeIterator *li = listTypeInitIterator(o,0,REDIS_TAIL); | |
114 | listTypeEntry entry; | |
115 | while(listTypeNext(li,&entry)) { | |
116 | robj *eleobj = listTypeGet(&entry); | |
117 | mixObjectDigest(digest,eleobj); | |
118 | decrRefCount(eleobj); | |
119 | } | |
120 | listTypeReleaseIterator(li); | |
121 | } else if (o->type == REDIS_SET) { | |
122 | setTypeIterator *si = setTypeInitIterator(o); | |
123 | robj *ele; | |
124 | while((ele = setTypeNextObject(si)) != NULL) { | |
125 | xorObjectDigest(digest,ele); | |
126 | decrRefCount(ele); | |
127 | } | |
128 | setTypeReleaseIterator(si); | |
129 | } else if (o->type == REDIS_ZSET) { | |
130 | unsigned char eledigest[20]; | |
131 | ||
132 | if (o->encoding == REDIS_ENCODING_ZIPLIST) { | |
133 | unsigned char *zl = o->ptr; | |
134 | unsigned char *eptr, *sptr; | |
135 | unsigned char *vstr; | |
136 | unsigned int vlen; | |
137 | long long vll; | |
138 | double score; | |
139 | ||
140 | eptr = ziplistIndex(zl,0); | |
141 | redisAssert(eptr != NULL); | |
142 | sptr = ziplistNext(zl,eptr); | |
143 | redisAssert(sptr != NULL); | |
144 | ||
145 | while (eptr != NULL) { | |
146 | redisAssert(ziplistGet(eptr,&vstr,&vlen,&vll)); | |
147 | score = zzlGetScore(sptr); | |
148 | ||
149 | memset(eledigest,0,20); | |
150 | if (vstr != NULL) { | |
151 | mixDigest(eledigest,vstr,vlen); | |
152 | } else { | |
153 | ll2string(buf,sizeof(buf),vll); | |
154 | mixDigest(eledigest,buf,strlen(buf)); | |
155 | } | |
156 | ||
157 | snprintf(buf,sizeof(buf),"%.17g",score); | |
158 | mixDigest(eledigest,buf,strlen(buf)); | |
159 | xorDigest(digest,eledigest,20); | |
160 | zzlNext(zl,&eptr,&sptr); | |
161 | } | |
162 | } else if (o->encoding == REDIS_ENCODING_SKIPLIST) { | |
163 | zset *zs = o->ptr; | |
164 | dictIterator *di = dictGetIterator(zs->dict); | |
165 | dictEntry *de; | |
166 | ||
167 | while((de = dictNext(di)) != NULL) { | |
168 | robj *eleobj = dictGetKey(de); | |
169 | double *score = dictGetVal(de); | |
170 | ||
171 | snprintf(buf,sizeof(buf),"%.17g",*score); | |
172 | memset(eledigest,0,20); | |
173 | mixObjectDigest(eledigest,eleobj); | |
174 | mixDigest(eledigest,buf,strlen(buf)); | |
175 | xorDigest(digest,eledigest,20); | |
176 | } | |
177 | dictReleaseIterator(di); | |
178 | } else { | |
179 | redisPanic("Unknown sorted set encoding"); | |
180 | } | |
181 | } else if (o->type == REDIS_HASH) { | |
182 | hashTypeIterator *hi; | |
183 | robj *obj; | |
184 | ||
185 | hi = hashTypeInitIterator(o); | |
186 | while (hashTypeNext(hi) != REDIS_ERR) { | |
187 | unsigned char eledigest[20]; | |
188 | ||
189 | memset(eledigest,0,20); | |
190 | obj = hashTypeCurrentObject(hi,REDIS_HASH_KEY); | |
191 | mixObjectDigest(eledigest,obj); | |
192 | decrRefCount(obj); | |
193 | obj = hashTypeCurrentObject(hi,REDIS_HASH_VALUE); | |
194 | mixObjectDigest(eledigest,obj); | |
195 | decrRefCount(obj); | |
196 | xorDigest(digest,eledigest,20); | |
197 | } | |
198 | hashTypeReleaseIterator(hi); | |
199 | } else { | |
200 | redisPanic("Unknown object type"); | |
201 | } | |
202 | /* If the key has an expire, add it to the mix */ | |
203 | if (expiretime != -1) xorDigest(digest,"!!expire!!",10); | |
204 | /* We can finally xor the key-val digest to the final digest */ | |
205 | xorDigest(final,digest,20); | |
206 | decrRefCount(keyobj); | |
207 | } | |
208 | dictReleaseIterator(di); | |
209 | } | |
210 | } | |
211 | ||
212 | void debugCommand(redisClient *c) { | |
213 | if (!strcasecmp(c->argv[1]->ptr,"segfault")) { | |
214 | *((char*)-1) = 'x'; | |
215 | } else if (!strcasecmp(c->argv[1]->ptr,"assert")) { | |
216 | if (c->argc >= 3) c->argv[2] = tryObjectEncoding(c->argv[2]); | |
217 | redisAssertWithInfo(c,c->argv[0],1 == 2); | |
218 | } else if (!strcasecmp(c->argv[1]->ptr,"reload")) { | |
219 | if (rdbSave(server.dbfilename) != REDIS_OK) { | |
220 | addReply(c,shared.err); | |
221 | return; | |
222 | } | |
223 | emptyDb(); | |
224 | if (rdbLoad(server.dbfilename) != REDIS_OK) { | |
225 | addReplyError(c,"Error trying to load the RDB dump"); | |
226 | return; | |
227 | } | |
228 | redisLog(REDIS_WARNING,"DB reloaded by DEBUG RELOAD"); | |
229 | addReply(c,shared.ok); | |
230 | } else if (!strcasecmp(c->argv[1]->ptr,"loadaof")) { | |
231 | emptyDb(); | |
232 | if (loadAppendOnlyFile(server.appendfilename) != REDIS_OK) { | |
233 | addReply(c,shared.err); | |
234 | return; | |
235 | } | |
236 | redisLog(REDIS_WARNING,"Append Only File loaded by DEBUG LOADAOF"); | |
237 | addReply(c,shared.ok); | |
238 | } else if (!strcasecmp(c->argv[1]->ptr,"object") && c->argc == 3) { | |
239 | dictEntry *de; | |
240 | robj *val; | |
241 | char *strenc; | |
242 | ||
243 | if ((de = dictFind(c->db->dict,c->argv[2]->ptr)) == NULL) { | |
244 | addReply(c,shared.nokeyerr); | |
245 | return; | |
246 | } | |
247 | val = dictGetVal(de); | |
248 | strenc = strEncoding(val->encoding); | |
249 | ||
250 | addReplyStatusFormat(c, | |
251 | "Value at:%p refcount:%d " | |
252 | "encoding:%s serializedlength:%lld " | |
253 | "lru:%d lru_seconds_idle:%lu", | |
254 | (void*)val, val->refcount, | |
255 | strenc, (long long) rdbSavedObjectLen(val), | |
256 | val->lru, estimateObjectIdleTime(val)); | |
257 | } else if (!strcasecmp(c->argv[1]->ptr,"populate") && c->argc == 3) { | |
258 | long keys, j; | |
259 | robj *key, *val; | |
260 | char buf[128]; | |
261 | ||
262 | if (getLongFromObjectOrReply(c, c->argv[2], &keys, NULL) != REDIS_OK) | |
263 | return; | |
264 | for (j = 0; j < keys; j++) { | |
265 | snprintf(buf,sizeof(buf),"key:%lu",j); | |
266 | key = createStringObject(buf,strlen(buf)); | |
267 | if (lookupKeyRead(c->db,key) != NULL) { | |
268 | decrRefCount(key); | |
269 | continue; | |
270 | } | |
271 | snprintf(buf,sizeof(buf),"value:%lu",j); | |
272 | val = createStringObject(buf,strlen(buf)); | |
273 | dbAdd(c->db,key,val); | |
274 | decrRefCount(key); | |
275 | } | |
276 | addReply(c,shared.ok); | |
277 | } else if (!strcasecmp(c->argv[1]->ptr,"digest") && c->argc == 2) { | |
278 | unsigned char digest[20]; | |
279 | sds d = sdsempty(); | |
280 | int j; | |
281 | ||
282 | computeDatasetDigest(digest); | |
283 | for (j = 0; j < 20; j++) | |
284 | d = sdscatprintf(d, "%02x",digest[j]); | |
285 | addReplyStatus(c,d); | |
286 | sdsfree(d); | |
287 | } else if (!strcasecmp(c->argv[1]->ptr,"sleep") && c->argc == 3) { | |
288 | double dtime = strtod(c->argv[2]->ptr,NULL); | |
289 | long long utime = dtime*1000000; | |
290 | ||
291 | usleep(utime); | |
292 | addReply(c,shared.ok); | |
293 | } else { | |
294 | addReplyError(c, | |
295 | "Syntax error, try DEBUG [SEGFAULT|OBJECT <key>|SWAPIN <key>|SWAPOUT <key>|RELOAD]"); | |
296 | } | |
297 | } | |
298 | ||
299 | void _redisAssert(char *estr, char *file, int line) { | |
300 | bugReportStart(); | |
301 | redisLog(REDIS_WARNING,"=== ASSERTION FAILED ==="); | |
302 | redisLog(REDIS_WARNING,"==> %s:%d '%s' is not true",file,line,estr); | |
303 | #ifdef HAVE_BACKTRACE | |
304 | server.assert_failed = estr; | |
305 | server.assert_file = file; | |
306 | server.assert_line = line; | |
307 | redisLog(REDIS_WARNING,"(forcing SIGSEGV to print the bug report.)"); | |
308 | *((char*)-1) = 'x'; | |
309 | #endif | |
310 | } | |
311 | ||
312 | void _redisAssertPrintClientInfo(redisClient *c) { | |
313 | int j; | |
314 | ||
315 | bugReportStart(); | |
316 | redisLog(REDIS_WARNING,"=== ASSERTION FAILED CLIENT CONTEXT ==="); | |
317 | redisLog(REDIS_WARNING,"client->flags = %d", c->flags); | |
318 | redisLog(REDIS_WARNING,"client->fd = %d", c->fd); | |
319 | redisLog(REDIS_WARNING,"client->argc = %d", c->argc); | |
320 | for (j=0; j < c->argc; j++) { | |
321 | char buf[128]; | |
322 | char *arg; | |
323 | ||
324 | if (c->argv[j]->type == REDIS_STRING && | |
325 | c->argv[j]->encoding == REDIS_ENCODING_RAW) | |
326 | { | |
327 | arg = (char*) c->argv[j]->ptr; | |
328 | } else { | |
329 | snprintf(buf,sizeof(buf),"Object type: %d, encoding: %d", | |
330 | c->argv[j]->type, c->argv[j]->encoding); | |
331 | arg = buf; | |
332 | } | |
333 | redisLog(REDIS_WARNING,"client->argv[%d] = \"%s\" (refcount: %d)", | |
334 | j, arg, c->argv[j]->refcount); | |
335 | } | |
336 | } | |
337 | ||
338 | void _redisAssertPrintObject(robj *o) { | |
339 | bugReportStart(); | |
340 | redisLog(REDIS_WARNING,"=== ASSERTION FAILED OBJECT CONTEXT ==="); | |
341 | redisLog(REDIS_WARNING,"Object type: %d", o->type); | |
342 | redisLog(REDIS_WARNING,"Object encoding: %d", o->encoding); | |
343 | redisLog(REDIS_WARNING,"Object refcount: %d", o->refcount); | |
344 | if (o->type == REDIS_STRING && o->encoding == REDIS_ENCODING_RAW) { | |
345 | redisLog(REDIS_WARNING,"Object raw string len: %d", sdslen(o->ptr)); | |
346 | if (sdslen(o->ptr) < 4096) | |
347 | redisLog(REDIS_WARNING,"Object raw string content: \"%s\"", (char*)o->ptr); | |
348 | } | |
349 | } | |
350 | ||
351 | void _redisAssertWithInfo(redisClient *c, robj *o, char *estr, char *file, int line) { | |
352 | if (c) _redisAssertPrintClientInfo(c); | |
353 | if (o) _redisAssertPrintObject(o); | |
354 | _redisAssert(estr,file,line); | |
355 | } | |
356 | ||
357 | void _redisPanic(char *msg, char *file, int line) { | |
358 | bugReportStart(); | |
359 | redisLog(REDIS_WARNING,"------------------------------------------------"); | |
360 | redisLog(REDIS_WARNING,"!!! Software Failure. Press left mouse button to continue"); | |
361 | redisLog(REDIS_WARNING,"Guru Meditation: %s #%s:%d",msg,file,line); | |
362 | #ifdef HAVE_BACKTRACE | |
363 | redisLog(REDIS_WARNING,"(forcing SIGSEGV in order to print the stack trace)"); | |
364 | redisLog(REDIS_WARNING,"------------------------------------------------"); | |
365 | *((char*)-1) = 'x'; | |
366 | #endif | |
367 | } |