memcached.c C 6,262 lines View on github.com → Search inside
File is large — showing lines 1–2,000 of 6,262.
1/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */2/*3 *  memcached - memory caching daemon4 *5 *       https://www.memcached.org/6 *7 *  Copyright 2003 Danga Interactive, Inc.  All rights reserved.8 *9 *  Use and distribution licensed under the BSD license.  See10 *  the LICENSE file for full text.11 *12 *  Authors:13 *      Anatoly Vorobey <mellon@pobox.com>14 *      Brad Fitzpatrick <brad@danga.com>15 */16#include "memcached.h"17#include "storage.h"18#include "authfile.h"19#include "restart.h"20#include "slabs_mover.h"21#include <sys/stat.h>22#include <sys/socket.h>23#include <sys/un.h>24#include <signal.h>25#include <sys/param.h>26#include <sys/resource.h>27#include <sys/uio.h>28#include <ctype.h>29#include <stdarg.h>3031/* some POSIX systems need the following definition32 * to get mlockall flags out of sys/mman.h.  */33#ifndef _P1003_1B_VISIBLE34#define _P1003_1B_VISIBLE35#endif36#include <pwd.h>37#include <sys/mman.h>38#include <fcntl.h>39#include <netinet/tcp.h>40#include <arpa/inet.h>41#include <errno.h>42#include <stdlib.h>43#include <stdio.h>44#include <string.h>45#include <time.h>46#include <assert.h>47#include <sysexits.h>48#include <stddef.h>4950#ifdef HAVE_GETOPT_LONG51#include <getopt.h>52#endif5354#include "tls.h"5556#include "proto_text.h"57#include "proto_bin.h"58#include "proto_proxy.h"5960#if defined(__FreeBSD__)61#include <sys/sysctl.h>62#endif6364/*65 * forward declarations66 */67static void drive_machine(conn *c);68static int new_socket(struct addrinfo *ai);69static ssize_t tcp_read(conn *arg, void *buf, size_t count);70static ssize_t tcp_sendmsg(conn *arg, struct msghdr *msg, int flags);71static ssize_t tcp_write(conn *arg, void *buf, size_t count);7273enum try_read_result {74    READ_DATA_RECEIVED,75    READ_NO_DATA_RECEIVED,76    READ_ERROR,            /** an error occurred (on the socket) (or client closed connection) */77    READ_MEMORY_ERROR      /** failed to allocate more memory */78};7980static int try_read_command_negotiate(conn *c);81static int try_read_command_udp(conn *c);8283static enum try_read_result try_read_network(conn *c);84static enum try_read_result try_read_udp(conn *c);8586static int start_conn_timeout_thread(void);8788/* stats */89static void stats_init(void);90static void conn_to_str(const conn *c, char *addr, char *svr_addr);9192/* defaults */93static void settings_init(void);9495/* event handling, network IO */96static void event_handler(const evutil_socket_t fd, const short which, void *arg);97static void conn_close(conn *c);98static void conn_init(void);99static bool update_event(conn *c, const int new_flags);100static void complete_nread(conn *c);101102static void conn_free(conn *c);103104/** exported globals **/105struct stats stats;106struct stats_state stats_state;107struct settings settings;108time_t process_started;     /* when the process was started */109conn **conns;110111#ifdef EXTSTORE112/* hoping this is temporary; I'd prefer to cut globals, but will complete this113 * battle another day.114 */115void *ext_storage = NULL;116#endif117/** file scope variables **/118static conn *listen_conn = NULL;119static int max_fds;120static struct event_base *main_base;121122enum transmit_result {123    TRANSMIT_COMPLETE,   /** All done writing. */124    TRANSMIT_INCOMPLETE, /** More data remaining to write. */125    TRANSMIT_SOFT_ERROR, /** Can't write any more right now. */126    TRANSMIT_HARD_ERROR  /** Can't write (c->state is set to conn_closing) */127};128129/* Default methods to read from/ write to a socket */130ssize_t tcp_read(conn *c, void *buf, size_t count) {131    assert (c != NULL);132    return read(c->sfd, buf, count);133}134135ssize_t tcp_sendmsg(conn *c, struct msghdr *msg, int flags) {136    assert (c != NULL);137    return sendmsg(c->sfd, msg, flags);138}139140ssize_t tcp_write(conn *c, void *buf, size_t count) {141    assert (c != NULL);142    return write(c->sfd, buf, count);143}144145static enum transmit_result transmit(conn *c);146147/* This reduces the latency without adding lots of extra wiring to be able to148 * notify the listener thread of when to listen again.149 * Also, the clock timer could be broken out into its own thread and we150 * can block the listener via a condition.151 */152static volatile bool allow_new_conns = true;153static int stop_main_loop = NOT_STOP;154static struct event maxconnsevent;155static void maxconns_handler(const evutil_socket_t fd, const short which, void *arg) {156    struct timeval t = {.tv_sec = 0, .tv_usec = 10000};157158    if (fd == -42 || allow_new_conns == false) {159        /* reschedule in 10ms if we need to keep polling */160        evtimer_set(&maxconnsevent, maxconns_handler, 0);161        event_base_set(main_base, &maxconnsevent);162        evtimer_add(&maxconnsevent, &t);163    } else {164        evtimer_del(&maxconnsevent);165        accept_new_conns(true);166    }167}168169/*170 * given time value that's either unix time or delta from current unix time, return171 * unix time. Use the fact that delta can't exceed one month (and real time value can't172 * be that low).173 */174rel_time_t realtime(const time_t exptime) {175    /* no. of seconds in 30 days - largest possible delta exptime */176177    if (exptime == 0) return 0; /* 0 means never expire */178179    if (exptime > REALTIME_MAXDELTA) {180        /* if item expiration is at/before the server started, give it an181           expiration time of 1 second after the server started.182           (because 0 means don't expire).  without this, we'd183           underflow and wrap around to some large value way in the184           future, effectively making items expiring in the past185           really expiring never */186        if (exptime <= process_started)187            return (rel_time_t)1;188        return (rel_time_t)(exptime - process_started);189    } else {190        return (rel_time_t)(exptime + current_time);191    }192}193194static void stats_init(void) {195    memset(&stats, 0, sizeof(struct stats));196    memset(&stats_state, 0, sizeof(struct stats_state));197    stats_state.accepting_conns = true; /* assuming we start in this state. */198199    /* make the time we started always be 2 seconds before we really200       did, so time(0) - time.started is never zero.  if so, things201       like 'settings.oldest_live' which act as booleans as well as202       values are now false in boolean context... */203    process_started = time(0) - ITEM_UPDATE_INTERVAL - 2;204    stats_prefix_init(settings.prefix_delimiter);205}206207void stats_reset(void) {208    STATS_LOCK();209    memset(&stats, 0, sizeof(struct stats));210    stats_prefix_clear();211    STATS_UNLOCK();212    threadlocal_stats_reset();213    item_stats_reset();214}215216static void settings_init(void) {217    settings.use_cas = true;218    settings.access = 0700;219    settings.port = 11211;220    settings.udpport = 0;221    ssl_init_settings();222    /* By default this string should be NULL for getaddrinfo() */223    settings.inter = NULL;224    settings.maxbytes = 64 * 1024 * 1024; /* default is 64MB */225    settings.maxconns = 1024;         /* to limit connections-related memory to about 5MB */226    settings.verbose = 0;227    settings.oldest_live = 0;228    settings.evict_to_free = 1;       /* push old items out of cache when memory runs out */229    settings.socketpath = NULL;       /* by default, not using a unix socket */230    settings.auth_file = NULL;        /* by default, not using ASCII authentication tokens */231    settings.factor = 1.25;232    settings.chunk_size = 48;         /* space for a modest key and value */233    settings.num_threads = 4;         /* N workers */234    settings.num_threads_per_udp = 0;235    settings.prefix_delimiter = ':';236    settings.detail_enabled = 0;237    settings.reqs_per_event = 20;238    settings.backlog = 1024;239    settings.binding_protocol = negotiating_prot;240    settings.item_size_max = 1024 * 1024; /* The famous 1MB upper limit. */241    settings.slab_page_size = 1024 * 1024; /* chunks are split from 1MB pages. */242    settings.slab_chunk_size_max = settings.slab_page_size / 2;243    settings.sasl = false;244    settings.maxconns_fast = true;245    settings.lru_crawler = false;246    settings.lru_crawler_sleep = 100;247    settings.lru_crawler_tocrawl = 0;248    settings.lru_maintainer_thread = false;249    settings.lru_segmented = true;250    settings.hot_lru_pct = 20;251    settings.warm_lru_pct = 40;252    settings.hot_max_factor = 0.2;253    settings.warm_max_factor = 2.0;254    settings.temp_lru = false;255    settings.temporary_ttl = 61;256    settings.idle_timeout = 0; /* disabled */257    settings.hashpower_init = 0;258    settings.slab_reassign = true;259    settings.slab_automove = 1;260    settings.slab_automove_version = 0;261    settings.slab_automove_ratio = 0.8;262    settings.slab_automove_window = 10;263    settings.shutdown_command = false;264    settings.tail_repair_time = TAIL_REPAIR_TIME_DEFAULT;265    settings.flush_enabled = true;266    settings.dump_enabled = true;267    settings.crawls_persleep = 1000;268    settings.logger_watcher_buf_size = LOGGER_WATCHER_BUF_SIZE;269    settings.logger_buf_size = LOGGER_BUF_SIZE;270    settings.drop_privileges = false;271    settings.watch_enabled = true;272    settings.read_buf_mem_limit = 0;273#ifdef MEMCACHED_DEBUG274    settings.relaxed_privileges = false;275#endif276    settings.num_napi_ids = 0;277    settings.memory_file = NULL;278#ifdef SOCK_COOKIE_ID279    settings.sock_cookie_id = 0;280#endif281}282283extern pthread_mutex_t conn_lock;284285/* Connection timeout thread bits */286static pthread_t conn_timeout_tid;287static int do_run_conn_timeout_thread;288static pthread_cond_t conn_timeout_cond = PTHREAD_COND_INITIALIZER;289static pthread_mutex_t conn_timeout_lock = PTHREAD_MUTEX_INITIALIZER;290291#define CONNS_PER_SLICE 100292static void *conn_timeout_thread(void *arg) {293    int i;294    conn *c;295    rel_time_t oldest_last_cmd;296    int sleep_time;297    int sleep_slice = max_fds / CONNS_PER_SLICE;298    if (sleep_slice == 0)299        sleep_slice = CONNS_PER_SLICE;300301    useconds_t timeslice = 1000000 / sleep_slice;302303    mutex_lock(&conn_timeout_lock);304    while(do_run_conn_timeout_thread) {305        if (settings.verbose > 2)306            fprintf(stderr, "idle timeout thread at top of connection list\n");307308        oldest_last_cmd = current_time;309310        for (i = 0; i < max_fds; i++) {311            if ((i % CONNS_PER_SLICE) == 0) {312                if (settings.verbose > 2)313                    fprintf(stderr, "idle timeout thread sleeping for %ulus\n",314                        (unsigned int)timeslice);315                usleep(timeslice);316            }317318            if (!conns[i])319                continue;320321            c = conns[i];322323            if (!IS_TCP(c->transport))324                continue;325326            if (c->state != conn_new_cmd && c->state != conn_read)327                continue;328329            if ((current_time - c->last_cmd_time) > settings.idle_timeout) {330                timeout_conn(c);331            } else {332                if (c->last_cmd_time < oldest_last_cmd)333                    oldest_last_cmd = c->last_cmd_time;334            }335        }336337        /* This is the soonest we could have another connection time out */338        sleep_time = settings.idle_timeout - (current_time - oldest_last_cmd) + 1;339        if (sleep_time <= 0)340            sleep_time = 1;341342        if (settings.verbose > 2)343            fprintf(stderr,344                    "idle timeout thread finished pass, sleeping for %ds\n",345                    sleep_time);346347        struct timeval now;348        struct timespec to_sleep;349        gettimeofday(&now, NULL);350        to_sleep.tv_sec = now.tv_sec + sleep_time;351        to_sleep.tv_nsec = 0;352353        pthread_cond_timedwait(&conn_timeout_cond, &conn_timeout_lock, &to_sleep);354    }355356    mutex_unlock(&conn_timeout_lock);357    return NULL;358}359360static int start_conn_timeout_thread(void) {361    int ret;362363    if (settings.idle_timeout == 0)364        return -1;365366    do_run_conn_timeout_thread = 1;367    if ((ret = pthread_create(&conn_timeout_tid, NULL,368        conn_timeout_thread, NULL)) != 0) {369        fprintf(stderr, "Can't create idle connection timeout thread: %s\n",370            strerror(ret));371        return -1;372    }373    thread_setname(conn_timeout_tid, "mc-idletimeout");374375    return 0;376}377378int stop_conn_timeout_thread(void) {379    if (!do_run_conn_timeout_thread)380        return -1;381    mutex_lock(&conn_timeout_lock);382    do_run_conn_timeout_thread = 0;383    pthread_cond_signal(&conn_timeout_cond);384    mutex_unlock(&conn_timeout_lock);385    pthread_join(conn_timeout_tid, NULL);386    return 0;387}388389/*390 * read buffer cache helper functions391 */392static void rbuf_release(conn *c) {393    if (c->rbuf != NULL && c->rbytes == 0 && !IS_UDP(c->transport)) {394        if (c->rbuf_malloced) {395            free(c->rbuf);396            c->rbuf_malloced = false;397        } else {398            do_cache_free(c->thread->rbuf_cache, c->rbuf);399        }400        c->rsize = 0;401        c->rbuf = NULL;402        c->rcurr = NULL;403    }404}405406static bool rbuf_alloc(conn *c) {407    if (c->rbuf == NULL) {408        c->rbuf = do_cache_alloc(c->thread->rbuf_cache);409        if (!c->rbuf) {410            THR_STATS_LOCK(c->thread);411            c->thread->stats.read_buf_oom++;412            THR_STATS_UNLOCK(c->thread);413            return false;414        }415        c->rsize = READ_BUFFER_SIZE;416        c->rcurr = c->rbuf;417    }418    return true;419}420421// Just for handling huge ASCII multigets.422// The previous system was essentially the same; realloc'ing until big enough,423// then realloc'ing back down after the request finished.424bool rbuf_switch_to_malloc(conn *c) {425    // Might as well start with x2 and work from there.426    size_t size = c->rsize * 2;427    char *tmp = malloc(size);428    if (!tmp)429        return false;430431    memcpy(tmp, c->rcurr, c->rbytes);432    do_cache_free(c->thread->rbuf_cache, c->rbuf);433434    c->rcurr = c->rbuf = tmp;435    c->rsize = size;436    c->rbuf_malloced = true;437    return true;438}439440/*441 * Initializes the connections array. We don't actually allocate connection442 * structures until they're needed, so as to avoid wasting memory when the443 * maximum connection count is much higher than the actual number of444 * connections.445 *446 * This does end up wasting a few pointers' worth of memory for FDs that are447 * used for things other than connections, but that's worth it in exchange for448 * being able to directly index the conns array by FD.449 */450static void conn_init(void) {451    /* We're unlikely to see an FD much higher than maxconns. */452    int next_fd = dup(1);453    if (next_fd < 0) {454        perror("Failed to duplicate file descriptor\n");455        exit(1);456    }457    int headroom = 10;      /* account for extra unexpected open FDs */458    struct rlimit rl;459460    max_fds = settings.maxconns + headroom + next_fd;461462    /* But if possible, get the actual highest FD we can possibly ever see. */463    if (getrlimit(RLIMIT_NOFILE, &rl) == 0) {464        max_fds = rl.rlim_max;465    } else {466        fprintf(stderr, "Failed to query maximum file descriptor; "467                        "falling back to maxconns\n");468    }469470    close(next_fd);471472    if ((conns = calloc(max_fds, sizeof(conn *))) == NULL) {473        fprintf(stderr, "Failed to allocate connection structures\n");474        /* This is unrecoverable so bail out early. */475        exit(1);476    }477}478479static const char *prot_text(enum protocol prot) {480    char *rv = "unknown";481    switch(prot) {482        case ascii_prot:483            rv = "ascii";484            break;485        case binary_prot:486            rv = "binary";487            break;488        case negotiating_prot:489            rv = "auto-negotiate";490            break;491#ifdef PROXY492        case proxy_prot:493            rv = "proxy";494            break;495#endif496    }497    return rv;498}499500void conn_close_idle(conn *c) {501    if (settings.idle_timeout > 0 &&502        (current_time - c->last_cmd_time) > settings.idle_timeout) {503        if (c->state != conn_new_cmd && c->state != conn_read) {504            if (settings.verbose > 1)505                fprintf(stderr,506                    "fd %d wants to timeout, but isn't in read state", c->sfd);507            return;508        }509510        if (settings.verbose > 1)511            fprintf(stderr, "Closing idle fd %d\n", c->sfd);512513        pthread_mutex_lock(&c->thread->stats.mutex);514        c->thread->stats.idle_kicks++;515        pthread_mutex_unlock(&c->thread->stats.mutex);516517        c->close_reason = IDLE_TIMEOUT_CLOSE;518519        conn_set_state(c, conn_closing);520        drive_machine(c);521    }522}523524static void _conn_event_readd(conn *c) {525    c->ev_flags = EV_READ | EV_PERSIST;526    event_set(&c->event, c->sfd, c->ev_flags, event_handler, (void *)c);527    event_base_set(c->thread->base, &c->event);528529    // TODO: call conn_cleanup/fail/etc530    if (event_add(&c->event, 0) == -1) {531        perror("event_add");532    }533}534535/* bring conn back from a sidethread. could have had its event base moved. */536void conn_worker_readd(conn *c) {537    assert(c->resps_suspended == 0); // TODO: remove assert.538539    switch (c->state) {540        case conn_closing:541            drive_machine(c);542            break;543        case conn_io_pending:544            // The event listener was removed as more data showed up while545            // waiting for the async response.546            _conn_event_readd(c);547            // Explicit fall-through.548        case conn_io_queue:549            conn_set_state(c, conn_io_resume);550            // schedule the event, which just runs drive_machine outside of551            // any recursion here.552            event_active(&c->event, 0, 0);553            break;554        case conn_nread:555            // ran IO queue while waiting for set payload.556        case conn_write:557        case conn_mwrite:558        case conn_read:559        case conn_parse_cmd:560            // No-ops if we weren't in a suspended state to begin with561            // TODO: which other states for this?562            break;563        default:564            event_del(&c->event);565            _conn_event_readd(c);566            conn_set_state(c, conn_new_cmd);567    }568569}570571void thread_io_queue_add(LIBEVENT_THREAD *t, int type, void *ctx, io_queue_stack_cb cb) {572    io_queue_t *q = t->io_queues;573    while (q->type != IO_QUEUE_NONE) {574        q++;575    }576    q->type = type;577    q->ctx = ctx;578    q->submit_cb = cb;579    STAILQ_INIT(&q->stack);580    return;581}582583io_queue_t *thread_io_queue_get(LIBEVENT_THREAD *t, int type) {584    io_queue_t *q = t->io_queues;585    while (q->type != IO_QUEUE_NONE) {586        if (q->type == type) {587            return q;588        }589        q++;590    }591    return NULL;592}593594void thread_io_queue_submit(LIBEVENT_THREAD *t) {595    t->conns_tosubmit = 0;596    for (io_queue_t *q = t->io_queues; q->type != IO_QUEUE_NONE; q++) {597        // submission callback must consume the queue598        if (!STAILQ_EMPTY(&q->stack)) {599            q->submit_cb(q);600            assert(STAILQ_EMPTY(&q->stack));601        }602    }603}604605// called to return a single IO object to the original worker thread.606void conn_io_queue_return(io_pending_t *io) {607    io->return_cb(io);608}609610conn *conn_new(const int sfd, enum conn_states init_state,611                const int event_flags,612                const int read_buffer_size, enum network_transport transport,613                struct event_base *base, void *ssl, uint64_t conntag,614                enum protocol bproto) {615    conn *c;616617    assert(sfd >= 0 && sfd < max_fds);618    c = conns[sfd];619620    if (NULL == c) {621        if (!(c = (conn *)calloc(1, sizeof(conn)))) {622            STATS_LOCK();623            stats.malloc_fails++;624            STATS_UNLOCK();625            fprintf(stderr, "Failed to allocate connection object\n");626            return NULL;627        }628        MEMCACHED_CONN_CREATE(c);629        c->read = NULL;630        c->sendmsg = NULL;631        c->write = NULL;632        c->rbuf = NULL;633634        c->rsize = read_buffer_size;635636        // UDP connections use a persistent static buffer.637        if (c->rsize) {638            c->rbuf = (char *)malloc((size_t)c->rsize);639        }640641        if (c->rsize && c->rbuf == NULL) {642            conn_free(c);643            STATS_LOCK();644            stats.malloc_fails++;645            STATS_UNLOCK();646            fprintf(stderr, "Failed to allocate buffers for connection\n");647            return NULL;648        }649650651        STATS_LOCK();652        stats_state.conn_structs++;653        STATS_UNLOCK();654655        c->sfd = sfd;656        conns[sfd] = c;657    }658659    c->transport = transport;660    c->protocol = bproto;661    c->tag = conntag;662663    /* unix socket mode doesn't need this, so zeroed out.  but why664     * is this done for every command?  presumably for UDP665     * mode.  */666    if (!settings.socketpath) {667        c->request_addr_size = sizeof(c->request_addr);668    } else {669        c->request_addr_size = 0;670    }671672    if (transport == tcp_transport && init_state == conn_new_cmd) {673        if (getpeername(sfd, (struct sockaddr *) &c->request_addr,674                        &c->request_addr_size)) {675            perror("getpeername");676            memset(&c->request_addr, 0, sizeof(c->request_addr));677        }678    }679680    if (init_state == conn_new_cmd) {681        LOGGER_LOG(NULL, LOG_CONNEVENTS, LOGGER_CONNECTION_NEW, NULL,682                &c->request_addr, c->request_addr_size, c->transport, 0, sfd);683    }684685    if (settings.verbose > 1) {686        if (init_state == conn_listening) {687            fprintf(stderr, "<%d server listening (%s)\n", sfd,688                prot_text(c->protocol));689        } else if (IS_UDP(transport)) {690            fprintf(stderr, "<%d server listening (udp)\n", sfd);691        } else if (c->protocol == negotiating_prot) {692            fprintf(stderr, "<%d new auto-negotiating client connection\n",693                    sfd);694        } else if (c->protocol == ascii_prot) {695            fprintf(stderr, "<%d new ascii client connection.\n", sfd);696        } else if (c->protocol == binary_prot) {697            fprintf(stderr, "<%d new binary client connection.\n", sfd);698#ifdef PROXY699        } else if (c->protocol == proxy_prot) {700            fprintf(stderr, "<%d new proxy client connection.\n", sfd);701#endif702        } else {703            fprintf(stderr, "<%d new unknown (%d) client connection\n",704                sfd, c->protocol);705            assert(false);706        }707    }708709    c->state = init_state;710    c->rlbytes = 0;711    c->cmd = -1;712    c->rbytes = 0;713    c->rcurr = c->rbuf;714    c->ritem = 0;715    c->rbuf_malloced = false;716    c->item_malloced = false;717    c->sasl_started = false;718    c->close_after_write = false;719    c->last_cmd_time = current_time; /* initialize for idle kicker */720    assert(c->resps_suspended == 0);721722    c->item = 0;723    c->ssl = NULL;724#ifdef TLS725    c->ssl_wbuf = NULL;726#endif727728    if (ssl) {729        // musn't get here without ssl enabled.730        assert(settings.ssl_enabled);731        ssl_init_conn(c, ssl);732        c->ssl_enabled = true;733    } else {734        c->read = tcp_read;735        c->sendmsg = tcp_sendmsg;736        c->write = tcp_write;737        c->ssl_enabled = false;738    }739740    if (IS_UDP(transport)) {741        c->try_read_command = try_read_command_udp;742    } else {743        switch (c->protocol) {744            case ascii_prot:745                if (settings.auth_file == NULL) {746                    c->authenticated = true;747                    c->try_read_command = try_read_command_ascii;748                } else {749                    c->authenticated = false;750                    c->try_read_command = try_read_command_asciiauth;751                }752                break;753            case binary_prot:754                // binprot handles its own authentication via SASL parsing.755                c->authenticated = false;756                c->try_read_command = try_read_command_binary;757                break;758            case negotiating_prot:759                c->try_read_command = try_read_command_negotiate;760                break;761#ifdef PROXY762            case proxy_prot:763                c->try_read_command = try_read_command_proxy;764                break;765#endif766        }767    }768769    event_set(&c->event, sfd, event_flags, event_handler, (void *)c);770    event_base_set(base, &c->event);771    c->ev_flags = event_flags;772773    if (event_add(&c->event, 0) == -1) {774        perror("event_add");775        return NULL;776    }777778    STATS_LOCK();779    stats_state.curr_conns++;780    stats.total_conns++;781    STATS_UNLOCK();782783    MEMCACHED_CONN_ALLOCATE(c->sfd);784785    return c;786}787788void conn_release_items(conn *c) {789    assert(c != NULL);790791    if (c->item) {792        if (c->item_malloced) {793            free(c->item);794            c->item_malloced = false;795        } else {796            item_remove(c->item);797        }798        c->item = 0;799    }800801    // Cull any unsent responses.802    if (c->resp_head) {803        mc_resp *resp = c->resp_head;804        // r_f() handles the chain maintenance.805        while (resp) {806            // temporary by default. hide behind a debug flag in the future:807            // double free detection. Transmit loops can drop out early, but808            // here we could infinite loop.809            if (resp->free) {810                fprintf(stderr, "ERROR: double free detected during conn_release_items(): [%d] [%s]\n",811                        c->sfd, c->protocol == binary_prot ? "binary" : "ascii");812                // Since this is a critical failure, just leak the memory.813                // If these errors are seen, an abort() can be used instead.814                c->resp_head = NULL;815                c->resp = NULL;816                break;817            }818            resp = resp_finish(c, resp);819        }820    }821}822823static void conn_cleanup(conn *c) {824    assert(c != NULL);825826    conn_release_items(c);827#ifdef PROXY828    if (c->proxy_rctx) {829        proxy_cleanup_conn(c);830    }831#endif832    if (c->sasl_conn) {833        assert(settings.sasl);834        sasl_dispose(&c->sasl_conn);835        c->sasl_conn = NULL;836    }837838    if (IS_UDP(c->transport)) {839        conn_set_state(c, conn_read);840    }841}842843/*844 * Frees a connection.845 */846void conn_free(conn *c) {847    if (c) {848        assert(c != NULL);849        assert(c->sfd >= 0 && c->sfd < max_fds);850851        MEMCACHED_CONN_DESTROY(c);852        conns[c->sfd] = NULL;853        if (c->rbuf)854            free(c->rbuf);855#ifdef TLS856        if (c->ssl_wbuf)857            c->ssl_wbuf = NULL;858#endif859860        free(c);861    }862}863864static void conn_close(conn *c) {865    assert(c != NULL);866867    if (c->thread) {868        LOGGER_LOG(c->thread->l, LOG_CONNEVENTS, LOGGER_CONNECTION_CLOSE, NULL,869                &c->request_addr, c->request_addr_size, c->transport,870                c->close_reason, c->sfd);871    }872873    /* delete the event, the socket and the conn */874    event_del(&c->event);875876    if (settings.verbose > 1)877        fprintf(stderr, "<%d connection closed.\n", c->sfd);878879    conn_cleanup(c);880881    // force release of read buffer.882    if (c->thread) {883        c->rbytes = 0;884        rbuf_release(c);885    }886887    MEMCACHED_CONN_RELEASE(c->sfd);888    conn_set_state(c, conn_closed);889    if (c->ssl_enabled) {890        ssl_conn_close(c->ssl);891    }892    close(c->sfd);893    c->close_reason = 0;894    pthread_mutex_lock(&conn_lock);895    allow_new_conns = true;896    pthread_mutex_unlock(&conn_lock);897898    STATS_LOCK();899    stats_state.curr_conns--;900    STATS_UNLOCK();901902    return;903}904905// Since some connections might be off on side threads and some are managed as906// listeners we need to walk through them all from a central point.907// Must be called with all worker threads hung or in the process of closing.908void conn_close_all(void) {909    int i;910    for (i = 0; i < max_fds; i++) {911        if (conns[i] && conns[i]->state != conn_closed) {912            conn_close(conns[i]);913        }914    }915}916917/**918 * Convert a state name to a human readable form.919 */920static const char *state_text(enum conn_states state) {921    const char* const statenames[] = { "conn_listening",922                                       "conn_new_cmd",923                                       "conn_waiting",924                                       "conn_read",925                                       "conn_parse_cmd",926                                       "conn_write",927                                       "conn_nread",928                                       "conn_swallow",929                                       "conn_closing",930                                       "conn_mwrite",931                                       "conn_closed",932                                       "conn_watch",933                                       "conn_io_queue",934                                       "conn_io_resume",935                                       "conn_io_pending" };936    return statenames[state];937}938939/*940 * Sets a connection's current state in the state machine. Any special941 * processing that needs to happen on certain state transitions can942 * happen here.943 */944void conn_set_state(conn *c, enum conn_states state) {945    assert(c != NULL);946    assert(state >= conn_listening && state < conn_max_state);947948    if (state != c->state) {949        if (settings.verbose > 2) {950            fprintf(stderr, "%d: going from %s to %s\n",951                    c->sfd, state_text(c->state),952                    state_text(state));953        }954955        if (state == conn_write || state == conn_mwrite) {956            MEMCACHED_PROCESS_COMMAND_END(c->sfd, c->resp->wbuf, c->resp->wbytes);957        }958        c->state = state;959    }960}961962/*963 * response object helper functions964 */965void resp_reset(mc_resp *resp) {966    if (resp->item) {967        item_remove(resp->item);968        resp->item = NULL;969    }970    if (resp->write_and_free) {971#ifdef PROXY972        if (resp->proxy_res) {973            LIBEVENT_THREAD *t = resp->bundle->thread;974            t->proxy_buffer_memory_used -= resp->wbytes;975        }976#endif977        free(resp->write_and_free);978        resp->write_and_free = NULL;979    }980    resp->wbytes = 0;981    resp->tosend = 0;982    resp->iovcnt = 0;983    resp->chunked_data_iov = 0;984    resp->chunked_total = 0;985    resp->skip = false;986}987988void resp_add_iov(mc_resp *resp, const void *buf, int len) {989    assert(resp->iovcnt < MC_RESP_IOVCOUNT);990    int x = resp->iovcnt;991    resp->iov[x].iov_base = (void *)buf;992    resp->iov[x].iov_len = len;993    resp->iovcnt++;994    resp->tosend += len;995}996997// Notes that an IOV should be handled as a chunked item header.998// TODO: I'm hoping this isn't a permanent abstraction while I learn what the999// API should be.1000void resp_add_chunked_iov(mc_resp *resp, const void *buf, int len) {1001    resp->chunked_data_iov = resp->iovcnt;1002    resp->chunked_total = len;1003    resp_add_iov(resp, buf, len);1004}10051006// resp_allocate and resp_free are a wrapper around read buffers which makes1007// read buffers the only network memory to track.1008// Normally this would be too excessive. In this case it allows end users to1009// track a single memory limit for ephemeral connection buffers.1010// Fancy bit twiddling tricks are avoided to help keep this straightforward.1011static mc_resp* resp_allocate(conn *c) {1012    LIBEVENT_THREAD *th = c->thread;1013    mc_resp *resp = NULL;1014    mc_resp_bundle *b = th->open_bundle;10151016    if (b != NULL) {1017        for (int i = 0; i < MAX_RESP_PER_BUNDLE; i++) {1018            // loop around starting from the most likely to be free1019            int x = (i + b->next_check) % MAX_RESP_PER_BUNDLE;1020            if (b->r[x].free) {1021                resp = &b->r[x];1022                b->next_check = x+1;1023                break;1024            }1025        }10261027        if (resp != NULL) {1028            b->refcount++;1029            memset(resp, 0, sizeof(*resp));1030            resp->free = false; // redundant, for clarity.1031            resp->bundle = b;1032            if (b->refcount == MAX_RESP_PER_BUNDLE) {1033                assert(b->prev == NULL);1034                // We only allocate off the head. Assign new head.1035                th->open_bundle = b->next;1036                // Remove ourselves from the list.1037                if (b->next) {1038                    b->next->prev = 0;1039                    b->next = 0;1040                }1041            }1042        }1043    }10441045    if (resp == NULL) {1046        assert(th->open_bundle == NULL);1047        b = do_cache_alloc(th->rbuf_cache);1048        if (b) {1049            THR_STATS_LOCK(th);1050            th->stats.response_obj_bytes += READ_BUFFER_SIZE;1051            THR_STATS_UNLOCK(th);1052            b->next_check = 1;1053            b->refcount = 1;1054            for (int i = 0; i < MAX_RESP_PER_BUNDLE; i++) {1055                b->r[i].free = true;1056            }1057            b->next = 0;1058            b->prev = 0;1059            b->thread = th;1060            th->open_bundle = b;1061            resp = &b->r[0];1062            memset(resp, 0, sizeof(*resp));1063            resp->free = false; // redundant. for clarity.1064            resp->bundle = b;1065        } else {1066            return NULL;1067        }1068    }10691070    return resp;1071}10721073void resp_free(LIBEVENT_THREAD *th, mc_resp *resp) {1074    mc_resp_bundle *b = resp->bundle;10751076    resp->free = true;1077    b->refcount--;1078    if (b->refcount == 0) {1079        if (b == th->open_bundle && b->next == 0) {1080            // This is the final bundle. Just hold and reuse to skip init loop1081            assert(b->prev == 0);1082            b->next_check = 0;1083        } else {1084            // Assert that we're either in the list or at the head.1085            assert((b->next || b->prev) || b == th->open_bundle);10861087            // unlink from list.1088            mc_resp_bundle **head = &th->open_bundle;1089            if (*head == b) *head = b->next;1090            // Not tracking the tail.1091            assert(b->next != b && b->prev != b);10921093            if (b->next) b->next->prev = b->prev;1094            if (b->prev) b->prev->next = b->next;10951096            // Now completely done with this buffer.1097            do_cache_free(th->rbuf_cache, b);1098            THR_STATS_LOCK(th);1099            th->stats.response_obj_bytes -= READ_BUFFER_SIZE;1100            THR_STATS_UNLOCK(th);1101        }1102    } else {1103        mc_resp_bundle **head = &th->open_bundle;1104        // NOTE: since we're not tracking tail, latest free ends up in head.1105        if (b == th->open_bundle || (b->prev || b->next)) {1106            // If we're already linked, leave it in place to save CPU.1107        } else {1108            // Non-zero refcount, need to link into the freelist.1109            b->prev = 0;1110            b->next = *head;1111            if (b->next) b->next->prev = b;1112            *head = b;1113        }11141115    }1116    THR_STATS_LOCK(th);1117    th->stats.response_obj_count--;1118    THR_STATS_UNLOCK(th);1119}11201121bool resp_start(conn *c) {1122    mc_resp *resp = resp_allocate(c);1123    if (!resp) {1124        THR_STATS_LOCK(c->thread);1125        c->thread->stats.response_obj_oom++;1126        THR_STATS_UNLOCK(c->thread);1127        return false;1128    }11291130    // handling the stats counters here to simplify testing1131    THR_STATS_LOCK(c->thread);1132    c->thread->stats.response_obj_count++;1133    THR_STATS_UNLOCK(c->thread);11341135    if (!c->resp_head) {1136        c->resp_head = resp;1137    }1138    if (!c->resp) {1139        c->resp = resp;1140    } else {1141        c->resp->next = resp;1142        c->resp = resp;1143    }1144    if (IS_UDP(c->transport)) {1145        // need to hold on to some data for async responses.1146        c->resp->request_id = c->request_id;1147        c->resp->request_addr = c->request_addr;1148        c->resp->request_addr_size = c->request_addr_size;1149    }1150    return true;1151}11521153mc_resp *resp_start_unlinked(conn *c) {1154    mc_resp *resp = resp_allocate(c);1155    if (!resp) {1156        THR_STATS_LOCK(c->thread);1157        c->thread->stats.response_obj_oom++;1158        THR_STATS_UNLOCK(c->thread);1159        return false;1160    }11611162    // handling the stats counters here to simplify testing1163    THR_STATS_LOCK(c->thread);1164    c->thread->stats.response_obj_count++;1165    THR_STATS_UNLOCK(c->thread);11661167    if (IS_UDP(c->transport)) {1168        // need to hold on to some data for async responses.1169        c->resp->request_id = c->request_id;1170        c->resp->request_addr = c->request_addr;1171        c->resp->request_addr_size = c->request_addr_size;1172    }11731174    return resp;1175}11761177// returns next response in chain.1178mc_resp* resp_finish(conn *c, mc_resp *resp) {1179    mc_resp *next = resp->next;1180    if (resp->item) {1181        // TODO: cache hash value in resp obj?1182        item_remove(resp->item);1183        resp->item = NULL;1184    }1185    if (resp->write_and_free) {1186#ifdef PROXY1187        if (resp->proxy_res) {1188            LIBEVENT_THREAD *t = resp->bundle->thread;1189            t->proxy_buffer_memory_used -= resp->wbytes;1190        }1191#endif1192        free(resp->write_and_free);1193    }1194    if (resp->io_pending) {1195        io_pending_t *io = resp->io_pending;1196        // If we had a pending IO, tell it to internally clean up then return1197        // the main object back to our thread cache.1198        io->finalize_cb(io);1199        do_cache_free(c->thread->io_cache, io);1200        resp->io_pending = NULL;1201    }1202    if (c->resp_head == resp) {1203        c->resp_head = next;1204    }1205    if (c->resp == resp) {1206        c->resp = NULL;1207    }1208    resp_free(c->thread, resp);1209    return next;1210}12111212// tells if connection has a depth of response objects to process.1213bool resp_has_stack(conn *c) {1214    return c->resp_head->next != NULL ? true : false;1215}12161217void out_string(conn *c, const char *str) {1218    size_t len;1219    assert(c != NULL);1220    mc_resp *resp = c->resp;12211222    // if response was original filled with something, but we're now writing1223    // out an error or similar, have to reset the object first.1224    // TODO: since this is often redundant with allocation, how many callers1225    // are actually requiring it be reset? Can we fast test by just looking at1226    // tosend and reset if nonzero?1227    resp_reset(resp);12281229    if (resp->noreply) {1230        // TODO: just invalidate the response since nothing's been attempted1231        // to send yet?1232        resp->skip = true;1233        if (settings.verbose > 1)1234            fprintf(stderr, ">%d NOREPLY %s\n", c->sfd, str);1235        conn_set_state(c, conn_new_cmd);1236        return;1237    }12381239    if (settings.verbose > 1)1240        fprintf(stderr, ">%d %s\n", c->sfd, str);12411242    // Fill response object with static string.12431244    len = strlen(str);1245    if ((len + 2) > WRITE_BUFFER_SIZE) {1246        /* ought to be always enough. just fail for simplicity */1247        str = "SERVER_ERROR output line too long";1248        len = strlen(str);1249    }12501251    memcpy(resp->wbuf, str, len);1252    memcpy(resp->wbuf + len, "\r\n", 2);1253    resp_add_iov(resp, resp->wbuf, len + 2);12541255    conn_set_state(c, conn_new_cmd);1256    return;1257}12581259// For metaget-style ASCII commands. Ignores noreply, ensuring clients see1260// protocol level errors.1261void out_errstring(conn *c, const char *str) {1262    c->resp->noreply = false;1263    out_string(c, str);1264}12651266/*1267 * Outputs a protocol-specific "out of memory" error. For ASCII clients,1268 * this is equivalent to out_string().1269 */1270void out_of_memory(conn *c, char *ascii_error) {1271    const static char error_prefix[] = "SERVER_ERROR ";1272    const static int error_prefix_len = sizeof(error_prefix) - 1;12731274    if (c->protocol == binary_prot) {1275        /* Strip off the generic error prefix; it's irrelevant in binary */1276        if (!strncmp(ascii_error, error_prefix, error_prefix_len)) {1277            ascii_error += error_prefix_len;1278        }1279        write_bin_error(c, PROTOCOL_BINARY_RESPONSE_ENOMEM, ascii_error, 0);1280    } else {1281        out_string(c, ascii_error);1282    }1283}12841285static void append_bin_stats(const char *key, const uint16_t klen,1286                             const char *val, const uint32_t vlen,1287                             conn *c) {1288    char *buf = c->stats.buffer + c->stats.offset;1289    uint32_t bodylen = klen + vlen;1290    protocol_binary_response_header header = {1291        .response.magic = (uint8_t)PROTOCOL_BINARY_RES,1292        .response.opcode = PROTOCOL_BINARY_CMD_STAT,1293        .response.keylen = (uint16_t)htons(klen),1294        .response.datatype = (uint8_t)PROTOCOL_BINARY_RAW_BYTES,1295        .response.bodylen = htonl(bodylen),1296        .response.opaque = c->opaque1297    };12981299    memcpy(buf, header.bytes, sizeof(header.response));1300    buf += sizeof(header.response);13011302    if (klen > 0) {1303        memcpy(buf, key, klen);1304        buf += klen;13051306        if (vlen > 0) {1307            memcpy(buf, val, vlen);1308        }1309    }13101311    c->stats.offset += sizeof(header.response) + bodylen;1312}13131314static void append_ascii_stats(const char *key, const uint16_t klen,1315                               const char *val, const uint32_t vlen,1316                               conn *c) {1317    char *pos = c->stats.buffer + c->stats.offset;1318    uint32_t nbytes = 0;1319    int remaining = c->stats.size - c->stats.offset;1320    int room = remaining - 1;13211322    if (klen == 0 && vlen == 0) {1323        nbytes = snprintf(pos, room, "END\r\n");1324    } else if (vlen == 0) {1325        nbytes = snprintf(pos, room, "STAT %s\r\n", key);1326    } else {1327        nbytes = snprintf(pos, room, "STAT %s %s\r\n", key, val);1328    }13291330    c->stats.offset += nbytes;1331}13321333static bool grow_stats_buf(conn *c, size_t needed) {1334    size_t nsize = c->stats.size;1335    size_t available = nsize - c->stats.offset;1336    bool rv = true;13371338    /* Special case: No buffer -- need to allocate fresh */1339    if (c->stats.buffer == NULL) {1340        nsize = 1024;1341        available = c->stats.size = c->stats.offset = 0;1342    }13431344    while (needed > available) {1345        assert(nsize > 0);1346        nsize = nsize << 1;1347        available = nsize - c->stats.offset;1348    }13491350    if (nsize != c->stats.size) {1351        char *ptr = realloc(c->stats.buffer, nsize);1352        if (ptr) {1353            c->stats.buffer = ptr;1354            c->stats.size = nsize;1355        } else {1356            STATS_LOCK();1357            stats.malloc_fails++;1358            STATS_UNLOCK();1359            rv = false;1360        }1361    }13621363    return rv;1364}13651366void append_stats(const char *key, const uint16_t klen,1367                  const char *val, const uint32_t vlen,1368                  const void *cookie)1369{1370    /* value without a key is invalid */1371    if (klen == 0 && vlen > 0) {1372        return;1373    }13741375    conn *c = (conn*)cookie;13761377    if (c->protocol == binary_prot) {1378        size_t needed = vlen + klen + sizeof(protocol_binary_response_header);1379        if (!grow_stats_buf(c, needed)) {1380            return;1381        }1382        append_bin_stats(key, klen, val, vlen, c);1383    } else {1384        size_t needed = vlen + klen + 10; // 10 == "STAT = \r\n"1385        if (!grow_stats_buf(c, needed)) {1386            return;1387        }1388        append_ascii_stats(key, klen, val, vlen, c);1389    }13901391    assert(c->stats.offset <= c->stats.size);1392}13931394static void reset_cmd_handler(conn *c) {1395    c->cmd = -1;1396    c->substate = bin_no_state;1397    if (c->item != NULL) {1398        // TODO: Any other way to get here?1399        // SASL auth was mistakenly using it. Nothing else should?1400        if (c->item_malloced) {1401            free(c->item);1402            c->item_malloced = false;1403        } else {1404            item_remove(c->item);1405        }1406        c->item = NULL;1407    }1408    if (c->rbytes > 0) {1409        conn_set_state(c, conn_parse_cmd);1410    } else if (c->resp_head) {1411        conn_set_state(c, conn_mwrite);1412    } else if (c->ssl_enabled && ssl_pending(c->ssl)) {1413        // We may have pending bytes in the TLS BIO because of a mismatch1414        // between TLS records and occasional direct reads from the network.1415        // Round-trip back through the read code instead of stopping.1416        conn_set_state(c, conn_read);1417    } else {1418        conn_set_state(c, conn_waiting);1419    }1420}14211422static void complete_nread(conn *c) {1423    assert(c != NULL);1424#ifdef PROXY1425    assert(c->protocol == ascii_prot1426           || c->protocol == binary_prot1427           || c->protocol == proxy_prot);1428#else1429    assert(c->protocol == ascii_prot1430           || c->protocol == binary_prot);1431#endif1432    if (c->protocol == ascii_prot) {1433        complete_nread_ascii(c);1434    } else if (c->protocol == binary_prot) {1435        complete_nread_binary(c);1436#ifdef PROXY1437    } else if (c->protocol == proxy_prot) {1438        complete_nread_proxy(c);1439#endif1440    }1441}14421443/* Destination must always be chunked */1444/* This should be part of item.c */1445static int _store_item_copy_chunks(item *d_it, item *s_it, const int len) {1446    item_chunk *dch = (item_chunk *) ITEM_schunk(d_it);1447    /* Advance dch until we find free space */1448    while (dch->size == dch->used) {1449        if (dch->next) {1450            dch = dch->next;1451        } else {1452            break;1453        }1454    }14551456    if (s_it->it_flags & ITEM_CHUNKED) {1457        int remain = len;1458        item_chunk *sch = (item_chunk *) ITEM_schunk(s_it);1459        int copied = 0;1460        /* Fills dch's to capacity, not straight copy sch in case data is1461         * being added or removed (ie append/prepend)1462         */1463        while (sch && dch && remain) {1464            assert(dch->used <= dch->size);1465            int todo = (dch->size - dch->used < sch->used - copied)1466                ? dch->size - dch->used : sch->used - copied;1467            if (remain < todo)1468                todo = remain;1469            memcpy(dch->data + dch->used, sch->data + copied, todo);1470            dch->used += todo;1471            copied += todo;1472            remain -= todo;1473            assert(dch->used <= dch->size);1474            if (dch->size == dch->used) {1475                item_chunk *tch = do_item_alloc_chunk(dch, remain);1476                if (tch) {1477                    dch = tch;1478                } else {1479                    return -1;1480                }1481            }1482            assert(copied <= sch->used);1483            if (copied == sch->used) {1484                copied = 0;1485                sch = sch->next;1486            }1487        }1488        /* assert that the destination had enough space for the source */1489        assert(remain == 0);1490    } else {1491        int done = 0;1492        /* Fill dch's via a non-chunked item. */1493        while (len > done && dch) {1494            int todo = (dch->size - dch->used < len - done)1495                ? dch->size - dch->used : len - done;1496            //assert(dch->size - dch->used != 0);1497            memcpy(dch->data + dch->used, ITEM_data(s_it) + done, todo);1498            done += todo;1499            dch->used += todo;1500            assert(dch->used <= dch->size);1501            if (dch->size == dch->used) {1502                item_chunk *tch = do_item_alloc_chunk(dch, len - done);1503                if (tch) {1504                    dch = tch;1505                } else {1506                    return -1;1507                }1508            }1509        }1510        assert(len == done);1511    }1512    return 0;1513}15141515static int _store_item_copy_data(int comm, item *old_it, item *new_it, item *add_it) {1516    if (comm == NREAD_APPEND || comm == NREAD_APPENDVIV) {1517        if (new_it->it_flags & ITEM_CHUNKED) {1518            if (_store_item_copy_chunks(new_it, old_it, old_it->nbytes - 2) == -1 ||1519                _store_item_copy_chunks(new_it, add_it, add_it->nbytes) == -1) {1520                return -1;1521            }1522        } else {1523            memcpy(ITEM_data(new_it), ITEM_data(old_it), old_it->nbytes);1524            memcpy(ITEM_data(new_it) + old_it->nbytes - 2 /* CRLF */, ITEM_data(add_it), add_it->nbytes);1525        }1526    } else {1527        /* NREAD_PREPEND */1528        if (new_it->it_flags & ITEM_CHUNKED) {1529            if (_store_item_copy_chunks(new_it, add_it, add_it->nbytes - 2) == -1 ||1530                _store_item_copy_chunks(new_it, old_it, old_it->nbytes) == -1) {1531                return -1;1532            }1533        } else {1534            memcpy(ITEM_data(new_it), ITEM_data(add_it), add_it->nbytes);1535            memcpy(ITEM_data(new_it) + add_it->nbytes - 2 /* CRLF */, ITEM_data(old_it), old_it->nbytes);1536        }1537    }1538    return 0;1539}15401541/*1542 * Stores an item in the cache according to the semantics of one of the set1543 * commands. Protected by the item lock.1544 *1545 * Returns the state of storage.1546 */1547enum store_item_type do_store_item(item *it, int comm, LIBEVENT_THREAD *t, const uint32_t hv, int *nbytes, uint64_t *cas, uint64_t cas_in, bool cas_stale) {1548    char *key = ITEM_key(it);1549    item *old_it = do_item_get(key, it->nkey, hv, t, DONT_UPDATE);1550    enum store_item_type stored = NOT_STORED;15511552    enum cas_result { CAS_NONE, CAS_MATCH, CAS_BADVAL, CAS_STALE, CAS_MISS };15531554    item *new_it = NULL;1555    client_flags_t flags;15561557    /* Do the CAS test up front so we can apply to all store modes */1558    enum cas_result cas_res = CAS_NONE;15591560    bool do_store = false;1561    if (old_it != NULL) {1562        // Most of the CAS work requires something to compare to.1563        uint64_t it_cas = ITEM_get_cas(it);1564        uint64_t old_cas = ITEM_get_cas(old_it);1565        if (it_cas == 0) {1566            cas_res = CAS_NONE;1567        } else if (it_cas == old_cas) {1568            cas_res = CAS_MATCH;1569        } else if (cas_stale && it_cas < old_cas) {1570            cas_res = CAS_STALE;1571        } else {1572            cas_res = CAS_BADVAL;1573        }15741575        switch (comm) {1576            case NREAD_ADD:1577                /* add only adds a nonexistent item, but promote to head of LRU */1578                do_item_update(old_it);1579                break;1580            case NREAD_CAS:1581                if (cas_res == CAS_MATCH) {1582                    // cas validates1583                    // it and old_it may belong to different classes.1584                    // I'm updating the stats for the one that's getting pushed out1585                    pthread_mutex_lock(&t->stats.mutex);1586                    t->stats.slab_stats[ITEM_clsid(old_it)].cas_hits++;1587                    pthread_mutex_unlock(&t->stats.mutex);1588                    do_store = true;1589                } else if (cas_res == CAS_STALE) {1590                    // if we're allowed to set a stale value, CAS must be lower than1591                    // the current item's CAS.1592                    // This replaces the value, but should preserve TTL, and stale1593                    // item marker bit + token sent if exists.1594                    it->exptime = old_it->exptime;1595                    it->it_flags |= ITEM_STALE;1596                    if (old_it->it_flags & ITEM_TOKEN_SENT) {1597                        it->it_flags |= ITEM_TOKEN_SENT;1598                    }15991600                    pthread_mutex_lock(&t->stats.mutex);1601                    t->stats.slab_stats[ITEM_clsid(old_it)].cas_hits++;1602                    pthread_mutex_unlock(&t->stats.mutex);1603                    do_store = true;1604                } else {1605                    // NONE or BADVAL are the same for CAS cmd1606                    pthread_mutex_lock(&t->stats.mutex);1607                    t->stats.slab_stats[ITEM_clsid(old_it)].cas_badval++;1608                    pthread_mutex_unlock(&t->stats.mutex);16091610                    if (settings.verbose > 1) {1611                        fprintf(stderr, "CAS:  failure: expected %llu, got %llu\n",1612                                (unsigned long long)ITEM_get_cas(old_it),1613                                (unsigned long long)ITEM_get_cas(it));1614                    }1615                    stored = EXISTS;1616                }1617                break;1618            case NREAD_APPEND:1619            case NREAD_PREPEND:1620            case NREAD_APPENDVIV:1621            case NREAD_PREPENDVIV:1622                if (cas_res != CAS_NONE && cas_res != CAS_MATCH) {1623                    stored = EXISTS;1624                    break;1625                }1626#ifdef EXTSTORE1627                if ((old_it->it_flags & ITEM_HDR) != 0) {1628                    /* block append/prepend from working with extstore-d items.1629                     * leave response code to NOT_STORED default */1630                    break;1631                }1632#endif1633                /* we have it and old_it here - alloc memory to hold both */1634                FLAGS_CONV(old_it, flags);1635                new_it = do_item_alloc(key, it->nkey, flags, old_it->exptime, it->nbytes + old_it->nbytes - 2 /* CRLF */);16361637                // OOM trying to copy.1638                if (new_it == NULL)1639                    break;1640                /* copy data from it and old_it to new_it */1641                if (_store_item_copy_data(comm, old_it, new_it, it) == -1) {1642                    // failed data copy1643                    break;1644                } else {1645                    // refcount of new_it is 1 here. will end up 2 after link.1646                    // it's original ref is managed outside of this function1647                    it = new_it;1648                    do_store = true;1649                    // Upstream final object size for meta1650                    if (nbytes != NULL) {1651                        *nbytes = it->nbytes;1652                    }1653                }1654                break;1655            case NREAD_REPLACE:1656            case NREAD_SET:1657                do_store = true;1658                break;1659        }16601661        if (do_store) {1662            STORAGE_delete(t->storage, old_it);1663            item_replace(old_it, it, hv, cas_in);1664            stored = STORED;1665        }16661667        do_item_remove(old_it);         /* release our reference */1668        if (new_it != NULL) {1669            // append/prepend end up with an extra reference for new_it.1670            do_item_remove(new_it);1671        }1672    } else {1673        /* No pre-existing item to replace or compare to. */1674        if (ITEM_get_cas(it) != 0) {1675            /* Asked for a CAS match but nothing to compare it to. */1676            cas_res = CAS_MISS;1677        }16781679        switch (comm) {1680            case NREAD_ADD:1681            case NREAD_SET:1682            case NREAD_APPENDVIV:1683            case NREAD_PREPENDVIV:1684                do_store = true;1685                break;1686            case NREAD_CAS:1687                // LRU expired1688                stored = NOT_FOUND;1689                pthread_mutex_lock(&t->stats.mutex);1690                t->stats.cas_misses++;1691                pthread_mutex_unlock(&t->stats.mutex);1692                break;1693            case NREAD_REPLACE:1694            case NREAD_APPEND:1695            case NREAD_PREPEND:1696                /* Requires an existing item. */1697                break;1698        }16991700        if (do_store) {1701            do_item_link(it, hv, cas_in);1702            stored = STORED;1703        }1704    }17051706    if (stored == STORED && cas != NULL) {1707        *cas = ITEM_get_cas(it);1708    }1709    LOGGER_LOG(t->l, LOG_MUTATIONS, LOGGER_ITEM_STORE, NULL,1710            stored, comm, ITEM_key(it), it->nkey, it->nbytes, it->exptime,1711            ITEM_clsid(it), t->cur_sfd);17121713    return stored;1714}17151716/* set up a connection to write a buffer then free it, used for stats */1717void write_and_free(conn *c, char *buf, int bytes) {1718    if (buf) {1719        mc_resp *resp = c->resp;1720        resp->write_and_free = buf;1721        resp_add_iov(resp, buf, bytes);1722        conn_set_state(c, conn_new_cmd);1723    } else {1724        out_of_memory(c, "SERVER_ERROR out of memory writing stats");1725    }1726}17271728void append_stat(const char *name, ADD_STAT add_stats, conn *c,1729                 const char *fmt, ...) {1730    char val_str[STAT_VAL_LEN];1731    int vlen;1732    va_list ap;17331734    assert(name);1735    assert(add_stats);1736    assert(c);1737    assert(fmt);17381739    va_start(ap, fmt);1740    vlen = vsnprintf(val_str, sizeof(val_str) - 1, fmt, ap);1741    va_end(ap);17421743    add_stats(name, strlen(name), val_str, vlen, c);1744}17451746/* return server specific stats only */1747void server_stats(ADD_STAT add_stats, void *c) {1748    pid_t pid = getpid();1749    rel_time_t now = current_time;17501751    struct thread_stats thread_stats;1752    threadlocal_stats_aggregate(&thread_stats);1753    struct slab_stats slab_stats;1754    slab_stats_aggregate(&thread_stats, &slab_stats);1755#ifndef WIN321756    struct rusage usage;1757    getrusage(RUSAGE_SELF, &usage);1758#endif /* !WIN32 */17591760    STATS_LOCK();17611762    APPEND_STAT("pid", "%lu", (long)pid);1763    APPEND_STAT("uptime", "%u", now - ITEM_UPDATE_INTERVAL);1764    APPEND_STAT("time", "%ld", now + (long)process_started);1765    APPEND_STAT("version", "%s", VERSION);1766    APPEND_STAT("libevent", "%s", event_get_version());1767    APPEND_STAT("pointer_size", "%d", (int)(8 * sizeof(void *)));17681769#ifndef WIN321770    append_stat("rusage_user", add_stats, c, "%ld.%06ld",1771                (long)usage.ru_utime.tv_sec,1772                (long)usage.ru_utime.tv_usec);1773    append_stat("rusage_system", add_stats, c, "%ld.%06ld",1774                (long)usage.ru_stime.tv_sec,1775                (long)usage.ru_stime.tv_usec);1776#endif /* !WIN32 */17771778    APPEND_STAT("max_connections", "%d", settings.maxconns);1779    APPEND_STAT("curr_connections", "%llu", (unsigned long long)stats_state.curr_conns - 1);1780    APPEND_STAT("total_connections", "%llu", (unsigned long long)stats.total_conns);1781    if (settings.maxconns_fast) {1782        APPEND_STAT("rejected_connections", "%llu", (unsigned long long)stats.rejected_conns);1783    }1784    APPEND_STAT("connection_structures", "%u", stats_state.conn_structs);1785    APPEND_STAT("response_obj_oom", "%llu", (unsigned long long)thread_stats.response_obj_oom);1786    APPEND_STAT("response_obj_count", "%llu", (unsigned long long)thread_stats.response_obj_count);1787    APPEND_STAT("response_obj_bytes", "%llu", (unsigned long long)thread_stats.response_obj_bytes);1788    APPEND_STAT("read_buf_count", "%llu", (unsigned long long)thread_stats.read_buf_count);1789    APPEND_STAT("read_buf_bytes", "%llu", (unsigned long long)thread_stats.read_buf_bytes);1790    APPEND_STAT("read_buf_bytes_free", "%llu", (unsigned long long)thread_stats.read_buf_bytes_free);1791    APPEND_STAT("read_buf_oom", "%llu", (unsigned long long)thread_stats.read_buf_oom);1792    APPEND_STAT("reserved_fds", "%u", stats_state.reserved_fds);1793#ifdef PROXY1794    if (settings.proxy_enabled) {1795        APPEND_STAT("proxy_conn_requests", "%llu", (unsigned long long)thread_stats.proxy_conn_requests);1796        APPEND_STAT("proxy_conn_errors", "%llu", (unsigned long long)thread_stats.proxy_conn_errors);1797        APPEND_STAT("proxy_conn_oom", "%llu", (unsigned long long)thread_stats.proxy_conn_oom);1798        APPEND_STAT("proxy_req_active", "%lld", (long long int)thread_stats.proxy_req_active);1799    }1800#endif1801    APPEND_STAT("cmd_get", "%llu", (unsigned long long)thread_stats.get_cmds);1802    APPEND_STAT("cmd_set", "%llu", (unsigned long long)slab_stats.set_cmds);1803    APPEND_STAT("cmd_flush", "%llu", (unsigned long long)thread_stats.flush_cmds);1804    APPEND_STAT("cmd_touch", "%llu", (unsigned long long)thread_stats.touch_cmds);1805    APPEND_STAT("cmd_meta", "%llu", (unsigned long long)thread_stats.meta_cmds);1806    APPEND_STAT("get_hits", "%llu", (unsigned long long)slab_stats.get_hits);1807    APPEND_STAT("get_misses", "%llu", (unsigned long long)thread_stats.get_misses);1808    APPEND_STAT("get_expired", "%llu", (unsigned long long)thread_stats.get_expired);1809    APPEND_STAT("get_flushed", "%llu", (unsigned long long)thread_stats.get_flushed);1810#ifdef EXTSTORE1811    if (ext_storage) {1812        APPEND_STAT("get_extstore", "%llu", (unsigned long long)thread_stats.get_extstore);1813        APPEND_STAT("get_aborted_extstore", "%llu", (unsigned long long)thread_stats.get_aborted_extstore);1814        APPEND_STAT("get_oom_extstore", "%llu", (unsigned long long)thread_stats.get_oom_extstore);1815        APPEND_STAT("recache_from_extstore", "%llu", (unsigned long long)thread_stats.recache_from_extstore);1816        APPEND_STAT("miss_from_extstore", "%llu", (unsigned long long)thread_stats.miss_from_extstore);1817        APPEND_STAT("badcrc_from_extstore", "%llu", (unsigned long long)thread_stats.badcrc_from_extstore);1818    }1819#endif1820    APPEND_STAT("delete_misses", "%llu", (unsigned long long)thread_stats.delete_misses);1821    APPEND_STAT("delete_hits", "%llu", (unsigned long long)slab_stats.delete_hits);1822    APPEND_STAT("incr_misses", "%llu", (unsigned long long)thread_stats.incr_misses);1823    APPEND_STAT("incr_hits", "%llu", (unsigned long long)slab_stats.incr_hits);1824    APPEND_STAT("decr_misses", "%llu", (unsigned long long)thread_stats.decr_misses);1825    APPEND_STAT("decr_hits", "%llu", (unsigned long long)slab_stats.decr_hits);1826    APPEND_STAT("cas_misses", "%llu", (unsigned long long)thread_stats.cas_misses);1827    APPEND_STAT("cas_hits", "%llu", (unsigned long long)slab_stats.cas_hits);1828    APPEND_STAT("cas_badval", "%llu", (unsigned long long)slab_stats.cas_badval);1829    APPEND_STAT("touch_hits", "%llu", (unsigned long long)slab_stats.touch_hits);1830    APPEND_STAT("touch_misses", "%llu", (unsigned long long)thread_stats.touch_misses);1831    APPEND_STAT("store_too_large", "%llu", (unsigned long long)thread_stats.store_too_large);1832    APPEND_STAT("store_no_memory", "%llu", (unsigned long long)thread_stats.store_no_memory);1833    APPEND_STAT("auth_cmds", "%llu", (unsigned long long)thread_stats.auth_cmds);1834    APPEND_STAT("auth_errors", "%llu", (unsigned long long)thread_stats.auth_errors);1835    if (settings.idle_timeout) {1836        APPEND_STAT("idle_kicks", "%llu", (unsigned long long)thread_stats.idle_kicks);1837    }1838    APPEND_STAT("bytes_read", "%llu", (unsigned long long)thread_stats.bytes_read);1839    APPEND_STAT("bytes_written", "%llu", (unsigned long long)thread_stats.bytes_written);1840    APPEND_STAT("limit_maxbytes", "%llu", (unsigned long long)settings.maxbytes);1841    APPEND_STAT("accepting_conns", "%u", stats_state.accepting_conns);1842    APPEND_STAT("listen_disabled_num", "%llu", (unsigned long long)stats.listen_disabled_num);1843    APPEND_STAT("time_in_listen_disabled_us", "%llu", stats.time_in_listen_disabled_us);1844    APPEND_STAT("threads", "%d", settings.num_threads);1845    APPEND_STAT("conn_yields", "%llu", (unsigned long long)thread_stats.conn_yields);1846    APPEND_STAT("hash_power_level", "%u", stats_state.hash_power_level);1847    APPEND_STAT("hash_bytes", "%llu", (unsigned long long)stats_state.hash_bytes);1848    APPEND_STAT("hash_is_expanding", "%u", stats_state.hash_is_expanding);1849    if (settings.slab_reassign) {1850        const char *busy_status = stats.slab_reassign_last_busy_status;1851        if (!busy_status) {1852            // Ensure we can't be NULL, for portability reasons.1853            busy_status = "none";1854        }1855        APPEND_STAT("slab_reassign_rescues", "%llu", stats.slab_reassign_rescues);1856        APPEND_STAT("slab_reassign_chunk_rescues", "%llu", stats.slab_reassign_chunk_rescues);1857        APPEND_STAT("slab_reassign_inline_reclaim", "%llu", stats.slab_reassign_inline_reclaim);1858        APPEND_STAT("slab_reassign_busy_items", "%llu", stats.slab_reassign_busy_items);1859        APPEND_STAT("slab_reassign_busy_deletes", "%llu", stats.slab_reassign_busy_deletes);1860        APPEND_STAT("slab_reassign_busy_nomem", "%llu", stats.slab_reassign_busy_nomem);1861        APPEND_STAT("slab_reassign_last_busy_status", "%s", busy_status);1862        APPEND_STAT("slab_reassign_running", "%u", stats_state.slab_reassign_running);1863        APPEND_STAT("slabs_moved", "%llu", stats.slabs_moved);1864    }1865    if (settings.lru_crawler) {1866        APPEND_STAT("lru_crawler_running", "%u", stats_state.lru_crawler_running);1867        APPEND_STAT("lru_crawler_starts", "%u", stats.lru_crawler_starts);1868    }1869    if (settings.lru_maintainer_thread) {1870        APPEND_STAT("lru_maintainer_juggles", "%llu", (unsigned long long)stats.lru_maintainer_juggles);1871    }1872    APPEND_STAT("malloc_fails", "%llu",1873                (unsigned long long)stats.malloc_fails);1874    APPEND_STAT("log_worker_dropped", "%llu", (unsigned long long)stats.log_worker_dropped);1875    APPEND_STAT("log_worker_written", "%llu", (unsigned long long)stats.log_worker_written);1876    APPEND_STAT("log_watcher_skipped", "%llu", (unsigned long long)stats.log_watcher_skipped);1877    APPEND_STAT("log_watcher_sent", "%llu", (unsigned long long)stats.log_watcher_sent);1878    APPEND_STAT("log_watchers", "%llu", (unsigned long long)stats_state.log_watchers);1879    STATS_UNLOCK();1880#ifdef EXTSTORE1881    storage_stats(add_stats, c);1882#endif1883#ifdef PROXY1884    proxy_stats(settings.proxy_ctx, add_stats, c);1885#endif1886#ifdef TLS1887    if (settings.ssl_enabled) {1888        if (settings.ssl_session_cache) {1889            APPEND_STAT("ssl_new_sessions", "%llu", (unsigned long long)stats.ssl_new_sessions);1890        }1891        APPEND_STAT("ssl_handshake_errors", "%llu", (unsigned long long)stats.ssl_handshake_errors);1892        APPEND_STAT("ssl_proto_errors", "%llu", (unsigned long long)stats.ssl_proto_errors);1893        APPEND_STAT("time_since_server_cert_refresh", "%u", now - settings.ssl_last_cert_refresh_time);1894    }1895#endif1896    APPEND_STAT("unexpected_napi_ids", "%llu", (unsigned long long)stats.unexpected_napi_ids);1897    APPEND_STAT("round_robin_fallback", "%llu", (unsigned long long)stats.round_robin_fallback);1898}18991900void process_stat_settings(ADD_STAT add_stats, void *c) {1901    assert(add_stats);1902    APPEND_STAT("maxbytes", "%llu", (unsigned long long)settings.maxbytes);1903    APPEND_STAT("maxconns", "%d", settings.maxconns);1904    APPEND_STAT("tcpport", "%d", settings.port);1905    APPEND_STAT("udpport", "%d", settings.udpport);1906    APPEND_STAT("inter", "%s", settings.inter ? settings.inter : "NULL");1907    APPEND_STAT("verbosity", "%d", settings.verbose);1908    APPEND_STAT("oldest", "%lu", (unsigned long)settings.oldest_live);1909    APPEND_STAT("evictions", "%s", settings.evict_to_free ? "on" : "off");1910    APPEND_STAT("domain_socket", "%s",1911                settings.socketpath ? settings.socketpath : "NULL");1912    APPEND_STAT("umask", "%o", settings.access);1913    APPEND_STAT("shutdown_command", "%s",1914                settings.shutdown_command ? "yes" : "no");1915    APPEND_STAT("growth_factor", "%.2f", settings.factor);1916    APPEND_STAT("chunk_size", "%d", settings.chunk_size);1917    APPEND_STAT("num_threads", "%d", settings.num_threads);1918    APPEND_STAT("num_threads_per_udp", "%d", settings.num_threads_per_udp);1919    APPEND_STAT("stat_key_prefix", "%c", settings.prefix_delimiter);1920    APPEND_STAT("detail_enabled", "%s",1921                settings.detail_enabled ? "yes" : "no");1922    APPEND_STAT("reqs_per_event", "%d", settings.reqs_per_event);1923    APPEND_STAT("cas_enabled", "%s", settings.use_cas ? "yes" : "no");1924    APPEND_STAT("tcp_backlog", "%d", settings.backlog);1925    APPEND_STAT("binding_protocol", "%s",1926                prot_text(settings.binding_protocol));1927    APPEND_STAT("auth_enabled_sasl", "%s", settings.sasl ? "yes" : "no");1928    APPEND_STAT("auth_enabled_ascii", "%s", settings.auth_file ? settings.auth_file : "no");1929    APPEND_STAT("item_size_max", "%d", settings.item_size_max);1930    APPEND_STAT("maxconns_fast", "%s", settings.maxconns_fast ? "yes" : "no");1931    APPEND_STAT("hashpower_init", "%d", settings.hashpower_init);1932    APPEND_STAT("slab_reassign", "%s", settings.slab_reassign ? "yes" : "no");1933    APPEND_STAT("slab_automove", "%d", settings.slab_automove);1934    APPEND_STAT("slab_automove_ratio", "%.2f", settings.slab_automove_ratio);1935    APPEND_STAT("slab_automove_window", "%u", settings.slab_automove_window);1936    APPEND_STAT("slab_chunk_max", "%d", settings.slab_chunk_size_max);1937    APPEND_STAT("lru_crawler", "%s", settings.lru_crawler ? "yes" : "no");1938    APPEND_STAT("lru_crawler_sleep", "%d", settings.lru_crawler_sleep);1939    APPEND_STAT("lru_crawler_tocrawl", "%lu", (unsigned long)settings.lru_crawler_tocrawl);1940    APPEND_STAT("tail_repair_time", "%d", settings.tail_repair_time);1941    APPEND_STAT("flush_enabled", "%s", settings.flush_enabled ? "yes" : "no");1942    APPEND_STAT("dump_enabled", "%s", settings.dump_enabled ? "yes" : "no");1943    APPEND_STAT("hash_algorithm", "%s", settings.hash_algorithm);1944    APPEND_STAT("lru_maintainer_thread", "%s", settings.lru_maintainer_thread ? "yes" : "no");1945    APPEND_STAT("lru_segmented", "%s", settings.lru_segmented ? "yes" : "no");1946    APPEND_STAT("hot_lru_pct", "%d", settings.hot_lru_pct);1947    APPEND_STAT("warm_lru_pct", "%d", settings.warm_lru_pct);1948    APPEND_STAT("hot_max_factor", "%.2f", settings.hot_max_factor);1949    APPEND_STAT("warm_max_factor", "%.2f", settings.warm_max_factor);1950    APPEND_STAT("temp_lru", "%s", settings.temp_lru ? "yes" : "no");1951    APPEND_STAT("temporary_ttl", "%u", settings.temporary_ttl);1952    APPEND_STAT("idle_timeout", "%d", settings.idle_timeout);1953    APPEND_STAT("watcher_logbuf_size", "%u", settings.logger_watcher_buf_size);1954    APPEND_STAT("worker_logbuf_size", "%u", settings.logger_buf_size);1955    APPEND_STAT("read_buf_mem_limit", "%u", settings.read_buf_mem_limit);1956    APPEND_STAT("track_sizes", "%s", item_stats_sizes_status() ? "yes" : "no");1957    APPEND_STAT("inline_ascii_response", "%s", "no"); // setting is dead, cannot be yes.1958#ifdef HAVE_DROP_PRIVILEGES1959    APPEND_STAT("drop_privileges", "%s", settings.drop_privileges ? "yes" : "no");1960#endif1961#ifdef EXTSTORE1962    APPEND_STAT("ext_item_size", "%u", settings.ext_item_size);1963    APPEND_STAT("ext_item_age", "%u", settings.ext_item_age);1964    APPEND_STAT("ext_low_ttl", "%u", settings.ext_low_ttl);1965    APPEND_STAT("ext_recache_rate", "%u", settings.ext_recache_rate);1966    APPEND_STAT("ext_wbuf_size", "%u", settings.ext_wbuf_size);1967    APPEND_STAT("ext_compact_under", "%u", settings.ext_compact_under);1968    APPEND_STAT("ext_drop_under", "%u", settings.ext_drop_under);1969    APPEND_STAT("ext_max_sleep", "%u", settings.ext_max_sleep);1970    APPEND_STAT("ext_max_frag", "%.2f", settings.ext_max_frag);1971    APPEND_STAT("slab_automove_freeratio", "%.3f", settings.slab_automove_freeratio);1972    APPEND_STAT("ext_drop_unread", "%s", settings.ext_drop_unread ? "yes" : "no");1973#endif1974#ifdef TLS1975    APPEND_STAT("ssl_enabled", "%s", settings.ssl_enabled ? "yes" : "no");1976    APPEND_STAT("ssl_chain_cert", "%s", settings.ssl_chain_cert);1977    APPEND_STAT("ssl_key", "%s", settings.ssl_key);1978    APPEND_STAT("ssl_verify_mode", "%d", settings.ssl_verify_mode);1979    APPEND_STAT("ssl_keyformat", "%d", settings.ssl_keyformat);1980    APPEND_STAT("ssl_ciphers", "%s", settings.ssl_ciphers ? settings.ssl_ciphers : "NULL");1981    APPEND_STAT("ssl_ca_cert", "%s", settings.ssl_ca_cert ? settings.ssl_ca_cert : "NULL");1982    APPEND_STAT("ssl_wbuf_size", "%u", settings.ssl_wbuf_size);1983    APPEND_STAT("ssl_session_cache", "%s", settings.ssl_session_cache ? "yes" : "no");1984    APPEND_STAT("ssl_kernel_tls", "%s", settings.ssl_kernel_tls ? "yes" : "no");1985    APPEND_STAT("ssl_min_version", "%s", ssl_proto_text(settings.ssl_min_version));1986#endif1987#ifdef PROXY1988    APPEND_STAT("proxy_enabled", "%s", settings.proxy_enabled ? "yes" : "no");1989    APPEND_STAT("proxy_uring_enabled", "%s", settings.proxy_uring ? "yes" : "no");1990#endif1991    APPEND_STAT("num_napi_ids", "%d", settings.num_napi_ids);1992    APPEND_STAT("memory_file", "%s", settings.memory_file);1993    APPEND_STAT("client_flags_size", "%d", sizeof(client_flags_t));1994}19951996static int nz_strcmp(int nzlength, const char *nz, const char *z) {1997    int zlength=strlen(z);1998    return (zlength == nzlength) && (strncmp(nz, z, zlength) == 0) ? 0 : -1;1999}

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.