| 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 | time_t expiretime; |
| 95 | |
| 96 | memset(digest,0,20); /* This key-val digest */ |
| 97 | key = dictGetEntryKey(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 = lookupKeyRead(db,keyobj); |
| 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 | zset *zs = o->ptr; |
| 131 | dictIterator *di = dictGetIterator(zs->dict); |
| 132 | dictEntry *de; |
| 133 | |
| 134 | while((de = dictNext(di)) != NULL) { |
| 135 | robj *eleobj = dictGetEntryKey(de); |
| 136 | double *score = dictGetEntryVal(de); |
| 137 | unsigned char eledigest[20]; |
| 138 | |
| 139 | snprintf(buf,sizeof(buf),"%.17g",*score); |
| 140 | memset(eledigest,0,20); |
| 141 | mixObjectDigest(eledigest,eleobj); |
| 142 | mixDigest(eledigest,buf,strlen(buf)); |
| 143 | xorDigest(digest,eledigest,20); |
| 144 | } |
| 145 | dictReleaseIterator(di); |
| 146 | } else if (o->type == REDIS_HASH) { |
| 147 | hashTypeIterator *hi; |
| 148 | robj *obj; |
| 149 | |
| 150 | hi = hashTypeInitIterator(o); |
| 151 | while (hashTypeNext(hi) != REDIS_ERR) { |
| 152 | unsigned char eledigest[20]; |
| 153 | |
| 154 | memset(eledigest,0,20); |
| 155 | obj = hashTypeCurrentObject(hi,REDIS_HASH_KEY); |
| 156 | mixObjectDigest(eledigest,obj); |
| 157 | decrRefCount(obj); |
| 158 | obj = hashTypeCurrentObject(hi,REDIS_HASH_VALUE); |
| 159 | mixObjectDigest(eledigest,obj); |
| 160 | decrRefCount(obj); |
| 161 | xorDigest(digest,eledigest,20); |
| 162 | } |
| 163 | hashTypeReleaseIterator(hi); |
| 164 | } else { |
| 165 | redisPanic("Unknown object type"); |
| 166 | } |
| 167 | /* If the key has an expire, add it to the mix */ |
| 168 | if (expiretime != -1) xorDigest(digest,"!!expire!!",10); |
| 169 | /* We can finally xor the key-val digest to the final digest */ |
| 170 | xorDigest(final,digest,20); |
| 171 | decrRefCount(keyobj); |
| 172 | } |
| 173 | dictReleaseIterator(di); |
| 174 | } |
| 175 | } |
| 176 | |
| 177 | void debugCommand(redisClient *c) { |
| 178 | if (!strcasecmp(c->argv[1]->ptr,"segfault")) { |
| 179 | *((char*)-1) = 'x'; |
| 180 | } else if (!strcasecmp(c->argv[1]->ptr,"flushcache")) { |
| 181 | if (!server.ds_enabled) { |
| 182 | addReplyError(c, "DEBUG FLUSHCACHE called with diskstore off."); |
| 183 | return; |
| 184 | } else if (server.bgsavethread != (pthread_t) -1) { |
| 185 | addReplyError(c, "Can't flush cache while BGSAVE is in progress."); |
| 186 | return; |
| 187 | } else { |
| 188 | /* To flush the whole cache we need to wait for everything to |
| 189 | * be flushed on disk... */ |
| 190 | cacheForcePointInTime(); |
| 191 | emptyDb(); |
| 192 | addReply(c,shared.ok); |
| 193 | return; |
| 194 | } |
| 195 | } else if (!strcasecmp(c->argv[1]->ptr,"reload")) { |
| 196 | if (server.ds_enabled) { |
| 197 | addReply(c,shared.ok); |
| 198 | return; |
| 199 | } |
| 200 | if (rdbSave(server.dbfilename) != REDIS_OK) { |
| 201 | addReply(c,shared.err); |
| 202 | return; |
| 203 | } |
| 204 | emptyDb(); |
| 205 | if (rdbLoad(server.dbfilename) != REDIS_OK) { |
| 206 | addReply(c,shared.err); |
| 207 | return; |
| 208 | } |
| 209 | redisLog(REDIS_WARNING,"DB reloaded by DEBUG RELOAD"); |
| 210 | addReply(c,shared.ok); |
| 211 | } else if (!strcasecmp(c->argv[1]->ptr,"loadaof")) { |
| 212 | emptyDb(); |
| 213 | if (loadAppendOnlyFile(server.appendfilename) != REDIS_OK) { |
| 214 | addReply(c,shared.err); |
| 215 | return; |
| 216 | } |
| 217 | redisLog(REDIS_WARNING,"Append Only File loaded by DEBUG LOADAOF"); |
| 218 | addReply(c,shared.ok); |
| 219 | } else if (!strcasecmp(c->argv[1]->ptr,"object") && c->argc == 3) { |
| 220 | dictEntry *de; |
| 221 | robj *val; |
| 222 | char *strenc; |
| 223 | |
| 224 | if (server.ds_enabled) lookupKeyRead(c->db,c->argv[2]); |
| 225 | if ((de = dictFind(c->db->dict,c->argv[2]->ptr)) == NULL) { |
| 226 | addReply(c,shared.nokeyerr); |
| 227 | return; |
| 228 | } |
| 229 | val = dictGetEntryVal(de); |
| 230 | strenc = strEncoding(val->encoding); |
| 231 | |
| 232 | addReplyStatusFormat(c, |
| 233 | "Value at:%p refcount:%d " |
| 234 | "encoding:%s serializedlength:%lld " |
| 235 | "lru:%d lru_seconds_idle:%lu", |
| 236 | (void*)val, val->refcount, |
| 237 | strenc, (long long) rdbSavedObjectLen(val), |
| 238 | val->lru, estimateObjectIdleTime(val)); |
| 239 | } else if (!strcasecmp(c->argv[1]->ptr,"populate") && c->argc == 3) { |
| 240 | long keys, j; |
| 241 | robj *key, *val; |
| 242 | char buf[128]; |
| 243 | |
| 244 | if (getLongFromObjectOrReply(c, c->argv[2], &keys, NULL) != REDIS_OK) |
| 245 | return; |
| 246 | for (j = 0; j < keys; j++) { |
| 247 | snprintf(buf,sizeof(buf),"key:%lu",j); |
| 248 | key = createStringObject(buf,strlen(buf)); |
| 249 | if (lookupKeyRead(c->db,key) != NULL) { |
| 250 | decrRefCount(key); |
| 251 | continue; |
| 252 | } |
| 253 | snprintf(buf,sizeof(buf),"value:%lu",j); |
| 254 | val = createStringObject(buf,strlen(buf)); |
| 255 | dbAdd(c->db,key,val); |
| 256 | decrRefCount(key); |
| 257 | } |
| 258 | addReply(c,shared.ok); |
| 259 | } else if (!strcasecmp(c->argv[1]->ptr,"digest") && c->argc == 2) { |
| 260 | unsigned char digest[20]; |
| 261 | sds d = sdsempty(); |
| 262 | int j; |
| 263 | |
| 264 | computeDatasetDigest(digest); |
| 265 | for (j = 0; j < 20; j++) |
| 266 | d = sdscatprintf(d, "%02x",digest[j]); |
| 267 | addReplyStatus(c,d); |
| 268 | sdsfree(d); |
| 269 | } else { |
| 270 | addReplyError(c, |
| 271 | "Syntax error, try DEBUG [SEGFAULT|OBJECT <key>|SWAPIN <key>|SWAPOUT <key>|RELOAD]"); |
| 272 | } |
| 273 | } |
| 274 | |
| 275 | void _redisAssert(char *estr, char *file, int line) { |
| 276 | redisLog(REDIS_WARNING,"=== ASSERTION FAILED ==="); |
| 277 | redisLog(REDIS_WARNING,"==> %s:%d '%s' is not true",file,line,estr); |
| 278 | #ifdef HAVE_BACKTRACE |
| 279 | redisLog(REDIS_WARNING,"(forcing SIGSEGV in order to print the stack trace)"); |
| 280 | *((char*)-1) = 'x'; |
| 281 | #endif |
| 282 | } |
| 283 | |
| 284 | void _redisPanic(char *msg, char *file, int line) { |
| 285 | redisLog(REDIS_WARNING,"------------------------------------------------"); |
| 286 | redisLog(REDIS_WARNING,"!!! Software Failure. Press left mouse button to continue"); |
| 287 | redisLog(REDIS_WARNING,"Guru Meditation: %s #%s:%d",msg,file,line); |
| 288 | #ifdef HAVE_BACKTRACE |
| 289 | redisLog(REDIS_WARNING,"(forcing SIGSEGV in order to print the stack trace)"); |
| 290 | redisLog(REDIS_WARNING,"------------------------------------------------"); |
| 291 | *((char*)-1) = 'x'; |
| 292 | #endif |
| 293 | } |