restart.c C 447 lines View on github.com → Search inside
1#include "memcached.h"23#include "restart.h"45#include <stdio.h>6#include <stdlib.h>7#include <sys/mman.h>8#include <sys/types.h>9#include <sys/stat.h>10#include <fcntl.h>11#include <string.h>1213typedef struct _restart_data_cb restart_data_cb;1415struct _restart_data_cb {16    void *data; // user supplied opaque data.17    struct _restart_data_cb *next; // callbacks are ordered stack18    restart_check_cb ccb;19    restart_save_cb scb;20    char tag[RESTART_TAG_MAXLEN];21};2223// TODO: struct to hand back to caller.24static int mmap_fd = 0;25static void *mmap_base = NULL;26static size_t slabmem_limit = 0;27char *memory_file = NULL;2829static restart_data_cb *cb_stack = NULL;3031// Allows submodules and engines to have independent check and save metadata32// routines for the restart code.33void restart_register(const char *tag, restart_check_cb ccb, restart_save_cb scb, void *data) {34    restart_data_cb *cb = calloc(1, sizeof(restart_data_cb));35    if (cb == NULL) {36        fprintf(stderr, "[restart] failed to allocate callback register\n");37        abort();38    }3940    // Handle first time call initialization inline so we don't need separate41    // API call.42    if (cb_stack == NULL) {43        cb_stack = cb;44    } else {45        // Ensure we fire the callbacks in registration order.46        // Someday I'll get a queue.h overhaul.47        restart_data_cb *finder = cb_stack;48        while (finder->next != NULL) {49            finder = finder->next;50        }51        finder->next = cb;52    }5354    safe_strcpy(cb->tag, tag, RESTART_TAG_MAXLEN);55    cb->data = data;56    cb->ccb = *ccb;57    cb->scb = *scb;58}5960typedef struct {61    FILE *f;62    restart_data_cb *cb;63    char *line;64    bool done;65} restart_cb_ctx;6667// TODO: error string from cb?68// - look for final line with checksum69// - checksum entire file (up until final line)70// - seek to start7172static int restart_check(const char *file) {73    // metadata is kept in a separate file.74    size_t flen = strlen(file);75    const char *ext = ".meta";76    char *metafile = calloc(1, flen + strlen(ext) + 1);77    if (metafile == NULL) {78        // probably in a really bad position if we hit here, so don't start.79        fprintf(stderr, "[restart] failed to allocate memory for restart check\n");80        abort();81    }82    memcpy(metafile, file, flen);83    memcpy(metafile+flen, ext, strlen(ext));8485    FILE *f = fopen(metafile, "r");86    if (f == NULL) {87        fprintf(stderr, "[restart] no metadata save file, starting with a clean cache\n");88        free(metafile);89        return -1;90    }9192    restart_cb_ctx ctx;9394    ctx.f = f;95    ctx.cb = NULL;96    ctx.line = NULL;97    ctx.done = false;98    if (restart_get_kv(&ctx, NULL, NULL) != RESTART_DONE) {99        // First line must be a tag, so read it in and set up the proper100        // callback here.101        fprintf(stderr, "[restart] corrupt metadata file\n");102        // TODO: this should probably just return -1 and skip the reuse.103        abort();104    }105    if (ctx.cb == NULL) {106        fprintf(stderr, "[restart] Failed to read a tag from metadata file\n");107        abort();108    }109110    // loop call the callback, check result code.111    bool failed = false;112    while (!ctx.done) {113        restart_data_cb *cb = ctx.cb;114        if (cb->ccb(cb->tag, &ctx, cb->data) != 0) {115            failed = true;116            break;117        }118    }119120    if (ctx.line)121        free(ctx.line);122123    fclose(f);124125    unlink(metafile);126    free(metafile);127128    if (failed) {129        fprintf(stderr, "[restart] failed to validate metadata, starting with a clean cache\n");130        return -1;131    } else {132        return 0;133    }134}135136// This function advances the file read while being called directly from the137// callback.138// The control inversion here (callback calling in which might change the next139// callback) allows the callbacks to set up proper loops or sequences for140// reading data back, avoiding an event model.141enum restart_get_kv_ret restart_get_kv(void *ctx, char **key, char **val) {142    char *line = NULL;143    size_t len = 0;144    restart_data_cb *cb = NULL;145    restart_cb_ctx *c = (restart_cb_ctx *) ctx;146    // free previous line.147    // we could just pass it into getline, but it can randomly realloc so we'd148    // have to re-assign it into the structure anyway.149    if (c->line != NULL) {150        free(c->line);151        c->line = NULL;152    }153154    if (getline(&line, &len, c->f) != -1) {155        // First char is an indicator:156        // T for TAG, changing the callback we use.157        // K for key/value, to ship to the active callback.158        char *p = line;159        char *end = p + len;160        while (*p != '\n' && p != end) {161            p++;162        }163        *p = '\0';164165        if (line[0] == 'T') {166            cb = cb_stack;167            while (cb != NULL) {168                // NOTE: len is allocated size, not line len. need to chomp \n169                if (strcmp(cb->tag, line+1) == 0) {170                    break;171                }172                cb = cb->next;173            }174            if (cb == NULL) {175                fprintf(stderr, "[restart] internal handler for metadata tag not found: %s:\n", line+1);176                free(line);177                return RESTART_NOTAG;178            }179            c->cb = cb;180        } else if (line[0] == 'K') {181            char *p = line+1; // start just ahead of the token.182            // tokenize the string and return the pointers?183            if (key != NULL) {184                *key = p;185            }186187            // turn key into a normal NULL terminated string.188            while (*p != ' ' && (p - line < len)) {189                p++;190            }191            *p = '\0';192            p++;193194            // value _should_ run until where the newline was, which is \0 now195            if (val != NULL) {196                *val = p;197            }198            c->line = line;199200            return RESTART_OK;201        } else {202            // FIXME: proper error chain.203            fprintf(stderr, "[restart] invalid metadata line:\n\n%s\n", line);204            free(line);205            return RESTART_BADLINE;206        }207    } else {208        // EOF or error in read.209        c->done = true;210        free(line);211    }212213    return RESTART_DONE;214}215216// TODO:217// - rolling checksum along with the writes.218// - write final line + checksum + byte count or w/e.219220static int restart_save(const char *file) {221    // metadata is kept in a separate file.222    // FIXME: function.223    size_t flen = strlen(file);224    const char *ext = ".meta";225    size_t extlen = strlen(ext);226    char *metafile = calloc(1, flen + extlen + 1);227    if (metafile == NULL) {228        fprintf(stderr, "[restart] failed to allocate memory during metadata save\n");229        return -1;230    }231    memcpy(metafile, file, flen);232    memcpy(metafile+flen, ext, extlen);233234    // restrictive permissions for the metadata file.235    // TODO: also for the mmap file eh? :P236    mode_t oldmask = umask(~(S_IRUSR | S_IWUSR));237    FILE *f = fopen(metafile, "w");238    umask(oldmask);239    if (f == NULL) {240        // FIXME: correct error handling.241        free(metafile);242        perror("failed to write metadata file");243        return -1;244    }245246    restart_data_cb *cb = cb_stack;247    restart_cb_ctx ctx;248    ctx.f = f;249    while (cb != NULL) {250        // Plugins/engines in the metadata file are separated by tag lines.251        fprintf(f, "T%s\n", cb->tag);252        if (cb->scb(cb->tag, &ctx, cb->data) != 0) {253            fclose(f);254            free(metafile);255            return -1;256        }257258        cb = cb->next;259    }260261    fclose(f);262    free(metafile);263264    return 0;265}266267// Keys and values must not contain spaces or newlines.268// Could offer an interface that uriencodes values for the caller, however269// nothing currently would use it, so add when necessary.270#define SET_VAL_MAX 4096271void restart_set_kv(void *ctx, const char *key, const char *fmt, ...) {272    va_list ap;273    restart_cb_ctx *c = (restart_cb_ctx *) ctx;274    char valbuf[SET_VAL_MAX];275276    va_start(ap, fmt);277    int vlen = vsnprintf(valbuf, SET_VAL_MAX-1, fmt, ap);278    va_end(ap);279    // This is heavy handed. We need to protect against corrupt data as much280    // as possible. The buffer is large and these values are currently small,281    // it will take a significant mistake to land here.282    if (vlen >= SET_VAL_MAX) {283        fprintf(stderr, "[restart] fatal error while saving metadata state, value too long for: %s %s",284                key, valbuf);285        abort();286    }287288    fprintf(c->f, "K%s %s\n", key, valbuf);289    // TODO: update crc32c290}291292static long _find_pagesize(void) {293#if defined(HAVE_SYSCONF) && defined(_SC_PAGESIZE)294    return sysconf(_SC_PAGESIZE);295#else296    // A good guess.297    return 4096;298#endif299}300301bool restart_mmap_open(const size_t limit, const char *file, void **mem_base) {302    bool reuse_mmap = true;303304    long pagesize = _find_pagesize();305    memory_file = strdup(file);306    mmap_fd = open(file, O_RDWR|O_CREAT, S_IRWXU);307    if (mmap_fd == -1) {308        perror("failed to open file for mmap");309        abort();310    }311    if (ftruncate(mmap_fd, limit) != 0) {312        perror("ftruncate failed");313        abort();314    }315    /* Allocate everything in a big chunk with malloc */316    if (limit % pagesize) {317        // This is a sanity check; shouldn't ever be possible since we318        // increase memory by whole megabytes.319        fprintf(stderr, "[restart] memory limit not divisible evenly by pagesize (please report bug)\n");320        abort();321    }322    mmap_base = mmap(NULL, limit, PROT_READ|PROT_WRITE, MAP_SHARED, mmap_fd, 0);323    if (mmap_base == MAP_FAILED) {324        perror("failed to mmap, aborting");325        abort();326    }327    // Set the limit before calling check_mmap, so we can find the meta page..328    slabmem_limit = limit;329    if (restart_check(file) != 0) {330        reuse_mmap = false;331    }332    *mem_base = mmap_base;333334    return reuse_mmap;335}336337/* Gracefully stop/close the shared memory segment */338void restart_mmap_close(void) {339    msync(mmap_base, slabmem_limit, MS_SYNC);340341    if (restart_save(memory_file) != 0) {342        fprintf(stderr, "[restart] failed to save metadata");343    }344345    if (munmap(mmap_base, slabmem_limit) != 0) {346        perror("[restart] failed to munmap shared memory");347    } else if (close(mmap_fd) != 0) {348        perror("[restart] failed to close shared memory fd");349    }350351    free(memory_file);352}353354// given memory base, quickly walk memory and do pointer fixup.355// do this once on startup to avoid having to do pointer fixup on every356// reference from hash table or LRU.357unsigned int restart_fixup(void *orig_addr) {358    struct timeval tv;359    uint64_t checked = 0;360    const unsigned int page_size = settings.slab_page_size;361    unsigned int page_remain = page_size;362363    gettimeofday(&tv, NULL);364    if (settings.verbose > 0) {365        fprintf(stderr, "[restart] original memory base: [%p] new base: [%p]\n", orig_addr, mmap_base);366        fprintf(stderr, "[restart] recovery start [%d.%d]\n", (int)tv.tv_sec, (int)tv.tv_usec);367    }368369    // since chunks don't align with pages, we have to also track page size.370    while (checked < slabmem_limit) {371        //fprintf(stderr, "checked: %lu\n", checked);372        item *it = (item *)((char *)mmap_base + checked);373374        int size = slabs_fixup((char *)mmap_base + checked,375                checked % settings.slab_page_size);376        //fprintf(stderr, "id: %d, size: %d remain: %u\n", it->slabs_clsid, size, page_remain);377        // slabber gobbled an entire page, skip and move on.378        if (size == -1) {379            assert(page_remain % page_size == 0);380            assert(page_remain == page_size);381            checked += page_remain;382            page_remain = page_size;383            continue;384        }385386        if (it->it_flags & ITEM_LINKED) {387            // fixup next/prev links while on LRU.388            if (it->next) {389                it->next = (item *)((mc_ptr_t)it->next - (mc_ptr_t)orig_addr);390                it->next = (item *)((mc_ptr_t)it->next + (mc_ptr_t)mmap_base);391            }392            if (it->prev) {393                it->prev = (item *)((mc_ptr_t)it->prev - (mc_ptr_t)orig_addr);394                it->prev = (item *)((mc_ptr_t)it->prev + (mc_ptr_t)mmap_base);395            }396397            //fprintf(stderr, "item was linked\n");398            do_item_link_fixup(it);399        }400401        if (it->it_flags & (ITEM_CHUNKED|ITEM_CHUNK)) {402            item_chunk *ch;403            if (it->it_flags & ITEM_CHUNKED) {404                ch = (item_chunk *) ITEM_schunk(it);405                // Sigh. Chunked items are a hack; the clsid is the clsid of406                // the full object (always the largest slab class) rather than407                // the actual chunk.408                // I bet this is fixable :(409                size = slabs_size(ch->orig_clsid);410                //fprintf(stderr, "fixing chunked item header [%d]\n", size);411            } else {412                //fprintf(stderr, "fixing item chunk [%d]\n", size);413                ch = (item_chunk *) it;414            }415            if (ch->next) {416                ch->next = (item_chunk *)((mc_ptr_t)ch->next - (mc_ptr_t)orig_addr);417                ch->next = (item_chunk *)((mc_ptr_t)ch->next + (mc_ptr_t)mmap_base);418            }419            if (ch->prev) {420                ch->prev = (item_chunk *)((mc_ptr_t)ch->prev - (mc_ptr_t)orig_addr);421                ch->prev = (item_chunk *)((mc_ptr_t)ch->prev + (mc_ptr_t)mmap_base);422            }423            if (ch->head) {424                ch->head = (item *)((mc_ptr_t)ch->head - (mc_ptr_t)orig_addr);425                ch->head = (item *)((mc_ptr_t)ch->head + (mc_ptr_t)mmap_base);426            }427        }428429        // next chunk430        checked += size;431        page_remain -= size;432        if (size > page_remain) {433            //fprintf(stderr, "doot %d\n", page_remain);434            checked += page_remain;435            page_remain = settings.slab_page_size;436        }437        //assert(checked != 3145728);438    }439440    if (settings.verbose > 0) {441        gettimeofday(&tv, NULL);442        fprintf(stderr, "[restart] recovery end [%d.%d]\n", (int)tv.tv_sec, (int)tv.tv_usec);443    }444445    return 0;446}

Security findings 2

Info: Ensure umask() is used correctly to set restrictive permissions (e.g., 077 for private files/dirs). Calls setting permissive masks (like 0, 02, 022) might be problematic.
info security permissive-umask
mode_t oldmask = umask(~(S_IRUSR | S_IWUSR));
Info: Ensure umask() is used correctly to set restrictive permissions (e.g., 077 for private files/dirs). Calls setting permissive masks (like 0, 02, 022) might be problematic.
info security permissive-umask
umask(oldmask);

Get this view in your editor

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

Generated by searchcode.com · powered by code_get_file + code_get_findings