/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include #include #include #define NC_CONF_PATH "conf/nutcracker.yml" #define NC_LOG_DEFAULT LOG_NOTICE #define NC_LOG_MIN LOG_EMERG #define NC_LOG_MAX LOG_PVERB #define NC_LOG_PATH NULL #define NC_STATS_PORT STATS_PORT #define NC_STATS_ADDR STATS_ADDR #define NC_STATS_INTERVAL STATS_INTERVAL #define NC_PID_FILE NULL #define NC_MBUF_SIZE MBUF_SIZE #define NC_MBUF_MIN_SIZE MBUF_MIN_SIZE #define NC_MBUF_MAX_SIZE MBUF_MAX_SIZE static int show_help; static int show_version; static int test_conf; static int daemonize; static int describe_stats; static struct option long_options[] = { { "help", no_argument, NULL, 'h' }, { "version", no_argument, NULL, 'V' }, { "test-conf", no_argument, NULL, 't' }, { "daemonize", no_argument, NULL, 'd' }, { "describe-stats", no_argument, NULL, 'D' }, { "verbose", required_argument, NULL, 'v' }, { "output", required_argument, NULL, 'o' }, { "conf-file", required_argument, NULL, 'c' }, { "stats-port", required_argument, NULL, 's' }, { "stats-interval", required_argument, NULL, 'i' }, { "stats-addr", required_argument, NULL, 'a' }, { "pid-file", required_argument, NULL, 'p' }, { "mbuf-size", required_argument, NULL, 'm' }, { NULL, 0, NULL, 0 } }; static char short_options[] = "hVtdDv:o:c:s:i:a:p:m:"; static rstatus_t nc_daemonize(int dump_core) { rstatus_t status; pid_t pid, sid; int fd; pid = fork(); switch (pid) { case -1: log_error("fork() failed: %s", strerror(errno)); return NC_ERROR; case 0: break; default: /* parent terminates */ _exit(0); } /* 1st child continues and becomes the session leader */ sid = setsid(); if (sid < 0) { log_error("setsid() failed: %s", strerror(errno)); return NC_ERROR; } if (signal(SIGHUP, SIG_IGN) == SIG_ERR) { log_error("signal(SIGHUP, SIG_IGN) failed: %s", strerror(errno)); return NC_ERROR; } pid = fork(); switch (pid) { case -1: log_error("fork() failed: %s", strerror(errno)); return NC_ERROR; case 0: break; default: /* 1st child terminates */ _exit(0); } /* 2nd child continues */ /* change working directory */ if (dump_core == 0) { status = chdir("/"); if (status < 0) { log_error("chdir(\"/\") failed: %s", strerror(errno)); return NC_ERROR; } } /* clear file mode creation mask */ umask(0); /* redirect stdin, stdout and stderr to "/dev/null" */ fd = open("/dev/null", O_RDWR); if (fd < 0) { log_error("open(\"/dev/null\") failed: %s", strerror(errno)); return NC_ERROR; } status = dup2(fd, STDIN_FILENO); if (status < 0) { log_error("dup2(%d, STDIN) failed: %s", fd, strerror(errno)); close(fd); return NC_ERROR; } status = dup2(fd, STDOUT_FILENO); if (status < 0) { log_error("dup2(%d, STDOUT) failed: %s", fd, strerror(errno)); close(fd); return NC_ERROR; } status = dup2(fd, STDERR_FILENO); if (status < 0) { log_error("dup2(%d, STDERR) failed: %s", fd, strerror(errno)); close(fd); return NC_ERROR; } if (fd > STDERR_FILENO) { status = close(fd); if (status < 0) { log_error("close(%d) failed: %s", fd, strerror(errno)); return NC_ERROR; } } return NC_OK; } static void nc_print_run(struct instance *nci) { int status; struct utsname name; status = uname(&name); if (status < 0) { loga("nutcracker-%s started on pid %d", NC_VERSION_STRING, nci->pid); } else { loga("nutcracker-%s built for %s %s %s started on pid %d", NC_VERSION_STRING, name.sysname, name.release, name.machine, nci->pid); } loga("run, rabbit run / dig that hole, forget the sun / " "and when at last the work is done / don't sit down / " "it's time to dig another one"); } static void nc_print_done(void) { loga("done, rabbit done"); } static void nc_show_usage(void) { log_stderr( "Usage: nutcracker [-?hVdDt] [-v verbosity level] [-o output file]" CRLF " [-c conf file] [-s stats port] [-a stats addr]" CRLF " [-i stats interval] [-p pid file] [-m mbuf size]" CRLF ""); log_stderr( "Options:" CRLF " -h, --help : this help" CRLF " -V, --version : show version and exit" CRLF " -t, --test-conf : test configuration for syntax errors and exit" CRLF " -d, --daemonize : run as a daemon" CRLF " -D, --describe-stats : print stats description and exit"); log_stderr( " -v, --verbose=N : set logging level (default: %d, min: %d, max: %d)" CRLF " -o, --output=S : set logging file (default: %s)" CRLF " -c, --conf-file=S : set configuration file (default: %s)" CRLF " -s, --stats-port=N : set stats monitoring port (default: %d)" CRLF " -a, --stats-addr=S : set stats monitoring ip (default: %s)" CRLF " -i, --stats-interval=N : set stats aggregation interval in msec (default: %d msec)" CRLF " -p, --pid-file=S : set pid file (default: %s)" CRLF " -m, --mbuf-size=N : set size of mbuf chunk in bytes (default: %d bytes)" CRLF "", NC_LOG_DEFAULT, NC_LOG_MIN, NC_LOG_MAX, NC_LOG_PATH != NULL ? NC_LOG_PATH : "stderr", NC_CONF_PATH, NC_STATS_PORT, NC_STATS_ADDR, NC_STATS_INTERVAL, NC_PID_FILE != NULL ? NC_PID_FILE : "off", NC_MBUF_SIZE); } static rstatus_t nc_create_pidfile(struct instance *nci) { char pid[NC_UINTMAX_MAXLEN]; int fd, pid_len; ssize_t n; fd = open(nci->pid_filename, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd < 0) { log_error("opening pid file '%s' failed: %s", nci->pid_filename, strerror(errno)); return NC_ERROR; } nci->pidfile = 1; pid_len = nc_snprintf(pid, NC_UINTMAX_MAXLEN, "%d", nci->pid); n = nc_write(fd, pid, pid_len); if (n < 0) { log_error("write to pid file '%s' failed: %s", nci->pid_filename, strerror(errno)); return NC_ERROR; } close(fd); return NC_OK; } static void nc_remove_pidfile(struct instance *nci) { int status; status = unlink(nci->pid_filename); if (status < 0) { log_error("unlink of pid file '%s' failed, ignored: %s", nci->pid_filename, strerror(errno)); } } static void nc_set_default_options(struct instance *nci) { int status; nci->ctx = NULL; nci->log_level = NC_LOG_DEFAULT; nci->log_filename = NC_LOG_PATH; nci->conf_filename = NC_CONF_PATH; nci->stats_port = NC_STATS_PORT; nci->stats_addr = NC_STATS_ADDR; nci->stats_interval = NC_STATS_INTERVAL; status = nc_gethostname(nci->hostname, NC_MAXHOSTNAMELEN); if (status < 0) { log_warn("gethostname failed, ignored: %s", strerror(errno)); nc_snprintf(nci->hostname, NC_MAXHOSTNAMELEN, "unknown"); } nci->hostname[NC_MAXHOSTNAMELEN - 1] = '\0'; nci->mbuf_chunk_size = NC_MBUF_SIZE; nci->pid = (pid_t)-1; nci->pid_filename = NULL; nci->pidfile = 0; } static rstatus_t nc_get_options(int argc, char **argv, struct instance *nci) { int c, value; opterr = 0; for (;;) { c = getopt_long(argc, argv, short_options, long_options, NULL); if (c == -1) { /* no more options */ break; } switch (c) { case 'h': show_version = 1; show_help = 1; break; case 'V': show_version = 1; break; case 't': test_conf = 1; break; case 'd': daemonize = 1; break; case 'D': describe_stats = 1; show_version = 1; break; case 'v': value = nc_atoi(optarg, strlen(optarg)); if (value < 0) { log_stderr("nutcracker: option -v requires a number"); return NC_ERROR; } nci->log_level = value; break; case 'o': nci->log_filename = optarg; break; case 'c': nci->conf_filename = optarg; break; case 's': value = nc_atoi(optarg, strlen(optarg)); if (value < 0) { log_stderr("nutcracker: option -s requires a number"); return NC_ERROR; } if (!nc_valid_port(value)) { log_stderr("nutcracker: option -s value %d is not a valid " "port", value); return NC_ERROR; } nci->stats_port = (uint16_t)value; break; case 'i': value = nc_atoi(optarg, strlen(optarg)); if (value < 0) { log_stderr("nutcracker: option -i requires a number"); return NC_ERROR; } nci->stats_interval = value; break; case 'a': nci->stats_addr = optarg; break; case 'p': nci->pid_filename = optarg; break; case 'm': value = nc_atoi(optarg, strlen(optarg)); if (value <= 0) { log_stderr("nutcracker: option -m requires a non-zero number"); return NC_ERROR; } if (value < NC_MBUF_MIN_SIZE || value > NC_MBUF_MAX_SIZE) { log_stderr("nutcracker: mbuf chunk size must be between %zu and" " %zu bytes", NC_MBUF_MIN_SIZE, NC_MBUF_MAX_SIZE); return NC_ERROR; } nci->mbuf_chunk_size = (size_t)value; break; case '?': switch (optopt) { case 'o': case 'c': case 'p': log_stderr("nutcracker: option -%c requires a file name", optopt); break; case 'm': case 'v': case 's': case 'i': log_stderr("nutcracker: option -%c requires a number", optopt); break; case 'a': log_stderr("nutcracker: option -%c requires a string", optopt); break; default: log_stderr("nutcracker: invalid option -- '%c'", optopt); break; } return NC_ERROR; default: log_stderr("nutcracker: invalid option -- '%c'", optopt); return NC_ERROR; } } return NC_OK; } /* * Returns true if configuration file has a valid syntax, otherwise * returns false */ static bool nc_test_conf(struct instance *nci) { struct conf *cf; cf = conf_create(nci->conf_filename); if (cf == NULL) { log_stderr("nutcracker: configuration file '%s' syntax is invalid", nci->conf_filename); return false; } conf_destroy(cf); log_stderr("nutcracker: configuration file '%s' syntax is ok", nci->conf_filename); return true; } static rstatus_t nc_pre_run(struct instance *nci) { rstatus_t status; status = log_init(nci->log_level, nci->log_filename); if (status != NC_OK) { return status; } if (daemonize) { status = nc_daemonize(1); if (status != NC_OK) { return status; } } nci->pid = getpid(); status = signal_init(); if (status != NC_OK) { return status; } if (nci->pid_filename) { status = nc_create_pidfile(nci); if (status != NC_OK) { return status; } } nc_print_run(nci); return NC_OK; } static void nc_post_run(struct instance *nci) { if (nci->pidfile) { nc_remove_pidfile(nci); } signal_deinit(); nc_print_done(); log_deinit(); } static void nc_run(struct instance *nci) { rstatus_t status; struct context *ctx; ctx = core_start(nci); if (ctx == NULL) { return; } /* run rabbit run */ for (;;) { status = core_loop(ctx); if (status != NC_OK) { break; } } core_stop(ctx); } int main(int argc, char **argv) { rstatus_t status; struct instance nci; nc_set_default_options(&nci); status = nc_get_options(argc, argv, &nci); if (status != NC_OK) { nc_show_usage(); exit(1); } if (show_version) { log_stderr("This is nutcracker-%s" CRLF, NC_VERSION_STRING); if (show_help) { nc_show_usage(); } if (describe_stats) { stats_describe(); } exit(0); } if (test_conf) { if (!nc_test_conf(&nci)) { exit(1); } exit(0); } status = nc_pre_run(&nci); if (status != NC_OK) { nc_post_run(&nci); exit(1); } nc_run(&nci); nc_post_run(&nci); exit(1); }