/* Check the specified RDB file. Return 0 if the RDB looks sane, otherwise * 1 is returned. */ int redis_check_rdb(char *rdbfilename) { uint64_t dbid; int type, rdbver; char buf[1024]; long long expiretime, now = mstime(); FILE *fp; static rio rdb; /* Pointed by global struct riostate. */ if ((fp = fopen(rdbfilename,"r")) == NULL) return 1; rioInitWithFile(&rdb,fp); rdbstate.rio = &rdb; rdb.update_cksum = rdbLoadProgressCallback; if (rioRead(&rdb,buf,9) == 0) goto eoferr; buf[9] = '\0'; if (memcmp(buf,"REDIS",5) != 0) { rdbCheckError("Wrong signature trying to load DB from file"); return 1; } rdbver = atoi(buf+5); if (rdbver < 1 || rdbver > RDB_VERSION) { rdbCheckError("Can't handle RDB format version %d",rdbver); return 1; } startLoading(fp); while(1) { robj *key, *val; expiretime = -1; /* Read type. */ rdbstate.doing = RDB_CHECK_DOING_READ_TYPE; if ((type = rdbLoadType(&rdb)) == -1) goto eoferr; /* Handle special types. */ if (type == RDB_OPCODE_EXPIRETIME) { rdbstate.doing = RDB_CHECK_DOING_READ_EXPIRE; /* EXPIRETIME: load an expire associated with the next key * to load. Note that after loading an expire we need to * load the actual type, and continue. */ if ((expiretime = rdbLoadTime(&rdb)) == -1) goto eoferr; /* We read the time so we need to read the object type again. */ rdbstate.doing = RDB_CHECK_DOING_READ_TYPE; if ((type = rdbLoadType(&rdb)) == -1) goto eoferr; /* the EXPIRETIME opcode specifies time in seconds, so convert * into milliseconds. */ expiretime *= 1000; } else if (type == RDB_OPCODE_EXPIRETIME_MS) { /* EXPIRETIME_MS: milliseconds precision expire times introduced * with RDB v3. Like EXPIRETIME but no with more precision. */ rdbstate.doing = RDB_CHECK_DOING_READ_EXPIRE; if ((expiretime = rdbLoadMillisecondTime(&rdb)) == -1) goto eoferr; /* We read the time so we need to read the object type again. */ rdbstate.doing = RDB_CHECK_DOING_READ_TYPE; if ((type = rdbLoadType(&rdb)) == -1) goto eoferr; } else if (type == RDB_OPCODE_EOF) { /* EOF: End of file, exit the main loop. */ break; } else if (type == RDB_OPCODE_SELECTDB) { /* SELECTDB: Select the specified database. */ rdbstate.doing = RDB_CHECK_DOING_READ_LEN; if ((dbid = rdbLoadLen(&rdb,NULL)) == RDB_LENERR) goto eoferr; rdbCheckInfo("Selecting DB ID %d", dbid); continue; /* Read type again. */ } else if (type == RDB_OPCODE_RESIZEDB) { /* RESIZEDB: Hint about the size of the keys in the currently * selected data base, in order to avoid useless rehashing. */ uint64_t db_size, expires_size; rdbstate.doing = RDB_CHECK_DOING_READ_LEN; if ((db_size = rdbLoadLen(&rdb,NULL)) == RDB_LENERR) goto eoferr; if ((expires_size = rdbLoadLen(&rdb,NULL)) == RDB_LENERR) goto eoferr; continue; /* Read type again. */ } else if (type == RDB_OPCODE_AUX) { /* AUX: generic string-string fields. Use to add state to RDB * which is backward compatible. Implementations of RDB loading * are requierd to skip AUX fields they don't understand. * * An AUX field is composed of two strings: key and value. */ robj *auxkey, *auxval; rdbstate.doing = RDB_CHECK_DOING_READ_AUX; if ((auxkey = rdbLoadStringObject(&rdb)) == NULL) goto eoferr; if ((auxval = rdbLoadStringObject(&rdb)) == NULL) goto eoferr; rdbCheckInfo("AUX FIELD %s = '%s'", (char*)auxkey->ptr, (char*)auxval->ptr); decrRefCount(auxkey); decrRefCount(auxval); continue; /* Read type again. */ } else { if (!rdbIsObjectType(type)) { rdbCheckError("Invalid object type: %d", type); return 1; } rdbstate.key_type = type; } /* Read key */ rdbstate.doing = RDB_CHECK_DOING_READ_KEY; if ((key = rdbLoadStringObject(&rdb)) == NULL) goto eoferr; rdbstate.key = key; rdbstate.keys++; /* Read value */ rdbstate.doing = RDB_CHECK_DOING_READ_OBJECT_VALUE; if ((val = rdbLoadObject(type,&rdb)) == NULL) goto eoferr; /* Check if the key already expired. This function is used when loading * an RDB file from disk, either at startup, or when an RDB was * received from the master. In the latter case, the master is * responsible for key expiry. If we would expire keys here, the * snapshot taken by the master may not be reflected on the slave. */ if (server.masterhost == NULL && expiretime != -1 && expiretime < now) rdbstate.already_expired++; if (expiretime != -1) rdbstate.expires++; rdbstate.key = NULL; decrRefCount(key); decrRefCount(val); rdbstate.key_type = -1; } /* Verify the checksum if RDB version is >= 5 */ if (rdbver >= 5 && server.rdb_checksum) { uint64_t cksum, expected = rdb.cksum; rdbstate.doing = RDB_CHECK_DOING_CHECK_SUM; if (rioRead(&rdb,&cksum,8) == 0) goto eoferr; memrev64ifbe(&cksum); if (cksum == 0) { rdbCheckInfo("RDB file was saved with checksum disabled: no check performed."); } else if (cksum != expected) { rdbCheckError("RDB CRC error"); } else { rdbCheckInfo("Checksum OK"); } } fclose(fp); return 0; eoferr: /* unexpected end of file is handled here with a fatal exit */ if (rdbstate.error_set) { rdbCheckError(rdbstate.error); } else { rdbCheckError("Unexpected EOF reading RDB file"); } return 1; }
int rdbParse(char *rdbFile, keyValueHandler handler) { int type, loops = 0, dbid, valType; unsigned int rlen; char buf[1024]; time_t expiretime = -1; FILE *fp; sds key, sval; /* sval is simple string value.*/ sds *cval; /*complicatae value*/ /* Double constants initialization */ R_Zero = 0.0; R_PosInf = 1.0/R_Zero; R_NegInf = -1.0/R_Zero; R_Nan = R_Zero/R_Zero; if((fp = fopen(rdbFile, "r")) == NULL) { return PARSE_ERR; } if (freadCheck(buf, 9, 1, fp) == 0) { fclose(fp); fprintf(stderr, "fread err :%s\n", strerror(errno)); return PARSE_ERR; } buf[9] = '\0'; if (memcmp(buf, "REDIS", 5) != 0) { fclose(fp); fprintf(stderr, "Wrong signature trying to load DB from file\n"); return PARSE_ERR; } rdb_version = atoi(buf+5); if (rdb_version > 6) { fclose(fp); fprintf(stderr, "Can't handle RDB format version %d\n", rdb_version); return PARSE_ERR; } startParse(fp); while(1) { if(!(loops++ % 1000)) { /* record parse progress every 1000 loops. */ parseProgress(ftello(fp)); } if((type = rdbLoadType(fp)) == -1) return PARSE_ERR; if(type == REDIS_EXPIRETIME) { if ((expiretime = rdbLoadTime(fp)) == -1) return PARSE_ERR; if((type = rdbLoadType(fp)) == -1) return PARSE_ERR; } /* file end. */ if(type == REDIS_EOF) { break; } /* select db */ if(type == REDIS_SELECTDB) { dbid = rdbLoadLen(fp,NULL); continue; } /* load key. */ if ((key = rdbLoadStringObject(fp)) == NULL) { return PARSE_ERR; } if(type == REDIS_HASH_ZIPMAP) { valType = REDIS_HASH; } else if(type == REDIS_LIST_ZIPLIST) { valType = REDIS_LIST; } else if(type == REDIS_SET_INTSET) { valType = REDIS_SET; } else if(type == REDIS_ZSET_ZIPLIST) { valType = REDIS_ZSET; } else { valType = type; } /* load value. */ if(type == REDIS_STRING) { sval = rdbLoadValueObject(fp, type, &rlen); handler(valType, key, sval, rlen, expiretime); } else { cval = rdbLoadValueObject(fp, type, &rlen); handler(valType, key, cval, rlen, expiretime); } /* clean*/ sdsfree(key); if(valType == REDIS_STRING) { sdsfree(sval); } else { unsigned int k; for(k = 0; k < rlen; k++) { sdsfree(cval[k]); } zfree(cval); } } /* checksum */ uint64_t checksum = 0; int ret; ret = fread(&checksum, sizeof(checksum), 1, fp); if (ret == 1) { if ((long long)checksum != digest) { fprintf(stderr, "DB load failed, checksum does not match: %016llx != %016llx\n", (long long)checksum, digest); exit(1); } fprintf(stderr, "DB loaded, checksum: %016llx\n", digest); } parser_stats.stop_time = time(NULL); fclose(fp); return PARSE_OK; }