src/rdb.c C 5,547 lines View on github.com → Search inside
File is large — showing lines 1–2,000 of 5,547.
1/*2 * Copyright (c) 2009-Present, Redis Ltd.3 * All rights reserved.4 *5 * Copyright (c) 2024-present, Valkey contributors.6 * All rights reserved.7 *8 * Licensed under your choice of (a) the Redis Source Available License 2.09 * (RSALv2); or (b) the Server Side Public License v1 (SSPLv1); or (c) the10 * GNU Affero General Public License v3 (AGPLv3).11 *12 * Portions of this file are available under BSD3 terms; see REDISCONTRIBUTIONS for more information.13 */1415#include "server.h"16#include "lzf.h"    /* LZF compression library */17#include "zipmap.h"18#include "endianconv.h"19#include "fpconv_dtoa.h"20#include "stream.h"21#include "functions.h"22#include "intset.h"  /* Compact integer set structure */23#include "bio.h"24#include "cluster_asm.h"25#include "keymeta.h"2627#include <math.h>28#include <fcntl.h>29#include <sys/types.h>30#include <sys/time.h>31#include <sys/resource.h>32#include <sys/wait.h>33#include <arpa/inet.h>34#include <sys/stat.h>35#include <sys/param.h>3637/* This macro is called when the internal RDB structure is corrupt */38#define rdbReportCorruptRDB(...) rdbReportError(1, __LINE__,__VA_ARGS__)39/* This macro is called when RDB read failed (possibly a short read) */40#define rdbReportReadError(...) rdbReportError(0, __LINE__,__VA_ARGS__)4142/* This macro tells if we are in the context of a RESTORE command, and not loading an RDB or AOF. */43#define isRestoreContext() \44    ((server.current_client == NULL || server.current_client->id == CLIENT_ID_AOF) ? 0 : 1)4546char* rdbFileBeingLoaded = NULL; /* used for rdb checking on read error */4748extern int rdbCheckMode;49void rdbCheckError(const char *fmt, ...);50void rdbCheckSetError(const char *fmt, ...);5152#ifdef __GNUC__53void rdbReportError(int corruption_error, int linenum, char *reason, ...) __attribute__ ((format (printf, 3, 4)));54#endif55void rdbReportError(int corruption_error, int linenum, char *reason, ...) {56    va_list ap;57    char msg[1024];58    int len;5960    len = snprintf(msg,sizeof(msg),61        "Internal error in RDB reading offset %llu, function at rdb.c:%d -> ",62        (unsigned long long)server.loading_loaded_bytes, linenum);63    va_start(ap,reason);64    vsnprintf(msg+len,sizeof(msg)-len,reason,ap);65    va_end(ap);6667    if (isRestoreContext()) {68        /* If we're in the context of a RESTORE command, just propagate the error. */69        /* log in VERBOSE, and return (don't exit). */70        serverLog(LL_VERBOSE, "%s", msg);71        return;72    } else if (rdbCheckMode) {73        /* If we're inside the rdb checker, let it handle the error. */74        rdbCheckError("%s",msg);75    } else if (rdbFileBeingLoaded) {76        /* If we're loading an rdb file form disk, run rdb check (and exit) */77        serverLog(LL_WARNING, "%s", msg);78        char *argv[2] = {"",rdbFileBeingLoaded};79        if (anetIsFifo(argv[1])) {80            /* Cannot check RDB FIFO because we cannot reopen the FIFO and check already streamed data. */81            rdbCheckError("Cannot check RDB that is a FIFO: %s", argv[1]);82            return;83        }84        rdbClearHashTemplates(); /* Clear loading map if any */85        redis_check_rdb_main(2,argv,NULL);86    } else if (corruption_error) {87        /* In diskless loading, in case of corrupt file, log and exit. */88        serverLog(LL_WARNING, "%s. Failure loading rdb format", msg);89    } else {90        /* In diskless loading, in case of a short read (not a corrupt91         * file), log and proceed (don't exit). */92        serverLog(LL_WARNING, "%s. Failure loading rdb format from socket, assuming connection error, resuming operation.", msg);93        return;94    }95    serverLog(LL_WARNING, "Terminating server after rdb file reading failure.");96    exit(1);97}9899ssize_t rdbWriteRaw(rio *rdb, void *p, size_t len) {100    if (rdb && rioWrite(rdb,p,len) == 0)101        return -1;102    return len;103}104105static inline int rdbIsDumpPayload(const rio *rdb) {106    /* A NULL rio is used by DEBUG OBJECT, which historically reports the DUMP107     * payload serialization size. */108    return rdb == NULL || (rdb->flags & RIO_FLAG_DUMP_PAYLOAD);109}110111int rdbSaveType(rio *rdb, unsigned char type) {112    return rdbWriteRaw(rdb,&type,1);113}114115/* Load a "type" in RDB format, that is a one byte unsigned integer.116 * This function is not only used to load object types, but also special117 * "types" like the end-of-file type, the EXPIRE type, and so forth. */118int rdbLoadType(rio *rdb) {119    unsigned char type;120    if (rioRead(rdb,&type,1) == 0) return -1;121    return type;122}123124/* This is only used to load old databases stored with the RDB_OPCODE_EXPIRETIME125 * opcode. New versions of Redis store using the RDB_OPCODE_EXPIRETIME_MS126 * opcode. On error -1 is returned, however this could be a valid time, so127 * to check for loading errors the caller should call rioGetReadError() after128 * calling this function. */129time_t rdbLoadTime(rio *rdb) {130    int32_t t32;131    if (rioRead(rdb,&t32,4) == 0) return -1;132    return (time_t)t32;133}134135/* Save a signed 64-bit integer in little-endian format. */136ssize_t rdbSaveSignedInteger(rio *rdb, int64_t val) {137    memrev64ifbe(&val); /* Store in little endian. */138    return rdbWriteRaw(rdb, &val, 8);139}140141/* This function loads a signed 64-bit integer from the RDB file. It gets the142 * version of the RDB because, unfortunately, before Redis 5 (RDB version 9),143 * the function failed to convert data to/from little endian, so RDB files with144 * keys having expires could not be shared between big endian and little endian145 * systems (because the expire time will be totally wrong). The fix for this is146 * just to call memrev64ifbe(), however if we fix this for all the RDB versions,147 * this call will introduce an incompatibility for big endian systems:148 * after upgrading to Redis version 5 they will no longer be able to load their149 * own old RDB files. Because of that, we instead fix the function only for new150 * RDB versions, and load older RDB versions as we used to do in the past,151 * allowing big endian systems to load their own old RDB files.152 *153 * On I/O error the function returns INT64_MAX, however if this is also a154 * valid stored value, the caller should use rioGetReadError() to check for155 * errors after calling this function. */156int64_t rdbLoadSignedInteger(rio *rdb, int rdbver) {157    int64_t val;158    if (rioRead(rdb, &val, 8) == 0) return INT64_MAX;159    if (rdbver >= 9) /* Check the top comment of this function. */160        memrev64ifbe(&val); /* Convert in big endian if the system is BE. */161    return val;162}163164/* Wrappers for millisecond time - these just call the signed integer functions */165ssize_t rdbSaveMillisecondTime(rio *rdb, long long t) {166    return rdbSaveSignedInteger(rdb, (int64_t)t);167}168169long long rdbLoadMillisecondTime(rio *rdb, int rdbver) {170    return (long long)rdbLoadSignedInteger(rdb, rdbver);171}172173/* Saves an encoded length. The first two bits in the first byte are used to174 * hold the encoding type. See the RDB_* definitions for more information175 * on the types of encoding. */176int rdbSaveLen(rio *rdb, uint64_t len) {177    unsigned char buf[2];178    size_t nwritten;179180    if (len < (1<<6)) {181        /* Save a 6 bit len */182        buf[0] = (len&0xFF)|(RDB_6BITLEN<<6);183        if (rdbWriteRaw(rdb,buf,1) == -1) return -1;184        nwritten = 1;185    } else if (len < (1<<14)) {186        /* Save a 14 bit len */187        buf[0] = ((len>>8)&0xFF)|(RDB_14BITLEN<<6);188        buf[1] = len&0xFF;189        if (rdbWriteRaw(rdb,buf,2) == -1) return -1;190        nwritten = 2;191    } else if (len <= UINT32_MAX) {192        /* Save a 32 bit len */193        buf[0] = RDB_32BITLEN;194        if (rdbWriteRaw(rdb,buf,1) == -1) return -1;195        uint32_t len32 = htonl(len);196        if (rdbWriteRaw(rdb,&len32,4) == -1) return -1;197        nwritten = 1+4;198    } else {199        /* Save a 64 bit len */200        buf[0] = RDB_64BITLEN;201        if (rdbWriteRaw(rdb,buf,1) == -1) return -1;202        len = htonu64(len);203        if (rdbWriteRaw(rdb,&len,8) == -1) return -1;204        nwritten = 1+8;205    }206    return nwritten;207}208209210/* Load an encoded length. If the loaded length is a normal length as stored211 * with rdbSaveLen(), the read length is set to '*lenptr'. If instead the212 * loaded length describes a special encoding that follows, then '*isencoded'213 * is set to 1 and the encoding format is stored at '*lenptr'.214 *215 * See the RDB_ENC_* definitions in rdb.h for more information on special216 * encodings.217 *218 * The function returns -1 on error, 0 on success. */219int rdbLoadLenByRef(rio *rdb, int *isencoded, uint64_t *lenptr) {220    unsigned char buf[2];221    int type;222223    if (isencoded) *isencoded = 0;224    if (rioRead(rdb,buf,1) == 0) return -1;225    type = (buf[0]&0xC0)>>6;226    if (type == RDB_ENCVAL) {227        /* Read a 6 bit encoding type. */228        if (isencoded) *isencoded = 1;229        *lenptr = buf[0]&0x3F;230    } else if (type == RDB_6BITLEN) {231        /* Read a 6 bit len. */232        *lenptr = buf[0]&0x3F;233    } else if (type == RDB_14BITLEN) {234        /* Read a 14 bit len. */235        if (rioRead(rdb,buf+1,1) == 0) return -1;236        *lenptr = ((buf[0]&0x3F)<<8)|buf[1];237    } else if (buf[0] == RDB_32BITLEN) {238        /* Read a 32 bit len. */239        uint32_t len;240        if (rioRead(rdb,&len,4) == 0) return -1;241        *lenptr = ntohl(len);242    } else if (buf[0] == RDB_64BITLEN) {243        /* Read a 64 bit len. */244        uint64_t len;245        if (rioRead(rdb,&len,8) == 0) return -1;246        *lenptr = ntohu64(len);247    } else {248        rdbReportCorruptRDB(249            "Unknown length encoding %d in rdbLoadLen()",type);250        return -1; /* Never reached. */251    }252    return 0;253}254255/* This is like rdbLoadLenByRef() but directly returns the value read256 * from the RDB stream, signaling an error by returning RDB_LENERR257 * (since it is a too large count to be applicable in any Redis data258 * structure). */259uint64_t rdbLoadLen(rio *rdb, int *isencoded) {260    uint64_t len;261262    if (rdbLoadLenByRef(rdb,isencoded,&len) == -1) return RDB_LENERR;263    return len;264}265266/* Encodes the "value" argument as integer when it fits in the supported ranges267 * for encoded types. If the function successfully encodes the integer, the268 * representation is stored in the buffer pointer to by "enc" and the string269 * length is returned. Otherwise 0 is returned. */270int rdbEncodeInteger(long long value, unsigned char *enc) {271    if (value >= -(1<<7) && value <= (1<<7)-1) {272        enc[0] = (RDB_ENCVAL<<6)|RDB_ENC_INT8;273        enc[1] = value&0xFF;274        return 2;275    } else if (value >= -(1<<15) && value <= (1<<15)-1) {276        enc[0] = (RDB_ENCVAL<<6)|RDB_ENC_INT16;277        enc[1] = value&0xFF;278        enc[2] = (value>>8)&0xFF;279        return 3;280    } else if (value >= -((long long)1<<31) && value <= ((long long)1<<31)-1) {281        enc[0] = (RDB_ENCVAL<<6)|RDB_ENC_INT32;282        enc[1] = value&0xFF;283        enc[2] = (value>>8)&0xFF;284        enc[3] = (value>>16)&0xFF;285        enc[4] = (value>>24)&0xFF;286        return 5;287    } else {288        return 0;289    }290}291292/* Loads an integer-encoded object with the specified encoding type "enctype".293 * The returned value changes according to the flags, see294 * rdbGenericLoadStringObject() for more info. */295void *rdbLoadIntegerObject(rio *rdb, int enctype, int flags, size_t *lenptr, size_t *usable) {296    int plainFlag = flags & RDB_LOAD_PLAIN;297    int sdsFlag = flags & RDB_LOAD_SDS;298    int encode = flags & RDB_LOAD_ENC;299    unsigned char enc[4];300    long long val;301302    if (enctype == RDB_ENC_INT8) {303        if (rioRead(rdb,enc,1) == 0) return NULL;304        val = (signed char)enc[0];305    } else if (enctype == RDB_ENC_INT16) {306        uint16_t v;307        if (rioRead(rdb,enc,2) == 0) return NULL;308        v = ((uint32_t)enc[0])|309            ((uint32_t)enc[1]<<8);310        val = (int16_t)v;311    } else if (enctype == RDB_ENC_INT32) {312        uint32_t v;313        if (rioRead(rdb,enc,4) == 0) return NULL;314        v = ((uint32_t)enc[0])|315            ((uint32_t)enc[1]<<8)|316            ((uint32_t)enc[2]<<16)|317            ((uint32_t)enc[3]<<24);318        val = (int32_t)v;319    } else {320        rdbReportCorruptRDB("Unknown RDB integer encoding type %d",enctype);321        return NULL; /* Never reached. */322    }323    if (plainFlag || sdsFlag) {324        char buf[LONG_STR_SIZE], *p;325        int len = ll2string(buf,sizeof(buf),val);326        if (lenptr) *lenptr = len;327        if (plainFlag) {328            p = zmalloc_usable(len, usable);329        } else {330            debugServerAssert(sdsFlag);331            p = sdsnewlen(SDS_NOINIT,len);332            if (usable) *usable = sdsAllocSize(p);333        } 334        335        memcpy(p,buf,len);336        return p;337    } else if (encode) {338        return createStringObjectFromLongLongForValue(val);339    } else {340        return createStringObjectFromLongLongWithSds(val);341    }342}343344/* String objects in the form "2391" "-100" without any space and with a345 * range of values that can fit in an 8, 16 or 32 bit signed value can be346 * encoded as integers to save space */347int rdbTryIntegerEncoding(char *s, size_t len, unsigned char *enc) {348    long long value;349    if (string2ll(s, len, &value)) {350        return rdbEncodeInteger(value, enc);351    } else {352        return 0;353    }354}355356ssize_t rdbSaveLzfBlob(rio *rdb, void *data, size_t compress_len,357                       size_t original_len) {358    unsigned char byte;359    ssize_t n, nwritten = 0;360361    /* Data compressed! Let's save it on disk */362    byte = (RDB_ENCVAL<<6)|RDB_ENC_LZF;363    if ((n = rdbWriteRaw(rdb,&byte,1)) == -1) goto writeerr;364    nwritten += n;365366    if ((n = rdbSaveLen(rdb,compress_len)) == -1) goto writeerr;367    nwritten += n;368369    if ((n = rdbSaveLen(rdb,original_len)) == -1) goto writeerr;370    nwritten += n;371372    if ((n = rdbWriteRaw(rdb,data,compress_len)) == -1) goto writeerr;373    nwritten += n;374375    return nwritten;376377writeerr:378    return -1;379}380381ssize_t rdbSaveLzfStringObject(rio *rdb, unsigned char *s, size_t len) {382    size_t comprlen, outlen;383    void *out;384385    /* We require at least four bytes compression for this to be worth it */386    if (len <= 4) return 0;387    outlen = len-4;388    if ((out = zmalloc(outlen+1)) == NULL) return 0;389    comprlen = lzf_compress(s, len, out, outlen);390    if (comprlen == 0) {391        zfree(out);392        return 0;393    }394    ssize_t nwritten = rdbSaveLzfBlob(rdb, out, comprlen, len);395    zfree(out);396    return nwritten;397}398399/* Load an LZF compressed string in RDB format. The returned value400 * changes according to 'flags'. For more info check the401 * rdbGenericLoadStringObject() function. */402void *rdbLoadLzfStringObject(rio *rdb, int flags, size_t *lenptr, size_t *usable) {403    int plainFlag = flags & RDB_LOAD_PLAIN;404    int sdsFlag = flags & RDB_LOAD_SDS;405    int robjFlag = !(plainFlag || sdsFlag); /* not plain/sds */406407    uint64_t len, clen;408    unsigned char *c = NULL;409    char *val = NULL;410411    if ((clen = rdbLoadLen(rdb,NULL)) == RDB_LENERR) return NULL;412    if ((len = rdbLoadLen(rdb,NULL)) == RDB_LENERR) return NULL;413    if ((c = ztrymalloc(clen)) == NULL) {414        serverLog(isRestoreContext()? LL_VERBOSE: LL_WARNING, "rdbLoadLzfStringObject failed allocating %llu bytes", (unsigned long long)clen);415        goto err;416    }417418    /* Allocate our target according to the uncompressed size. */419    if (plainFlag) {420        val = ztrymalloc_usable(len, usable);421    } else {422        debugServerAssert(sdsFlag || robjFlag);423        val = sdstrynewlen(SDS_NOINIT,len);424        if (usable) *usable = sdsAllocSize(val);425    }426427    if (!val) {428        serverLog(isRestoreContext()? LL_VERBOSE: LL_WARNING, "rdbLoadLzfStringObject failed allocating %llu bytes", (unsigned long long)len);429        goto err;430    }431432    if (lenptr) *lenptr = len;433434    /* Load the compressed representation and uncompress it to target. */435    if (rioRead(rdb,c,clen) == 0) goto err;436    if (lzf_decompress(c,clen,val,len) != len) {437        rdbReportCorruptRDB("Invalid LZF compressed string");438        goto err;439    }440    zfree(c);441442    return (robjFlag) ? createObject(OBJ_STRING,val) : (void *) val;443444err:445    zfree(c);446    if (plainFlag) {447        zfree(val);448    } else {449        debugServerAssert(sdsFlag || robjFlag);450        sdsfree(val);451    }452    return NULL;453}454455/* Save a string object as [len][data] on disk. If the object is a string456 * representation of an integer value we try to save it in a special form */457ssize_t rdbSaveRawString(rio *rdb, unsigned char *s, size_t len) {458    int enclen;459    ssize_t n, nwritten = 0;460461    /* Try integer encoding */462    if (len <= 11) {463        unsigned char buf[5];464        if ((enclen = rdbTryIntegerEncoding((char*)s,len,buf)) > 0) {465            if (rdbWriteRaw(rdb,buf,enclen) == -1) return -1;466            return enclen;467        }468    }469470    /* Try LZF compression - under 20 bytes it's unable to compress even471     * aaaaaaaaaaaaaaaaaa so skip it */472    if (server.rdb_compression && len > 20) {473        n = rdbSaveLzfStringObject(rdb,s,len);474        if (n == -1) return -1;475        if (n > 0) return n;476        /* Return value of 0 means data can't be compressed, save the old way */477    }478479    /* Store verbatim */480    if ((n = rdbSaveLen(rdb,len)) == -1) return -1;481    nwritten += n;482    if (len > 0) {483        if (rdbWriteRaw(rdb,s,len) == -1) return -1;484        nwritten += len;485    }486    return nwritten;487}488489/* Save a long long value as either an encoded string or a string. */490ssize_t rdbSaveLongLongAsStringObject(rio *rdb, long long value) {491    unsigned char buf[32];492    ssize_t n, nwritten = 0;493    int enclen = rdbEncodeInteger(value,buf);494    if (enclen > 0) {495        return rdbWriteRaw(rdb,buf,enclen);496    } else {497        /* Encode as string */498        enclen = ll2string((char*)buf,32,value);499        serverAssert(enclen < 32);500        if ((n = rdbSaveLen(rdb,enclen)) == -1) return -1;501        nwritten += n;502        if ((n = rdbWriteRaw(rdb,buf,enclen)) == -1) return -1;503        nwritten += n;504    }505    return nwritten;506}507508/* Like rdbSaveRawString() gets a Redis object instead. */509ssize_t rdbSaveStringObject(rio *rdb, robj *obj) {510    /* Avoid to decode the object, then encode it again, if the511     * object is already integer encoded. */512    if (obj->encoding == OBJ_ENCODING_INT) {513        return rdbSaveLongLongAsStringObject(rdb,(long)obj->ptr);514    } else {515        serverAssertWithInfo(NULL,obj,sdsEncodedObject(obj));516        return rdbSaveRawString(rdb,obj->ptr,sdslen(obj->ptr));517    }518}519520/* Load a string object from an RDB file according to flags:521 *522 * RDB_LOAD_NONE (no flags): load an RDB object, unencoded.523 * RDB_LOAD_ENC: If the returned type is a Redis object, try to524 *               encode it in a special way to be more memory525 *               efficient. When this flag is passed the function526 *               no longer guarantees that obj->ptr is an SDS string.527 * RDB_LOAD_PLAIN: Return a plain string allocated with zmalloc()528 *                 instead of a Redis object with an sds in it.529 * RDB_LOAD_SDS: Return an SDS string instead of a Redis object.530 *531 * On I/O error NULL is returned.532 */533void *rdbGenericLoadStringObjectUsable(rio *rdb, int flags, size_t *lenptr, size_t *usable) {534    void *buf;535    int plainFlag = flags & RDB_LOAD_PLAIN;536    int sdsFlag = flags & RDB_LOAD_SDS;537    int robjFlag = !(plainFlag || sdsFlag); /* not plain/sds */538539    int isencoded;540    unsigned long long len;541542    len = rdbLoadLen(rdb,&isencoded);543    if (len == RDB_LENERR) return NULL;544545    if (isencoded) {546        switch(len) {547        case RDB_ENC_INT8:548        case RDB_ENC_INT16:549        case RDB_ENC_INT32:550            return rdbLoadIntegerObject(rdb,len,flags,lenptr,usable);551        case RDB_ENC_LZF:552            return rdbLoadLzfStringObject(rdb,flags,lenptr,usable);553        default:554            rdbReportCorruptRDB("Unknown RDB string encoding type %llu",len);555            return NULL;556        }557    }558559    /* return robj */560    if (robjFlag) {561        if (usable) *usable = 0;562        robj *o = tryCreateStringObject(SDS_NOINIT,len);563        if (!o) {564            serverLog(isRestoreContext()? LL_VERBOSE: LL_WARNING, "rdbGenericLoadStringObject failed allocating %llu bytes", len);565            return NULL;566        }567        if (len && rioRead(rdb,o->ptr,len) == 0) {568            decrRefCount(o);569            return NULL;570        }571        return o;572    }573574    /* plain/sds */575    if (plainFlag) {576        buf = ztrymalloc_usable(len, usable);577    } else {578        debugServerAssert(sdsFlag);579        buf = sdstrynewlen(SDS_NOINIT,len);580        if (usable) *usable = sdsAllocSize(buf);581    }  582    583    if (!buf) {584        serverLog(isRestoreContext()? LL_VERBOSE: LL_WARNING, "rdbGenericLoadStringObject failed allocating %llu bytes", len);585        return NULL;586    }587588    if (lenptr) *lenptr = len;589    if (len && rioRead(rdb,buf,len) == 0) {590        if (plainFlag)591            zfree(buf);592        else593            sdsfree(buf);594        return NULL;595    }596    return buf;597}598599void *rdbGenericLoadStringObject(rio *rdb, int flags, size_t *lenptr) {600    return rdbGenericLoadStringObjectUsable(rdb,flags,lenptr,NULL);601}602603robj *rdbLoadStringObject(rio *rdb) {604    return rdbGenericLoadStringObject(rdb,RDB_LOAD_NONE,NULL);605}606607robj *rdbLoadEncodedStringObject(rio *rdb) {608    return rdbGenericLoadStringObject(rdb,RDB_LOAD_ENC,NULL);609}610611/* Save a double value. Doubles are saved as strings prefixed by an unsigned612 * 8 bit integer specifying the length of the representation.613 * This 8 bit integer has special values in order to specify the following614 * conditions:615 * 253: not a number616 * 254: + inf617 * 255: - inf618 */619ssize_t rdbSaveDoubleValue(rio *rdb, double val) {620    unsigned char buf[128];621    int len;622623    if (isnan(val)) {624        buf[0] = 253;625        len = 1;626    } else if (!isfinite(val)) {627        len = 1;628        buf[0] = (val < 0) ? 255 : 254;629    } else {630        long long lvalue;631        /* Integer printing function is much faster, check if we can safely use it. */632        if (double2ll(val, &lvalue))633            ll2string((char*)buf+1,sizeof(buf)-1,lvalue);634        else {635            const int dlen = fpconv_dtoa(val, (char*)buf+1);636            buf[dlen+1] = '\0';637        }638        buf[0] = strlen((char*)buf+1);639        len = buf[0]+1;640    }641    return rdbWriteRaw(rdb,buf,len);642}643644/* For information about double serialization check rdbSaveDoubleValue() */645int rdbLoadDoubleValue(rio *rdb, double *val) {646    char buf[256];647    unsigned char len;648649    if (rioRead(rdb,&len,1) == 0) return -1;650    switch(len) {651    case 255: *val = R_NegInf; return 0;652    case 254: *val = R_PosInf; return 0;653    case 253: *val = R_Nan; return 0;654    default:655        if (rioRead(rdb,buf,len) == 0) return -1;656        buf[len] = '\0';657        if (sscanf(buf, "%lg", val)!=1) return -1;658        return 0;659    }660}661662/* Saves a double for RDB 8 or greater, where IE754 binary64 format is assumed.663 * We just make sure the integer is always stored in little endian, otherwise664 * the value is copied verbatim from memory to disk.665 *666 * Return -1 on error, the size of the serialized value on success. */667int rdbSaveBinaryDoubleValue(rio *rdb, double val) {668    memrev64ifbe(&val);669    return rdbWriteRaw(rdb,&val,sizeof(val));670}671672/* Loads a double from RDB 8 or greater. See rdbSaveBinaryDoubleValue() for673 * more info. On error -1 is returned, otherwise 0. */674int rdbLoadBinaryDoubleValue(rio *rdb, double *val) {675    if (rioRead(rdb,val,sizeof(*val)) == 0) return -1;676    memrev64ifbe(val);677    return 0;678}679680/* Like rdbSaveBinaryDoubleValue() but single precision. */681int rdbSaveBinaryFloatValue(rio *rdb, float val) {682    memrev32ifbe(&val);683    return rdbWriteRaw(rdb,&val,sizeof(val));684}685686/* Like rdbLoadBinaryDoubleValue() but single precision. */687int rdbLoadBinaryFloatValue(rio *rdb, float *val) {688    if (rioRead(rdb,val,sizeof(*val)) == 0) return -1;689    memrev32ifbe(val);690    return 0;691}692693/* Save the object type of object "o". */694int rdbSaveObjectType(rio *rdb, robj *o) {695    switch (o->type) {696    case OBJ_STRING:697        return rdbSaveType(rdb,RDB_TYPE_STRING);698    case OBJ_LIST:699        if (o->encoding == OBJ_ENCODING_QUICKLIST || o->encoding == OBJ_ENCODING_LISTPACK)700            return rdbSaveType(rdb, RDB_TYPE_LIST_QUICKLIST_2);701        else702            serverPanic("Unknown list encoding");703    case OBJ_SET:704        if (o->encoding == OBJ_ENCODING_INTSET)705            return rdbSaveType(rdb,RDB_TYPE_SET_INTSET);706        else if (o->encoding == OBJ_ENCODING_HT)707            return rdbSaveType(rdb,RDB_TYPE_SET);708        else if (o->encoding == OBJ_ENCODING_LISTPACK)709            return rdbSaveType(rdb,RDB_TYPE_SET_LISTPACK);710        else711            serverPanic("Unknown set encoding");712    case OBJ_ZSET:713        if (o->encoding == OBJ_ENCODING_LISTPACK)714            return rdbSaveType(rdb,RDB_TYPE_ZSET_LISTPACK);715        else if (o->encoding == OBJ_ENCODING_SKIPLIST)716            return rdbSaveType(rdb,RDB_TYPE_ZSET_2);717        else718            serverPanic("Unknown sorted set encoding");719    case OBJ_HASH:720        if (o->encoding == OBJ_ENCODING_LISTPACK)721            return rdbSaveType(rdb,RDB_TYPE_HASH_LISTPACK);722        else if (o->encoding == OBJ_ENCODING_LISTPACK_EX)723            return rdbSaveType(rdb,RDB_TYPE_HASH_LISTPACK_EX);724        else if (o->encoding == OBJ_ENCODING_HT) {725            if (hashTypeGetMinExpire(o, /*accurate*/ 1) == EB_EXPIRE_TIME_INVALID)726                return rdbSaveType(rdb,RDB_TYPE_HASH);727            else728                return rdbSaveType(rdb,RDB_TYPE_HASH_METADATA);729        } else if (o->encoding == OBJ_ENCODING_TMPL_LP ||730                   o->encoding == OBJ_ENCODING_TMPL_ARRAY) {731            /* Template encodings. RDB save: compact "ref" form storing only the732             * template id + values. DUMP/RESTORE: "full" form that includes 733             * field names. */734            if (!rdbIsDumpPayload(rdb)) {735                return rdbSaveType(rdb, o->encoding == OBJ_ENCODING_TMPL_LP ?736                                                            RDB_TYPE_HASH_TMPL_LP_REF :737                                                            RDB_TYPE_HASH_TMPL_ARRAY_REF);738            } else {739                return rdbSaveType(rdb, o->encoding == OBJ_ENCODING_TMPL_LP ?740                                                            RDB_TYPE_HASH_TMPL_LP :741                                                            RDB_TYPE_HASH_TMPL_ARRAY);742            }743        } else744            serverPanic("Unknown hash encoding");745    case OBJ_STREAM:746        return rdbSaveType(rdb,RDB_TYPE_STREAM_LISTPACKS_5);747#ifdef ENABLE_GCRA748    case OBJ_GCRA:749        return rdbSaveType(rdb,RDB_TYPE_GCRA);750#endif751    case OBJ_MODULE:752        return rdbSaveType(rdb,RDB_TYPE_MODULE_2);753    case OBJ_ARRAY:754        return rdbSaveType(rdb,RDB_TYPE_ARRAY);755    default:756        serverPanic("Unknown object type");757    }758    return -1; /* avoid warning */759}760761/* Use rdbLoadType() to load a TYPE in RDB format, but returns -1 if the762 * type is not specifically a valid Object Type. */763int rdbLoadObjectType(rio *rdb) {764    int type;765    if ((type = rdbLoadType(rdb)) == -1) return -1;766    if (!rdbIsObjectType(type)) return -1;767    return type;768}769770/* This helper function serializes a consumer group Pending Entries List (PEL)771 * into the RDB file. The 'nacks' argument tells the function if also persist772 * the information about the not acknowledged message, or if to persist773 * just the IDs: this is useful because for the global consumer group PEL774 * we serialized the NACKs as well, but when serializing the local consumer775 * PELs we just add the ID, that will be resolved inside the global PEL to776 * put a reference to the same structure. */777ssize_t rdbSaveStreamPEL(rio *rdb, rax *pel, int nacks) {778    ssize_t n, nwritten = 0;779780    /* Number of entries in the PEL. */781    if ((n = rdbSaveLen(rdb,raxSize(pel))) == -1) return -1;782    nwritten += n;783784    /* Save each entry. */785    raxIterator ri;786    raxStart(&ri,pel);787    raxSeek(&ri,"^",NULL,0);788    while(raxNext(&ri)) {789        /* We store IDs in raw form as 128 big big endian numbers, like790         * they are inside the radix tree key. */791        if ((n = rdbWriteRaw(rdb,ri.key,sizeof(streamID))) == -1) {792            raxStop(&ri);793            return -1;794        }795        nwritten += n;796797        if (nacks) {798            streamNACK *nack = ri.data;799            if ((n = rdbSaveMillisecondTime(rdb,nack->delivery_time)) == -1) {800                raxStop(&ri);801                return -1;802            }803            nwritten += n;804            if ((n = rdbSaveLen(rdb,nack->delivery_count)) == -1) {805                raxStop(&ri);806                return -1;807            }808            nwritten += n;809            /* We don't save the consumer name: we'll save the pending IDs810             * for each consumer in the consumer PEL, and resolve the consumer811             * at loading time. */812        }813    }814    raxStop(&ri);815    return nwritten;816}817818/* Serialize the IDMP entries for a stream into the RDB file.819 * This saves all the idempotent producer tracking entries (IID -> stream ID mappings).820 * Expired entries are filtered out. Producers whose entries all expired are still821 * written with count=0; the load side skips them.822 * Format: num_producers, then for each producer: pid, num_entries, entries... */823ssize_t rdbSaveStreamIdmpEntries(rio *rdb, stream *s) {824    ssize_t n, nwritten = 0;825826    /* Save the number of producers. */827    size_t num_producers = s->idmp_producers ? raxSize(s->idmp_producers) : 0;828    if ((n = rdbSaveLen(rdb,num_producers)) == -1) return -1;829    nwritten += n;830831    if (num_producers == 0) return nwritten;832833    uint64_t expire_time = server.mstime - (s->idmp_duration * 1000);834835    /* Iterate through all producers. */836    raxIterator ri;837    raxStart(&ri, s->idmp_producers);838    raxSeek(&ri, "^", NULL, 0);839    while (raxNext(&ri)) {840        idmpProducer *producer = ri.data;841842        /* Save the producer ID (pid). */843        if ((n = rdbSaveRawString(rdb, ri.key, ri.key_len)) == -1) {844            raxStop(&ri);845            return -1;846        }847        nwritten += n;848849        /* Find the first non-expired entry. The linked list is ordered by850         * timestamp, so all entries after the first valid one are also valid. */851        idmpEntry *first_valid = producer->idmp_head;852        size_t expired = 0;853        while (first_valid && first_valid->id.ms <= expire_time) {854            first_valid = first_valid->next;855            expired++;856        }857858        /* Save the number of entries for this producer. */859        size_t count = dictSize(producer->idmp_dict) - expired;860        if ((n = rdbSaveLen(rdb, count)) == -1) {861            raxStop(&ri);862            return -1;863        }864        nwritten += n;865866        /* Save each non-expired entry in insertion order. */867        idmpEntry *entry = first_valid;868        while (entry != NULL) {869            /* Save the IID string (length + data). */870            if ((n = rdbSaveRawString(rdb,(unsigned char *)entry->iid,entry->iid_len)) == -1) {871                raxStop(&ri);872                return -1;873            }874            nwritten += n;875876            /* Save the associated stream ID. */877            if ((n = rdbSaveLen(rdb,entry->id.ms)) == -1) {878                raxStop(&ri);879                return -1;880            }881            nwritten += n;882            if ((n = rdbSaveLen(rdb,entry->id.seq)) == -1) {883                raxStop(&ri);884                return -1;885            }886            nwritten += n;887888            entry = entry->next;889        }890    }891    raxStop(&ri);892    return nwritten;893}894895/* Load IDMP entries for a stream from the RDB file.896 * This loads all the idempotent producer tracking entries (IID -> stream ID mappings)897 * and inserts them into the stream's idmp_producers rax tree.898 * Format: num_producers, then for each producer: pid, num_entries, entries... */899int rdbLoadStreamIdmpEntries(rio *rdb, stream *s) {900    /* Load the number of producers. */901    uint64_t num_producers = rdbLoadLen(rdb, NULL);902    if (num_producers == RDB_LENERR) {903        return -1;904    }905906    if (num_producers == 0) return 0;907908    uint64_t expire_time = server.mstime - (s->idmp_duration * 1000);909910    /* Create the producers rax tree. */911    s->idmp_producers = raxNewEx(0, &s->alloc_size, 0);912    if (s->idmp_producers == NULL) {913        return -1;914    }915916    /* Track pid across error paths so cleanup can free it. */917    char *pid = NULL;918    size_t pid_len = 0;919920    /* Load each producer. */921    for (uint64_t p = 0; p < num_producers; p++) {922        /* Load the producer ID (pid). */923        pid = rdbGenericLoadStringObject(rdb, RDB_LOAD_SDS, &pid_len);924        if (pid == NULL) goto cleanup;925926        /* Load the number of entries for this producer. */927        uint64_t count = rdbLoadLen(rdb, NULL);928        if (count == RDB_LENERR) goto cleanup;929930        /* Skip producers with 0 entries (written by save when all entries931         * were expired). Just consume the RDB data and move on. */932        if (count == 0) {933            sdsfree(pid);934            pid = NULL;935            continue;936        }937938        /* Create the producer. */939        idmpProducer *producer = idmpProducerCreate(&s->alloc_size);940941        /* Insert producer into rax tree. */942        int inserted = raxTryInsert(s->idmp_producers, (unsigned char *)pid, pid_len, producer, NULL);943        if (!inserted) {944            idmpProducerFree(producer, &s->alloc_size);945            goto cleanup;946        }947948        /* Load each entry for this producer. */949        for (uint64_t i = 0; i < count; i++) {950            /* Load the IID string. */951            size_t iid_len;952            char *iid = rdbGenericLoadStringObject(rdb, RDB_LOAD_SDS, &iid_len);953            if (iid == NULL) goto cleanup;954955            /* Load the associated stream ID. */956            streamID id;957            id.ms = rdbLoadLen(rdb, NULL);958            id.seq = rdbLoadLen(rdb, NULL);959            if (rioGetReadError(rdb)) {960                sdsfree(iid);961                goto cleanup;962            }963964            /* Skip entries that have already expired. */965            if (id.ms <= expire_time) {966                sdsfree(iid);967                continue;968            }969970            /* Create the idmpEntry. */971            idmpEntry *entry = idmpEntryCreate(iid, iid_len, &s->alloc_size);972            sdsfree(iid); /* idmpEntryCreate makes a copy */973974            /* Set the stream ID. */975            entry->id = id;976            entry->next = NULL;977978            /* Insert into dict. If insertion fails (e.g., duplicate), skip. */979            int ret = dictAdd(producer->idmp_dict, entry, NULL);980            if (ret != DICT_OK) {981                /* Insertion failed (duplicate). For RDB loading, we'll just skip982                 * duplicates rather than failing the entire load. */983                idmpEntryFree(entry, &s->alloc_size);984            } else {985                /* Add to linked list tail. */986                if (producer->idmp_tail == NULL) {987                    producer->idmp_head = producer->idmp_tail = entry;988                } else {989                    producer->idmp_tail->next = entry;990                    producer->idmp_tail = entry;991                }992            }993        }994995        /* If all entries were expired, remove the empty producer. */996        if (producer->idmp_head == NULL) {997            raxRemove(s->idmp_producers, (unsigned char *)pid, pid_len, NULL);998            idmpProducerFree(producer, &s->alloc_size);999        }1000        sdsfree(pid);1001        pid = NULL;1002    }10031004    /* If no producers remain after filtering, free the rax tree. */1005    if (raxSize(s->idmp_producers) == 0) {1006        raxFree(s->idmp_producers);1007        s->idmp_producers = NULL;1008    }10091010    return 0;10111012cleanup:1013    if (pid) sdsfree(pid);1014    /* Clean up partially constructed producers tree on error.1015     * This prevents use-after-free when the stream is later freed. */1016    if (s->idmp_producers) {1017        raxFreeWithCbAndContext(s->idmp_producers, streamFreeIdmpProducerGeneric, s);1018        s->idmp_producers = NULL;1019    }1020    return -1;1021}10221023/* Serialize the consumers of a stream consumer group into the RDB. Helper1024 * function for the stream data type serialization. What we do here is to1025 * persist the consumer metadata, and it's PEL, for each consumer. */1026size_t rdbSaveStreamConsumers(rio *rdb, streamCG *cg) {1027    ssize_t n, nwritten = 0;10281029    /* Number of consumers in this consumer group. */1030    if ((n = rdbSaveLen(rdb,raxSize(cg->consumers))) == -1) return -1;1031    nwritten += n;10321033    /* Save each consumer. */1034    raxIterator ri;1035    raxStart(&ri,cg->consumers);1036    raxSeek(&ri,"^",NULL,0);1037    while(raxNext(&ri)) {1038        streamConsumer *consumer = ri.data;10391040        /* Consumer name. */1041        if ((n = rdbSaveRawString(rdb,ri.key,ri.key_len)) == -1) {1042            raxStop(&ri);1043            return -1;1044        }1045        nwritten += n;10461047        /* Seen time. */1048        if ((n = rdbSaveMillisecondTime(rdb,consumer->seen_time)) == -1) {1049            raxStop(&ri);1050            return -1;1051        }1052        nwritten += n;10531054        /* Active time. */1055        if ((n = rdbSaveMillisecondTime(rdb,consumer->active_time)) == -1) {1056            raxStop(&ri);1057            return -1;1058        }1059        nwritten += n;10601061        /* Consumer PEL, without the ACKs (see last parameter of the function1062         * passed with value of 0), at loading time we'll lookup the ID1063         * in the consumer group global PEL and will put a reference in the1064         * consumer local PEL. */1065        if ((n = rdbSaveStreamPEL(rdb,consumer->pel,0)) == -1) {1066            raxStop(&ri);1067            return -1;1068        }1069        nwritten += n;1070    }1071    raxStop(&ri);1072    return nwritten;1073}10741075/* Save a Redis object.1076 * Returns -1 on error, number of bytes written on success. */1077static ssize_t rdbSaveArrayElement(rio *rdb, uint64_t idx, void *v) {1078    ssize_t n, nwritten = 0;10791080    if ((n = rdbSaveLen(rdb, idx)) == -1) return -1;1081    nwritten += n;10821083    if (arIsInt(v)) {1084        if ((n = rdbSaveLen(rdb, AR_RDB_TAG_INT)) == -1) return -1;1085        nwritten += n;1086        int64_t ival = arToInt(v);1087        if ((n = rdbSaveSignedInteger(rdb, ival)) == -1) return -1;1088        nwritten += n;1089    } else if (arIsFloat(v)) {1090        if ((n = rdbSaveLen(rdb, AR_RDB_TAG_FLOAT)) == -1) return -1;1091        nwritten += n;1092        double d = arToDouble(v);1093        if (rdbSaveBinaryDoubleValue(rdb, d) == -1) return -1;1094        nwritten += 8;1095    } else if (arIsSmallStr(v)) {1096        char buf[AR_SMALLSTR_MAXLEN + 1];1097        int len = arToSmallStr(v, buf);1098        if ((n = rdbSaveLen(rdb, AR_RDB_TAG_SMALLSTR)) == -1) return -1;1099        nwritten += n;1100        if ((n = rdbSaveRawString(rdb, (unsigned char *)buf, len)) == -1) return -1;1101        nwritten += n;1102    } else {1103        if ((n = rdbSaveLen(rdb, AR_RDB_TAG_SDS)) == -1) return -1;1104        nwritten += n;1105        if ((n = rdbSaveRawString(rdb, (unsigned char *)arStringData(v), arStringLen(v))) == -1) return -1;1106        nwritten += n;1107    }11081109    return nwritten;1110}11111112static ssize_t rdbSaveArraySlice(rio *rdb, arSlice *s, uint64_t slice_id,1113                                 uint32_t slice_size) {1114    ssize_t n, nwritten = 0;11151116    if (s->encoding == AR_SLICE_DENSE) {1117        for (uint32_t i = 0; i < s->layout.dense.winsize; i++) {1118            void *v = s->layout.dense.items[i];1119            if (arIsEmpty(v)) continue;11201121            uint64_t idx = arMakeIdx(slice_id, s->layout.dense.offset + i, slice_size);1122            if ((n = rdbSaveArrayElement(rdb, idx, v)) == -1) return -1;1123            nwritten += n;1124        }1125    } else {1126        uint16_t *offsets = s->layout.sparse.offsets;1127        void **values = s->layout.sparse.values;11281129        for (uint32_t i = 0; i < s->count; i++) {1130            uint64_t idx = arMakeIdx(slice_id, offsets[i], slice_size);1131            if ((n = rdbSaveArrayElement(rdb, idx, values[i])) == -1) return -1;1132            nwritten += n;1133        }1134    }11351136    return nwritten;1137}11381139ssize_t rdbSaveObject(rio *rdb, robj *o, robj *key, int dbid) {1140    ssize_t n = 0, nwritten = 0;11411142    if (o->type == OBJ_STRING) {1143        /* Save a string value */1144        if ((n = rdbSaveStringObject(rdb,o)) == -1) return -1;1145        nwritten += n;1146    } else if (o->type == OBJ_LIST) {1147        /* Save a list value */1148        if (o->encoding == OBJ_ENCODING_QUICKLIST) {1149            quicklist *ql = o->ptr;1150            quicklistNode *node = ql->head;11511152            if ((n = rdbSaveLen(rdb,ql->len)) == -1) return -1;1153            nwritten += n;11541155            while(node) {1156                if ((n = rdbSaveLen(rdb,node->container)) == -1) return -1;1157                nwritten += n;11581159                if (quicklistNodeIsCompressed(node)) {1160                    void *data;1161                    size_t compress_len = quicklistGetLzf(node, &data);1162                    if ((n = rdbSaveLzfBlob(rdb,data,compress_len,node->sz)) == -1) return -1;1163                    nwritten += n;1164                } else {1165                    if ((n = rdbSaveRawString(rdb,node->entry,node->sz)) == -1) return -1;1166                    nwritten += n;1167                }1168                node = node->next;1169            }1170        } else if (o->encoding == OBJ_ENCODING_LISTPACK) {1171            unsigned char *lp = o->ptr;11721173            /* Save list listpack as a fake quicklist that only has a single node. */1174            if ((n = rdbSaveLen(rdb,1)) == -1) return -1;1175            nwritten += n;1176            if ((n = rdbSaveLen(rdb,QUICKLIST_NODE_CONTAINER_PACKED)) == -1) return -1;1177            nwritten += n;1178            if ((n = rdbSaveRawString(rdb,lp,lpBytes(lp))) == -1) return -1;1179            nwritten += n;1180        } else {1181            serverPanic("Unknown list encoding");1182        }1183    } else if (o->type == OBJ_SET) {1184        /* Save a set value */1185        if (o->encoding == OBJ_ENCODING_HT) {1186            dict *set = o->ptr;1187            dictIterator di;1188            dictEntry *de;11891190            if ((n = rdbSaveLen(rdb,dictSize(set))) == -1) {1191                return -1;1192            }1193            nwritten += n;11941195            dictInitIterator(&di, set);1196            while((de = dictNext(&di)) != NULL) {1197                sds ele = dictGetKey(de);1198                if ((n = rdbSaveRawString(rdb,(unsigned char*)ele,sdslen(ele)))1199                    == -1)1200                {1201                    dictResetIterator(&di);1202                    return -1;1203                }1204                nwritten += n;1205            }1206            dictResetIterator(&di);1207        } else if (o->encoding == OBJ_ENCODING_INTSET) {1208            size_t l = intsetBlobLen((intset*)o->ptr);12091210            if ((n = rdbSaveRawString(rdb,o->ptr,l)) == -1) return -1;1211            nwritten += n;1212        } else if (o->encoding == OBJ_ENCODING_LISTPACK) {1213            size_t l = lpBytes((unsigned char *)o->ptr);1214            if ((n = rdbSaveRawString(rdb, o->ptr, l)) == -1) return -1;1215            nwritten += n;1216        } else {1217            serverPanic("Unknown set encoding");1218        }1219    } else if (o->type == OBJ_ZSET) {1220        /* Save a sorted set value */1221        if (o->encoding == OBJ_ENCODING_LISTPACK) {1222            size_t l = lpBytes((unsigned char*)o->ptr);12231224            if ((n = rdbSaveRawString(rdb,o->ptr,l)) == -1) return -1;1225            nwritten += n;1226        } else if (o->encoding == OBJ_ENCODING_SKIPLIST) {1227            zset *zs = o->ptr;1228            zskiplist *zsl = zs->zsl;12291230            if ((n = rdbSaveLen(rdb,zsl->length)) == -1) return -1;1231            nwritten += n;12321233            /* We save the skiplist elements from the greatest to the smallest1234             * (that's trivial since the elements are already ordered in the1235             * skiplist): this improves the load process, since the next loaded1236             * element will always be the smaller, so adding to the skiplist1237             * will always immediately stop at the head, making the insertion1238             * O(1) instead of O(log(N)). */1239            zskiplistNode *zn = zsl->tail;1240            while (zn != NULL) {1241                sds ele = zslGetNodeElement(zn);1242                if ((n = rdbSaveRawString(rdb,1243                    (unsigned char*)ele,sdslen(ele))) == -1)1244                {1245                    return -1;1246                }1247                nwritten += n;1248                if ((n = rdbSaveBinaryDoubleValue(rdb,zn->score)) == -1)1249                    return -1;1250                nwritten += n;1251                zn = zn->backward;1252            }1253        } else {1254            serverPanic("Unknown sorted set encoding");1255        }1256    } else if (o->type == OBJ_HASH) {1257        /* Save a hash value */1258        if (o->encoding == OBJ_ENCODING_TMPL_LP ||1259            o->encoding == OBJ_ENCODING_TMPL_ARRAY) {12601261            hashTemplate *tmpl = hashTypeGetTemplate(o);1262            unsigned long long field_count = tmpl->field_count;12631264            /* If this is a BGSAVE, we can use the compact ref format.1265             * DUMP payload is self-contained (field names included). */1266            if (!rdbIsDumpPayload(rdb)) {1267                if (o->encoding == OBJ_ENCODING_TMPL_LP) {1268                    /* TMPL_LP compact: raw listpack blob. The template ID is1269                     * the first listpack entry, so no separate field needed. */1270                    unsigned char *lp = o->ptr;1271                    if ((n = rdbSaveRawString(rdb, lp, lpBytes(lp))) == -1)1272                        return -1;1273                    nwritten += n;1274                } else {1275                    /* TMPL_ARRAY compact: [template_id][value1][value2]... */1276                    if ((n = rdbSaveLen(rdb, tmpl->id)) == -1) return -1;1277                    nwritten += n;1278                    hashTemplateArray *hta = o->ptr;1279                    for (unsigned long long i = 0; i < field_count; i++) {1280                        sds value = hta->values[i];1281                        if ((n = rdbSaveRawString(rdb, (unsigned char *)value, sdslen(value))) == -1)1282                            return -1;1283                        nwritten += n;1284                    }1285                }1286            } else {1287                /* Self-contained format (DUMP/RESTORE).1288                 * Layout: [fields_fmt][field names][values]1289                 *1290                 * fields_fmt (1 len-encoded byte)  how field names are stored:1291                 *   0 = FIELDS_LP : field names as one listpack blob. Lets the 1292                 *       destination resolve the template with one O(1) blob 1293                 *       lookup. Used when tmpl->fits_in_listpack.1294                 *   1 = FIELDS_RAW: [count] then count field-name strings. Used1295                 *       when the field names don't fit a listpack.1296                 *1297                 * Values follow per the RDB type:1298                 *   TMPL_LP    : values are one listpack blob1299                 *   TMPL_ARRAY : field_count value strings1300                 *1301                 * The two are independent: fits_in_listpack picks the field format,1302                 * the value encoding picks the value format (e.g. ARRAY+FIELDS_LP1303                 * is valid: huge values but lp-able field names). */1304                int fields_lp = tmpl->fits_in_listpack;1305                if ((n = rdbSaveLen(rdb, fields_lp ? 0 : 1)) == -1) return -1;1306                nwritten += n;13071308                /* Field names. */1309                if (fields_lp) {1310                    /* Get listpack blob and skip caching in fork. */1311                    int cache = (server.in_fork_child == CHILD_TYPE_NONE);1312                    unsigned char *blob = hashTemplateGetFieldsLp(tmpl, &cache);1313                    n = rdbSaveRawString(rdb, blob, lpBytes(blob));1314                    if (!cache) lpFree(blob);1315                    if (n == -1) return -1;1316                    nwritten += n;1317                } else {1318                    if ((n = rdbSaveLen(rdb, field_count)) == -1) return -1;1319                    nwritten += n;1320                    for (unsigned long long i = 0; i < field_count; i++) {1321                        sds field = tmpl->fields[i];1322                        if ((n = rdbSaveRawString(rdb, (unsigned char *)field, sdslen(field))) == -1)1323                            return -1;1324                        nwritten += n;1325                    }1326                }13271328                /* Values. */1329                if (o->encoding == OBJ_ENCODING_TMPL_LP) {1330                    unsigned char *lp = o->ptr;1331                    if ((n = rdbSaveRawString(rdb, lp, lpBytes(lp))) == -1) return -1;1332                    nwritten += n;1333                } else {1334                    hashTemplateArray *hta = o->ptr;1335                    for (unsigned long long i = 0; i < field_count; i++) {1336                        sds value = hta->values[i];1337                        if ((n = rdbSaveRawString(rdb, (unsigned char *)value, sdslen(value))) == -1)1338                            return -1;1339                        nwritten += n;1340                    }1341                }1342            }1343        } else if ((o->encoding == OBJ_ENCODING_LISTPACK) ||1344            (o->encoding == OBJ_ENCODING_LISTPACK_EX))1345        {1346            /* Save min/next HFE expiration time if needed */1347            if (o->encoding == OBJ_ENCODING_LISTPACK_EX) {1348                uint64_t minExpire = hashTypeGetMinExpire(o, 0);1349                /* if invalid time then save 0 */1350                if (minExpire == EB_EXPIRE_TIME_INVALID)1351                    minExpire = 0;1352                if (rdbSaveMillisecondTime(rdb, minExpire) == -1)1353                    return -1;1354            }1355            unsigned char *lp_ptr = hashTypeListpackGetLp(o);1356            size_t l = lpBytes(lp_ptr);13571358            if ((n = rdbSaveRawString(rdb,lp_ptr,l)) == -1) return -1;1359            nwritten += n;1360        } else if (o->encoding == OBJ_ENCODING_HT) {1361            int hashWithMeta = 0;  /* RDB_TYPE_HASH_METADATA */1362            dictIterator di;1363            dictEntry *de;1364            /* Determine the hash layout to use based on the presence of at least1365             * one field with a valid TTL. If such a field exists, employ the1366             * RDB_TYPE_HASH_METADATA layout, including tuples of [ttl][field][value].1367             * Otherwise, use the standard RDB_TYPE_HASH layout containing only1368             * the tuples [field][value]. */1369            uint64_t minExpire = hashTypeGetMinExpire(o, 1);13701371            /* if RDB_TYPE_HASH_METADATA (Can have TTLs on fields) */1372            if (minExpire != EB_EXPIRE_TIME_INVALID) {1373                hashWithMeta = 1;1374                /* Save next field expire time of hash */1375                if (rdbSaveMillisecondTime(rdb, minExpire) == -1) {1376                    return -1;1377                }1378            }13791380            /* save number of fields in hash */1381            if ((n = rdbSaveLen(rdb,dictSize((dict*)o->ptr))) == -1) {1382                return -1;1383            }1384            nwritten += n;13851386            /* save all hash fields */1387            dictInitIterator(&di, o->ptr);1388            while((de = dictNext(&di)) != NULL) {1389                Entry *entry = dictGetKey(de);1390                sds field = entryGetField(entry);1391                sds value = entryGetValue(entry);13921393                /* save the TTL */1394                if (hashWithMeta) {1395                    uint64_t ttl, expiryTime= entryGetExpiry(entry);13961397                    /* Saved TTL value:1398                     *  - 0: Indicates no TTL. This is common case so we keep it small.1399                     *  - Otherwise: TTL is relative to minExpire (with +1 to avoid 0 that already taken)1400                     */1401                    ttl = (expiryTime == EB_EXPIRE_TIME_INVALID) ? 0 : expiryTime - minExpire + 1;1402                    if ((n = rdbSaveLen(rdb, ttl)) == -1) {1403                        dictResetIterator(&di);1404                        return -1;1405                    }1406                    nwritten += n;1407                }14081409                /* save the key */1410                if ((n = rdbSaveRawString(rdb,(unsigned char*)field,1411                        sdslen(field))) == -1)1412                {1413                    dictResetIterator(&di);1414                    return -1;1415                }1416                nwritten += n;14171418                /* save the value */1419                if ((n = rdbSaveRawString(rdb,(unsigned char*)value,1420                        sdslen(value))) == -1)1421                {1422                    dictResetIterator(&di);1423                    return -1;1424                }1425                nwritten += n;1426            }1427            dictResetIterator(&di);1428        } else {1429            serverPanic("Unknown hash encoding");1430        }1431    } else if (o->type == OBJ_STREAM) {1432        /* Store how many listpacks we have inside the radix tree. */1433        stream *s = o->ptr;1434        rax *rax = s->rax;1435        if ((n = rdbSaveLen(rdb,raxSize(rax))) == -1) return -1;1436        nwritten += n;14371438        /* Serialize all the listpacks inside the radix tree as they are,1439         * when loading back, we'll use the first entry of each listpack1440         * to insert it back into the radix tree. */1441        raxIterator ri;1442        raxStart(&ri,rax);1443        raxSeek(&ri,"^",NULL,0);1444        while (raxNext(&ri)) {1445            unsigned char *lp = ri.data;1446            size_t lp_bytes = lpBytes(lp);1447            if ((n = rdbSaveRawString(rdb,ri.key,ri.key_len)) == -1) {1448                raxStop(&ri);1449                return -1;1450            }1451            nwritten += n;1452            if ((n = rdbSaveRawString(rdb,lp,lp_bytes)) == -1) {1453                raxStop(&ri);1454                return -1;1455            }1456            nwritten += n;1457        }1458        raxStop(&ri);14591460        /* Save the number of elements inside the stream. We cannot obtain1461         * this easily later, since our macro nodes should be checked for1462         * number of items: not a great CPU / space tradeoff. */1463        if ((n = rdbSaveLen(rdb,s->length)) == -1) return -1;1464        nwritten += n;1465        /* Save the last entry ID. */1466        if ((n = rdbSaveLen(rdb,s->last_id.ms)) == -1) return -1;1467        nwritten += n;1468        if ((n = rdbSaveLen(rdb,s->last_id.seq)) == -1) return -1;1469        nwritten += n;1470        /* Save the first entry ID. */1471        if ((n = rdbSaveLen(rdb,s->first_id.ms)) == -1) return -1;1472        nwritten += n;1473        if ((n = rdbSaveLen(rdb,s->first_id.seq)) == -1) return -1;1474        nwritten += n;1475        /* Save the maximal tombstone ID. */1476        if ((n = rdbSaveLen(rdb,s->max_deleted_entry_id.ms)) == -1) return -1;1477        nwritten += n;1478        if ((n = rdbSaveLen(rdb,s->max_deleted_entry_id.seq)) == -1) return -1;1479        nwritten += n;1480        /* Save the offset. */1481        if ((n = rdbSaveLen(rdb,s->entries_added)) == -1) return -1;1482        nwritten += n;14831484        /* The consumer groups and their clients are part of the stream1485         * type, so serialize every consumer group. */14861487        /* Save the number of groups. */1488        size_t num_cgroups = s->cgroups ? raxSize(s->cgroups) : 0;1489        if ((n = rdbSaveLen(rdb,num_cgroups)) == -1) return -1;1490        nwritten += n;14911492        if (num_cgroups) {1493            /* Serialize each consumer group. */1494            raxStart(&ri,s->cgroups);1495            raxSeek(&ri,"^",NULL,0);1496            while(raxNext(&ri)) {1497                streamCG *cg = ri.data;14981499                /* Save the group name. */1500                if ((n = rdbSaveRawString(rdb,ri.key,ri.key_len)) == -1) {1501                    raxStop(&ri);1502                    return -1;1503                }1504                nwritten += n;15051506                /* Last ID. */1507                if ((n = rdbSaveLen(rdb,cg->last_id.ms)) == -1) {1508                    raxStop(&ri);1509                    return -1;1510                }1511                nwritten += n;1512                if ((n = rdbSaveLen(rdb,cg->last_id.seq)) == -1) {1513                    raxStop(&ri);1514                    return -1;1515                }1516                nwritten += n;1517                1518                /* Save the group's logical reads counter. */1519                if ((n = rdbSaveLen(rdb,cg->entries_read)) == -1) {1520                    raxStop(&ri);1521                    return -1;1522                }1523                nwritten += n;15241525                /* Save the global PEL. */1526                if ((n = rdbSaveStreamPEL(rdb,cg->pel,1)) == -1) {1527                    raxStop(&ri);1528                    return -1;1529                }1530                nwritten += n;15311532                /* Save the consumers of this group. */1533                if ((n = rdbSaveStreamConsumers(rdb,cg)) == -1) {1534                    raxStop(&ri);1535                    return -1;1536                }1537                nwritten += n;15381539                /* Save NACK zone: count followed by the IDs of NACKed entries. */1540                uint64_t nacked_count = pelListNackedCount(cg);1541                if ((n = rdbSaveLen(rdb, nacked_count)) == -1) {1542                    raxStop(&ri);1543                    return -1;1544                }1545                nwritten += n;15461547                if (cg->pel_nack_tail) {1548                    streamNACK *nack = cg->pel_time_head;1549                    while (nack) {1550                        unsigned char buf[sizeof(streamID)];1551                        streamEncodeID(buf, &nack->id);1552                        if ((n = rdbWriteRaw(rdb, buf, sizeof(buf))) == -1) {1553                            raxStop(&ri);1554                            return -1;1555                        }1556                        nwritten += n;1557                        if (nack == cg->pel_nack_tail) break;1558                        nack = nack->pel_next;1559                    }1560                }1561            }1562            raxStop(&ri);1563        }15641565        /* Save IDMP (Idempotent Message Producer) configuration and entries. */1566        1567        /* Save IDMP duration (in seconds). */1568        if ((n = rdbSaveLen(rdb,s->idmp_duration)) == -1) return -1;1569        nwritten += n;1570        1571        /* Save IDMP max entries. */1572        if ((n = rdbSaveLen(rdb,s->idmp_max_entries)) == -1) return -1;1573        nwritten += n;1574        1575        /* Save all IDMP entries. */1576        if ((n = rdbSaveStreamIdmpEntries(rdb,s)) == -1) return -1;1577        nwritten += n;15781579        /* Save the all-time count of IIDs added. */1580        if ((n = rdbSaveLen(rdb,s->iids_added)) == -1) return -1;1581        nwritten += n;15821583        /* Save the all-time count of duplicate IIDs detected. */1584        if ((n = rdbSaveLen(rdb,s->iids_duplicates)) == -1) return -1;1585        nwritten += n;1586#ifdef ENABLE_GCRA1587    } else if (o->type == OBJ_GCRA) {1588        long long t;1589        getLongLongFromGCRAObject(o, &t);1590        if ((n = rdbSaveLen(rdb,t)) == -1) return -1;1591        nwritten += n;1592#endif1593    } else if (o->type == OBJ_MODULE) {1594        /* Save a module-specific value. */1595        RedisModuleIO io;1596        moduleValue *mv = o->ptr;1597        moduleType *mt = mv->type;15981599        /* Write the "module" identifier as prefix, so that we'll be able1600         * to call the right module during loading. */1601        int retval = rdbSaveLen(rdb,mt->entity.id);1602        if (retval == -1) return -1;1603        moduleInitIOContext(&io, &mt->entity, rdb, key, dbid);1604        io.bytes += retval;16051606        /* Then write the module-specific representation + EOF marker. */1607        mt->rdb_save(&io,mv->value);1608        retval = rdbSaveLen(rdb,RDB_MODULE_OPCODE_EOF);1609        if (retval == -1)1610            io.error = 1;1611        else1612            io.bytes += retval;16131614        if (io.ctx) {1615            moduleFreeContext(io.ctx);1616            zfree(io.ctx);1617        }1618        return io.error ? -1 : (ssize_t)io.bytes;1619    } else if (o->type == OBJ_ARRAY) {1620        /* Save an array value. We persist only elements and insert_idx - no1621         * implementation details like slice_size. Arrays are loaded using1622         * the current ar_slice_size config. */1623        redisArray *ar = o->ptr;16241625        /* Save count */1626        if ((n = rdbSaveLen(rdb, ar->count)) == -1) return -1;1627        nwritten += n;16281629        /* Save insert_idx: 0 = none, 1 = has value followed by actual value.1630         * We can't save UINT64_MAX directly with rdbSaveLen/rdbLoadLen because1631         * rdbLoadLen returns UINT64_MAX (RDB_LENERR) to signal an error, making1632         * it impossible to distinguish a valid UINT64_MAX value from an error. */1633        if (ar->insert_idx == AR_INSERT_IDX_NONE) {1634            if ((n = rdbSaveLen(rdb, 0)) == -1) return -1;1635            nwritten += n;1636        } else {1637            if ((n = rdbSaveLen(rdb, 1)) == -1) return -1;1638            nwritten += n;1639            if ((n = rdbSaveLen(rdb, ar->insert_idx)) == -1) return -1;1640            nwritten += n;1641        }16421643        /* Save elements in index order.1644         * We need to iterate through all slices, handling both flat directory1645         * mode and superdir mode. In superdir mode, blocks are sorted by1646         * block_id, so we iterate through blocks in order. */1647        if (ar->superdir) {1648            /* Superdir mode: iterate through blocks */1649            for (uint32_t bi = 0; bi < ar->sdir_len; bi++) {1650                arSDirEntry *e = ar->superdir + bi;1651                uint64_t block_base = e->block_id * AR_SUPER_BLOCK_SLOTS;16521653                for (uint32_t si = 0; si < AR_SUPER_BLOCK_SLOTS; si++) {1654                    arSlice *s = e->slots[si];1655                    if (!s) continue;1656                    uint64_t slice_id = block_base + si;1657                    if ((n = rdbSaveArraySlice(rdb, s, slice_id, ar->slice_size)) == -1) return -1;1658                    nwritten += n;1659                }1660            }1661        } else {1662            /* Flat directory mode */1663            for (uint64_t slice_id = 0; slice_id <= ar->dir_highest_used && slice_id < ar->dir_alloc; slice_id++) {1664                arSlice *s = ar->dir[slice_id];1665                if (!s) continue;1666                if ((n = rdbSaveArraySlice(rdb, s, slice_id, ar->slice_size)) == -1) return -1;1667                nwritten += n;1668            }1669        }1670    } else {1671        serverPanic("Unknown object type");1672    }1673    return nwritten;1674}16751676/* Return the length the object will have on disk if saved with1677 * the rdbSaveObject() function. Currently we use a trick to get1678 * this length with very little changes to the code. In the future1679 * we could switch to a faster solution. */1680size_t rdbSavedObjectLen(robj *o, robj *key, int dbid) {1681    ssize_t len = rdbSaveObject(NULL,o,key,dbid);1682    serverAssertWithInfo(NULL,o,len != -1);1683    return len;1684}16851686/* Save a key-value pair, with expire time, type, key, value.1687 * On error -1 is returned.1688 * On success if the key was actually saved 1 is returned. */1689int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val, long long expiretime, int dbid) {1690    int savelru = server.maxmemory_policy & MAXMEMORY_FLAG_LRU;1691    int savelfu = server.maxmemory_policy & MAXMEMORY_FLAG_LFU;16921693    /* Save the expire time */1694    if (expiretime != -1) {1695        if (rdbSaveType(rdb,RDB_OPCODE_EXPIRETIME_MS) == -1) return -1;1696        if (rdbSaveMillisecondTime(rdb,expiretime) == -1) return -1;1697    }16981699    /* Save the LRU info. */1700    if (savelru) {1701        uint64_t idletime = estimateObjectIdleTime(val);1702        idletime /= 1000; /* Using seconds is enough and requires less space.*/1703        if (rdbSaveType(rdb,RDB_OPCODE_IDLE) == -1) return -1;1704        if (rdbSaveLen(rdb,idletime) == -1) return -1;1705    }17061707    /* Save the LFU info. */1708    if (savelfu) {1709        uint8_t buf[1];1710        buf[0] = LFUDecrAndReturn(val);1711        /* We can encode this in exactly two bytes: the opcode and an 81712         * bit counter, since the frequency is logarithmic with a 0-255 range.1713         * Note that we do not store the halving time because to reset it1714         * a single time when loading does not affect the frequency much. */1715        if (rdbSaveType(rdb,RDB_OPCODE_FREQ) == -1) return -1;1716        if (rdbWriteRaw(rdb,buf,1) == -1) return -1;1717    }17181719    /* if needed save key metadata  */1720    if (getModuleMetaBits(val->metabits)) {1721        if (rdbSaveKeyMetadata(rdb, key, val, dbid) == -1)1722            return -1;1723    }17241725    /* Save type, key, value */1726    if (rdbSaveObjectType(rdb,val) == -1) return -1;1727    if (rdbSaveStringObject(rdb,key) == -1) return -1;1728    if (rdbSaveObject(rdb,val,key,dbid) == -1) return -1;17291730    /* Delay return if required (for testing) */1731    if (server.rdb_key_save_delay)1732        debugDelay(server.rdb_key_save_delay);17331734    return 1;1735}17361737/* Save an AUX field. */1738ssize_t rdbSaveAuxField(rio *rdb, void *key, size_t keylen, void *val, size_t vallen) {1739    ssize_t ret, len = 0;1740    if ((ret = rdbSaveType(rdb,RDB_OPCODE_AUX)) == -1) return -1;1741    len += ret;1742    if ((ret = rdbSaveRawString(rdb,key,keylen)) == -1) return -1;1743    len += ret;1744    if ((ret = rdbSaveRawString(rdb,val,vallen)) == -1) return -1;1745    len += ret;1746    return len;1747}17481749/* Wrapper for rdbSaveAuxField() used when key/val length can be obtained1750 * with strlen(). */1751ssize_t rdbSaveAuxFieldStrStr(rio *rdb, char *key, char *val) {1752    return rdbSaveAuxField(rdb,key,strlen(key),val,strlen(val));1753}17541755/* Wrapper for strlen(key) + integer type (up to long long range). */1756ssize_t rdbSaveAuxFieldStrInt(rio *rdb, char *key, long long val) {1757    char buf[LONG_STR_SIZE];1758    int vlen = ll2string(buf,sizeof(buf),val);1759    return rdbSaveAuxField(rdb,key,strlen(key),buf,vlen);1760}17611762/* Save a few default AUX fields with information about the RDB generated. */1763int rdbSaveInfoAuxFields(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {1764    int redis_bits = (sizeof(void*) == 8) ? 64 : 32;1765    int aof_base = (rdbflags & RDBFLAGS_AOF_PREAMBLE) != 0;17661767    /* Add a few fields about the state when the RDB was created. */1768    if (rdbSaveAuxFieldStrStr(rdb,"redis-ver",REDIS_VERSION) == -1) return -1;1769    if (rdbSaveAuxFieldStrInt(rdb,"redis-bits",redis_bits) == -1) return -1;1770    if (rdbSaveAuxFieldStrInt(rdb,"ctime",time(NULL)) == -1) return -1;1771    if (rdbSaveAuxFieldStrInt(rdb,"used-mem",zmalloc_used_memory()) == -1) return -1;17721773    /* Handle saving options that generate aux fields. */1774    if (rsi) {1775        if (rdbSaveAuxFieldStrInt(rdb,"repl-stream-db",rsi->repl_stream_db)1776            == -1) return -1;1777        if (rdbSaveAuxFieldStrStr(rdb,"repl-id",server.replid)1778            == -1) return -1;1779        if (rdbSaveAuxFieldStrInt(rdb,"repl-offset",server.master_repl_offset)1780            == -1) return -1;1781    }1782    if (rdbSaveAuxFieldStrInt(rdb, "aof-base", aof_base) == -1) return -1;17831784    /* Save the active import ASM task if cluster is enabled. */1785    if (server.cluster_enabled) {1786        sds task_info = asmDumpActiveImportTask();1787        int ret = rdbSaveAuxFieldStrStr(rdb, "cluster-asm-task",1788                                        task_info ? task_info : "");1789        if (task_info) sdsfree(task_info);1790        if (ret == -1) return -1;1791    }17921793    return 1;1794}17951796ssize_t rdbSaveSingleModuleAux(rio *rdb, int when, moduleType *mt) {1797    /* Save a module-specific aux value. */1798    RedisModuleIO io;1799    int retval = 0;1800    moduleInitIOContext(&io, &mt->entity, rdb, NULL, -1);18011802    /* We save the AUX field header in a temporary buffer so we can support aux_save2 API.1803     * If aux_save2 is used the buffer will be flushed at the first time the module will perform1804     * a write operation to the RDB and will be ignored is case there was no writes. */1805    rio aux_save_headers_rio;1806    rioInitWithBuffer(&aux_save_headers_rio, sdsempty());18071808    if (rdbSaveType(&aux_save_headers_rio, RDB_OPCODE_MODULE_AUX) == -1) goto error;18091810    /* Write the "module" identifier as prefix, so that we'll be able1811     * to call the right module during loading. */1812    if (rdbSaveLen(&aux_save_headers_rio,mt->entity.id) == -1) goto error;18131814    /* write the 'when' so that we can provide it on loading. add a UINT opcode1815     * for backwards compatibility, everything after the MT needs to be prefixed1816     * by an opcode. */1817    if (rdbSaveLen(&aux_save_headers_rio,RDB_MODULE_OPCODE_UINT) == -1) goto error;1818    if (rdbSaveLen(&aux_save_headers_rio,when) == -1) goto error;18191820    /* Then write the module-specific representation + EOF marker. */1821    if (mt->aux_save2) {1822        io.pre_flush_buffer = aux_save_headers_rio.io.buffer.ptr;1823        mt->aux_save2(&io,when);1824        if (io.pre_flush_buffer) {1825            /* aux_save did not save any data to the RDB.1826             * We will avoid saving any data related to this aux type1827             * to allow loading this RDB if the module is not present. */1828            sdsfree(io.pre_flush_buffer);1829            io.pre_flush_buffer = NULL;1830            return 0;1831        }1832    } else {1833        /* Write headers now, aux_save does not do lazy saving of the headers. */1834        retval = rdbWriteRaw(rdb, aux_save_headers_rio.io.buffer.ptr, sdslen(aux_save_headers_rio.io.buffer.ptr));1835        if (retval == -1) goto error;1836        io.bytes += retval;1837        sdsfree(aux_save_headers_rio.io.buffer.ptr);1838        mt->aux_save(&io,when);1839    }1840    retval = rdbSaveLen(rdb,RDB_MODULE_OPCODE_EOF);1841    serverAssert(!io.pre_flush_buffer);1842    if (retval == -1)1843        io.error = 1;1844    else1845        io.bytes += retval;18461847    if (io.ctx) {1848        moduleFreeContext(io.ctx);1849        zfree(io.ctx);1850    }1851    if (io.error)1852        return -1;1853    return io.bytes;1854error:1855    sdsfree(aux_save_headers_rio.io.buffer.ptr);1856    return -1;1857}18581859ssize_t rdbSaveFunctions(rio *rdb) {1860    dict *functions = functionsLibGet();1861    dictIterator iter;1862    dictEntry *entry = NULL;1863    ssize_t written = 0;1864    ssize_t ret;18651866    dictInitIterator(&iter, functions);1867    while ((entry = dictNext(&iter))) {1868        if ((ret = rdbSaveType(rdb, RDB_OPCODE_FUNCTION2)) < 0) goto werr;1869        written += ret;1870        functionLibInfo *li = dictGetVal(entry);1871        if ((ret = rdbSaveRawString(rdb, (unsigned char *) li->code, sdslen(li->code))) < 0) goto werr;1872        written += ret;1873    }1874    dictResetIterator(&iter);1875    return written;18761877werr:1878    dictResetIterator(&iter);1879    return -1;1880}18811882/* RDB save path (not DUMP): Save the hash template registry (id -> field names)1883 * so REF-encoded hashes, which store only [id][values...], can resolve their 1884 * fields at load time.1885 *1886 * Each template is written as its own RDB_OPCODE_HASH_TEMPLATE record; the1887 * loader reads them until the next opcode differs.1888 * Record: [OPCODE][id][field_count][field1][field2]... */1889ssize_t rdbSaveHashTemplates(rio *rdb) {1890    ssize_t written = 0;1891    ssize_t ret;18921893    if (!server.htemplates || !server.htemplates->by_fields) return 0;18941895    dictIterator *di = dictGetIterator(server.htemplates->by_fields);1896    dictEntry *de;1897    while ((de = dictNext(di)) != NULL) {1898        hashTemplate *tmpl = dictGetKey(de);1899        if (tmpl->key_refcount == 0) continue;19001901        if ((ret = rdbSaveType(rdb, RDB_OPCODE_HASH_TEMPLATE)) < 0) goto werr;1902        written += ret;1903        if ((ret = rdbSaveLen(rdb, tmpl->id)) < 0) goto werr;1904        written += ret;1905        if ((ret = rdbSaveLen(rdb, tmpl->field_count)) < 0) goto werr;1906        written += ret;1907        for (unsigned long long i = 0; i < tmpl->field_count; i++) {1908            sds field = tmpl->fields[i];1909            if ((ret = rdbSaveRawString(rdb, (unsigned char *)field, sdslen(field))) < 0)1910                goto werr;1911            written += ret;1912        }1913    }1914    dictReleaseIterator(di);1915    return written;19161917werr:1918    dictReleaseIterator(di);1919    return -1;1920}19211922ssize_t rdbSaveDb(rio *rdb, int dbid, int rdbflags, long *key_counter, unsigned long long *skipped) {1923    dictEntry *de;1924    ssize_t written = 0;1925    ssize_t res;1926    size_t oldsize = 0;1927    kvstoreIterator kvs_it;1928    static long long info_updated_time = 0;1929    char *pname = (rdbflags & RDBFLAGS_AOF_PREAMBLE) ? "AOF rewrite" :  "RDB";19301931    redisDb *db = server.db + dbid;1932    unsigned long long int db_size = kvstoreSize(db->keys);1933    if (db_size == 0) return 0;19341935    /* Write the SELECT DB opcode */1936    if ((res = rdbSaveType(rdb,RDB_OPCODE_SELECTDB)) < 0) goto werr;1937    written += res;1938    if ((res = rdbSaveLen(rdb, dbid)) < 0) goto werr;1939    written += res;19401941    /* Write the RESIZE DB opcode. */1942    unsigned long long expires_size = kvstoreSize(db->expires);1943    if ((res = rdbSaveType(rdb,RDB_OPCODE_RESIZEDB)) < 0) goto werr;1944    written += res;1945    if ((res = rdbSaveLen(rdb,db_size)) < 0) goto werr;1946    written += res;1947    if ((res = rdbSaveLen(rdb,expires_size)) < 0) goto werr;1948    written += res;19491950    kvstoreIteratorInit(&kvs_it, db->keys);1951    int last_slot = -1;1952    /* Iterate this DB writing every entry */1953    while ((de = kvstoreIteratorNext(&kvs_it)) != NULL) {1954        int curr_slot = kvstoreIteratorGetCurrentDictIndex(&kvs_it);1955        /* Save slot info. */1956        if (server.cluster_enabled && curr_slot != last_slot) {1957            if ((res = rdbSaveType(rdb, RDB_OPCODE_SLOT_INFO)) < 0) goto werr2;1958            written += res;1959            if ((res = rdbSaveLen(rdb, curr_slot)) < 0) goto werr2;1960            written += res;1961            if ((res = rdbSaveLen(rdb, kvstoreDictSize(db->keys, curr_slot))) < 0) goto werr2;1962            written += res;1963            if ((res = rdbSaveLen(rdb, kvstoreDictSize(db->expires, curr_slot))) < 0) goto werr2;1964            written += res;1965            /* Dismiss bucket arrays of the previous slot to reduce CoW.1966             * The final slot is not dismissed since the child exits shortly after. */1967            if (server.in_fork_child && last_slot != -1)1968                dismissDictBucketsMemory(kvstoreGetDict(db->keys, last_slot));1969            last_slot = curr_slot;1970        }1971        kvobj *kv = dictGetKV(de);1972        robj key;1973        long long expire;1974        size_t rdb_bytes_before_key = rdb->processed_bytes;19751976        /* Skip keys that are being trimmed */1977        if (server.cluster_enabled && isSlotInTrimJob(curr_slot)) {1978            (*skipped)++;1979            continue;1980        }19811982        initStaticStringObject(key,kvobjGetKey(kv));1983        expire = kvobjGetExpire(kv);1984        if (server.memory_tracking_enabled)1985            oldsize = kvobjAllocSize(kv);1986        res = rdbSaveKeyValuePair(rdb, &key, kv, expire, dbid);1987        if (server.memory_tracking_enabled)1988            updateSlotAllocSize(db, curr_slot, kv, oldsize, kvobjAllocSize(kv));1989        if (res < 0) goto werr2;1990        written += res;19911992        /* In fork child process, we can try to release memory back to the1993         * OS and possibly avoid or decrease COW. We give the dismiss1994         * mechanism a hint about an estimated size of the object we stored. */1995        size_t dump_size = rdb->processed_bytes - rdb_bytes_before_key;1996        if (server.in_fork_child && dump_size > server.page_size/2)1997            dismissObject(kv, dump_size);19981999        /* Update child info every 1 second (approximately).2000         * in order to avoid calling mstime() on each iteration, we will

Findings

✓ No findings reported for this file.

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.