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