| 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 | } |