ljr/wcmtools/memcached/memcached.c

1555 lines
46 KiB
C
Executable File

/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
* memcached - memory caching daemon
*
* http://www.danga.com/memcached/
*
* Copyright 2003 Danga Interactive, Inc. All rights reserved.
*
* Use and distribution licensed under the BSD license. See
* the LICENSE file for full text.
*
* Authors:
* Anatoly Vorobey <mellon@pobox.com>
* Brad Fitzpatrick <brad@danga.com>
*
* $Id: memcached.c,v 1.57 2005/05/25 18:40:19 bradfitz Exp $
*/
#include "config.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/signal.h>
#include <sys/resource.h>
/* some POSIX systems need the following definition
* to get mlockall flags out of sys/mman.h. */
#ifndef _P1003_1B_VISIBLE
#define _P1003_1B_VISIBLE
#endif
#include <pwd.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <errno.h>
#include <time.h>
#include <event.h>
#include <assert.h>
#ifdef HAVE_MALLOC_H
#include <malloc.h>
#endif
#include "memcached.h"
struct stats stats;
struct settings settings;
static item **todelete = 0;
static int delcurr;
static int deltotal;
time_t realtime(time_t exptime) {
time_t now;
/* no. of seconds in 30 days - largest possible delta exptime */
#define REALTIME_MAXDELTA 60*60*24*30
if (exptime == 0) return 0; /* 0 means never expire */
if (exptime > REALTIME_MAXDELTA)
return exptime;
else {
now = time(0);
return exptime + now;
}
}
void stats_init(void) {
stats.curr_items = stats.total_items = stats.curr_conns = stats.total_conns = stats.conn_structs = 0;
stats.get_cmds = stats.set_cmds = stats.get_hits = stats.get_misses = 0;
stats.curr_bytes = stats.bytes_read = stats.bytes_written = 0;
stats.started = time(0);
}
void stats_reset(void) {
stats.total_items = stats.total_conns = 0;
stats.get_cmds = stats.set_cmds = stats.get_hits = stats.get_misses = 0;
stats.bytes_read = stats.bytes_written = 0;
}
void settings_init(void) {
settings.port = 11211;
settings.interface.s_addr = htonl(INADDR_ANY);
settings.maxbytes = 64*1024*1024; /* default is 64MB */
settings.maxconns = 1024; /* to limit connections-related memory to about 5MB */
settings.verbose = 0;
settings.oldest_live = 0;
settings.evict_to_free = 1; /* push old items out of cache when memory runs out */
}
conn **freeconns;
int freetotal;
int freecurr;
void set_cork (conn *c, int val) {
if (c->is_corked == val) return;
c->is_corked = val;
#ifdef TCP_NOPUSH
setsockopt(c->sfd, IPPROTO_TCP, TCP_NOPUSH, &val, sizeof(val));
#endif
}
void conn_init(void) {
freetotal = 200;
freecurr = 0;
freeconns = (conn **)malloc(sizeof (conn *)*freetotal);
return;
}
conn *conn_new(int sfd, int init_state, int event_flags) {
conn *c;
/* do we have a free conn structure from a previous close? */
if (freecurr > 0) {
c = freeconns[--freecurr];
} else { /* allocate a new one */
if (!(c = (conn *)malloc(sizeof(conn)))) {
perror("malloc()");
return 0;
}
c->rbuf = c->wbuf = 0;
c->ilist = 0;
c->rbuf = (char *) malloc(DATA_BUFFER_SIZE);
c->wbuf = (char *) malloc(DATA_BUFFER_SIZE);
c->ilist = (item **) malloc(sizeof(item *)*200);
if (c->rbuf == 0 || c->wbuf == 0 || c->ilist == 0) {
if (c->rbuf != 0) free(c->rbuf);
if (c->wbuf != 0) free(c->wbuf);
if (c->ilist !=0) free(c->ilist);
free(c);
perror("malloc()");
return 0;
}
c->rsize = c->wsize = DATA_BUFFER_SIZE;
c->isize = 200;
stats.conn_structs++;
}
if (settings.verbose > 1) {
if (init_state == conn_listening)
fprintf(stderr, "<%d server listening\n", sfd);
else
fprintf(stderr, "<%d new client connection\n", sfd);
}
c->sfd = sfd;
c->state = init_state;
c->rlbytes = 0;
c->rbytes = c->wbytes = 0;
c->wcurr = c->wbuf;
c->rcurr = c->rbuf;
c->icurr = c->ilist;
c->ileft = 0;
c->iptr = c->ibuf;
c->ibytes = 0;
c->write_and_go = conn_read;
c->write_and_free = 0;
c->item = 0;
c->is_corked = 0;
event_set(&c->event, sfd, event_flags, event_handler, (void *)c);
c->ev_flags = event_flags;
if (event_add(&c->event, 0) == -1) {
if (freecurr < freetotal) {
freeconns[freecurr++] = c;
} else {
free (c->rbuf);
free (c->wbuf);
free (c->ilist);
free (c);
}
return 0;
}
stats.curr_conns++;
stats.total_conns++;
return c;
}
void conn_close(conn *c) {
/* delete the event, the socket and the conn */
event_del(&c->event);
if (settings.verbose > 1)
fprintf(stderr, "<%d connection closed.\n", c->sfd);
close(c->sfd);
if (c->item) {
item_free(c->item);
}
if (c->ileft) {
for (; c->ileft > 0; c->ileft--,c->icurr++) {
item_remove(*(c->icurr));
}
}
if (c->write_and_free) {
free(c->write_and_free);
}
/* if we have enough space in the free connections array, put the structure there */
if (freecurr < freetotal) {
freeconns[freecurr++] = c;
} else {
/* try to enlarge free connections array */
conn **new_freeconns = realloc(freeconns, sizeof(conn *)*freetotal*2);
if (new_freeconns) {
freetotal *= 2;
freeconns = new_freeconns;
freeconns[freecurr++] = c;
} else {
free(c->rbuf);
free(c->wbuf);
free(c->ilist);
free(c);
}
}
stats.curr_conns--;
return;
}
void out_string(conn *c, char *str) {
int len;
if (settings.verbose > 1)
fprintf(stderr, ">%d %s\n", c->sfd, str);
len = strlen(str);
if (len + 2 > c->wsize) {
/* ought to be always enough. just fail for simplicity */
str = "SERVER_ERROR output line too long";
len = strlen(str);
}
strcpy(c->wbuf, str);
strcat(c->wbuf, "\r\n");
c->wbytes = len + 2;
c->wcurr = c->wbuf;
c->state = conn_write;
c->write_and_go = conn_read;
return;
}
/*
* we get here after reading the value in set/add/replace commands. The command
* has been stored in c->item_comm, and the item is ready in c->item.
*/
void complete_nread(conn *c) {
item *it = c->item;
int comm = c->item_comm;
item *old_it;
time_t now = time(0);
stats.set_cmds++;
while(1) {
if (strncmp(ITEM_data(it) + it->nbytes - 2, "\r\n", 2) != 0) {
out_string(c, "CLIENT_ERROR bad data chunk");
break;
}
old_it = assoc_find(ITEM_key(it));
if (old_it && settings.oldest_live &&
old_it->time <= settings.oldest_live) {
item_unlink(old_it);
old_it = 0;
}
if (old_it && old_it->exptime && old_it->exptime < now) {
item_unlink(old_it);
old_it = 0;
}
if (old_it && comm==NREAD_ADD) {
item_update(old_it);
out_string(c, "NOT_STORED");
break;
}
if (!old_it && comm == NREAD_REPLACE) {
out_string(c, "NOT_STORED");
break;
}
if (old_it && (old_it->it_flags & ITEM_DELETED) && (comm == NREAD_REPLACE || comm == NREAD_ADD)) {
out_string(c, "NOT_STORED");
break;
}
if (old_it) {
item_replace(old_it, it);
} else item_link(it);
c->item = 0;
out_string(c, "STORED");
return;
}
item_free(it);
c->item = 0;
return;
}
void process_stat(conn *c, char *command) {
time_t now = time(0);
if (strcmp(command, "stats") == 0) {
char temp[1024];
pid_t pid = getpid();
char *pos = temp;
struct rusage usage;
getrusage(RUSAGE_SELF, &usage);
pos += sprintf(pos, "STAT pid %u\r\n", pid);
pos += sprintf(pos, "STAT uptime %lu\r\n", now - stats.started);
pos += sprintf(pos, "STAT time %ld\r\n", now);
pos += sprintf(pos, "STAT version " VERSION "\r\n");
pos += sprintf(pos, "STAT rusage_user %ld.%06ld\r\n", usage.ru_utime.tv_sec, usage.ru_utime.tv_usec);
pos += sprintf(pos, "STAT rusage_system %ld.%06ld\r\n", usage.ru_stime.tv_sec, usage.ru_stime.tv_usec);
pos += sprintf(pos, "STAT curr_items %u\r\n", stats.curr_items);
pos += sprintf(pos, "STAT total_items %u\r\n", stats.total_items);
pos += sprintf(pos, "STAT bytes %llu\r\n", stats.curr_bytes);
pos += sprintf(pos, "STAT curr_connections %u\r\n", stats.curr_conns - 1); /* ignore listening conn */
pos += sprintf(pos, "STAT total_connections %u\r\n", stats.total_conns);
pos += sprintf(pos, "STAT connection_structures %u\r\n", stats.conn_structs);
pos += sprintf(pos, "STAT cmd_get %u\r\n", stats.get_cmds);
pos += sprintf(pos, "STAT cmd_set %u\r\n", stats.set_cmds);
pos += sprintf(pos, "STAT get_hits %u\r\n", stats.get_hits);
pos += sprintf(pos, "STAT get_misses %u\r\n", stats.get_misses);
pos += sprintf(pos, "STAT bytes_read %llu\r\n", stats.bytes_read);
pos += sprintf(pos, "STAT bytes_written %llu\r\n", stats.bytes_written);
pos += sprintf(pos, "STAT limit_maxbytes %u\r\n", settings.maxbytes);
pos += sprintf(pos, "END");
out_string(c, temp);
return;
}
if (strcmp(command, "stats reset") == 0) {
stats_reset();
out_string(c, "RESET");
return;
}
#ifdef HAVE_MALLOC_H
#ifdef HAVE_STRUCT_MALLINFO
if (strcmp(command, "stats malloc") == 0) {
char temp[512];
struct mallinfo info;
char *pos = temp;
info = mallinfo();
pos += sprintf(pos, "STAT arena_size %d\r\n", info.arena);
pos += sprintf(pos, "STAT free_chunks %d\r\n", info.ordblks);
pos += sprintf(pos, "STAT fastbin_blocks %d\r\n", info.smblks);
pos += sprintf(pos, "STAT mmapped_regions %d\r\n", info.hblks);
pos += sprintf(pos, "STAT mmapped_space %d\r\n", info.hblkhd);
pos += sprintf(pos, "STAT max_total_alloc %d\r\n", info.usmblks);
pos += sprintf(pos, "STAT fastbin_space %d\r\n", info.fsmblks);
pos += sprintf(pos, "STAT total_alloc %d\r\n", info.uordblks);
pos += sprintf(pos, "STAT total_free %d\r\n", info.fordblks);
pos += sprintf(pos, "STAT releasable_space %d\r\nEND", info.keepcost);
out_string(c, temp);
return;
}
#endif /* HAVE_STRUCT_MALLINFO */
#endif /* HAVE_MALLOC_H */
if (strcmp(command, "stats maps") == 0) {
char *wbuf;
int wsize = 8192; /* should be enough */
int fd;
int res;
wbuf = (char *)malloc(wsize);
if (wbuf == 0) {
out_string(c, "SERVER_ERROR out of memory");
return;
}
fd = open("/proc/self/maps", O_RDONLY);
if (fd == -1) {
out_string(c, "SERVER_ERROR cannot open the maps file");
free(wbuf);
return;
}
res = read(fd, wbuf, wsize - 6); /* 6 = END\r\n\0 */
if (res == wsize - 6) {
out_string(c, "SERVER_ERROR buffer overflow");
free(wbuf); close(fd);
return;
}
if (res == 0 || res == -1) {
out_string(c, "SERVER_ERROR can't read the maps file");
free(wbuf); close(fd);
return;
}
strcpy(wbuf + res, "END\r\n");
c->write_and_free=wbuf;
c->wcurr=wbuf;
c->wbytes = res + 6;
c->state = conn_write;
c->write_and_go = conn_read;
close(fd);
return;
}
if (strncmp(command, "stats cachedump", 15) == 0) {
char *buf;
unsigned int bytes, id, limit = 0;
char *start = command + 15;
if (sscanf(start, "%u %u\r\n", &id, &limit) < 1) {
out_string(c, "CLIENT_ERROR bad command line");
return;
}
buf = item_cachedump(id, limit, &bytes);
if (buf == 0) {
out_string(c, "SERVER_ERROR out of memory");
return;
}
c->write_and_free = buf;
c->wcurr = buf;
c->wbytes = bytes;
c->state = conn_write;
c->write_and_go = conn_read;
return;
}
if (strcmp(command, "stats slabs")==0) {
int bytes = 0;
char *buf = slabs_stats(&bytes);
if (!buf) {
out_string(c, "SERVER_ERROR out of memory");
return;
}
c->write_and_free = buf;
c->wcurr = buf;
c->wbytes = bytes;
c->state = conn_write;
c->write_and_go = conn_read;
return;
}
if (strcmp(command, "stats items")==0) {
char buffer[4096];
item_stats(buffer, 4096);
out_string(c, buffer);
return;
}
if (strcmp(command, "stats sizes")==0) {
int bytes = 0;
char *buf = item_stats_sizes(&bytes);
if (! buf) {
out_string(c, "SERVER_ERROR out of memory");
return;
}
c->write_and_free = buf;
c->wcurr = buf;
c->wbytes = bytes;
c->state = conn_write;
c->write_and_go = conn_read;
return;
}
out_string(c, "ERROR");
}
void process_command(conn *c, char *command) {
int comm = 0;
int incr = 0;
/*
* for commands set/add/replace, we build an item and read the data
* directly into it, then continue in nread_complete().
*/
if (settings.verbose > 1)
fprintf(stderr, "<%d %s\n", c->sfd, command);
/* All incoming commands will require a response, so we cork at the beginning,
and uncork at the very end (usually by means of out_string) */
set_cork(c, 1);
if ((strncmp(command, "add ", 4) == 0 && (comm = NREAD_ADD)) ||
(strncmp(command, "set ", 4) == 0 && (comm = NREAD_SET)) ||
(strncmp(command, "replace ", 8) == 0 && (comm = NREAD_REPLACE))) {
char key[251];
int flags;
time_t expire;
int len, res;
item *it;
res = sscanf(command, "%*s %250s %u %ld %d\n", key, &flags, &expire, &len);
if (res!=4 || strlen(key)==0 ) {
out_string(c, "CLIENT_ERROR bad command line format");
return;
}
expire = realtime(expire);
it = item_alloc(key, flags, expire, len+2);
if (it == 0) {
out_string(c, "SERVER_ERROR out of memory");
/* swallow the data line */
c->write_and_go = conn_swallow;
c->sbytes = len+2;
return;
}
c->item_comm = comm;
c->item = it;
c->rcurr = ITEM_data(it);
c->rlbytes = it->nbytes;
c->state = conn_nread;
return;
}
if ((strncmp(command, "incr ", 5) == 0 && (incr = 1)) ||
(strncmp(command, "decr ", 5) == 0)) {
char temp[32];
unsigned int value;
item *it;
unsigned int delta;
char key[251];
int res;
char *ptr;
time_t now = time(0);
res = sscanf(command, "%*s %250s %u\n", key, &delta);
if (res!=2 || strlen(key)==0 ) {
out_string(c, "CLIENT_ERROR bad command line format");
return;
}
it = assoc_find(key);
if (it && (it->it_flags & ITEM_DELETED)) {
it = 0;
}
if (it && it->exptime && it->exptime < now) {
item_unlink(it);
it = 0;
}
if (!it) {
out_string(c, "NOT_FOUND");
return;
}
ptr = ITEM_data(it);
while (*ptr && (*ptr<'0' && *ptr>'9')) ptr++;
value = atoi(ptr);
if (incr)
value+=delta;
else {
if (delta >= value) value = 0;
else value-=delta;
}
sprintf(temp, "%u", value);
res = strlen(temp);
if (res + 2 > it->nbytes) { /* need to realloc */
item *new_it;
new_it = item_alloc(ITEM_key(it), it->flags, it->exptime, res + 2 );
if (new_it == 0) {
out_string(c, "SERVER_ERROR out of memory");
return;
}
memcpy(ITEM_data(new_it), temp, res);
memcpy(ITEM_data(new_it) + res, "\r\n", 2);
item_replace(it, new_it);
} else { /* replace in-place */
memcpy(ITEM_data(it), temp, res);
memset(ITEM_data(it) + res, ' ', it->nbytes-res-2);
}
out_string(c, temp);
return;
}
if (strncmp(command, "get ", 4) == 0) {
char *start = command + 4;
char key[251];
int next;
int i = 0;
item *it;
time_t now = time(0);
while(sscanf(start, " %250s%n", key, &next) >= 1) {
start+=next;
stats.get_cmds++;
it = assoc_find(key);
if (it && (it->it_flags & ITEM_DELETED)) {
it = 0;
}
if (settings.oldest_live && it &&
it->time <= settings.oldest_live) {
item_unlink(it);
it = 0;
}
if (it && it->exptime && it->exptime < now) {
item_unlink(it);
it = 0;
}
if (it) {
if (i >= c->isize) {
item **new_list = realloc(c->ilist, sizeof(item *)*c->isize*2);
if (new_list) {
c->isize *= 2;
c->ilist = new_list;
} else break;
}
stats.get_hits++;
it->refcount++;
item_update(it);
*(c->ilist + i) = it;
i++;
} else stats.get_misses++;
}
c->icurr = c->ilist;
c->ileft = i;
if (c->ileft) {
c->ipart = 0;
c->state = conn_mwrite;
c->ibytes = 0;
return;
} else {
out_string(c, "END");
return;
}
}
if (strncmp(command, "delete ", 7) == 0) {
char key[251];
item *it;
int res;
time_t exptime = 0;
res = sscanf(command, "%*s %250s %ld", key, &exptime);
it = assoc_find(key);
if (!it) {
out_string(c, "NOT_FOUND");
return;
}
if (exptime == 0) {
item_unlink(it);
out_string(c, "DELETED");
return;
}
if (delcurr >= deltotal) {
item **new_delete = realloc(todelete, sizeof(item *) * deltotal * 2);
if (new_delete) {
todelete = new_delete;
deltotal *= 2;
} else {
/*
* can't delete it immediately, user wants a delay,
* but we ran out of memory for the delete queue
*/
out_string(c, "SERVER_ERROR out of memory");
return;
}
}
exptime = realtime(exptime);
it->refcount++;
/* use its expiration time as its deletion time now */
it->exptime = exptime;
it->it_flags |= ITEM_DELETED;
todelete[delcurr++] = it;
out_string(c, "DELETED");
return;
}
if (strncmp(command, "stats", 5) == 0) {
process_stat(c, command);
return;
}
if (strcmp(command, "flush_all") == 0) {
settings.oldest_live = time(0);
out_string(c, "OK");
return;
}
if (strcmp(command, "version") == 0) {
out_string(c, "VERSION " VERSION);
return;
}
if (strcmp(command, "quit") == 0) {
c->state = conn_closing;
return;
}
if (strncmp(command, "slabs reassign ", 15) == 0) {
int src, dst;
char *start = command+15;
if (sscanf(start, "%u %u\r\n", &src, &dst) == 2) {
int rv = slabs_reassign(src, dst);
if (rv == 1) {
out_string(c, "DONE");
return;
}
if (rv == 0) {
out_string(c, "CANT");
return;
}
if (rv == -1) {
out_string(c, "BUSY");
return;
}
}
out_string(c, "CLIENT_ERROR bogus command");
return;
}
out_string(c, "ERROR");
return;
}
/*
* if we have a complete line in the buffer, process it and move whatever
* remains in the buffer to its beginning.
*/
int try_read_command(conn *c) {
char *el, *cont;
if (!c->rbytes)
return 0;
el = memchr(c->rbuf, '\n', c->rbytes);
if (!el)
return 0;
cont = el + 1;
if (el - c->rbuf > 1 && *(el - 1) == '\r') {
el--;
}
*el = '\0';
process_command(c, c->rbuf);
if (cont - c->rbuf < c->rbytes) { /* more stuff in the buffer */
memmove(c->rbuf, cont, c->rbytes - (cont - c->rbuf));
}
c->rbytes -= (cont - c->rbuf);
return 1;
}
/*
* read from network as much as we can, handle buffer overflow and connection
* close.
* return 0 if there's nothing to read on the first read.
*/
int try_read_network(conn *c) {
int gotdata = 0;
int res;
while (1) {
if (c->rbytes >= c->rsize) {
char *new_rbuf = realloc(c->rbuf, c->rsize*2);
if (!new_rbuf) {
if (settings.verbose > 0)
fprintf(stderr, "Couldn't realloc input buffer\n");
c->rbytes = 0; /* ignore what we read */
out_string(c, "SERVER_ERROR out of memory");
c->write_and_go = conn_closing;
return 1;
}
c->rbuf = new_rbuf; c->rsize *= 2;
}
res = read(c->sfd, c->rbuf + c->rbytes, c->rsize - c->rbytes);
if (res > 0) {
stats.bytes_read += res;
gotdata = 1;
c->rbytes += res;
continue;
}
if (res == 0) {
/* connection closed */
c->state = conn_closing;
return 1;
}
if (res == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) break;
else return 0;
}
}
return gotdata;
}
int update_event(conn *c, int new_flags) {
if (c->ev_flags == new_flags)
return 1;
if (event_del(&c->event) == -1) return 0;
event_set(&c->event, c->sfd, new_flags, event_handler, (void *)c);
c->ev_flags = new_flags;
if (event_add(&c->event, 0) == -1) return 0;
return 1;
}
void drive_machine(conn *c) {
int exit = 0;
int sfd, flags = 1;
socklen_t addrlen;
struct sockaddr addr;
conn *newc;
int res;
while (!exit) {
/* printf("state %d\n", c->state);*/
switch(c->state) {
case conn_listening:
addrlen = sizeof(addr);
if ((sfd = accept(c->sfd, &addr, &addrlen)) == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
exit = 1;
break;
} else {
perror("accept()");
}
break;
}
if ((flags = fcntl(sfd, F_GETFL, 0)) < 0 ||
fcntl(sfd, F_SETFL, flags | O_NONBLOCK) < 0) {
perror("setting O_NONBLOCK");
close(sfd);
break;
}
newc = conn_new(sfd, conn_read, EV_READ | EV_PERSIST);
if (!newc) {
if (settings.verbose > 0)
fprintf(stderr, "couldn't create new connection\n");
close(sfd);
break;
}
break;
case conn_read:
if (try_read_command(c)) {
continue;
}
if (try_read_network(c)) {
continue;
}
/* we have no command line and no data to read from network */
if (!update_event(c, EV_READ | EV_PERSIST)) {
if (settings.verbose > 0)
fprintf(stderr, "Couldn't update event\n");
c->state = conn_closing;
break;
}
exit = 1;
break;
case conn_nread:
/* we are reading rlbytes into rcurr; */
if (c->rlbytes == 0) {
complete_nread(c);
break;
}
/* first check if we have leftovers in the conn_read buffer */
if (c->rbytes > 0) {
int tocopy = c->rbytes > c->rlbytes ? c->rlbytes : c->rbytes;
memcpy(c->rcurr, c->rbuf, tocopy);
c->rcurr += tocopy;
c->rlbytes -= tocopy;
if (c->rbytes > tocopy) {
memmove(c->rbuf, c->rbuf+tocopy, c->rbytes - tocopy);
}
c->rbytes -= tocopy;
break;
}
/* now try reading from the socket */
res = read(c->sfd, c->rcurr, c->rlbytes);
if (res > 0) {
stats.bytes_read += res;
c->rcurr += res;
c->rlbytes -= res;
break;
}
if (res == 0) { /* end of stream */
c->state = conn_closing;
break;
}
if (res == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
if (!update_event(c, EV_READ | EV_PERSIST)) {
if (settings.verbose > 0)
fprintf(stderr, "Couldn't update event\n");
c->state = conn_closing;
break;
}
exit = 1;
break;
}
/* otherwise we have a real error, on which we close the connection */
if (settings.verbose > 0)
fprintf(stderr, "Failed to read, and not due to blocking\n");
c->state = conn_closing;
break;
case conn_swallow:
/* we are reading sbytes and throwing them away */
if (c->sbytes == 0) {
c->state = conn_read;
break;
}
/* first check if we have leftovers in the conn_read buffer */
if (c->rbytes > 0) {
int tocopy = c->rbytes > c->sbytes ? c->sbytes : c->rbytes;
c->sbytes -= tocopy;
if (c->rbytes > tocopy) {
memmove(c->rbuf, c->rbuf+tocopy, c->rbytes - tocopy);
}
c->rbytes -= tocopy;
break;
}
/* now try reading from the socket */
res = read(c->sfd, c->rbuf, c->rsize > c->sbytes ? c->sbytes : c->rsize);
if (res > 0) {
stats.bytes_read += res;
c->sbytes -= res;
break;
}
if (res == 0) { /* end of stream */
c->state = conn_closing;
break;
}
if (res == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
if (!update_event(c, EV_READ | EV_PERSIST)) {
if (settings.verbose > 0)
fprintf(stderr, "Couldn't update event\n");
c->state = conn_closing;
break;
}
exit = 1;
break;
}
/* otherwise we have a real error, on which we close the connection */
if (settings.verbose > 0)
fprintf(stderr, "Failed to read, and not due to blocking\n");
c->state = conn_closing;
break;
case conn_write:
/* we are writing wbytes bytes starting from wcurr */
if (c->wbytes == 0) {
if (c->write_and_free) {
free(c->write_and_free);
c->write_and_free = 0;
}
c->state = c->write_and_go;
if (c->state == conn_read)
set_cork(c, 0);
break;
}
res = write(c->sfd, c->wcurr, c->wbytes);
if (res > 0) {
stats.bytes_written += res;
c->wcurr += res;
c->wbytes -= res;
break;
}
if (res == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
if (!update_event(c, EV_WRITE | EV_PERSIST)) {
if (settings.verbose > 0)
fprintf(stderr, "Couldn't update event\n");
c->state = conn_closing;
break;
}
exit = 1;
break;
}
/* if res==0 or res==-1 and error is not EAGAIN or EWOULDBLOCK,
we have a real error, on which we close the connection */
if (settings.verbose > 0)
fprintf(stderr, "Failed to write, and not due to blocking\n");
c->state = conn_closing;
break;
case conn_mwrite:
/*
* we're writing ibytes bytes from iptr. iptr alternates between
* ibuf, where we build a string "VALUE...", and ITEM_data(it) for the
* current item. When we finish a chunk, we choose the next one using
* ipart, which has the following semantics: 0 - start the loop, 1 -
* we finished ibuf, go to current ITEM_data(it); 2 - we finished ITEM_data(it),
* move to the next item and build its ibuf; 3 - we finished all items,
* write "END".
*/
if (c->ibytes > 0) {
res = write(c->sfd, c->iptr, c->ibytes);
if (res > 0) {
stats.bytes_written += res;
c->iptr += res;
c->ibytes -= res;
break;
}
if (res == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
if (!update_event(c, EV_WRITE | EV_PERSIST)) {
if (settings.verbose > 0)
fprintf(stderr, "Couldn't update event\n");
c->state = conn_closing;
break;
}
exit = 1;
break;
}
/* if res==0 or res==-1 and error is not EAGAIN or EWOULDBLOCK,
we have a real error, on which we close the connection */
if (settings.verbose > 0)
fprintf(stderr, "Failed to write, and not due to blocking\n");
c->state = conn_closing;
break;
} else {
item *it;
/* we finished a chunk, decide what to do next */
switch (c->ipart) {
case 1:
it = *(c->icurr);
assert((it->it_flags & ITEM_SLABBED) == 0);
c->iptr = ITEM_data(it);
c->ibytes = it->nbytes;
c->ipart = 2;
break;
case 2:
it = *(c->icurr);
item_remove(it);
c->ileft--;
if (c->ileft <= 0) {
c->ipart = 3;
break;
} else {
c->icurr++;
}
/* FALL THROUGH */
case 0:
it = *(c->icurr);
assert((it->it_flags & ITEM_SLABBED) == 0);
c->ibytes = sprintf(c->ibuf, "VALUE %s %u %u\r\n", ITEM_key(it), it->flags, it->nbytes - 2);
if (settings.verbose > 1)
fprintf(stderr, ">%d sending key %s\n", c->sfd, ITEM_key(it));
c->iptr = c->ibuf;
c->ipart = 1;
break;
case 3:
out_string(c, "END");
break;
}
}
break;
case conn_closing:
conn_close(c);
exit = 1;
break;
}
}
return;
}
void event_handler(int fd, short which, void *arg) {
conn *c;
c = (conn *)arg;
c->which = which;
/* sanity */
if (fd != c->sfd) {
if (settings.verbose > 0)
fprintf(stderr, "Catastrophic: event fd doesn't match conn fd!\n");
conn_close(c);
return;
}
/* do as much I/O as possible until we block */
drive_machine(c);
/* wait for next event */
return;
}
int new_socket(void) {
int sfd;
int flags;
if ((sfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket()");
return -1;
}
if ((flags = fcntl(sfd, F_GETFL, 0)) < 0 ||
fcntl(sfd, F_SETFL, flags | O_NONBLOCK) < 0) {
perror("setting O_NONBLOCK");
close(sfd);
return -1;
}
return sfd;
}
int server_socket(int port) {
int sfd;
struct linger ling = {0, 0};
struct sockaddr_in addr;
int flags =1;
if ((sfd = new_socket()) == -1) {
return -1;
}
setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &flags, sizeof(flags));
setsockopt(sfd, SOL_SOCKET, SO_KEEPALIVE, &flags, sizeof(flags));
setsockopt(sfd, SOL_SOCKET, SO_LINGER, &ling, sizeof(ling));
#if !defined(TCP_NOPUSH)
setsockopt(sfd, IPPROTO_TCP, TCP_NODELAY, &flags, sizeof(flags));
#endif
/*
* the memset call clears nonstandard fields in some impementations
* that otherwise mess things up.
*/
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr = settings.interface;
if (bind(sfd, (struct sockaddr *) &addr, sizeof(addr)) == -1) {
perror("bind()");
close(sfd);
return -1;
}
if (listen(sfd, 1024) == -1) {
perror("listen()");
close(sfd);
return -1;
}
return sfd;
}
/* invoke right before gdb is called, on assert */
void pre_gdb () {
int i = 0;
if(l_socket) close(l_socket);
for (i=3; i<=500; i++) close(i); /* so lame */
kill(getpid(), SIGABRT);
}
struct event deleteevent;
void delete_handler(int fd, short which, void *arg) {
struct timeval t;
static int initialized = 0;
if (initialized) {
/* some versions of libevent don't like deleting events that don't exist,
so only delete once we know this event has been added. */
evtimer_del(&deleteevent);
} else {
initialized = 1;
}
evtimer_set(&deleteevent, delete_handler, 0);
t.tv_sec = 5; t.tv_usec=0;
evtimer_add(&deleteevent, &t);
{
int i, j=0;
time_t now = time(0);
for (i=0; i<delcurr; i++) {
item *it = todelete[i];
if (it->exptime < now) {
assert(it->refcount > 0);
it->it_flags &= ~ITEM_DELETED;
item_unlink(it);
item_remove(it);
} else {
todelete[j++] = it;
}
}
delcurr = j;
}
return;
}
void usage(void) {
printf(PACKAGE " " VERSION "\n");
printf("-p <num> port number to listen on\n");
printf("-l <ip_addr> interface to listen on, default is INDRR_ANY\n");
printf("-d run as a daemon\n");
printf("-r maximize core file limit\n");
printf("-u <username> assume identity of <username> (only when run as root)\n");
printf("-m <num> max memory to use for items in megabytes, default is 64 MB\n");
printf("-M return error on memory exhausted (rather than removing items)\n");
printf("-c <num> max simultaneous connections, default is 1024\n");
printf("-k lock down all paged memory\n");
printf("-v verbose (print errors/warnings while in event loop)\n");
printf("-vv very verbose (also print client commands/reponses)\n");
printf("-h print this help and exit\n");
printf("-i print memcached and libevent license\n");
printf("-P <file> save PID in <file>, only used with -d option\n");
return;
}
void usage_license(void) {
printf(PACKAGE " " VERSION "\n\n");
printf(
"Copyright (c) 2003, Danga Interactive, Inc. <http://www.danga.com/>\n"
"All rights reserved.\n"
"\n"
"Redistribution and use in source and binary forms, with or without\n"
"modification, are permitted provided that the following conditions are\n"
"met:\n"
"\n"
" * Redistributions of source code must retain the above copyright\n"
"notice, this list of conditions and the following disclaimer.\n"
"\n"
" * Redistributions in binary form must reproduce the above\n"
"copyright notice, this list of conditions and the following disclaimer\n"
"in the documentation and/or other materials provided with the\n"
"distribution.\n"
"\n"
" * Neither the name of the Danga Interactive nor the names of its\n"
"contributors may be used to endorse or promote products derived from\n"
"this software without specific prior written permission.\n"
"\n"
"THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n"
"\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n"
"LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n"
"A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n"
"OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n"
"SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n"
"LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n"
"DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n"
"THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n"
"(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n"
"OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
"\n"
"\n"
"This product includes software developed by Niels Provos.\n"
"\n"
"[ libevent ]\n"
"\n"
"Copyright 2000-2003 Niels Provos <provos@citi.umich.edu>\n"
"All rights reserved.\n"
"\n"
"Redistribution and use in source and binary forms, with or without\n"
"modification, are permitted provided that the following conditions\n"
"are met:\n"
"1. Redistributions of source code must retain the above copyright\n"
" notice, this list of conditions and the following disclaimer.\n"
"2. Redistributions in binary form must reproduce the above copyright\n"
" notice, this list of conditions and the following disclaimer in the\n"
" documentation and/or other materials provided with the distribution.\n"
"3. All advertising materials mentioning features or use of this software\n"
" must display the following acknowledgement:\n"
" This product includes software developed by Niels Provos.\n"
"4. The name of the author may not be used to endorse or promote products\n"
" derived from this software without specific prior written permission.\n"
"\n"
"THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR\n"
"IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\n"
"OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n"
"IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,\n"
"INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT\n"
"NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n"
"DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n"
"THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n"
"(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF\n"
"THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
);
return;
}
void save_pid(pid_t pid,char *pid_file) {
FILE *fp;
if (!pid_file)
return;
if (!(fp = fopen(pid_file,"w"))) {
fprintf(stderr,"Could not open the pid file %s for writing\n",pid_file);
return;
}
fprintf(fp,"%ld\n",(long) pid);
if (fclose(fp) == -1) {
fprintf(stderr,"Could not close the pid file %s.\n",pid_file);
return;
}
}
void remove_pidfile(char *pid_file) {
if (!pid_file)
return;
if (unlink(pid_file)) {
fprintf(stderr,"Could not remove the pid file %s.\n",pid_file);
}
}
int l_socket=0;
int main (int argc, char **argv) {
int c;
conn *l_conn;
struct in_addr addr;
int lock_memory = 0;
int daemonize = 0;
int maxcore = 0;
char *username = 0;
struct passwd *pw;
struct sigaction sa;
struct rlimit rlim;
char *pid_file = NULL;
/* init settings */
settings_init();
/* set stderr non-buffering (for running under, say, daemontools) */
setbuf(stderr, NULL);
/* process arguments */
while ((c = getopt(argc, argv, "p:m:Mc:khirvdl:u:P:")) != -1) {
switch (c) {
case 'p':
settings.port = atoi(optarg);
break;
case 'm':
settings.maxbytes = atoi(optarg)*1024*1024;
break;
case 'M':
settings.evict_to_free = 0;
break;
case 'c':
settings.maxconns = atoi(optarg);
break;
case 'h':
usage();
exit(0);
case 'i':
usage_license();
exit(0);
case 'k':
lock_memory = 1;
break;
case 'v':
settings.verbose++;
break;
case 'l':
if (!inet_aton(optarg, &addr)) {
fprintf(stderr, "Illegal address: %s\n", optarg);
return 1;
} else {
settings.interface = addr;
}
break;
case 'd':
daemonize = 1;
break;
case 'r':
maxcore = 1;
break;
case 'u':
username = optarg;
break;
case 'P':
pid_file = optarg;
break;
default:
fprintf(stderr, "Illegal argument \"%c\"\n", c);
return 1;
}
}
if (maxcore) {
struct rlimit rlim_new;
/*
* First try raising to infinity; if that fails, try bringing
* the soft limit to the hard.
*/
if (getrlimit(RLIMIT_CORE, &rlim)==0) {
rlim_new.rlim_cur = rlim_new.rlim_max = RLIM_INFINITY;
if (setrlimit(RLIMIT_CORE, &rlim_new)!=0) {
/* failed. try raising just to the old max */
rlim_new.rlim_cur = rlim_new.rlim_max =
rlim.rlim_max;
(void) setrlimit(RLIMIT_CORE, &rlim_new);
}
}
/*
* getrlimit again to see what we ended up with. Only fail if
* the soft limit ends up 0, because then no core files will be
* created at all.
*/
if ((getrlimit(RLIMIT_CORE, &rlim)!=0) || rlim.rlim_cur==0) {
fprintf(stderr, "failed to ensure corefile creation\n");
exit(1);
}
}
/*
* If needed, increase rlimits to allow as many connections
* as needed.
*/
if (getrlimit(RLIMIT_NOFILE, &rlim) != 0) {
fprintf(stderr, "failed to getrlimit number of files\n");
exit(1);
} else {
int maxfiles = settings.maxconns;
if (rlim.rlim_cur < maxfiles)
rlim.rlim_cur = maxfiles + 3;
if (rlim.rlim_max < rlim.rlim_cur)
rlim.rlim_max = rlim.rlim_cur;
if (setrlimit(RLIMIT_NOFILE, &rlim) != 0) {
fprintf(stderr, "failed to set rlimit for open files. Try running as root or requesting smaller maxconns value.\n");
exit(1);
}
}
/*
* initialization order: first create the listening socket
* (may need root on low ports), then drop root if needed,
* then daemonise if needed, then init libevent (in some cases
* descriptors created by libevent wouldn't survive forking).
*/
/* create the listening socket and bind it */
l_socket = server_socket(settings.port);
if (l_socket == -1) {
fprintf(stderr, "failed to listen\n");
exit(1);
}
/* lose root privileges if we have them */
if (getuid()== 0 || geteuid()==0) {
if (username==0 || *username=='\0') {
fprintf(stderr, "can't run as root without the -u switch\n");
return 1;
}
if ((pw = getpwnam(username)) == 0) {
fprintf(stderr, "can't find the user %s to switch to\n", username);
return 1;
}
if (setgid(pw->pw_gid)<0 || setuid(pw->pw_uid)<0) {
fprintf(stderr, "failed to assume identity of user %s\n", username);
return 1;
}
}
/* daemonize if requested */
/* if we want to ensure our ability to dump core, don't chdir to / */
if (daemonize) {
int res;
res = daemon(maxcore, settings.verbose);
if (res == -1) {
fprintf(stderr, "failed to daemon() in order to daemonize\n");
return 1;
}
}
/* initialize other stuff */
item_init();
event_init();
stats_init();
assoc_init();
conn_init();
slabs_init(settings.maxbytes);
/* lock paged memory if needed */
if (lock_memory) {
#ifdef HAVE_MLOCKALL
mlockall(MCL_CURRENT | MCL_FUTURE);
#else
fprintf(stderr, "warning: mlockall() not supported on this platform. proceeding without.\n");
#endif
}
/*
* ignore SIGPIPE signals; we can use errno==EPIPE if we
* need that information
*/
sa.sa_handler = SIG_IGN;
sa.sa_flags = 0;
if (sigemptyset(&sa.sa_mask) == -1 ||
sigaction(SIGPIPE, &sa, 0) == -1) {
perror("failed to ignore SIGPIPE; sigaction");
exit(1);
}
/* create the initial listening connection */
if (!(l_conn = conn_new(l_socket, conn_listening, EV_READ | EV_PERSIST))) {
fprintf(stderr, "failed to create listening connection");
exit(1);
}
/* initialise deletion array and timer event */
deltotal = 200; delcurr = 0;
todelete = malloc(sizeof(item *)*deltotal);
delete_handler(0,0,0); /* sets up the event */
/* save the PID in if we're a daemon */
if (daemonize)
save_pid(getpid(),pid_file);
/* enter the loop */
event_loop(0);
/* remove the PID file if we're a daemon */
if (daemonize)
remove_pidfile(pid_file);
return 0;
}