Adding a copy of memcached to the tree.
authorBrian Aker <brian@tangent.org>
Fri, 23 Mar 2012 19:12:34 +0000 (12:12 -0700)
committerBrian Aker <brian@tangent.org>
Fri, 23 Mar 2012 19:12:34 +0000 (12:12 -0700)
130 files changed:
.bzrignore
Makefile.am
clients/include.am
configure.ac
libtest/include.am
libtest/memcached.cc
libtest/server.cc
m4/bottom.m4
m4/memcached.m4
memcached/.shipit [new file with mode: 0644]
memcached/AUTHORS [new file with mode: 0644]
memcached/BUILD [new file with mode: 0644]
memcached/CONTRIBUTING [new file with mode: 0644]
memcached/COPYING [new file with mode: 0644]
memcached/ChangeLog [new file with mode: 0644]
memcached/Doxyfile [new file with mode: 0644]
memcached/HACKING [new file with mode: 0644]
memcached/LICENSE [new file with mode: 0644]
memcached/Makefile.am [new file with mode: 0644]
memcached/NEWS [new file with mode: 0644]
memcached/README [new file with mode: 0644]
memcached/assoc.c [new file with mode: 0644]
memcached/assoc.h [new file with mode: 0644]
memcached/autogen.sh [new file with mode: 0755]
memcached/cache.c [new file with mode: 0644]
memcached/cache.h [new file with mode: 0644]
memcached/configure.ac [new file with mode: 0644]
memcached/daemon.c [new file with mode: 0644]
memcached/devtools/bench_noreply.pl [new file with mode: 0644]
memcached/devtools/clean-whitespace.pl [new file with mode: 0755]
memcached/doc/CONTRIBUTORS [new file with mode: 0644]
memcached/doc/Doxyfile [new file with mode: 0644]
memcached/doc/Makefile.am [new file with mode: 0644]
memcached/doc/memcached.1 [new file with mode: 0644]
memcached/doc/protocol-binary-range.xml [new file with mode: 0644]
memcached/doc/protocol-binary.xml [new file with mode: 0644]
memcached/doc/protocol.txt [new file with mode: 0644]
memcached/doc/readme.txt [new file with mode: 0644]
memcached/doc/xml2rfc/reference.RFC.0768.xml [new file with mode: 0644]
memcached/doc/xml2rfc/rfc2629-noinc.xsl [new file with mode: 0644]
memcached/doc/xml2rfc/rfc2629-other.ent [new file with mode: 0644]
memcached/doc/xml2rfc/rfc2629-refchk.xsl [new file with mode: 0644]
memcached/doc/xml2rfc/rfc2629-xhtml.ent [new file with mode: 0644]
memcached/doc/xml2rfc/rfc2629.dtd [new file with mode: 0644]
memcached/globals.c [new file with mode: 0644]
memcached/hash.c [new file with mode: 0644]
memcached/hash.h [new file with mode: 0644]
memcached/include.am [new file with mode: 0644]
memcached/items.c [new file with mode: 0644]
memcached/items.h [new file with mode: 0644]
memcached/m4/c99-backport.m4 [new file with mode: 0644]
memcached/memcached.c [new file with mode: 0644]
memcached/memcached.h [new file with mode: 0644]
memcached/memcached.spec.in [new file with mode: 0644]
memcached/memcached_dtrace.d [new file with mode: 0644]
memcached/protocol_binary.h [new file with mode: 0644]
memcached/sasl_defs.c [new file with mode: 0644]
memcached/sasl_defs.h [new file with mode: 0644]
memcached/scripts/README.damemtop [new file with mode: 0644]
memcached/scripts/damemtop [new file with mode: 0755]
memcached/scripts/damemtop.yaml [new file with mode: 0644]
memcached/scripts/mc_slab_mover [new file with mode: 0755]
memcached/scripts/memcached-init [new file with mode: 0755]
memcached/scripts/memcached-tool [new file with mode: 0755]
memcached/scripts/memcached.service [new file with mode: 0644]
memcached/scripts/memcached.sysv [new file with mode: 0755]
memcached/scripts/start-memcached [new file with mode: 0755]
memcached/sizes.c [new file with mode: 0644]
memcached/slabs.c [new file with mode: 0644]
memcached/slabs.h [new file with mode: 0644]
memcached/solaris_priv.c [new file with mode: 0644]
memcached/stats.c [new file with mode: 0644]
memcached/stats.h [new file with mode: 0644]
memcached/t/00-startup.t [new file with mode: 0755]
memcached/t/64bit.t [new file with mode: 0755]
memcached/t/binary-get.t [new file with mode: 0755]
memcached/t/binary-sasl.t [new file with mode: 0755]
memcached/t/binary.t [new file with mode: 0755]
memcached/t/bogus-commands.t [new file with mode: 0755]
memcached/t/cas.t [new file with mode: 0644]
memcached/t/daemonize.t [new file with mode: 0755]
memcached/t/dash-M.t [new file with mode: 0644]
memcached/t/evictions.t [new file with mode: 0644]
memcached/t/expirations.t [new file with mode: 0755]
memcached/t/flags.t [new file with mode: 0755]
memcached/t/flush-all.t [new file with mode: 0755]
memcached/t/getset.t [new file with mode: 0755]
memcached/t/incrdecr.t [new file with mode: 0755]
memcached/t/issue_104.t [new file with mode: 0755]
memcached/t/issue_108.t [new file with mode: 0644]
memcached/t/issue_14.t [new file with mode: 0644]
memcached/t/issue_140.t [new file with mode: 0644]
memcached/t/issue_152.t [new file with mode: 0644]
memcached/t/issue_163.t [new file with mode: 0644]
memcached/t/issue_183.t [new file with mode: 0644]
memcached/t/issue_22.t [new file with mode: 0644]
memcached/t/issue_29.t [new file with mode: 0644]
memcached/t/issue_3.t [new file with mode: 0644]
memcached/t/issue_41.t [new file with mode: 0644]
memcached/t/issue_42.t [new file with mode: 0644]
memcached/t/issue_50.t [new file with mode: 0644]
memcached/t/issue_61.t [new file with mode: 0644]
memcached/t/issue_67.t [new file with mode: 0644]
memcached/t/issue_68.t [new file with mode: 0644]
memcached/t/issue_70.t [new file with mode: 0644]
memcached/t/item_size_max.t [new file with mode: 0644]
memcached/t/lib/MemcachedTest.pm [new file with mode: 0644]
memcached/t/line-lengths.t [new file with mode: 0755]
memcached/t/lru.t [new file with mode: 0755]
memcached/t/maxconns.t [new file with mode: 0755]
memcached/t/multiversioning.t [new file with mode: 0755]
memcached/t/noreply.t [new file with mode: 0644]
memcached/t/sasl/memcached.conf [new file with mode: 0644]
memcached/t/slabs_reassign.t [new file with mode: 0644]
memcached/t/stats-detail.t [new file with mode: 0644]
memcached/t/stats.t [new file with mode: 0755]
memcached/t/stress-memcached.pl [new file with mode: 0755]
memcached/t/touch.t [new file with mode: 0755]
memcached/t/udp.t [new file with mode: 0755]
memcached/t/unixsocket.t [new file with mode: 0755]
memcached/t/whitespace.t [new file with mode: 0755]
memcached/testapp.c [new file with mode: 0644]
memcached/thread.c [new file with mode: 0644]
memcached/timedrun.c [new file with mode: 0644]
memcached/trace.h [new file with mode: 0644]
memcached/util.c [new file with mode: 0644]
memcached/util.h [new file with mode: 0644]
memcached/version.m4 [new file with mode: 0644]
memcached/version.pl [new file with mode: 0755]
memcached/version.sh [new file with mode: 0755]

index a3cc73e5df7e401c53abda212eb6279cf8093b80..efe2f1bd2d133e77babcbfc333ff965e0e0e2e7c 100644 (file)
@@ -145,3 +145,6 @@ unittests/unittests
 tests/libmemcached-1.0/testsocket
 example/t/memcached_light
 libtest/abort
+memcached/.git
+memcached/.gitignore
+memcached/memcached
index f033421ad07b8955ba651647ce7d8e3618676796..4d94c2fc2267fd9667a92c12ef8703dd16114e98 100644 (file)
@@ -30,6 +30,8 @@ EXTRA_DIST= \
 
 include libtest/include.am
 
+libtest_libtest_la_DEPENDENCIES+= memcached/memcached
+
 include libhashkit-1.0/include.am
 include libmemcached-1.0/include.am
 include libmemcachedprotocol-0.0/include.am
@@ -49,6 +51,7 @@ include support/include.am
 include tests/include.am
 include util/include.am
 include win32/include.am
+include memcached/include.am
 
 docs: html man
 
index bb35bdf5f27e44c1dd3d6029054f67002b4ed0b7..aeda9f85bdb340df8c4325bd942592cbd39fa0ea 100644 (file)
@@ -11,19 +11,19 @@ CLIENTS_LDADDS+= $(LIBSASL)
 endif
 
 bin_PROGRAMS+= \
-       clients/memcapable \
-       clients/memcat \
-       clients/memcp \
-       clients/memdump \
-       clients/memerror \
-       clients/memexist \
-       clients/memtouch \
-       clients/memflush \
-       clients/memparse \
-       clients/memping \
-       clients/memrm \
-       clients/memslap \
-       clients/memstat
+              clients/memcapable \
+              clients/memcat \
+              clients/memcp \
+              clients/memdump \
+              clients/memerror \
+              clients/memexist \
+              clients/memtouch \
+              clients/memflush \
+              clients/memparse \
+              clients/memping \
+              clients/memrm \
+              clients/memslap \
+              clients/memstat
 
 if BUILD_MEMASLAP
 if HAVE_LIBEVENT
@@ -34,18 +34,18 @@ endif
 endif
 
 noinst_HEADERS+= \
-               clients/client_options.h \
-               clients/execute.h \
-               clients/generator.h \
-               clients/ms_atomic.h \
-               clients/ms_conn.h \
-               clients/ms_memslap.h \
-               clients/ms_setting.h \
-               clients/ms_sigsegv.h \
-               clients/ms_stats.h \
-               clients/ms_task.h \
-               clients/ms_thread.h \
-               clients/utilities.h
+                clients/client_options.h \
+                clients/execute.h \
+                clients/generator.h \
+                clients/ms_atomic.h \
+                clients/ms_conn.h \
+                clients/ms_memslap.h \
+                clients/ms_setting.h \
+                clients/ms_sigsegv.h \
+                clients/ms_stats.h \
+                clients/ms_task.h \
+                clients/ms_thread.h \
+                clients/utilities.h
 
 noinst_LTLIBRARIES+= clients/libutilities.la
 clients_libutilities_la_SOURCES= clients/utilities.cc
index 965ad3bf163cba06e79609a1aafe1b46ea67a2c9..7deac7115115312fa5186f6e6340a5a55d391a51 100644 (file)
@@ -67,6 +67,7 @@ m4_include([m4/memcached.m4])
 m4_include([m4/memcached_sasl.m4])
 m4_include([m4/gearmand.m4])
 m4_include([m4/libgearman.m4])
+m4_include([memcached/version.m4])
 
 AM_CONDITIONAL(BUILDING_LIBMEMCACHED, true)
 AM_CONDITIONAL(HAVE_LIBMEMCACHED, false)
@@ -134,6 +135,14 @@ AC_CHECK_HEADERS([cxxabi.h],
 
 AC_CHECK_HEADERS([sys/sysctl.h])
 
+AC_CHECK_HEADER(umem.h, [
+   AC_DEFINE([HAVE_UMEM_H], 1,
+         [Define this if you have umem.h])
+   build_cache=no
+], [build_cache=yes])
+
+AM_CONDITIONAL([BUILD_CACHE], [test "x$build_cache" = "xyes"])
+
 AX_COMPILER_VENDOR
 
 AC_FUNC_ALLOCA
@@ -195,6 +204,7 @@ AC_CHECK_LIB([rt], [clock_gettime],
               ])
 
 AC_CHECK_LIB([m], [floor])
+AC_CHECK_FUNCS([sigignore])
 
 AC_CHECK_HEADERS([atomic.h])
 AS_IF([test "x$ac_cv_header_atomic_h" = "xyes"],[
@@ -214,6 +224,20 @@ AC_DEFINE([HAVE_LIBCURL], [0], [Support for libcurl])
 AC_DEFINE([HAVE_MEMCACHED_LIGHT_BINARY], [1], [Support for memcached_light])
 AC_DEFINE([MEMCACHED_LIGHT_BINARY], ["example/memcached_light"], [Support for memcached_light])
 
+dnl Check for the requirements for running memcached with less privileges
+dnl than the default privilege set. On Solaris we need setppriv and priv.h
+dnl If you want to add support for other platforms you should check for
+dnl your requirements, define HAVE_DROP_PRIVILEGES, and make sure you add
+dnl the source file containing the implementation into memcached_SOURCE
+dnl in Makefile.am
+AC_CHECK_FUNCS(setppriv, [
+   AC_CHECK_HEADER(priv.h, [
+      AC_DEFINE([HAVE_DROP_PRIVILEGES], 1,
+         [Define this if you have an implementation of drop_privileges()])
+      build_solaris_privs=yes
+   ], [])
+],[])
+
 AC_CHECK_HEADERS_ONCE(winsock2.h poll.h sys/wait.h fnmatch.h)
 AM_CONDITIONAL(BUILD_POLL, test "x$ac_cv_header_poll_h" = "xno")
 AM_CONDITIONAL(BUILD_WIN32_WRAPPERS, test "x$ac_cv_header_winsock2_h" = "xyes")
@@ -233,6 +257,32 @@ AX_CHECK_LIBRARY([LIBEVENT], [event.h], [event],
                   ])
 AM_CONDITIONAL(HAVE_LIBEVENT, test "x${ax_cv_have_LIBEVENT}" = "xyes")
 
+dnl Check if we're a little-endian or a big-endian system, needed by hash code
+AC_DEFUN([AC_C_ENDIAN],
+[AC_CACHE_CHECK(for endianness, ac_cv_c_endian,
+[
+  AC_RUN_IFELSE(
+    [AC_LANG_PROGRAM([], [dnl
+        long val = 1;
+        char *c = (char *) &val;
+        exit(*c == 1);
+    ])
+  ],[
+    ac_cv_c_endian=big
+  ],[
+    ac_cv_c_endian=little
+  ])
+])
+if test $ac_cv_c_endian = big; then
+  AC_DEFINE(ENDIAN_BIG, 1, [machine is bigendian])
+fi
+if test $ac_cv_c_endian = little; then
+  AC_DEFINE(ENDIAN_LITTLE, 1, [machine is littleendian])
+fi
+])
+
+AC_C_ENDIAN
+
 AC_CONFIG_FILES([
   Makefile
   docs/conf.py
index f4970642f93554698b3a47d076e2d194792bfba3..c5f0b5cc3c93b1f695a68b8aa0cac702da33ffd3 100644 (file)
@@ -124,15 +124,21 @@ libtest_libtest_la_SOURCES= \
                            libtest/vchar.cc
 
 libtest_libtest_la_CXXFLAGS=
+libtest_libtest_la_DEPENDENCIES=
+libtest_libtest_la_LIBADD=
+
 libtest_libtest_la_CXXFLAGS+= ${NO_CONVERSION}
 libtest_libtest_la_CXXFLAGS+= -DBUILDING_LIBTEST
 libtest_libtest_la_CXXFLAGS+= $(PTHREAD_CFLAGS)
 libtest_libtest_la_CXXFLAGS+= -DLIBTEST_TEMP="\"tmp_chroot\""
 libtest_libtest_la_CXXFLAGS+= $(CURL_CFLAGS)
-libtest_libtest_la_LIBADD=
+
 libtest_libtest_la_LIBADD+= $(PTHREAD_LIBS)
 libtest_libtest_la_LIBADD+= $(CURL_LIBS)
-libtest_libtest_la_DEPENDENCIES= libtest_tmp_dir
+
+libtest_libtest_la_DEPENDENCIES+= libtest_tmp_dir
+libtest_libtest_la_DEPENDENCIES+=libtest/abort
+libtest_libtest_la_DEPENDENCIES+=libtest/wait
 
 # Declare unittest so that we can append to it
 libtest_unittest_CXXFLAGS=
@@ -203,7 +209,7 @@ tmp_chroot/var/run: tmp_chroot/var
        @$(mkdir_p) tmp_chroot/var/run
 
 
-libtest_unittest_DEPENDENCIES+= libtest/libtest.la libtest_tmp_dir libtest/abort libtest/wait
+libtest_unittest_DEPENDENCIES+= libtest/libtest.la
 libtest_unittest_LDADD+= libtest/libtest.la
 libtest_unittest_SOURCES= libtest/unittest.cc
 check_PROGRAMS+= libtest/unittest
index 5327e9b8dbb971a1181cda97aaa175b48507fa13..ce523a7afc134d208c39d509fb8831e81a95d0a2 100644 (file)
@@ -56,7 +56,11 @@ class Memcached : public libtest::Server
   std::string _password;
 
 public:
-  Memcached(const std::string& host_arg, const in_port_t port_arg, const bool is_socket_arg, const std::string& username_arg, const std::string& password_arg) :
+  Memcached(const std::string& host_arg,
+            const in_port_t port_arg,
+            const bool is_socket_arg,
+            const std::string& username_arg,
+            const std::string& password_arg) :
     libtest::Server(host_arg, port_arg, is_socket_arg),
     _username(username_arg),
     _password(password_arg)
@@ -162,6 +166,16 @@ public:
     return MEMCACHED_BINARY;
   }
 
+  bool is_libtool()
+  {
+    if (MEMCACHED_BINARY and strcmp(MEMCACHED_BINARY, "memcached/memcached") == 0) 
+    {
+      return true;
+    }
+
+    return false;
+  }
+
   virtual void pid_file_option(Application& app, const std::string& arg)
   {
     if (arg.empty() == false)
@@ -205,11 +219,6 @@ public:
     }
   }
 
-  bool is_libtool()
-  {
-    return false;
-  }
-
   bool broken_socket_cleanup()
   {
     return true;
index 3e891bba2b6540d2b1815814447796fb515e5a78..7591e8bfafe2863cc163a070e54e95a5b47eca18 100644 (file)
@@ -181,7 +181,7 @@ bool Server::start()
     return false;
   }
 
-  if (is_helgrind() or is_valgrind())
+  if (is_valgrind())
   {
     dream(5, 50000);
   }
index 289c299413493626f8c28203b43c286a2efff528..798ff2c4541f8f81621abf2e68d47561ffd84bdc 100644 (file)
@@ -23,8 +23,20 @@ AH_BOTTOM([
 #if !defined(HAVE_ULONG) && !defined(__USE_MISC)
 # define HAVE_ULONG 1
 typedef unsigned long int ulong;
+#endif 
+#if defined(RMEMCACHED_PACKAGE) 
+#undef VERSION 
+#define VERSION VERSION_NUMBER
 #endif
 
+#if defined(RMEMCACHED_PACKAGE)
+#undef PACKAGE
+#define PACKAGE RMEMCACHED_PACKAGE
+#endif
+
+
 ])
 
 ])dnl CONFIG_EXTRA
index 7d648bf23497cab751d8b76c24b7867cf28ad0ec..c33f1da2f8418737cc00aca6607df4c14c89bfc6 100644 (file)
@@ -1,10 +1,10 @@
-AX_WITH_PROG(MEMCACHED_BINARY,memcached)
+AX_WITH_PROG(MEMCACHED_BINARY, [memcached])
 AS_IF([test -f "$ac_cv_path_MEMCACHED_BINARY"],
       [
         AC_DEFINE([HAVE_MEMCACHED_BINARY], [1], [If Memcached binary is available])
         AC_DEFINE_UNQUOTED([MEMCACHED_BINARY], "$ac_cv_path_MEMCACHED_BINARY", [Name of the memcached binary used in make test])
        ],
        [
-        AC_DEFINE([HAVE_MEMCACHED_BINARY], [0], [If Memcached binary is available])
-        AC_DEFINE([MEMCACHED_BINARY], [0], [Name of the memcached binary used in make test])
+        AC_DEFINE([HAVE_MEMCACHED_BINARY], [1], [If Memcached binary is available])
+        AC_DEFINE([MEMCACHED_BINARY], ["memcached/memcached"], [Name of the memcached binary used in make test])
       ])
diff --git a/memcached/.shipit b/memcached/.shipit
new file mode 100644 (file)
index 0000000..9b97267
--- /dev/null
@@ -0,0 +1,4 @@
+steps = FindVersion, ChangeVersion, CheckChangeLog, DistTest, Commit, Tag, MakeDist, AddToSVNDir
+
+AddToSVNDir.dir = ../website/dist
+svn.tagpattern = %v
diff --git a/memcached/AUTHORS b/memcached/AUTHORS
new file mode 100644 (file)
index 0000000..fc95232
--- /dev/null
@@ -0,0 +1,2 @@
+Anatoly Vorobey <mellon@pobox.com>
+Brad Fitzpatrick <brad@danga.com>
diff --git a/memcached/BUILD b/memcached/BUILD
new file mode 100644 (file)
index 0000000..d6ca4c4
--- /dev/null
@@ -0,0 +1,37 @@
+Ideally, you want to make a static binary, otherwise the dynamic
+linker pollutes your address space with shared libs right in the
+middle.  (NOTE: actually, this shouldn't matter so much anymore, now
+that we only allocate huge, fixed-size slabs)
+
+Make sure your libevent has epoll (Linux) or kqueue (BSD) support.
+Using poll or select only is slow, and works for testing, but
+shouldn't be used for high-traffic memcache installations.
+
+To build libevent with epoll on Linux, you need two things. First,
+you need /usr/include/sys/epoll.h . To get it, you can install the
+userspace epoll library, epoll-lib. The link to the latest version
+is buried inside
+http://www.xmailserver.org/linux-patches/nio-improve.html ; currently
+it's http://www.xmailserver.org/linux-patches/epoll-lib-0.9.tar.gz .
+If you're having any trouble building/installing it, you can just copy
+epoll.h from that tarball to /usr/include/sys as that's the only thing
+from there that libevent really needs.
+
+Secondly, you need to declare syscall numbers of epoll syscalls, so
+libevent can use them. Put these declarations somewhere
+inside <sys/epoll.h>:
+
+#define __NR_epoll_create               254
+#define __NR_epoll_ctl          255
+#define __NR_epoll_wait         256
+
+After this you should be able to build libevent with epoll support.
+Once you build/install libevent, you don't need <sys/epoll.h> to
+compile memcache or link it against libevent. Don't forget that for epoll
+support to actually work at runtime you need to use a kernel with epoll
+support patch applied, as explained in the README file.
+
+BSD users are luckier, and will get kqueue support by default.
+
+
+
diff --git a/memcached/CONTRIBUTING b/memcached/CONTRIBUTING
new file mode 100644 (file)
index 0000000..e93b005
--- /dev/null
@@ -0,0 +1,3 @@
+Want to contribute?  Up-to-date pointers should be at:
+
+    http://contributing.appspot.com/memcached
diff --git a/memcached/COPYING b/memcached/COPYING
new file mode 100644 (file)
index 0000000..4746b00
--- /dev/null
@@ -0,0 +1,30 @@
+Copyright (c) 2003, Danga Interactive, Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+    * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+
+    * Neither the name of the Danga Interactive nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/memcached/ChangeLog b/memcached/ChangeLog
new file mode 100644 (file)
index 0000000..db7800d
--- /dev/null
@@ -0,0 +1,621 @@
+2010-10-11
+       * ChangeLog is no longer being updated.
+       See http://code.google.com/p/memcached/wiki/ReleaseNotes
+
+2009-04-10
+
+       * *critical bugfix*. In 1.2.7 under multithreaded mode, memcached
+         would never restart accepting connections after hitting the
+         maximum connection limit.
+
+2009-04-02
+
+       * Fix: Rewrote stat buffer handling (Trond)
+
+2009-03-31
+
+       * Feature:  -b option for configuring backlog
+
+       * Feature:  stats settings
+
+       * Fix: binary stat alignment bug (bug26)
+
+       * Fix: buffer overflow in stats (bug27)
+
+       * Fix: recycle memory more aggressively (bug14)
+
+       * Fix: incr validation (bug31)
+
+       * Fix: 64-bit incr/decr delta problem (bug21)
+
+       * Fix: Ascii UDP set (bug36)
+
+       * Fix: stats slabs' used chunks (bug29)
+
+       * Fix: stats reset resetting more (bug22)
+
+       * Misc: More tests, documentation, cleanliness (godliness)
+
+       * Stable merge (stats, debuggability, listen bugs)
+
+2009-03-11
+
+       * Protocol:  Binary complete (Dustin, Trond, Toru, etc...)
+
+       * Performance:  improvements from facebook (Dormando)
+
+       * Performance:  per-thread stats (Trond)
+
+       * Performance:  Hash expansion in its own thread (Trond)
+
+       * Stats:  Lots of new stats (Dustin)
+
+       * Bug fixes (various)
+
+       See the 1.3.2 release notes or revision control for more detail.
+
+2008-09-06
+       * Display error status on listen failures (Dormando)
+
+       * Remove managed instance code. Incomplete/etc. (Dormando)
+
+          * Handle broken IPV6 stacks better (Brian Aker)
+
+          * Generate warnings on setsockopt() failures (Brian Aker)
+
+          * Fix some indentation issues (Brian Aker)
+
+          * UDP/TCP can be disabled by setting their port to zero (Brian Aker)
+
+          * Zero out libevent thread structures before use (Ricky Zhou)
+
+          * New stat: Last accessed time for last evicted item per slab class.
+            (Dormando)
+
+          * Use a dedicated socket accept thread (Facebook)
+
+          * Add -R option. Limit the number of requests processed by a connection
+            at once. Prevents starving other threads if bulk loading. (Facebook)
+
+2008-07-29 [Version 1.2.6 released]
+
+2008-07-24 [Version 1.2.6-rc1 released]
+
+       * Add support for newer automake (Facebook)
+
+       * DTrace support for Solaris/etc (Trond Norbye)
+
+       * LRU tests (Steve Yen)
+
+       * Handle negative length items properly (Dormando)
+
+       * Don't leave stale data after failed set attempts (Dormando)
+
+       * Fix refcount leaks, which would result in OOM's on all sets
+              (Dormando)
+
+       * Fix buffer overruns (Dustin Sallings, Tomash Brechko)
+
+       * Fix memory corruption with CAS (Dustin Sallings)
+
+2008-06-11
+
+       * Fix -k to work with -d. (reported by Gary Zhu)
+
+2008-03-02 [Version 1.2.5-rc1 released]
+
+       * Add per-item-class tracking of evictions and OOM errors (dormando)
+
+       * Optimize item_alloc() a little (dormando)
+
+       * Give 'SERVER_ERROR out of memory' errors more context (dormando)
+
+       * Enable usage of large memory pages under solaris
+         (Trond.Norbye@Sun.COM)
+
+       * Enable UDP by default, clean up server socket code
+         (brian@tangent.org)
+
+       * 'noreply' support (Tomash Brechko)
+
+       * IPv6 support, and IPv6 multi-interface support (brian@tangent.org)
+
+       * Add compiler options for Sun Studio compilers with --enable-threads
+            (Trond.Norbye@Sun.COM)
+
+       * Add --enable-64bit for mulitarget platforms (Trond.Norbye@Sun.COM)
+
+       * Use gettimeofday(2) instead of time(2).
+
+       * Make -k option work (Tomash Brechko)
+
+          * Fix chunk slab alignment (Trond.Norbye@Sun.COM)
+
+2007-12-06 [Version 1.2.4 released]
+
+2007-12-05
+
+       * Fix compilation on panther (JS and Dormando)
+
+          * More CAS tests (Chris Goffinet)
+
+          * Final fixes for all 1.2.4 features are in, -rc2 sent out.
+
+2007-11-19 [Version 1.2.4-rc1 released]
+
+2007-11-19  Dormando <dormando@rydia.net>
+
+       * Patch series from Tomash Brechko <tomash.brechko@gmail.com>:
+         Minor fixes and optimisations.
+
+       * Patches from Chris, Dustin, and Dormando to fix CAS.
+
+       * Prepping for 1.2.4 release.
+
+2007-11-13  Dormando <dormando@rydia.net>
+
+       * Adjusted patch from js <ebgssth@gmail.com>: Compile on OS X Panther
+         and earlier.
+
+2007-11-12  Steven Grimm  <sgrimm@facebook.com>
+
+       * Patch from Tomash Brechko <tomash.brechko@gmail.com>: Always send
+         "SERVER_ERROR out of memory" when memory exhausted.
+
+2007-10-15  Paul Lindner  <lindner@inuus.com>
+
+       * Patch from David Bremner <bremner@unb.ca> that implements
+         a new option "-a" which takes an octal permission mask
+         (like chmod) sets the permissions on the unix domain socket 
+         (specified by "-s").
+
+2007-10-03 Paul Lindner <lindner@inuus.com>
+       * Incorporate "cas" operation developed by Dustin
+         Sallings <dustin@spy.net> This change allows you
+         to do atomic changes to an existing key.
+
+       * Fix for stats.evictions not incrementing
+         when exptime == 0 items are kicked off the cache. 
+         from Jean-Francois BUSTARRET <jfbustarret@wat.tv>.
+
+       * Fix for do_item_cachedump() which was returning
+         an incorrect timestamp.
+       
+       * Switch to unsigned 64-bit increment/decrement counters
+         from Evan Miller and Dustin Sallings.
+
+       * Add append command support written by Filipe Laborde.
+         Thread safe version plus prepend command from Maxim Dounin
+         <mdounin@mdounin.ru>
+
+       * The memcached-tool script can now display stats.  Patch
+         provided by Dan Christian <dchristian@google.com>
+
+       * Fix for Unix Domain sockets on FreeBSD
+         FreeBSD's sendmsg() requires msg_name in msghdr structure 
+         to be NULL if not used, setting msg_namelen to 0 isn't enough.
+         Patch from Maxim Dounin <mdounin@mdounin.ru>
+
+2007-08-21 Paul Lindner <lindner@inuus.com>
+       * Incorporate incrememnt patch from Evan Miller 
+         <emiller@imvu.com> to define increment overflow
+         behavior.
+
+2007-08-07 Leon Brocard <acme@astray.com>
+       * Bring the memcached.1 manpage up to date
+
+2007-08-06 Paul Lindner <lindner@inuus.com>
+       * Fix crash when using -P and -d flags on x86_64
+         with latest libevent release.
+
+2007-07-08  Steven Grimm  <sgrimm@facebook.com>
+
+       * Item stats commands weren't thread-safe; wrap them with locks
+         when compiled in multithreaded mode.
+       * The "stats items" command now works again; it broke with the
+         introduction of the powers-of-N chunk size change.
+
+2007-07-06 [Version 1.2.3 released]
+
+2007-06-19  Paul Lindner  <lindner@mirth.inuus.com>
+
+       * Solaris portability fixes from Trond Norbye
+
+2007-05-29  Paul Lindner  <lindner@mirth.inuus.com>
+
+       * Properly document evictions statistic value
+
+2007-05-10  Paul Lindner  <lindner@inuus.com>
+
+       * Flesh out tests for unix domain sockets and binary data.
+       * Update rpm spec file to run tests
+
+2007-05-07  Paul Lindner  <lindner@inuus.com>
+
+       * Fix compilation bug on freebsd 6.x (and maybe others)
+       * Update RPM spec file per redhat bugzilla #238994
+       * Move unistd.h to memcached.h to get rid of warnings
+       * Add string.h to thread.c to get correctly prototyped strerror()
+
+2007-05-04  Paul Lindner  <lindner@inuus.com>
+
+       * Add fedora/redhat style init script and RPM spec file
+
+2007-05-12 [Version 1.2.2 released]
+
+2007-04-16  Steven Grimm  <sgrimm@facebook.com>
+
+       * Command tokenizer performance and cleanliness improvement.
+         Patch contributed by Paolo Borelli <paolo.borelli@gmail.com>.
+
+2007-04-16  Paul Lindner  <lindner@inuus.com>
+
+       * Add notes to README about MacOS, libevent and kqueue.
+
+       * Windows Patch integration -- part 1, warnings elimination.
+
+2007-04-12  Paul Lindner  <lindner@mirth.inuus.com>
+
+       * Allow changes to the verbosity level of the server with a new
+         "verbosity" command and some compiler cleanups. 
+          Patch contributed by Paolo Borelli <paolo.borelli@gmail.com>.
+
+2007-04-08  Paul Lindner  <lindner@inuus.com>
+
+       * Add cleanup patch from "Tim Yardley" <liquid@haveheart.com> to
+         clean up source spacing issues, fix -Wall warnings, add some
+         null checks, adds asserts at the top of each function for any
+         use of conn *c without checking to see if c is NULL first.
+
+        * Also adjust clean-whitespace.pl to clean *.ac files.  Add
+          script to test-suite to test for tabs.
+
+2007-04-04  Paul Lindner  <lindner@inuus.com>
+
+       * Add clarification of flush_all in the protocol docs
+         from Elizabeth Mattijsen <liz@dijkmat.nl>
+
+2007-03-31  Paul Lindner  <lindner@inuus.com>
+
+       * Add patch from Eli Bingham <eli@pandora.com> to 
+         re-enable the -n switch to memcached.
+
+2007-03-20  Paul Lindner  <lindner@inuus.com>
+       * Add patch to collect eviction statistics from
+          Jean-Francois BUSTARRET <jfbustarret@wat.tv>.
+
+        * Updated docs, added new test cases for t/stats.t
+
+2007-03-18  Paul Lindner  <lindner@inuus.com>
+
+       * Add more test cases using larger buffer sizes up to and greater
+         than 1MB.
+
+       * Remove unused parameter to item_size_ok()
+
+       * Use a single printf() in usage()
+
+       * Add a failing test for conforming with maximum connections.
+
+2007-03-17
+       * crash fix from Thomas van Gulick <thomas@partyflock.nl> in
+         conn_shrink(), passing &ptr, instead of ptr to realloc().
+
+2007-03-05  Paul Lindner  <lindner@inuus.com>
+       * Fix a number of places where (s)printf calls were using unsigned
+         or signed formats that did not match their arguments.
+
+       * Add support for stdbool.h and stdint.h to use the bool and
+         uint8_t types.
+
+       * Major refactoring - move API calls for assoc/items/slabs to
+         their own individual header files.  Add apropriate const and
+         static declarations as appropriate.
+       
+       * Avoid type-punning.  Do a more efficient realloc inside the
+         conn_shrink routine.
+
+        * Fix overflow bug where uninitialized access to slabclass caused
+         size-0 mallocs during slab preallocation.
+
+       * Use EXIT_SUCCESS/EXIT_FAILURE constants.
+
+       * Convert some sprintf calls to snprintf to protect against
+         buffer overflows.
+
+       * Explicitly compare against NULL or zero in many places.
+
+2007-03-05
+       * Steven Grimm <sgrimm@facebook.com>: Per-object-type stats collection
+         support. Specify the object type delimiter with the -D command line
+         option. Turn stats gathering on and off with "stats detail on" and
+         "stats detail off". Dump the per-object-type details with
+         "stats detail dump".
+
+2007-03-01
+       * Steven Grimm <sgrimm@facebook.com>: Fix an off-by-one error in the
+         multithreaded version's message passing code.
+
+2006-12-23
+       * fix expirations of items set with absolute expiration times in
+         the past, before the server's start time.  bug was introduced in
+         1.2.0 with rel_time_t.  Thanks to Adam Dixon
+         <adamtdixon@gmail.com> for the bug report and test case!
+
+2006-11-26
+       * Steven Grimm <sgrimm@facebook.com>: Performance improvements:
+         
+         Dynamic sizing of hashtable to reduce collisions on very large
+         caches and conserve memory on small caches.
+
+         Only reposition items in the LRU queue once a minute, to reduce
+         overhead of accessing extremely frequently-used items.
+
+         Stop listening for new connections until an existing one closes
+         if we run out of available file descriptors.
+
+         Command parser refactoring: Add a single-pass tokenizer to cut
+         down on string scanning.  Split the command processing into
+         separate functions for easier profiling and better readability.
+         Pass key lengths along with the keys in all API functions that
+         need keys, to avoid needing to call strlen() repeatedly.
+
+2006-11-25
+       * Steve Peters <steve@fisharerojo.org>: OpenBSD has a malloc.h,
+       but warns to use stdlib.h instead
+
+2006-11-22
+       * Steven Grimm <sgrimm@facebook.com>: Add support for multithreaded
+         execution. Run configure with "--enable-threads" to enable. See
+         doc/threads.txt for details.
+
+2006-11-13
+       * Iain Wade <iwade@optusnet.com.au>: Fix for UDP responses on non-"get"
+        commands.
+
+2006-10-15
+       * Steven Grimm <sgrimm@facebook.com>: Dynamic sizing of hashtable to
+         reduce collisions on very large caches and conserve memory on
+         small caches.
+
+2006-10-13
+       * Steven Grimm <sgrimm@facebook.com>: New faster hash function.
+
+2006-09-20
+
+       * don't listen on UDP by default; more clear message when UDP port in use
+
+2006-09-09
+       * release 1.2.0 (along with 1.1.13, which is the more tested branch)
+
+       nobody has run 1.2.0 in production, to my knowledge.  facebook has run
+       their pre-merge-with-trunk version, but bugs were discovered (and fixed)
+       after the merge.  there might be more.  you've been warned.  :)
+
+2006-09-04
+       * improved autoconf libevent detection, from the Tor project.
+
+2006-09-03
+       * test suite and lot of expiration, delete, flush_all, etc corner
+         case bugs fixed (Brad Fitzpatrick)
+
+2006-09-02
+       * Nathan Neulinger <nneul@umr.edu>: fix breakage in expiration code
+         causing expiration times to not be processed correctly.
+
+2006-08-21
+       * Nathan Neulinger <nneul@umr.edu>: fix incompatabilities with
+         unix domain socket support and the UDP code and clean up stale 
+         sockets
+
+2006-08-20
+       * Nathan Neulinger <nneul@umr.edu>: unix domain socket support
+
+2006-05-03
+       * Steven Grimm <sgrimm@facebook.com>:  big bunch of changes:
+         big CPU reduction work, UDP-based interface, increased memory
+         efficiency.  (intertwined patch, committed all together)
+         <http://lists.danga.com/pipermail/memcached/2006-May/002164.html>
+         or see svn commit logs
+
+2006-04-30
+       * River Tarnell:  autoconf work for Solaris 10.  Brad:
+       merge and verify it works on Nexenta.
+
+2006-03-04
+       * avva: bucket/generation patch (old, but Brad's just finally
+       committing it)
+
+2006-01-01
+       * Brad Fitzpatrick <brad@danga.com>:  allocate 1 slab per class
+       on start-up, to avoid confusing users with out-of-memory errors
+       later.  this is 18 MB of allocation on start, unless max memory
+       allowed with -m is lower, in which case only the smaller slab
+       classes are allocated.
+
+2005-08-09
+       * Elizabeth Mattijsen <liz@dijkmat.nl>: needed a way to flush all
+       memcached backend servers, but not at exactly the same time (to
+       reduce load peaks), I've added some simple functionality to the
+       memcached protocol in the "flush_all" command that allows you to
+       specify a time at which the flush will actually occur (instead of
+       always at the moment the "flush_all" command is received).
+
+2005-05-25
+       * patch from Peter van Dijk <peter@nextgear.nl> to make
+         stderr unbuffered, for running under daemontools
+
+2005-04-04
+       * patch from Don MacAskill <don@smugmug.com> 'flush_all' doesn't
+       seem to work properly.  Basically, if you try to add a key which
+       is present, but expired, the store fails but the old key is no
+       longer expired.
+
+       * release 1.1.12
+
+2005-01-14
+       * Date: Thu, 18 Nov 2004 15:25:59 -0600
+         From: David Phillips <electrum@gmail.com>
+       Here is a patch to configure.ac and Makefile.am to put the man page in
+       the correct location.  Trying to install the man page from a
+       subdirectory results in the subdirectory being used in the install
+       path (it tries to install to doc/memcached.1).  This is the correct
+       thing to  do:
+
+       - create a Makefile.am in the doc directory that installs the man page
+         with man_MANS
+       - modify Makefile.am in the base directory to reference the doc
+         directory using SUBDIRS
+       - modify the AC_CONFIG_FILES macro in configure.ac to output the 
+         Makefile in doc
+
+       
+2005-01-14
+       * pidfile saving support from Lisa Seelye <lisa@gentoo.org>, sent
+         Jan 13, 2005
+
+2005-01-14
+       * don't delete libevent events that haven't been added (the deltimer)
+         patch from Ted Schundler <tschundler@gmail.com>
+
+2004-12-10
+       * document -M and -r in manpage (Doug Porter <dsp@dsp.name>)
+
+2004-07-22
+       * fix buffer overflow in items.c with 250 byte keys along with
+         other info on the same line going into a 256 byte char[].
+         thanks to Andrei Nigmatulin <anight@monamour.ru>
+       
+2004-06-15
+       * immediate deletes weren't being unlinked a few seconds,
+         preventing "add" commands to the same key in that time period.
+         thanks to Michael Alan Dorman <mdorman@debian.org> for the
+         bug report and demo script.
+       
+2004-04-30
+       * released 1.1.11
+
+2004-04-24
+       * Avva: Add a new command line option: -r , to maximize core file
+       limit.
+
+2004-03-31
+       * Avva: Use getrlimit and setrlimit to set limits for number of
+       simultaneously open file descriptors. Get the current limits and
+       try to raise them if they're not enough for the specified (or the
+       default) setting of max connections.
+       
+2004-02-24
+       * Adds a '-M' flag to turn off tossing items from the cache.
+         (Jason Titus <jtitus@postini.com>)
+
+2004-02-19 (Evan)
+       * Install manpage on "make install", etc.
+
+2003-12-30 (Brad)
+       * remove static build stuff.  interferes with PAM setuid stuff
+         and was only included as a possible fix with the old memory
+         allocator.  really shouldn't make a difference.
+       * add Jay Bonci's Debian scripts and manpage
+       * release version 1.1.10
+
+2003-12-01 (Avva)
+       * New command: flush_all, causes all existing items to
+         be invalidated immediately (without deleting them from
+         memory, merely causing memcached to no longer return them).
+2003-10-23
+       * Shift init code around to fix daemon mode on FreeBSD,
+       * and drop root only after creating the server socket (to
+       * allow the use of privileged ports)
+       * version 1.1.10pre
+
+2003-10-09
+       * BSD compile fixes from Ryan T. Dean
+       * version 1.1.9
+       
+2003-09-29
+       * ignore SIGPIPE at start instead of crashing in rare cases it
+         comes up.  no other code had to be modified, since everything
+         else is already dead-connection-aware.  (avva)
+       
+2003-09-09 (Avva, Lisa Marie Seelye <lisa@gentoo.org>)
+       * setuid support
+       
+2003-09-05 (Avva)
+       * accept all new connections in the same event (so we work with ET epoll)
+       * mark all items as clsid=0 after slab page reassignment to please future
+         asserts (on the road to making slab page reassignment work fully)
+
+2003-08-12 (Brad Fitzpatrick)
+       * use TCP_CORK on Linux or TCP_PUSH on BSD
+       * only use TCP_NODELAY when we don't have alternatives
+       
+2003-08-10
+       * disable Nagel's Algorithm (TCP_NODELAY) for better performance (avva)
+
+2003-08-10
+       * support multiple levels of verbosity (-vv)
+
+2003-08-10  (Evan Martin)
+       * Makefile.am: debug, optimization, and static flags are controlled
+         by the configure script.
+       * configure.ac:
+         - allow specifying libevent directory with --with-libevent=DIR
+         - check for malloc.h (unavailable on BSDs)
+         - check for socklen_t (unavailable on OSX)
+       * assoc.c, items.c, slabs.c:  Remove some unused headers.
+       * memcached.c:  allow for nonexistence of malloc.h; #define a POSIX
+         macro to import mlockall flags.
+
+2003-07-29
+       * version 1.1.7
+       * big bug fix: item exptime 0 meant expire immediately, not never
+       * version 1.1.8
+
+2003-07-22
+       * make 'delete' take second arg, of time to refuse new add/replace
+       * set/add/replace/delete can all take abs or delta time (delta can't
+         be larger than a month)
+
+2003-07-21
+       * added doc/protocol.txt
+
+2003-07-01
+       * report CPU usage in stats
+        
+2003-06-30
+       * version 1.1.6
+       * fix a number of obscure bugs
+       * more stats reporting
+       
+2003-06-10
+       * removing use of Judy; use a hash.  (judy caused memory fragmentation)
+       * shrink some structures
+       * security improvements
+       * version 1.1.0
+       
+2003-06-18
+       * changing maxsize back to an unsigned int
+       
+2003-06-16
+       * adding PHP support
+       * added CONTRIBUTORS file
+       * version 1.0.4
+       
+2003-06-15
+       * forgot to distribute website/api (still learning auto*)
+       * version 1.0.3
+       
+2003-06-15
+       * update to version 1.0.2
+       * autoconf/automake fixes for older versions
+       * make stats report version number
+       * change license from GPL to BSD
+       
+Fri, 13 Jun 2003 10:05:51 -0700  Evan Martin  <martine@danga.com>
+
+       * configure.ac, autogen.sh, Makefile.am:  Use autotools.
+       * items.c, memcached.c:  #include <time.h> for time(),
+         printf time_t as %lu (is this correct?),
+         minor warnings fixes.
+
diff --git a/memcached/Doxyfile b/memcached/Doxyfile
new file mode 100644 (file)
index 0000000..fc19d3d
--- /dev/null
@@ -0,0 +1,1510 @@
+# Doxyfile 1.5.8
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project
+#
+# All text after a hash (#) is considered a comment and will be ignored
+# The format is:
+#       TAG = value [value, ...]
+# For lists items can also be appended using:
+#       TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (" ")
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the config file
+# that follow. The default is UTF-8 which is also the encoding used for all
+# text before the first occurrence of this tag. Doxygen uses libiconv (or the
+# iconv built into libc) for the transcoding. See
+# http://www.gnu.org/software/libiconv for the list of possible encodings.
+
+DOXYFILE_ENCODING      = UTF-8
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded
+# by quotes) that should identify the project.
+
+PROJECT_NAME           = memcached
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number.
+# This could be handy for archiving the generated documentation or
+# if some version control system is used.
+
+PROJECT_NUMBER         =
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
+# base path where the generated documentation will be put.
+# If a relative path is entered, it will be relative to the location
+# where doxygen was started. If left blank the current directory will be used.
+
+OUTPUT_DIRECTORY       = doc/doxy
+
+# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create
+# 4096 sub-directories (in 2 levels) under the output directory of each output
+# format and will distribute the generated files over these directories.
+# Enabling this option can be useful when feeding doxygen a huge amount of
+# source files, where putting all generated files in the same directory would
+# otherwise cause performance problems for the file system.
+
+CREATE_SUBDIRS         = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# The default language is English, other supported languages are:
+# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional,
+# Croatian, Czech, Danish, Dutch, Farsi, Finnish, French, German, Greek,
+# Hungarian, Italian, Japanese, Japanese-en (Japanese with English messages),
+# Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, Polish,
+# Portuguese, Romanian, Russian, Serbian, Serbian-Cyrilic, Slovak, Slovene,
+# Spanish, Swedish, and Ukrainian.
+
+OUTPUT_LANGUAGE        = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will
+# include brief member descriptions after the members that are listed in
+# the file and class documentation (similar to JavaDoc).
+# Set to NO to disable this.
+
+BRIEF_MEMBER_DESC      = YES
+
+# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend
+# the brief description of a member or function before the detailed description.
+# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+
+REPEAT_BRIEF           = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator
+# that is used to form the text in various listings. Each string
+# in this list, if found as the leading text of the brief description, will be
+# stripped from the text and the result after processing the whole list, is
+# used as the annotated text. Otherwise, the brief description is used as-is.
+# If left blank, the following values are used ("$name" is automatically
+# replaced with the name of the entity): "The $name class" "The $name widget"
+# "The $name file" "is" "provides" "specifies" "contains"
+# "represents" "a" "an" "the"
+
+ABBREVIATE_BRIEF       =
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# Doxygen will generate a detailed section even if there is only a brief
+# description.
+
+ALWAYS_DETAILED_SEC    = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+
+INLINE_INHERITED_MEMB  = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full
+# path before files name in the file list and in the header files. If set
+# to NO the shortest path that makes the file name unique will be used.
+
+FULL_PATH_NAMES        = YES
+
+# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag
+# can be used to strip a user-defined part of the path. Stripping is
+# only done if one of the specified strings matches the left-hand part of
+# the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the
+# path to strip.
+
+STRIP_FROM_PATH        =
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of
+# the path mentioned in the documentation of a class, which tells
+# the reader which header file to include in order to use a class.
+# If left blank only the name of the header file containing the class
+# definition is used. Otherwise one should specify the include paths that
+# are normally passed to the compiler using the -I flag.
+
+STRIP_FROM_INC_PATH    =
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter
+# (but less readable) file names. This can be useful is your file systems
+# doesn't support long names like on DOS, Mac, or CD-ROM.
+
+SHORT_NAMES            = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen
+# will interpret the first line (until the first dot) of a JavaDoc-style
+# comment as the brief description. If set to NO, the JavaDoc
+# comments will behave just like regular Qt-style comments
+# (thus requiring an explicit @brief command for a brief description.)
+
+JAVADOC_AUTOBRIEF      = YES
+
+# If the QT_AUTOBRIEF tag is set to YES then Doxygen will
+# interpret the first line (until the first dot) of a Qt-style
+# comment as the brief description. If set to NO, the comments
+# will behave just like regular Qt-style comments (thus requiring
+# an explicit \brief command for a brief description.)
+
+QT_AUTOBRIEF           = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen
+# treat a multi-line C++ special comment block (i.e. a block of //! or ///
+# comments) as a brief description. This used to be the default behaviour.
+# The new default is to treat a multi-line C++ comment block as a detailed
+# description. Set this tag to YES if you prefer the old behaviour instead.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented
+# member inherits the documentation from any documented member that it
+# re-implements.
+
+INHERIT_DOCS           = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce
+# a new page for each member. If set to NO, the documentation of a member will
+# be part of the file/class/namespace that contains it.
+
+SEPARATE_MEMBER_PAGES  = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab.
+# Doxygen uses this value to replace tabs by spaces in code fragments.
+
+TAB_SIZE               = 8
+
+# This tag can be used to specify a number of aliases that acts
+# as commands in the documentation. An alias has the form "name=value".
+# For example adding "sideeffect=\par Side Effects:\n" will allow you to
+# put the command \sideeffect (or @sideeffect) in the documentation, which
+# will result in a user-defined paragraph with heading "Side Effects:".
+# You can put \n's in the value part of an alias to insert newlines.
+
+ALIASES                =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C
+# sources only. Doxygen will then generate output that is more tailored for C.
+# For instance, some of the names that are used will be different. The list
+# of all members will be omitted, etc.
+
+OPTIMIZE_OUTPUT_FOR_C  = NO
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java
+# sources only. Doxygen will then generate output that is more tailored for
+# Java. For instance, namespaces will be presented as packages, qualified
+# scopes will look different, etc.
+
+OPTIMIZE_OUTPUT_JAVA   = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources only. Doxygen will then generate output that is more tailored for
+# Fortran.
+
+OPTIMIZE_FOR_FORTRAN   = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for
+# VHDL.
+
+OPTIMIZE_OUTPUT_VHDL   = NO
+
+# Doxygen selects the parser to use depending on the extension of the files it parses.
+# With this tag you can assign which parser to use for a given extension.
+# Doxygen has a built-in mapping, but you can override or extend it using this tag.
+# The format is ext=language, where ext is a file extension, and language is one of
+# the parsers supported by doxygen: IDL, Java, Javascript, C#, C, C++, D, PHP,
+# Objective-C, Python, Fortran, VHDL, C, C++. For instance to make doxygen treat
+# .inc files as Fortran files (default is PHP), and .f files as C (default is Fortran),
+# use: inc=Fortran f=C
+
+EXTENSION_MAPPING      =
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should
+# set this tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string); v.s.
+# func(std::string) {}). This also make the inheritance and collaboration
+# diagrams that involve STL classes more complete and accurate.
+
+BUILTIN_STL_SUPPORT    = NO
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+
+CPP_CLI_SUPPORT        = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only.
+# Doxygen will parse them like normal C++ but will assume all classes use public
+# instead of private inheritance when no explicit protection keyword is present.
+
+SIP_SUPPORT            = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate getter
+# and setter methods for a property. Setting this option to YES (the default)
+# will make doxygen to replace the get and set methods by a property in the
+# documentation. This will only work if the methods are indeed getting or
+# setting a simple type. If this is not the case, or you want to show the
+# methods anyway, you should set this option to NO.
+
+IDL_PROPERTY_SUPPORT   = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES, then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+
+DISTRIBUTE_GROUP_DOC   = NO
+
+# Set the SUBGROUPING tag to YES (the default) to allow class member groups of
+# the same type (for instance a group of public functions) to be put as a
+# subgroup of that type (e.g. under the Public Functions section). Set it to
+# NO to prevent subgrouping. Alternatively, this can be done per class using
+# the \nosubgrouping command.
+
+SUBGROUPING            = YES
+
+# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum
+# is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically
+# be useful for C code in case the coding convention dictates that all compound
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+
+TYPEDEF_HIDES_STRUCT   = NO
+
+# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to
+# determine which symbols to keep in memory and which to flush to disk.
+# When the cache is full, less often used symbols will be written to disk.
+# For small to medium size projects (<1000 input files) the default value is
+# probably good enough. For larger projects a too small cache size can cause
+# doxygen to be busy swapping symbols to and from disk most of the time
+# causing a significant performance penality.
+# If the system has enough physical memory increasing the cache will improve the
+# performance by keeping more symbols in memory. Note that the value works on
+# a logarithmic scale so increasing the size by one will rougly double the
+# memory usage. The cache size is given by this formula:
+# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0,
+# corresponding to a cache size of 2^16 = 65536 symbols
+
+SYMBOL_CACHE_SIZE      = 0
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
+# documentation are documented, even if no documentation was available.
+# Private class members and static file members will be hidden unless
+# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
+
+EXTRACT_ALL            = YES
+
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class
+# will be included in the documentation.
+
+EXTRACT_PRIVATE        = NO
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file
+# will be included in the documentation.
+
+EXTRACT_STATIC         = NO
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs)
+# defined locally in source files will be included in the documentation.
+# If set to NO only classes defined in header files are included.
+
+EXTRACT_LOCAL_CLASSES  = YES
+
+# This flag is only useful for Objective-C code. When set to YES local
+# methods, which are defined in the implementation section but not in
+# the interface are included in the documentation.
+# If set to NO (the default) only methods in the interface are included.
+
+EXTRACT_LOCAL_METHODS  = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base
+# name of the file that contains the anonymous namespace. By default
+# anonymous namespace are hidden.
+
+EXTRACT_ANON_NSPACES   = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all
+# undocumented members of documented classes, files or namespaces.
+# If set to NO (the default) these members will be included in the
+# various overviews, but no documentation section is generated.
+# This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_MEMBERS     = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy.
+# If set to NO (the default) these classes will be included in the various
+# overviews. This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_CLASSES     = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all
+# friend (class|struct|union) declarations.
+# If set to NO (the default) these declarations will be included in the
+# documentation.
+
+HIDE_FRIEND_COMPOUNDS  = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any
+# documentation blocks found inside the body of a function.
+# If set to NO (the default) these blocks will be appended to the
+# function's detailed documentation block.
+
+HIDE_IN_BODY_DOCS      = NO
+
+# The INTERNAL_DOCS tag determines if documentation
+# that is typed after a \internal command is included. If the tag is set
+# to NO (the default) then the documentation will be excluded.
+# Set it to YES to include the internal documentation.
+
+INTERNAL_DOCS          = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate
+# file names in lower-case letters. If set to YES upper-case letters are also
+# allowed. This is useful if you have classes or files whose names only differ
+# in case and if your file system supports case sensitive file names. Windows
+# and Mac users are advised to set this option to NO.
+
+CASE_SENSE_NAMES       = NO
+
+# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen
+# will show members with their full class and namespace scopes in the
+# documentation. If set to YES the scope will be hidden.
+
+HIDE_SCOPE_NAMES       = NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen
+# will put a list of the files that are included by a file in the documentation
+# of that file.
+
+SHOW_INCLUDE_FILES     = YES
+
+# If the INLINE_INFO tag is set to YES (the default) then a tag [inline]
+# is inserted in the documentation for inline members.
+
+INLINE_INFO            = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen
+# will sort the (detailed) documentation of file and class members
+# alphabetically by member name. If set to NO the members will appear in
+# declaration order.
+
+SORT_MEMBER_DOCS       = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the
+# brief documentation of file, namespace and class members alphabetically
+# by member name. If set to NO (the default) the members will appear in
+# declaration order.
+
+SORT_BRIEF_DOCS        = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the
+# hierarchy of group names into alphabetical order. If set to NO (the default)
+# the group names will appear in their defined order.
+
+SORT_GROUP_NAMES       = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be
+# sorted by fully-qualified names, including namespaces. If set to
+# NO (the default), the class list will be sorted only by class name,
+# not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the
+# alphabetical list.
+
+SORT_BY_SCOPE_NAME     = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or
+# disable (NO) the todo list. This list is created by putting \todo
+# commands in the documentation.
+
+GENERATE_TODOLIST      = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or
+# disable (NO) the test list. This list is created by putting \test
+# commands in the documentation.
+
+GENERATE_TESTLIST      = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or
+# disable (NO) the bug list. This list is created by putting \bug
+# commands in the documentation.
+
+GENERATE_BUGLIST       = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or
+# disable (NO) the deprecated list. This list is created by putting
+# \deprecated commands in the documentation.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional
+# documentation sections, marked by \if sectionname ... \endif.
+
+ENABLED_SECTIONS       =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines
+# the initial value of a variable or define consists of for it to appear in
+# the documentation. If the initializer consists of more lines than specified
+# here it will be hidden. Use a value of 0 to hide initializers completely.
+# The appearance of the initializer of individual variables and defines in the
+# documentation can be controlled using \showinitializer or \hideinitializer
+# command in the documentation regardless of this setting.
+
+MAX_INITIALIZER_LINES  = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated
+# at the bottom of the documentation of classes and structs. If set to YES the
+# list will mention the files that were used to generate the documentation.
+
+SHOW_USED_FILES        = YES
+
+# If the sources in your project are distributed over multiple directories
+# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy
+# in the documentation. The default is NO.
+
+SHOW_DIRECTORIES       = NO
+
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page.
+# This will remove the Files entry from the Quick Index and from the
+# Folder Tree View (if specified). The default is YES.
+
+SHOW_FILES             = YES
+
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the
+# Namespaces page.
+# This will remove the Namespaces entry from the Quick Index
+# and from the Folder Tree View (if specified). The default is YES.
+
+SHOW_NAMESPACES        = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from
+# the version control system). Doxygen will invoke the program by executing (via
+# popen()) the command <command> <input-file>, where <command> is the value of
+# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file
+# provided by doxygen. Whatever the program writes to standard output
+# is used as the file version. See the manual for examples.
+
+FILE_VERSION_FILTER    =
+
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed by
+# doxygen. The layout file controls the global structure of the generated output files
+# in an output format independent way. The create the layout file that represents
+# doxygen's defaults, run doxygen with the -l option. You can optionally specify a
+# file name after the option, if omitted DoxygenLayout.xml will be used as the name
+# of the layout file.
+
+LAYOUT_FILE            =
+
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated
+# by doxygen. Possible values are YES and NO. If left blank NO is used.
+
+QUIET                  = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated by doxygen. Possible values are YES and NO. If left blank
+# NO is used.
+
+WARNINGS               = YES
+
+# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings
+# for undocumented members. If EXTRACT_ALL is set to YES then this flag will
+# automatically be disabled.
+
+WARN_IF_UNDOCUMENTED   = YES
+
+# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as not documenting some
+# parameters in a documented function, or documenting parameters that
+# don't exist or using markup commands wrongly.
+
+WARN_IF_DOC_ERROR      = YES
+
+# This WARN_NO_PARAMDOC option can be abled to get warnings for
+# functions that are documented, but have no documentation for their parameters
+# or return value. If set to NO (the default) doxygen will only warn about
+# wrong or incomplete parameter documentation, but not about the absence of
+# documentation.
+
+WARN_NO_PARAMDOC       = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that
+# doxygen can produce. The string should contain the $file, $line, and $text
+# tags, which will be replaced by the file and line number from which the
+# warning originated and the warning text. Optionally the format may contain
+# $version, which will be replaced by the version of the file (if it could
+# be obtained via FILE_VERSION_FILTER)
+
+WARN_FORMAT            = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning
+# and error messages should be written. If left blank the output is written
+# to stderr.
+
+WARN_LOGFILE           =
+
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag can be used to specify the files and/or directories that contain
+# documented source files. You may enter file names like "myfile.cpp" or
+# directories like "/usr/src/myproject". Separate the files or directories
+# with spaces.
+
+INPUT                  = .
+
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
+# also the default input encoding. Doxygen uses libiconv (or the iconv built
+# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for
+# the list of possible encodings.
+
+INPUT_ENCODING         = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank the following patterns are tested:
+# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx
+# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90
+
+FILE_PATTERNS          = *.h *.c
+
+# The RECURSIVE tag can be used to turn specify whether or not subdirectories
+# should be searched for input files as well. Possible values are YES and NO.
+# If left blank NO is used.
+
+RECURSIVE              = NO
+
+# The EXCLUDE tag can be used to specify files and/or directories that should
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+
+EXCLUDE                =
+
+# The EXCLUDE_SYMLINKS tag can be used select whether or not files or
+# directories that are symbolic links (a Unix filesystem feature) are excluded
+# from the input.
+
+EXCLUDE_SYMLINKS       = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories. Note that the wildcards are matched
+# against the file with absolute path, so to exclude all test directories
+# for example use the pattern */test/*
+
+EXCLUDE_PATTERNS       = testapp.c
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# AClass::ANamespace, ANamespace::*Test
+
+EXCLUDE_SYMBOLS        =
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or
+# directories that contain example code fragments that are included (see
+# the \include command).
+
+EXAMPLE_PATH           =
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank all files are included.
+
+EXAMPLE_PATTERNS       =
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude
+# commands irrespective of the value of the RECURSIVE tag.
+# Possible values are YES and NO. If left blank NO is used.
+
+EXAMPLE_RECURSIVE      = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or
+# directories that contain image that are included in the documentation (see
+# the \image command).
+
+IMAGE_PATH             =
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command <filter> <input-file>, where <filter>
+# is the value of the INPUT_FILTER tag, and <input-file> is the name of an
+# input file. Doxygen will then use the output that the filter program writes
+# to standard output.
+# If FILTER_PATTERNS is specified, this tag will be
+# ignored.
+
+INPUT_FILTER           =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis.
+# Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match.
+# The filters are a list of the form:
+# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further
+# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER
+# is applied to all files.
+
+FILTER_PATTERNS        =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER) will be used to filter the input files when producing source
+# files to browse (i.e. when SOURCE_BROWSER is set to YES).
+
+FILTER_SOURCE_FILES    = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will
+# be generated. Documented entities will be cross-referenced with these sources.
+# Note: To get rid of all source code in the generated output, make sure also
+# VERBATIM_HEADERS is set to NO.
+
+SOURCE_BROWSER         = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body
+# of functions and classes directly in the documentation.
+
+INLINE_SOURCES         = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct
+# doxygen to hide any special comment blocks from generated source code
+# fragments. Normal C and C++ comments will always remain visible.
+
+STRIP_CODE_COMMENTS    = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES
+# then for each documented function all documented
+# functions referencing it will be listed.
+
+REFERENCED_BY_RELATION = NO
+
+# If the REFERENCES_RELATION tag is set to YES
+# then for each documented function all documented entities
+# called/used by that function will be listed.
+
+REFERENCES_RELATION    = NO
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES (the default)
+# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from
+# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will
+# link to the source code.
+# Otherwise they will link to the documentation.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code
+# will point to the HTML generated by the htags(1) tool instead of doxygen
+# built-in source browser. The htags tool is part of GNU's global source
+# tagging system (see http://www.gnu.org/software/global/global.html). You
+# will need version 4.8.6 or higher.
+
+USE_HTAGS              = NO
+
+# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen
+# will generate a verbatim copy of the header file for each class for
+# which an include is specified. Set to NO to disable this.
+
+VERBATIM_HEADERS       = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index
+# of all compounds will be generated. Enable this if the project
+# contains a lot of classes, structs, unions or interfaces.
+
+ALPHABETICAL_INDEX     = NO
+
+# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then
+# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns
+# in which this list will be split (can be a number in the range [1..20])
+
+COLS_IN_ALPHA_INDEX    = 5
+
+# In case all classes in a project start with a common prefix, all
+# classes will be put under the same header in the alphabetical index.
+# The IGNORE_PREFIX tag can be used to specify one or more prefixes that
+# should be ignored while generating the index headers.
+
+IGNORE_PREFIX          =
+
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES (the default) Doxygen will
+# generate HTML output.
+
+GENERATE_HTML          = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `html' will be used as the default path.
+
+HTML_OUTPUT            = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for
+# each generated HTML page (for example: .htm,.php,.asp). If it is left blank
+# doxygen will generate files with .html extension.
+
+HTML_FILE_EXTENSION    = .html
+
+# The HTML_HEADER tag can be used to specify a personal HTML header for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard header.
+
+HTML_HEADER            =
+
+# The HTML_FOOTER tag can be used to specify a personal HTML footer for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard footer.
+
+HTML_FOOTER            =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading
+# style sheet that is used by each HTML page. It can be used to
+# fine-tune the look of the HTML output. If the tag is left blank doxygen
+# will generate a default style sheet. Note that doxygen will try to copy
+# the style sheet file to the HTML output directory, so don't put your own
+# stylesheet in the HTML output directory as well, or it will be erased!
+
+HTML_STYLESHEET        =
+
+# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes,
+# files or namespaces will be aligned in HTML using tables. If set to
+# NO a bullet list will be used.
+
+HTML_ALIGN_MEMBERS     = YES
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
+# documentation will contain sections that can be hidden and shown after the
+# page has loaded. For this to work a browser that supports
+# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox
+# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari).
+
+HTML_DYNAMIC_SECTIONS  = NO
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files
+# will be generated that can be used as input for Apple's Xcode 3
+# integrated development environment, introduced with OSX 10.5 (Leopard).
+# To create a documentation set, doxygen will generate a Makefile in the
+# HTML output directory. Running make will produce the docset in that
+# directory and running "make install" will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find
+# it at startup.
+# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html for more information.
+
+GENERATE_DOCSET        = NO
+
+# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the
+# feed. A documentation feed provides an umbrella under which multiple
+# documentation sets from a single provider (such as a company or product suite)
+# can be grouped.
+
+DOCSET_FEEDNAME        = "Doxygen generated docs"
+
+# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that
+# should uniquely identify the documentation set bundle. This should be a
+# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen
+# will append .docset to the name.
+
+DOCSET_BUNDLE_ID       = org.doxygen.Project
+
+# If the GENERATE_HTMLHELP tag is set to YES, additional index files
+# will be generated that can be used as input for tools like the
+# Microsoft HTML help workshop to generate a compiled HTML help file (.chm)
+# of the generated HTML documentation.
+
+GENERATE_HTMLHELP      = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can
+# be used to specify the file name of the resulting .chm file. You
+# can add a path in front of the file if the result should not be
+# written to the html output directory.
+
+CHM_FILE               =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can
+# be used to specify the location (absolute path including file name) of
+# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run
+# the HTML help compiler on the generated index.hhp.
+
+HHC_LOCATION           =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag
+# controls if a separate .chi index file is generated (YES) or that
+# it should be included in the master .chm file (NO).
+
+GENERATE_CHI           = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING
+# is used to encode HtmlHelp index (hhk), content (hhc) and project file
+# content.
+
+CHM_INDEX_ENCODING     =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag
+# controls whether a binary table of contents is generated (YES) or a
+# normal table of contents (NO) in the .chm file.
+
+BINARY_TOC             = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members
+# to the contents of the HTML help documentation and to the tree view.
+
+TOC_EXPAND             = NO
+
+# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and QHP_VIRTUAL_FOLDER
+# are set, an additional index file will be generated that can be used as input for
+# Qt's qhelpgenerator to generate a Qt Compressed Help (.qch) of the generated
+# HTML documentation.
+
+GENERATE_QHP           = NO
+
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can
+# be used to specify the file name of the resulting .qch file.
+# The path specified is relative to the HTML output folder.
+
+QCH_FILE               =
+
+# The QHP_NAMESPACE tag specifies the namespace to use when generating
+# Qt Help Project output. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#namespace
+
+QHP_NAMESPACE          =
+
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating
+# Qt Help Project output. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#virtual-folders
+
+QHP_VIRTUAL_FOLDER     = doc
+
+# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to add.
+# For more information please see
+# http://doc.trolltech.com/qthelpproject.html#custom-filters
+
+QHP_CUST_FILTER_NAME   =
+
+# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the custom filter to add.For more information please see
+# <a href="http://doc.trolltech.com/qthelpproject.html#custom-filters">Qt Help Project / Custom Filters</a>.
+
+QHP_CUST_FILTER_ATTRS  =
+
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this project's
+# filter section matches.
+# <a href="http://doc.trolltech.com/qthelpproject.html#filter-attributes">Qt Help Project / Filter Attributes</a>.
+
+QHP_SECT_FILTER_ATTRS  =
+
+# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can
+# be used to specify the location of Qt's qhelpgenerator.
+# If non-empty doxygen will try to run qhelpgenerator on the generated
+# .qhp file.
+
+QHG_LOCATION           =
+
+# The DISABLE_INDEX tag can be used to turn on/off the condensed index at
+# top of each HTML page. The value NO (the default) enables the index and
+# the value YES disables it.
+
+DISABLE_INDEX          = NO
+
+# This tag can be used to set the number of enum values (range [1..20])
+# that doxygen will group on one line in the generated HTML documentation.
+
+ENUM_VALUES_PER_LINE   = 4
+
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
+# structure should be generated to display hierarchical information.
+# If the tag value is set to FRAME, a side panel will be generated
+# containing a tree-like index structure (just like the one that
+# is generated for HTML Help). For this to work a browser that supports
+# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+,
+# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are
+# probably better off using the HTML help feature. Other possible values
+# for this tag are: HIERARCHIES, which will generate the Groups, Directories,
+# and Class Hierarchy pages using a tree view instead of an ordered list;
+# ALL, which combines the behavior of FRAME and HIERARCHIES; and NONE, which
+# disables this behavior completely. For backwards compatibility with previous
+# releases of Doxygen, the values YES and NO are equivalent to FRAME and NONE
+# respectively.
+
+GENERATE_TREEVIEW      = NONE
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be
+# used to set the initial width (in pixels) of the frame in which the tree
+# is shown.
+
+TREEVIEW_WIDTH         = 250
+
+# Use this tag to change the font size of Latex formulas included
+# as images in the HTML documentation. The default is 10. Note that
+# when you change the font size after a successful doxygen run you need
+# to manually remove any form_*.png images from the HTML output directory
+# to force them to be regenerated.
+
+FORMULA_FONTSIZE       = 10
+
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will
+# generate Latex output.
+
+GENERATE_LATEX         = NO
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `latex' will be used as the default path.
+
+LATEX_OUTPUT           = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked. If left blank `latex' will be used as the default command name.
+
+LATEX_CMD_NAME         = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to
+# generate index for LaTeX. If left blank `makeindex' will be used as the
+# default command name.
+
+MAKEINDEX_CMD_NAME     = makeindex
+
+# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact
+# LaTeX documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_LATEX          = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used
+# by the printer. Possible values are: a4, a4wide, letter, legal and
+# executive. If left blank a4wide will be used.
+
+PAPER_TYPE             = a4wide
+
+# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX
+# packages that should be included in the LaTeX output.
+
+EXTRA_PACKAGES         =
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for
+# the generated latex document. The header should contain everything until
+# the first chapter. If it is left blank doxygen will generate a
+# standard header. Notice: only use this tag if you know what you are doing!
+
+LATEX_HEADER           =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated
+# is prepared for conversion to pdf (using ps2pdf). The pdf file will
+# contain links (just like the HTML output) instead of page references
+# This makes the output suitable for online browsing using a pdf viewer.
+
+PDF_HYPERLINKS         = YES
+
+# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of
+# plain latex in the generated Makefile. Set this option to YES to get a
+# higher quality PDF documentation.
+
+USE_PDFLATEX           = YES
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode.
+# command to the generated LaTeX files. This will instruct LaTeX to keep
+# running if errors occur, instead of asking the user for help.
+# This option is also used when generating formulas in HTML.
+
+LATEX_BATCHMODE        = NO
+
+# If LATEX_HIDE_INDICES is set to YES then doxygen will not
+# include the index chapters (such as File Index, Compound Index, etc.)
+# in the output.
+
+LATEX_HIDE_INDICES     = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output
+# The RTF output is optimized for Word 97 and may not look very pretty with
+# other RTF readers or editors.
+
+GENERATE_RTF           = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `rtf' will be used as the default path.
+
+RTF_OUTPUT             = rtf
+
+# If the COMPACT_RTF tag is set to YES Doxygen generates more compact
+# RTF documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_RTF            = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated
+# will contain hyperlink fields. The RTF file will
+# contain links (just like the HTML output) instead of page references.
+# This makes the output suitable for online browsing using WORD or other
+# programs which support those fields.
+# Note: wordpad (write) and others do not support links.
+
+RTF_HYPERLINKS         = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's
+# config file, i.e. a series of assignments. You only have to provide
+# replacements, missing definitions are set to their default value.
+
+RTF_STYLESHEET_FILE    =
+
+# Set optional variables used in the generation of an rtf document.
+# Syntax is similar to doxygen's config file.
+
+RTF_EXTENSIONS_FILE    =
+
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES (the default) Doxygen will
+# generate man pages
+
+GENERATE_MAN           = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `man' will be used as the default path.
+
+MAN_OUTPUT             = man
+
+# The MAN_EXTENSION tag determines the extension that is added to
+# the generated man pages (default is the subroutine's section .3)
+
+MAN_EXTENSION          = .3
+
+# If the MAN_LINKS tag is set to YES and Doxygen generates man output,
+# then it will generate one additional man file for each entity
+# documented in the real man page(s). These additional files
+# only source the real man page, but without them the man command
+# would be unable to find the correct page. The default is NO.
+
+MAN_LINKS              = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES Doxygen will
+# generate an XML file that captures the structure of
+# the code including all documentation.
+
+GENERATE_XML           = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `xml' will be used as the default path.
+
+XML_OUTPUT             = xml
+
+# The XML_SCHEMA tag can be used to specify an XML schema,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_SCHEMA             =
+
+# The XML_DTD tag can be used to specify an XML DTD,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_DTD                =
+
+# If the XML_PROGRAMLISTING tag is set to YES Doxygen will
+# dump the program listings (including syntax highlighting
+# and cross-referencing information) to the XML output. Note that
+# enabling this will significantly increase the size of the XML output.
+
+XML_PROGRAMLISTING     = YES
+
+#---------------------------------------------------------------------------
+# configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will
+# generate an AutoGen Definitions (see autogen.sf.net) file
+# that captures the structure of the code including all
+# documentation. Note that this feature is still experimental
+# and incomplete at the moment.
+
+GENERATE_AUTOGEN_DEF   = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES Doxygen will
+# generate a Perl module file that captures the structure of
+# the code including all documentation. Note that this
+# feature is still experimental and incomplete at the
+# moment.
+
+GENERATE_PERLMOD       = NO
+
+# If the PERLMOD_LATEX tag is set to YES Doxygen will generate
+# the necessary Makefile rules, Perl scripts and LaTeX code to be able
+# to generate PDF and DVI output from the Perl module output.
+
+PERLMOD_LATEX          = NO
+
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be
+# nicely formatted so it can be parsed by a human reader.
+# This is useful
+# if you want to understand what is going on.
+# On the other hand, if this
+# tag is set to NO the size of the Perl module output will be much smaller
+# and Perl will parse it just the same.
+
+PERLMOD_PRETTY         = YES
+
+# The names of the make variables in the generated doxyrules.make file
+# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX.
+# This is useful so different doxyrules.make files included by the same
+# Makefile don't overwrite each other's variables.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will
+# evaluate all C-preprocessor directives found in the sources and include
+# files.
+
+ENABLE_PREPROCESSING   = YES
+
+# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro
+# names in the source code. If set to NO (the default) only conditional
+# compilation will be performed. Macro expansion can be done in a controlled
+# way by setting EXPAND_ONLY_PREDEF to YES.
+
+MACRO_EXPANSION        = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES
+# then the macro expansion is limited to the macros specified with the
+# PREDEFINED and EXPAND_AS_DEFINED tags.
+
+EXPAND_ONLY_PREDEF     = NO
+
+# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files
+# in the INCLUDE_PATH (see below) will be search if a #include is found.
+
+SEARCH_INCLUDES        = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by
+# the preprocessor.
+
+INCLUDE_PATH           =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will
+# be used.
+
+INCLUDE_FILE_PATTERNS  =
+
+# The PREDEFINED tag can be used to specify one or more macro names that
+# are defined before the preprocessor is started (similar to the -D option of
+# gcc). The argument of the tag is a list of macros of the form: name
+# or name=definition (no spaces). If the definition and the = are
+# omitted =1 is assumed. To prevent a macro definition from being
+# undefined via #undef or recursively expanded use the := operator
+# instead of the = operator.
+
+PREDEFINED             =
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then
+# this tag can be used to specify a list of macro names that should be expanded.
+# The macro definition that is found in the sources will be used.
+# Use the PREDEFINED tag if you want to use a different macro definition.
+
+EXPAND_AS_DEFINED      =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then
+# doxygen's preprocessor will remove all function-like macros that are alone
+# on a line, have an all uppercase name, and do not end with a semicolon. Such
+# function macros are typically used for boiler-plate code, and will confuse
+# the parser if not removed.
+
+SKIP_FUNCTION_MACROS   = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES option can be used to specify one or more tagfiles.
+# Optionally an initial location of the external documentation
+# can be added for each tagfile. The format of a tag file without
+# this location is as follows:
+#
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+#
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where "loc1" and "loc2" can be relative or absolute paths or
+# URLs. If a location is present for each tag, the installdox tool
+# does not have to be run to correct the links.
+# Note that each tag file must have a unique name
+# (where the name does NOT include the path)
+# If a tag file is not located in the directory in which doxygen
+# is run, you must also specify the path to the tagfile here.
+
+TAGFILES               =
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create
+# a tag file that is based on the input files it reads.
+
+GENERATE_TAGFILE       =
+
+# If the ALLEXTERNALS tag is set to YES all external classes will be listed
+# in the class index. If set to NO only the inherited external classes
+# will be listed.
+
+ALLEXTERNALS           = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed
+# in the modules index. If set to NO, only the current project's groups will
+# be listed.
+
+EXTERNAL_GROUPS        = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script
+# interpreter (i.e. the result of `which perl').
+
+PERL_PATH              = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will
+# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base
+# or super classes. Setting the tag to NO turns the diagrams off. Note that
+# this option is superseded by the HAVE_DOT option below. This is only a
+# fallback. It is recommended to install and use dot, since it yields more
+# powerful graphs.
+
+CLASS_DIAGRAMS         = YES
+
+# You can define message sequence charts within doxygen comments using the \msc
+# command. Doxygen will then run the mscgen tool (see
+# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the
+# documentation. The MSCGEN_PATH tag allows you to specify the directory where
+# the mscgen tool resides. If left empty the tool is assumed to be found in the
+# default search path.
+
+MSCGEN_PATH            =
+
+# If set to YES, the inheritance and collaboration graphs will hide
+# inheritance and usage relations if the target is undocumented
+# or is not a class.
+
+HIDE_UNDOC_RELATIONS   = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz, a graph visualization
+# toolkit from AT&T and Lucent Bell Labs. The other options in this section
+# have no effect if this option is set to NO (the default)
+
+HAVE_DOT               = YES
+
+# By default doxygen will write a font called FreeSans.ttf to the output
+# directory and reference it in all dot files that doxygen generates. This
+# font does not include all possible unicode characters however, so when you need
+# these (or just want a differently looking font) you can specify the font name
+# using DOT_FONTNAME. You need need to make sure dot is able to find the font,
+# which can be done by putting it in a standard location or by setting the
+# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory
+# containing the font.
+
+DOT_FONTNAME           = FreeSans
+
+# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs.
+# The default size is 10pt.
+
+DOT_FONTSIZE           = 10
+
+# By default doxygen will tell dot to use the output directory to look for the
+# FreeSans.ttf font (which doxygen will put there itself). If you specify a
+# different font using DOT_FONTNAME you can set the path where dot
+# can find it using this tag.
+
+DOT_FONTPATH           =
+
+# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect inheritance relations. Setting this tag to YES will force the
+# the CLASS_DIAGRAMS tag to NO.
+
+CLASS_GRAPH            = YES
+
+# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect implementation dependencies (inheritance, containment, and
+# class references variables) of the class with other documented classes.
+
+COLLABORATION_GRAPH    = YES
+
+# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for groups, showing the direct groups dependencies
+
+GROUP_GRAPHS           = YES
+
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+
+UML_LOOK               = NO
+
+# If set to YES, the inheritance and collaboration graphs will show the
+# relations between templates and their instances.
+
+TEMPLATE_RELATIONS     = NO
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT
+# tags are set to YES then doxygen will generate a graph for each documented
+# file showing the direct and indirect include dependencies of the file with
+# other documented files.
+
+INCLUDE_GRAPH          = YES
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and
+# HAVE_DOT tags are set to YES then doxygen will generate a graph for each
+# documented header file showing the documented files that directly or
+# indirectly include this file.
+
+INCLUDED_BY_GRAPH      = YES
+
+# If the CALL_GRAPH and HAVE_DOT options are set to YES then
+# doxygen will generate a call dependency graph for every global function
+# or class method. Note that enabling this option will significantly increase
+# the time of a run. So in most cases it will be better to enable call graphs
+# for selected functions only using the \callgraph command.
+
+CALL_GRAPH             = YES
+
+# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then
+# doxygen will generate a caller dependency graph for every global function
+# or class method. Note that enabling this option will significantly increase
+# the time of a run. So in most cases it will be better to enable caller
+# graphs for selected functions only using the \callergraph command.
+
+CALLER_GRAPH           = YES
+
+# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen
+# will graphical hierarchy of all classes instead of a textual one.
+
+GRAPHICAL_HIERARCHY    = YES
+
+# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES
+# then doxygen will show the dependencies a directory has on other directories
+# in a graphical way. The dependency relations are determined by the #include
+# relations between the files in the directories.
+
+DIRECTORY_GRAPH        = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot. Possible values are png, jpg, or gif
+# If left blank png will be used.
+
+DOT_IMAGE_FORMAT       = png
+
+# The tag DOT_PATH can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+
+DOT_PATH               =
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the
+# \dotfile command).
+
+DOTFILE_DIRS           =
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of
+# nodes that will be shown in the graph. If the number of nodes in a graph
+# becomes larger than this value, doxygen will truncate the graph, which is
+# visualized by representing a node as a red box. Note that doxygen if the
+# number of direct children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note
+# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+
+DOT_GRAPH_MAX_NODES    = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the
+# graphs generated by dot. A depth value of 3 means that only nodes reachable
+# from the root by following a path via at most 3 edges will be shown. Nodes
+# that lay further from the root node will be omitted. Note that setting this
+# option to 1 or 2 may greatly reduce the computation time needed for large
+# code bases. Also note that the size of a graph can be further restricted by
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+
+MAX_DOT_GRAPH_DEPTH    = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
+# background. This is disabled by default, because dot on Windows does not
+# seem to support this out of the box. Warning: Depending on the platform used,
+# enabling this option may lead to badly anti-aliased labels on the edges of
+# a graph (i.e. they become hard to read).
+
+DOT_TRANSPARENT        = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10)
+# support this, this feature is disabled by default.
+
+DOT_MULTI_TARGETS      = NO
+
+# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will
+# generate a legend page explaining the meaning of the various boxes and
+# arrows in the dot generated graphs.
+
+GENERATE_LEGEND        = YES
+
+# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will
+# remove the intermediate dot files that are used to generate
+# the various graphs.
+
+DOT_CLEANUP            = YES
+
+#---------------------------------------------------------------------------
+# Options related to the search engine
+#---------------------------------------------------------------------------
+
+# The SEARCHENGINE tag specifies whether or not a search engine should be
+# used. If set to NO the values of all tags below this one will be ignored.
+
+SEARCHENGINE           = NO
diff --git a/memcached/HACKING b/memcached/HACKING
new file mode 100644 (file)
index 0000000..60bfa9f
--- /dev/null
@@ -0,0 +1,52 @@
+* Hacking Memcached
+
+* Prerequisites
+
+ - autoconf
+ - automake
+ - autotools
+ - libevent
+
+* Getting Started
+
+After checking out a git repository, you must first run autogen.sh
+once in order to create the configure script.
+
+Next, run the configure script and start doing builds.
+
+* Setting up Git
+
+Though not required, there are a couple of things you can add to git
+to help development.
+
+** Pre Commit Hook
+
+The pre-commit hook can be used to ensure that your tree passes tests
+before allowing a commit.  To do so, add the following to
+.git/hooks/pre-commit (which must be executable):
+
+    #!/bin/sh
+    make test
+
+** Post Commit Hook
+
+Because the version number changes on each commit, it's good to use a
+post commit hook to update the version number after each commit so as
+to keep the reporting accurate.  To do so, add the following to
+.git/hooks/post-commit (which must be executable)
+
+    #!/bin/sh
+    ./version.sh
+
+** Running memcached in gdb for tests.
+
+By default `make test` will spawn a memcached daemon for each test.
+This doesn't let you easily drop into gdb or run verbosely.
+
+If you export the environment variable
+T_MEMD_USE_DAEMON="127.0.0.1:11211" the tests will use an existing
+daemon at that address.
+
+* Sending patches
+
+See current instructions at http://contributing.appspot.com/memcached
diff --git a/memcached/LICENSE b/memcached/LICENSE
new file mode 100644 (file)
index 0000000..4746b00
--- /dev/null
@@ -0,0 +1,30 @@
+Copyright (c) 2003, Danga Interactive, Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+    * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+
+    * Neither the name of the Danga Interactive nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/memcached/Makefile.am b/memcached/Makefile.am
new file mode 100644 (file)
index 0000000..3cc5952
--- /dev/null
@@ -0,0 +1,105 @@
+bin_PROGRAMS = memcached
+pkginclude_HEADERS = protocol_binary.h
+noinst_PROGRAMS = memcached-debug sizes testapp timedrun
+
+BUILT_SOURCES=
+
+testapp_SOURCES = testapp.c util.c util.h
+
+timedrun_SOURCES = timedrun.c
+
+memcached_SOURCES = memcached.c memcached.h \
+                    hash.c hash.h \
+                    slabs.c slabs.h \
+                    items.c items.h \
+                    assoc.c assoc.h \
+                    thread.c daemon.c \
+                    stats.c stats.h \
+                    util.c util.h \
+                    trace.h cache.h sasl_defs.h
+
+if BUILD_CACHE
+memcached_SOURCES += cache.c
+testapp_SOURCES += cache.c
+endif
+
+if BUILD_SOLARIS_PRIVS
+memcached_SOURCES += solaris_priv.c
+endif
+
+if ENABLE_SASL
+memcached_SOURCES += sasl_defs.c
+endif
+
+memcached_debug_SOURCES = $(memcached_SOURCES)
+memcached_CPPFLAGS = -DNDEBUG
+memcached_debug_LDADD = @PROFILER_LDFLAGS@
+memcached_debug_CFLAGS = @PROFILER_FLAGS@
+
+memcached_LDADD =
+memcached_DEPENDENCIES =
+memcached_debug_DEPENDENCIES =
+CLEANFILES=
+
+if BUILD_DTRACE
+BUILT_SOURCES += memcached_dtrace.h
+CLEANFILES += memcached_dtrace.h
+endif
+
+if DTRACE_INSTRUMENT_OBJ
+memcached_LDADD += memcached_dtrace.o
+memcached_DEPENDENCIES += memcached_dtrace.o
+memcached_debug_LDADD += memcached_debug_dtrace.o
+memcached_debug_DEPENDENCIES += memcached_debug_dtrace.o
+CLEANFILES += memcached_dtrace.o memcached_debug_dtrace.o
+endif
+
+memcached_dtrace.h: memcached_dtrace.d
+       ${DTRACE} -h -s memcached_dtrace.d
+       sed -e 's,void \*,const void \*,g' memcached_dtrace.h | \
+            sed -e 's,char \*,const char \*,g' | tr '\t' ' ' > mmc_dtrace.tmp
+       mv mmc_dtrace.tmp memcached_dtrace.h
+
+memcached_dtrace.o: $(memcached_OBJECTS)
+       $(DTRACE) $(DTRACEFLAGS) -G -o memcached_dtrace.o -s ${srcdir}/memcached_dtrace.d $(memcached_OBJECTS)
+
+memcached_debug_dtrace.o: $(memcached_debug_OBJECTS)
+       $(DTRACE) $(DTRACEFLAGS) -G -o memcached_debug_dtrace.o -s ${srcdir}/memcached_dtrace.d $(memcached_debug_OBJECTS)
+
+
+SUBDIRS = doc
+DIST_DIRS = scripts
+EXTRA_DIST = doc scripts t memcached.spec memcached_dtrace.d version.m4
+
+MOSTLYCLEANFILES = *.gcov *.gcno *.gcda *.tcov
+
+test:  memcached-debug sizes testapp
+       $(srcdir)/sizes
+       $(srcdir)/testapp
+       prove $(srcdir)/t
+       @if test `basename $(PROFILER)` = "gcov"; then \
+         for file in memcached_debug-*.gc??; do \
+           mv -f $$file `echo $$file | sed 's/memcached_debug-//'`; \
+         done && \
+         for file in *.gcda; do \
+           srcfile=`echo $$file | sed 's/.gcda/.c/'`; \
+           if test -n "`echo $(memcached_debug_SOURCES) | grep $$srcfile`"; then \
+             echo `$(PROFILER) $$srcfile` | sed 's/'$$srcfile':.*//'; \
+           fi \
+         done \
+       elif test `basename $(PROFILER)` = "tcov"; then \
+         files=`grep SRCFILE memcached-debug.profile/tcovd | sed 's/SRCFILE://' | sort | uniq` && \
+         $(PROFILER) -x memcached-debug.profile $$files 2>&1; \
+         for file in *.tcov; do \
+           srcfile=`echo $$file | sed 's/.tcov//'`; \
+           if test -n "`echo $(memcached_debug_SOURCES) | grep $$srcfile`"; then \
+             echo $$srcfile : `grep 'Percent of the file executed' $$file`; \
+           fi \
+         done \
+       else :; fi
+
+docs:
+       (cat Doxyfile ; echo "PROJECT_NUMBER=`cat version.num`") | doxygen -
+
+dist-hook:
+       rm -f $(distdir)/*/*~ $(distdir)/t/lib/*~ $(distdir)/*~
diff --git a/memcached/NEWS b/memcached/NEWS
new file mode 100644 (file)
index 0000000..78b1eca
--- /dev/null
@@ -0,0 +1 @@
+http://www.danga.com/memcached/news.bml
diff --git a/memcached/README b/memcached/README
new file mode 100644 (file)
index 0000000..f20dd8c
--- /dev/null
@@ -0,0 +1,28 @@
+Dependencies:
+
+   -- libevent, http://www.monkey.org/~provos/libevent/ (libevent-dev)
+
+If using Linux, you need a kernel with epoll.  Sure, libevent will
+work with normal select, but it sucks.
+
+epoll isn't in Linux 2.4, but there's a backport at:
+
+    http://www.xmailserver.org/linux-patches/nio-improve.html
+
+You want the epoll-lt patch (level-triggered).
+
+If you're using MacOS, you'll want libevent 1.1 or higher to deal with
+a kqueue bug.
+
+Also, be warned that the -k (mlockall) option to memcached might be
+dangerous when using a large cache.  Just make sure the memcached machines
+don't swap.  memcached does non-blocking network I/O, but not disk.  (it
+should never go to disk, or you've lost the whole point of it)
+
+The memcached website is at:
+
+    http://www.memcached.org
+
+Want to contribute?  Up-to-date pointers should be at:
+
+    http://contributing.appspot.com/memcached
diff --git a/memcached/assoc.c b/memcached/assoc.c
new file mode 100644 (file)
index 0000000..f14d85d
--- /dev/null
@@ -0,0 +1,280 @@
+/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Hash table
+ *
+ * The hash function used here is by Bob Jenkins, 1996:
+ *    <http://burtleburtle.net/bob/hash/doobs.html>
+ *       "By Bob Jenkins, 1996.  bob_jenkins@burtleburtle.net.
+ *       You may use this code any way you wish, private, educational,
+ *       or commercial.  It's free."
+ *
+ * The rest of the file is licensed under the BSD license.  See LICENSE.
+ */
+
+#include "memcached.h"
+
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/signal.h>
+#include <sys/resource.h>
+#include <fcntl.h>
+#include <netinet/in.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include <pthread.h>
+
+static pthread_cond_t maintenance_cond = PTHREAD_COND_INITIALIZER;
+
+#ifndef __INTEL_COMPILER
+#pragma GCC diagnostic ignored "-Wstrict-aliasing"
+#endif
+
+#ifndef __INTEL_COMPILER
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#endif
+
+typedef  unsigned long  int  ub4;   /* unsigned 4-byte quantities */
+typedef  unsigned       char ub1;   /* unsigned 1-byte quantities */
+
+/* how many powers of 2's worth of buckets we use */
+static unsigned int hashpower = HASHPOWER_DEFAULT;
+
+#define hashsize(n) ((ub4)1<<(n))
+#define hashmask(n) (hashsize(n)-1)
+
+/* Main hash table. This is where we look except during expansion. */
+static item** primary_hashtable = 0;
+
+/*
+ * Previous hash table. During expansion, we look here for keys that haven't
+ * been moved over to the primary yet.
+ */
+static item** old_hashtable = 0;
+
+/* Number of items in the hash table. */
+static unsigned int hash_items = 0;
+
+/* Flag: Are we in the middle of expanding now? */
+static bool expanding = false;
+
+/*
+ * During expansion we migrate values with bucket granularity; this is how
+ * far we've gotten so far. Ranges from 0 .. hashsize(hashpower - 1) - 1.
+ */
+static unsigned int expand_bucket = 0;
+
+void assoc_init(const int hashtable_init) {
+    if (hashtable_init) {
+        hashpower = hashtable_init;
+    }
+    primary_hashtable = calloc(hashsize(hashpower), sizeof(void *));
+    if (! primary_hashtable) {
+        fprintf(stderr, "Failed to init hashtable.\n");
+        exit(EXIT_FAILURE);
+    }
+    STATS_LOCK();
+    stats.hash_power_level = hashpower;
+    stats.hash_bytes = hashsize(hashpower) * sizeof(void *);
+    STATS_UNLOCK();
+}
+
+item *assoc_find(const char *key, const size_t nkey, const uint32_t hv) {
+    item *it;
+    unsigned int oldbucket;
+
+    if (expanding &&
+        (oldbucket = (hv & hashmask(hashpower - 1))) >= expand_bucket)
+    {
+        it = old_hashtable[oldbucket];
+    } else {
+        it = primary_hashtable[hv & hashmask(hashpower)];
+    }
+
+    item *ret = NULL;
+    int depth = 0;
+    while (it) {
+        if ((nkey == it->nkey) && (memcmp(key, ITEM_key(it), nkey) == 0)) {
+            ret = it;
+            break;
+        }
+        it = it->h_next;
+        ++depth;
+    }
+    MEMCACHED_ASSOC_FIND(key, nkey, depth);
+    return ret;
+}
+
+/* returns the address of the item pointer before the key.  if *item == 0,
+   the item wasn't found */
+
+static item** _hashitem_before (const char *key, const size_t nkey, const uint32_t hv) {
+    item **pos;
+    unsigned int oldbucket;
+
+    if (expanding &&
+        (oldbucket = (hv & hashmask(hashpower - 1))) >= expand_bucket)
+    {
+        pos = &old_hashtable[oldbucket];
+    } else {
+        pos = &primary_hashtable[hv & hashmask(hashpower)];
+    }
+
+    while (*pos && ((nkey != (*pos)->nkey) || memcmp(key, ITEM_key(*pos), nkey))) {
+        pos = &(*pos)->h_next;
+    }
+    return pos;
+}
+
+/* grows the hashtable to the next power of 2. */
+static void assoc_expand(void) {
+    old_hashtable = primary_hashtable;
+
+    primary_hashtable = calloc(hashsize(hashpower + 1), sizeof(void *));
+    if (primary_hashtable) {
+        if (settings.verbose > 1)
+            fprintf(stderr, "Hash table expansion starting\n");
+        hashpower++;
+        expanding = true;
+        expand_bucket = 0;
+        STATS_LOCK();
+        stats.hash_power_level = hashpower;
+        stats.hash_bytes += hashsize(hashpower) * sizeof(void *);
+        stats.hash_is_expanding = 1;
+        STATS_UNLOCK();
+        pthread_cond_signal(&maintenance_cond);
+    } else {
+        primary_hashtable = old_hashtable;
+        /* Bad news, but we can keep running. */
+    }
+}
+
+/* Note: this isn't an assoc_update.  The key must not already exist to call this */
+int assoc_insert(item *it, const uint32_t hv) {
+    unsigned int oldbucket;
+
+//    assert(assoc_find(ITEM_key(it), it->nkey) == 0);  /* shouldn't have duplicately named things defined */
+
+    if (expanding &&
+        (oldbucket = (hv & hashmask(hashpower - 1))) >= expand_bucket)
+    {
+        it->h_next = old_hashtable[oldbucket];
+        old_hashtable[oldbucket] = it;
+    } else {
+        it->h_next = primary_hashtable[hv & hashmask(hashpower)];
+        primary_hashtable[hv & hashmask(hashpower)] = it;
+    }
+
+    hash_items++;
+    if (! expanding && hash_items > (hashsize(hashpower) * 3) / 2) {
+        assoc_expand();
+    }
+
+    MEMCACHED_ASSOC_INSERT(ITEM_key(it), it->nkey, hash_items);
+    return 1;
+}
+
+void assoc_delete(const char *key, const size_t nkey, const uint32_t hv) {
+    item **before = _hashitem_before(key, nkey, hv);
+
+    if (*before) {
+        item *nxt;
+        hash_items--;
+        /* The DTrace probe cannot be triggered as the last instruction
+         * due to possible tail-optimization by the compiler
+         */
+        MEMCACHED_ASSOC_DELETE(key, nkey, hash_items);
+        nxt = (*before)->h_next;
+        (*before)->h_next = 0;   /* probably pointless, but whatever. */
+        *before = nxt;
+        return;
+    }
+    /* Note:  we never actually get here.  the callers don't delete things
+       they can't find. */
+    assert(*before != 0);
+}
+
+
+static volatile int do_run_maintenance_thread = 1;
+
+#define DEFAULT_HASH_BULK_MOVE 1
+int hash_bulk_move = DEFAULT_HASH_BULK_MOVE;
+
+static void *assoc_maintenance_thread(void *arg) {
+
+    while (do_run_maintenance_thread) {
+        int ii = 0;
+
+        /* Lock the cache, and bulk move multiple buckets to the new
+         * hash table. */
+        mutex_lock(&cache_lock);
+
+        for (ii = 0; ii < hash_bulk_move && expanding; ++ii) {
+            item *it, *next;
+            int bucket;
+
+            for (it = old_hashtable[expand_bucket]; NULL != it; it = next) {
+                next = it->h_next;
+
+                bucket = hash(ITEM_key(it), it->nkey, 0) & hashmask(hashpower);
+                it->h_next = primary_hashtable[bucket];
+                primary_hashtable[bucket] = it;
+            }
+
+            old_hashtable[expand_bucket] = NULL;
+
+            expand_bucket++;
+            if (expand_bucket == hashsize(hashpower - 1)) {
+                expanding = false;
+                free(old_hashtable);
+                STATS_LOCK();
+                stats.hash_bytes -= hashsize(hashpower - 1) * sizeof(void *);
+                stats.hash_is_expanding = 0;
+                STATS_UNLOCK();
+                if (settings.verbose > 1)
+                    fprintf(stderr, "Hash table expansion done\n");
+            }
+        }
+
+        if (!expanding) {
+            /* We are done expanding.. just wait for next invocation */
+            pthread_cond_wait(&maintenance_cond, &cache_lock);
+        }
+
+        pthread_mutex_unlock(&cache_lock);
+    }
+    return NULL;
+}
+
+static pthread_t maintenance_tid;
+
+int start_assoc_maintenance_thread() {
+    int ret;
+    char *env = getenv("MEMCACHED_HASH_BULK_MOVE");
+    if (env != NULL) {
+        hash_bulk_move = atoi(env);
+        if (hash_bulk_move == 0) {
+            hash_bulk_move = DEFAULT_HASH_BULK_MOVE;
+        }
+    }
+    if ((ret = pthread_create(&maintenance_tid, NULL,
+                              assoc_maintenance_thread, NULL)) != 0) {
+        fprintf(stderr, "Can't create thread: %s\n", strerror(ret));
+        return -1;
+    }
+    return 0;
+}
+
+void stop_assoc_maintenance_thread() {
+    mutex_lock(&cache_lock);
+    do_run_maintenance_thread = 0;
+    pthread_cond_signal(&maintenance_cond);
+    pthread_mutex_unlock(&cache_lock);
+
+    /* Wait for the maintenance thread to stop */
+    pthread_join(maintenance_tid, NULL);
+}
+
+
diff --git a/memcached/assoc.h b/memcached/assoc.h
new file mode 100644 (file)
index 0000000..ccdfdd5
--- /dev/null
@@ -0,0 +1,9 @@
+/* associative array */
+void assoc_init(const int hashpower_init);
+item *assoc_find(const char *key, const size_t nkey, const uint32_t hv);
+int assoc_insert(item *item, const uint32_t hv);
+void assoc_delete(const char *key, const size_t nkey, const uint32_t hv);
+void do_assoc_move_next_bucket(void);
+int start_assoc_maintenance_thread(void);
+void stop_assoc_maintenance_thread(void);
+
diff --git a/memcached/autogen.sh b/memcached/autogen.sh
new file mode 100755 (executable)
index 0000000..c735394
--- /dev/null
@@ -0,0 +1,52 @@
+#!/bin/sh
+
+# Get the initial version.
+perl version.pl
+
+die() {
+    echo "$@"
+    exit 1
+}
+
+# Try to locate a program by using which, and verify that the file is an
+# executable
+locate_binary() {
+  for f in $@
+  do
+    file=`which $f 2>/dev/null | grep -v '^no '`
+    if test -n "$file" -a -x "$file"; then
+      echo $file
+      return 0
+    fi
+  done
+
+  echo ""
+  return 1
+}
+
+echo "aclocal..."
+if test x$ACLOCAL = x; then
+  ACLOCAL=`locate_binary aclocal-1.11 aclocal-1.10 aclocal-1.9 aclocal19 aclocal-1.7 aclocal17 aclocal-1.5 aclocal15 aclocal`
+  if test x$ACLOCAL = x; then
+    die "Did not find a supported aclocal"
+  fi
+fi
+$ACLOCAL || exit 1
+
+echo "autoheader..."
+AUTOHEADER=${AUTOHEADER:-autoheader}
+$AUTOHEADER || exit 1
+
+echo "automake..."
+if test x$AUTOMAKE = x; then
+  AUTOMAKE=`locate_binary automake-1.11 automake-1.10 automake-1.9 automake-1.7`
+  if test x$AUTOMAKE = x; then
+    die "Did not find a supported automake"
+  fi
+fi
+$AUTOMAKE --foreign --add-missing || $AUTOMAKE --gnu --add-missing || exit 1
+
+echo "autoconf..."
+AUTOCONF=${AUTOCONF:-autoconf}
+$AUTOCONF || exit 1
+
diff --git a/memcached/cache.c b/memcached/cache.c
new file mode 100644 (file)
index 0000000..7dcf2fb
--- /dev/null
@@ -0,0 +1,150 @@
+/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+#include "memcached.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <inttypes.h>
+#include <string.h>
+
+#ifndef NDEBUG
+#include <signal.h>
+#endif
+
+#include "cache.h"
+
+#ifndef NDEBUG
+const uint64_t redzone_pattern = 0xdeadbeefcafebabe;
+int cache_error = 0;
+#endif
+
+const int initial_pool_size = 64;
+
+cache_t* cache_create(const char *name, size_t bufsize, size_t align,
+                      cache_constructor_t* constructor,
+                      cache_destructor_t* destructor) {
+    cache_t* ret = calloc(1, sizeof(cache_t));
+    char* nm = strdup(name);
+    void** ptr = calloc(initial_pool_size, sizeof(void*));
+    if (ret == NULL || nm == NULL || ptr == NULL ||
+        pthread_mutex_init(&ret->mutex, NULL) == -1) {
+        free(ret);
+        free(nm);
+        free(ptr);
+        return NULL;
+    }
+
+    ret->name = nm;
+    ret->ptr = ptr;
+    ret->freetotal = initial_pool_size;
+    ret->constructor = constructor;
+    ret->destructor = destructor;
+
+#ifndef NDEBUG
+    ret->bufsize = bufsize + 2 * sizeof(redzone_pattern);
+#else
+    ret->bufsize = bufsize;
+#endif
+
+    return ret;
+}
+
+static inline void* get_object(void *ptr) {
+#ifndef NDEBUG
+    uint64_t *pre = ptr;
+    return pre + 1;
+#else
+    return ptr;
+#endif
+}
+
+void cache_destroy(cache_t *cache) {
+    while (cache->freecurr > 0) {
+        void *ptr = cache->ptr[--cache->freecurr];
+        if (cache->destructor) {
+            cache->destructor(get_object(ptr), NULL);
+        }
+        free(ptr);
+    }
+    free(cache->name);
+    free(cache->ptr);
+    pthread_mutex_destroy(&cache->mutex);
+}
+
+void* cache_alloc(cache_t *cache) {
+    void *ret;
+    void *object;
+    pthread_mutex_lock(&cache->mutex);
+    if (cache->freecurr > 0) {
+        ret = cache->ptr[--cache->freecurr];
+        object = get_object(ret);
+    } else {
+        object = ret = malloc(cache->bufsize);
+        if (ret != NULL) {
+            object = get_object(ret);
+
+            if (cache->constructor != NULL &&
+                cache->constructor(object, NULL, 0) != 0) {
+                free(ret);
+                object = NULL;
+            }
+        }
+    }
+    pthread_mutex_unlock(&cache->mutex);
+
+#ifndef NDEBUG
+    if (object != NULL) {
+        /* add a simple form of buffer-check */
+        uint64_t *pre = ret;
+        *pre = redzone_pattern;
+        ret = pre+1;
+        memcpy(((char*)ret) + cache->bufsize - (2 * sizeof(redzone_pattern)),
+               &redzone_pattern, sizeof(redzone_pattern));
+    }
+#endif
+
+    return object;
+}
+
+void cache_free(cache_t *cache, void *ptr) {
+    pthread_mutex_lock(&cache->mutex);
+
+#ifndef NDEBUG
+    /* validate redzone... */
+    if (memcmp(((char*)ptr) + cache->bufsize - (2 * sizeof(redzone_pattern)),
+               &redzone_pattern, sizeof(redzone_pattern)) != 0) {
+        raise(SIGABRT);
+        cache_error = 1;
+        pthread_mutex_unlock(&cache->mutex);
+        return;
+    }
+    uint64_t *pre = ptr;
+    --pre;
+    if (*pre != redzone_pattern) {
+        raise(SIGABRT);
+        cache_error = -1;
+        pthread_mutex_unlock(&cache->mutex);
+        return;
+    }
+    ptr = pre;
+#endif
+    if (cache->freecurr < cache->freetotal) {
+        cache->ptr[cache->freecurr++] = ptr;
+    } else {
+        /* try to enlarge free connections array */
+        size_t newtotal = cache->freetotal * 2;
+        void **new_free = realloc(cache->ptr, sizeof(char *) * newtotal);
+        if (new_free) {
+            cache->freetotal = newtotal;
+            cache->ptr = new_free;
+            cache->ptr[cache->freecurr++] = ptr;
+        } else {
+            if (cache->destructor) {
+                cache->destructor(ptr, NULL);
+            }
+            free(ptr);
+
+        }
+    }
+    pthread_mutex_unlock(&cache->mutex);
+}
+
diff --git a/memcached/cache.h b/memcached/cache.h
new file mode 100644 (file)
index 0000000..1269503
--- /dev/null
@@ -0,0 +1,116 @@
+/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+#ifndef CACHE_H
+#define CACHE_H
+#include <pthread.h>
+
+#ifdef HAVE_UMEM_H
+#include <umem.h>
+#define cache_t umem_cache_t
+#define cache_alloc(a) umem_cache_alloc(a, UMEM_DEFAULT)
+#define cache_free(a, b) umem_cache_free(a, b)
+#define cache_create(a,b,c,d,e) umem_cache_create((char*)a, b, c, d, e, NULL, NULL, NULL, 0)
+#define cache_destroy(a) umem_cache_destroy(a);
+
+#else
+
+#ifndef NDEBUG
+/* may be used for debug purposes */
+extern int cache_error;
+#endif
+
+/**
+ * Constructor used to initialize allocated objects
+ *
+ * @param obj pointer to the object to initialized.
+ * @param notused1 This parameter is currently not used.
+ * @param notused2 This parameter is currently not used.
+ * @return you should return 0, but currently this is not checked
+ */
+typedef int cache_constructor_t(void* obj, void* notused1, int notused2);
+/**
+ * Destructor used to clean up allocated objects before they are
+ * returned to the operating system.
+ *
+ * @param obj pointer to the object to initialized.
+ * @param notused1 This parameter is currently not used.
+ * @param notused2 This parameter is currently not used.
+ * @return you should return 0, but currently this is not checked
+ */
+typedef void cache_destructor_t(void* obj, void* notused);
+
+/**
+ * Definition of the structure to keep track of the internal details of
+ * the cache allocator. Touching any of these variables results in
+ * undefined behavior.
+ */
+typedef struct {
+    /** Mutex to protect access to the structure */
+    pthread_mutex_t mutex;
+    /** Name of the cache objects in this cache (provided by the caller) */
+    char *name;
+    /** List of pointers to available buffers in this cache */
+    void **ptr;
+    /** The size of each element in this cache */
+    size_t bufsize;
+    /** The capacity of the list of elements */
+    int freetotal;
+    /** The current number of free elements */
+    int freecurr;
+    /** The constructor to be called each time we allocate more memory */
+    cache_constructor_t* constructor;
+    /** The destructor to be called each time before we release memory */
+    cache_destructor_t* destructor;
+} cache_t;
+
+/**
+ * Create an object cache.
+ *
+ * The object cache will let you allocate objects of the same size. It is fully
+ * MT safe, so you may allocate objects from multiple threads without having to
+ * do any syncrhonization in the application code.
+ *
+ * @param name the name of the object cache. This name may be used for debug purposes
+ *             and may help you track down what kind of object you have problems with
+ *             (buffer overruns, leakage etc)
+ * @param bufsize the size of each object in the cache
+ * @param align the alignment requirements of the objects in the cache.
+ * @param constructor the function to be called to initialize memory when we need
+ *                    to allocate more memory from the os.
+ * @param destructor the function to be called before we release the memory back
+ *                   to the os.
+ * @return a handle to an object cache if successful, NULL otherwise.
+ */
+cache_t* cache_create(const char* name, size_t bufsize, size_t align,
+                      cache_constructor_t* constructor,
+                      cache_destructor_t* destructor);
+/**
+ * Destroy an object cache.
+ *
+ * Destroy and invalidate an object cache. You should return all buffers allocated
+ * with cache_alloc by using cache_free before calling this function. Not doing
+ * so results in undefined behavior (the buffers may or may not be invalidated)
+ *
+ * @param handle the handle to the object cache to destroy.
+ */
+void cache_destroy(cache_t* handle);
+/**
+ * Allocate an object from the cache.
+ *
+ * @param handle the handle to the object cache to allocate from
+ * @return a pointer to an initialized object from the cache, or NULL if
+ *         the allocation cannot be satisfied.
+ */
+void* cache_alloc(cache_t* handle);
+/**
+ * Return an object back to the cache.
+ *
+ * The caller should return the object in an initialized state so that
+ * the object may be returned in an expected state from cache_alloc.
+ *
+ * @param handle handle to the object cache to return the object to
+ * @param ptr pointer to the object to return.
+ */
+void cache_free(cache_t* handle, void* ptr);
+#endif
+
+#endif
diff --git a/memcached/configure.ac b/memcached/configure.ac
new file mode 100644 (file)
index 0000000..9915a46
--- /dev/null
@@ -0,0 +1,557 @@
+AC_PREREQ(2.52)
+m4_include([version.m4])
+m4_include([m4/c99-backport.m4])
+AC_INIT(memcached, VERSION_NUMBER, memcached@googlegroups.com)
+AC_CANONICAL_SYSTEM
+AC_CONFIG_SRCDIR(memcached.c)
+AM_INIT_AUTOMAKE(AC_PACKAGE_NAME, AC_PACKAGE_VERSION)
+AM_CONFIG_HEADER(config.h)
+
+AC_PROG_CC
+
+dnl **********************************************************************
+dnl DETECT_ICC ([ACTION-IF-YES], [ACTION-IF-NO])
+dnl
+dnl check if this is the Intel ICC compiler, and if so run the ACTION-IF-YES
+dnl sets the $ICC variable to "yes" or "no"
+dnl **********************************************************************
+AC_DEFUN([DETECT_ICC],
+[
+    ICC="no"
+    AC_MSG_CHECKING([for icc in use])
+    if test "$GCC" = "yes"; then
+       dnl check if this is icc acting as gcc in disguise
+       AC_EGREP_CPP([^__INTEL_COMPILER], [__INTEL_COMPILER],
+         AC_MSG_RESULT([no])
+         [$2],
+         AC_MSG_RESULT([yes])
+         [$1]
+         ICC="yes")
+    else
+       AC_MSG_RESULT([no])
+       [$2]
+    fi
+])
+
+DETECT_ICC([], [])
+
+dnl **********************************************************************
+dnl DETECT_SUNCC ([ACTION-IF-YES], [ACTION-IF-NO])
+dnl
+dnl check if this is the Sun Studio compiler, and if so run the ACTION-IF-YES
+dnl sets the $SUNCC variable to "yes" or "no"
+dnl **********************************************************************
+AC_DEFUN([DETECT_SUNCC],
+[
+    AC_CHECK_DECL([__SUNPRO_C], [SUNCC="yes"], [SUNCC="no"])
+    AS_IF(test "x$SUNCC" = "xyes", [$1], [$2])
+
+])
+
+DETECT_SUNCC([CFLAGS="-mt $CFLAGS"], [])
+
+if test "$ICC" = "no"; then
+   AC_PROG_CC_C99
+fi
+
+AM_PROG_CC_C_O
+AC_PROG_INSTALL
+
+AC_ARG_ENABLE(sasl,
+  [AS_HELP_STRING([--enable-sasl],[Enable SASL authentication])])
+
+AC_ARG_ENABLE(sasl_pwdb,
+  [AS_HELP_STRING([--enable-sasl-pwdb],[Enable plaintext password db])])
+
+AS_IF([test "x$enable_sasl_pwdb" = "xyes"],
+      [enable_sasl=yes ])
+
+
+dnl **********************************************************************
+dnl DETECT_SASL_CB_GETCONF
+dnl
+dnl check if we can use SASL_CB_GETCONF
+dnl **********************************************************************
+AC_DEFUN([AC_C_DETECT_SASL_CB_GETCONF],
+[
+    AC_CACHE_CHECK([for SASL_CB_GETCONF],
+        [ac_cv_c_sasl_cb_getconf],
+        [AC_TRY_COMPILE(
+            [
+#include <sasl/sasl.h>
+            ], [
+unsigned long val = SASL_CB_GETCONF;
+            ],
+            [ ac_cv_c_sasl_cb_getconf=yes ],
+            [ ac_cv_c_sasl_cb_getconf=no ])
+        ])
+    AS_IF([test "$ac_cv_c_sasl_cb_getconf" = "yes"],
+          [AC_DEFINE([HAVE_SASL_CB_GETCONF], 1,
+                     [Set to nonzero if your SASL implementation supports SASL_CB_GETCONF])])
+])
+
+AC_CHECK_HEADERS([sasl/sasl.h])
+if test "x$enable_sasl" = "xyes"; then
+  AC_C_DETECT_SASL_CB_GETCONF
+  AC_DEFINE([ENABLE_SASL],1,[Set to nonzero if you want to include SASL])
+  AC_SEARCH_LIBS([sasl_server_init], [sasl2 sasl], [],
+    [
+      AC_MSG_ERROR([Failed to locate the library containing sasl_server_init])
+    ])
+
+  AS_IF([test "x$enable_sasl_pwdb" = "xyes"],
+        [AC_DEFINE([ENABLE_SASL_PWDB], 1,
+                   [Set to nonzero if you want to enable a SASL pwdb])])
+fi
+
+AC_ARG_ENABLE(dtrace,
+  [AS_HELP_STRING([--enable-dtrace],[Enable dtrace probes])])
+if test "x$enable_dtrace" = "xyes"; then
+  AC_PATH_PROG([DTRACE], [dtrace], "no", [/usr/sbin:$PATH])
+  if test "x$DTRACE" != "xno"; then
+    AC_DEFINE([ENABLE_DTRACE],1,[Set to nonzero if you want to include DTRACE])
+    build_dtrace=yes
+    # DTrace on MacOSX does not use -G option
+    $DTRACE -G -o conftest.$$ -s memcached_dtrace.d 2>/dev/zero
+    if test $? -eq 0
+    then
+        dtrace_instrument_obj=yes
+        rm conftest.$$
+    fi
+
+    if test "`which tr`" = "/usr/ucb/tr"; then
+        AC_MSG_ERROR([Please remove /usr/ucb from your path. See man standards for more info])
+    fi
+  else
+    AC_MSG_ERROR([Need dtrace binary and OS support.])
+  fi
+fi
+
+AM_CONDITIONAL([BUILD_DTRACE],[test "$build_dtrace" = "yes"])
+AM_CONDITIONAL([DTRACE_INSTRUMENT_OBJ],[test "$dtrace_instrument_obj" = "yes"])
+AM_CONDITIONAL([ENABLE_SASL],[test "$enable_sasl" = "yes"])
+
+AC_SUBST(DTRACE)
+AC_SUBST(DTRACEFLAGS)
+AC_SUBST(ENABLE_SASL)
+AC_SUBST(PROFILER_LDFLAGS)
+
+AC_ARG_ENABLE(coverage,
+  [AS_HELP_STRING([--disable-coverage],[Disable code coverage])])
+
+if test "x$enable_coverage" != "xno"; then
+   if test "$ICC" = "yes"
+   then
+      dnl ICC trying to be gcc, but not well
+      CFLAGS="$CFLAGS -pthread"
+   elif test "$GCC" = "yes"
+   then
+      CFLAGS="$CFLAGS -pthread"
+      AC_PATH_PROG([PROFILER], [gcov], "no", [$PATH])
+      if test "x$PROFILER" != "xno"; then
+         # Issue 97: The existense of gcov doesn't mean we have -lgcov
+         AC_CHECK_LIB(gcov, main,
+                    [
+                      PROFILER_FLAGS="-fprofile-arcs -ftest-coverage"
+                      PROFILER_LDFLAGS="-lgcov"
+                    ], [
+                      PROFILER_FLAGS=
+                      PROFILER_LDFLAGS=
+                    ])
+      fi
+   elif test "$SUNCC" = "yes"
+   then
+      AC_PATH_PROG([PROFILER], [tcov], "no", [$PATH])
+      if test "x$PROFILER" != "xno"; then
+         PROFILER_FLAGS=-xprofile=tcov
+      fi
+   fi
+fi
+AC_SUBST(PROFILER_FLAGS)
+
+
+AC_ARG_ENABLE(64bit,
+  [AS_HELP_STRING([--enable-64bit],[build 64bit version])])
+if test "x$enable_64bit" = "xyes"
+then
+    org_cflags=$CFLAGS
+    CFLAGS=-m64
+    AC_RUN_IFELSE(
+      [AC_LANG_PROGRAM([], [dnl
+return sizeof(void*) == 8 ? 0 : 1;
+      ])
+    ],[
+      CFLAGS="-m64 $org_cflags"
+    ],[
+    AC_MSG_ERROR([Don't know how to build a 64-bit object.])
+    ])
+fi
+
+# Issue 213: Search for clock_gettime to help people linking
+#            with a static version of libevent
+AC_SEARCH_LIBS(clock_gettime, rt)
+# Issue 214: Search for the network libraries _before_ searching
+#            for libevent (to help people linking with static libevent)
+AC_SEARCH_LIBS(socket, socket)
+AC_SEARCH_LIBS(gethostbyname, nsl)
+
+trylibeventdir=""
+AC_ARG_WITH(libevent,
+       [  --with-libevent=PATH     Specify path to libevent installation ],
+       [
+                if test "x$withval" != "xno" ; then
+                        trylibeventdir=$withval
+                fi
+       ]
+)
+
+dnl ------------------------------------------------------
+dnl libevent detection.  swiped from Tor.  modified a bit.
+
+LIBEVENT_URL=http://www.monkey.org/~provos/libevent/
+
+AC_CACHE_CHECK([for libevent directory], ac_cv_libevent_dir, [
+  saved_LIBS="$LIBS"
+  saved_LDFLAGS="$LDFLAGS"
+  saved_CPPFLAGS="$CPPFLAGS"
+  le_found=no
+  for ledir in $trylibeventdir "" $prefix /usr/local ; do
+    LDFLAGS="$saved_LDFLAGS"
+    LIBS="-levent $saved_LIBS"
+
+    # Skip the directory if it isn't there.
+    if test ! -z "$ledir" -a ! -d "$ledir" ; then
+       continue;
+    fi
+    if test ! -z "$ledir" ; then
+      if test -d "$ledir/lib" ; then
+        LDFLAGS="-L$ledir/lib $LDFLAGS"
+      else
+        LDFLAGS="-L$ledir $LDFLAGS"
+      fi
+      if test -d "$ledir/include" ; then
+        CPPFLAGS="-I$ledir/include $CPPFLAGS"
+      else
+        CPPFLAGS="-I$ledir $CPPFLAGS"
+      fi
+    fi
+    # Can I compile and link it?
+    AC_TRY_LINK([#include <sys/time.h>
+#include <sys/types.h>
+#include <event.h>], [ event_init(); ],
+       [ libevent_linked=yes ], [ libevent_linked=no ])
+    if test $libevent_linked = yes; then
+       if test ! -z "$ledir" ; then
+         ac_cv_libevent_dir=$ledir
+         _myos=`echo $target_os | cut -f 1 -d .`
+         AS_IF(test "$SUNCC" = "yes" -o "x$_myos" = "xsolaris2",
+               [saved_LDFLAGS="$saved_LDFLAGS -Wl,-R$ledir/lib"],
+               [AS_IF(test "$GCC" = "yes",
+                     [saved_LDFLAGS="$saved_LDFLAGS -Wl,-rpath,$ledir/lib"])])
+       else
+         ac_cv_libevent_dir="(system)"
+       fi
+       le_found=yes
+       break
+    fi
+  done
+  LIBS="$saved_LIBS"
+  LDFLAGS="$saved_LDFLAGS"
+  CPPFLAGS="$saved_CPPFLAGS"
+  if test $le_found = no ; then
+    AC_MSG_ERROR([libevent is required.  You can get it from $LIBEVENT_URL
+
+      If it's already installed, specify its path using --with-libevent=/dir/
+])
+  fi
+])
+LIBS="-levent $LIBS"
+if test $ac_cv_libevent_dir != "(system)"; then
+  if test -d "$ac_cv_libevent_dir/lib" ; then
+    LDFLAGS="-L$ac_cv_libevent_dir/lib $LDFLAGS"
+    le_libdir="$ac_cv_libevent_dir/lib"
+  else
+    LDFLAGS="-L$ac_cv_libevent_dir $LDFLAGS"
+    le_libdir="$ac_cv_libevent_dir"
+  fi
+  if test -d "$ac_cv_libevent_dir/include" ; then
+    CPPFLAGS="-I$ac_cv_libevent_dir/include $CPPFLAGS"
+  else
+    CPPFLAGS="-I$ac_cv_libevent_dir $CPPFLAGS"
+  fi
+fi
+
+dnl ----------------------------------------------------------------------------
+
+AC_SEARCH_LIBS(umem_cache_create, umem)
+AC_SEARCH_LIBS(gethugepagesizes, hugetlbfs)
+
+AC_HEADER_STDBOOL
+AH_BOTTOM([#if HAVE_STDBOOL_H
+#include <stdbool.h>
+#else
+#define bool char
+#define false 0
+#define true 1
+#endif ])
+
+AC_CHECK_HEADERS([inttypes.h])
+AH_BOTTOM([#ifdef HAVE_INTTYPES_H
+#include <inttypes.h>
+#endif
+])
+
+dnl **********************************************************************
+dnl Figure out if this system has the stupid sasl_callback_ft
+dnl **********************************************************************
+
+AC_DEFUN([AC_HAVE_SASL_CALLBACK_FT],
+[AC_CACHE_CHECK(for sasl_callback_ft, ac_cv_has_sasl_callback_ft,
+[
+  AC_TRY_COMPILE([
+    #ifdef HAVE_SASL_SASL_H
+    #include <sasl/sasl.h>
+    #include <sasl/saslplug.h>
+    #endif
+  ],[
+    sasl_callback_ft a_callback;
+  ],[
+    ac_cv_has_sasl_callback_ft=yes
+  ],[
+    ac_cv_has_sasl_callback_ft=no
+  ])
+])
+if test $ac_cv_has_sasl_callback_ft = yes; then
+  AC_DEFINE(HAVE_SASL_CALLBACK_FT, 1, [we have sasl_callback_ft])
+fi
+])
+
+AC_HAVE_SASL_CALLBACK_FT
+
+dnl **********************************************************************
+dnl DETECT_UINT64_SUPPORT
+dnl
+dnl check if we can use a uint64_t
+dnl **********************************************************************
+AC_DEFUN([AC_C_DETECT_UINT64_SUPPORT],
+[
+    AC_CACHE_CHECK([for print macros for integers (C99 section 7.8.1)],
+        [ac_cv_c_uint64_support],
+        [AC_TRY_COMPILE(
+            [
+#ifdef HAVE_INTTYPES_H
+#include <inttypes.h>
+#endif
+#include <stdio.h>
+            ], [
+  uint64_t val = 0;
+  fprintf(stderr, "%" PRIu64 "\n", val);
+            ],
+            [ ac_cv_c_uint64_support=yes ],
+            [ ac_cv_c_uint64_support=no ])
+        ])
+])
+
+AC_C_DETECT_UINT64_SUPPORT
+AS_IF([test "x$ac_cv_c_uint64_support" = "xno"],
+      [AC_MSG_WARN([
+
+Failed to use print macros (PRIu) as defined in C99 section 7.8.1.
+
+])])
+
+AC_C_CONST
+
+dnl From licq: Copyright (c) 2000 Dirk Mueller
+dnl Check if the type socklen_t is defined anywhere
+AC_DEFUN([AC_C_SOCKLEN_T],
+[AC_CACHE_CHECK(for socklen_t, ac_cv_c_socklen_t,
+[
+  AC_TRY_COMPILE([
+    #include <sys/types.h>
+    #include <sys/socket.h>
+  ],[
+    socklen_t foo;
+  ],[
+    ac_cv_c_socklen_t=yes
+  ],[
+    ac_cv_c_socklen_t=no
+  ])
+])
+if test $ac_cv_c_socklen_t = no; then
+  AC_DEFINE(socklen_t, int, [define to int if socklen_t not available])
+fi
+])
+
+AC_C_SOCKLEN_T
+
+dnl Check if we're a little-endian or a big-endian system, needed by hash code
+AC_DEFUN([AC_C_ENDIAN],
+[AC_CACHE_CHECK(for endianness, ac_cv_c_endian,
+[
+  AC_RUN_IFELSE(
+    [AC_LANG_PROGRAM([], [dnl
+        long val = 1;
+        char *c = (char *) &val;
+        exit(*c == 1);
+    ])
+  ],[
+    ac_cv_c_endian=big
+  ],[
+    ac_cv_c_endian=little
+  ])
+])
+if test $ac_cv_c_endian = big; then
+  AC_DEFINE(ENDIAN_BIG, 1, [machine is bigendian])
+fi
+if test $ac_cv_c_endian = little; then
+  AC_DEFINE(ENDIAN_LITTLE, 1, [machine is littleendian])
+fi
+])
+
+AC_C_ENDIAN
+
+AC_DEFUN([AC_C_HTONLL],
+[
+    AC_MSG_CHECKING([for htonll])
+    have_htoll="no"
+    AC_TRY_LINK([
+#include <sys/types.h>
+#include <netinet/in.h>
+#ifdef HAVE_INTTYPES_H
+#include <inttypes.h> */
+#endif
+       ], [
+          return htonll(0);
+       ], [
+          have_htoll="yes"
+          AC_DEFINE([HAVE_HTONLL], [1], [Have ntohll])
+    ], [
+          have_htoll="no"
+    ])
+
+    AC_MSG_RESULT([$have_htoll])
+])
+
+AC_C_HTONLL
+
+dnl Check whether the user's system supports pthread
+AC_SEARCH_LIBS(pthread_create, pthread)
+if test "x$ac_cv_search_pthread_create" = "xno"; then
+  AC_MSG_ERROR([Can't enable threads without the POSIX thread library.])
+fi
+
+AC_CHECK_FUNCS(mlockall)
+AC_CHECK_FUNCS(getpagesizes)
+AC_CHECK_FUNCS(memcntl)
+AC_CHECK_FUNCS(sigignore)
+AC_CHECK_FUNCS(clock_gettime)
+
+AC_DEFUN([AC_C_ALIGNMENT],
+[AC_CACHE_CHECK(for alignment, ac_cv_c_alignment,
+[
+  AC_RUN_IFELSE(
+    [AC_LANG_PROGRAM([
+#include <stdlib.h>
+#include <inttypes.h>
+    ], [
+       char *buf = malloc(32);
+
+       uint64_t *ptr = (uint64_t*)(buf+2);
+       // catch sigbus, etc.
+       *ptr = 0x1;
+
+       // catch unaligned word access (ARM cpus)
+       *buf =  1; *(buf +1) = 2; *(buf + 2) = 2; *(buf + 3) = 3; *(buf + 4) = 4;
+       int* i = (int*)(buf+1);
+       return (84148994 == i) ? 0 : 1;
+    ])
+  ],[
+    ac_cv_c_alignment=none
+  ],[
+    ac_cv_c_alignment=need
+  ],[
+    ac_cv_c_alignment=need
+  ])
+])
+if test $ac_cv_c_alignment = need; then
+  AC_DEFINE(NEED_ALIGN, 1, [Machine need alignment])
+fi
+])
+
+AC_C_ALIGNMENT
+
+dnl Check for our specific usage of GCC atomics.
+dnl These were added in 4.1.2, but 32bit OS's may lack shorts and 4.1.2
+dnl lacks testable defines.
+have_gcc_atomics=no
+AC_MSG_CHECKING(for GCC atomics)
+AC_TRY_LINK([],[
+  unsigned short a;
+  unsigned short b;
+  b = __sync_add_and_fetch(&a, 1);
+  b = __sync_sub_and_fetch(&a, 2);
+  ],[have_gcc_atomics=yes
+  AC_DEFINE(HAVE_GCC_ATOMICS, 1, [GCC Atomics available])])
+AC_MSG_RESULT($have_gcc_atomics)
+
+dnl Check for the requirements for running memcached with less privileges
+dnl than the default privilege set. On Solaris we need setppriv and priv.h
+dnl If you want to add support for other platforms you should check for
+dnl your requirements, define HAVE_DROP_PRIVILEGES, and make sure you add
+dnl the source file containing the implementation into memcached_SOURCE
+dnl in Makefile.am
+AC_CHECK_FUNCS(setppriv, [
+   AC_CHECK_HEADER(priv.h, [
+      AC_DEFINE([HAVE_DROP_PRIVILEGES], 1,
+         [Define this if you have an implementation of drop_privileges()])
+      build_solaris_privs=yes
+   ], [])
+],[])
+
+AM_CONDITIONAL([BUILD_SOLARIS_PRIVS],[test "$build_solaris_privs" = "yes"])
+
+AC_CHECK_HEADER(umem.h, [
+   AC_DEFINE([HAVE_UMEM_H], 1,
+         [Define this if you have umem.h])
+   build_cache=no
+], [build_cache=yes])
+
+AM_CONDITIONAL([BUILD_CACHE], [test "x$build_cache" = "xyes"])
+
+AC_ARG_ENABLE(docs,
+  [AS_HELP_STRING([--disable-docs],[Disable documentation generation])])
+
+AC_PATH_PROG([XML2RFC], [xml2rfc], "no")
+AC_PATH_PROG([XSLTPROC], [xsltproc], "no")
+
+AM_CONDITIONAL([BUILD_SPECIFICATIONS],
+               [test "x$enable_docs" != "xno" -a "x$XML2RFC" != "xno" -a "x$XSLTPROC" != "xno"])
+
+
+dnl Let the compiler be a bit more picky. Please note that you cannot
+dnl specify these flags to the compiler before AC_CHECK_FUNCS, because
+dnl the test program will generate a compilation warning and hence fail
+dnl to detect the function ;-)
+if test "$ICC" = "yes"
+then
+   dnl ICC trying to be gcc.
+   CFLAGS="$CFLAGS -diag-disable 187 -Wall -Werror"
+   AC_DEFINE([_GNU_SOURCE],[1],[find sigignore on Linux])
+elif test "$GCC" = "yes"
+then
+  GCC_VERSION=`$CC -dumpversion`
+  CFLAGS="$CFLAGS -Wall -Werror -pedantic -Wmissing-prototypes -Wmissing-declarations -Wredundant-decls"
+  case $GCC_VERSION in
+    4.4.*)
+    CFLAGS="$CFLAGS -fno-strict-aliasing"
+    ;;
+  esac
+  AC_DEFINE([_GNU_SOURCE],[1],[find sigignore on Linux])
+elif test "$SUNCC" = "yes"
+then
+  CFLAGS="$CFLAGS -errfmt=error -errwarn -errshort=tags"
+fi
+
+AC_CONFIG_FILES(Makefile doc/Makefile)
+AC_OUTPUT
diff --git a/memcached/daemon.c b/memcached/daemon.c
new file mode 100644 (file)
index 0000000..2f18915
--- /dev/null
@@ -0,0 +1,89 @@
+/*    $Header: /cvsroot/wikipedia/willow/src/bin/willow/daemon.c,v 1.1 2005/05/02 19:15:21 kateturner Exp $    */
+/*    $NetBSD: daemon.c,v 1.9 2003/08/07 16:42:46 agc Exp $    */
+/*-
+ * Copyright (c) 1990, 1993
+ *    The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if defined __SUNPRO_C || defined __DECC || defined __HP_cc
+# pragma ident "@(#)$Header: /cvsroot/wikipedia/willow/src/bin/willow/daemon.c,v 1.1 2005/05/02 19:15:21 kateturner Exp $"
+# pragma ident "$NetBSD: daemon.c,v 1.9 2003/08/07 16:42:46 agc Exp $"
+#endif
+
+#include "memcached.h"
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+int daemonize(int nochdir, int noclose)
+{
+    int fd;
+
+    switch (fork()) {
+    case -1:
+        return (-1);
+    case 0:
+        break;
+    default:
+        _exit(EXIT_SUCCESS);
+    }
+
+    if (setsid() == -1)
+        return (-1);
+
+    if (nochdir == 0) {
+        if(chdir("/") != 0) {
+            perror("chdir");
+            return (-1);
+        }
+    }
+
+    if (noclose == 0 && (fd = open("/dev/null", O_RDWR, 0)) != -1) {
+        if(dup2(fd, STDIN_FILENO) < 0) {
+            perror("dup2 stdin");
+            return (-1);
+        }
+        if(dup2(fd, STDOUT_FILENO) < 0) {
+            perror("dup2 stdout");
+            return (-1);
+        }
+        if(dup2(fd, STDERR_FILENO) < 0) {
+            perror("dup2 stderr");
+            return (-1);
+        }
+
+        if (fd > STDERR_FILENO) {
+            if(close(fd) < 0) {
+                perror("close");
+                return (-1);
+            }
+        }
+    }
+    return (0);
+}
diff --git a/memcached/devtools/bench_noreply.pl b/memcached/devtools/bench_noreply.pl
new file mode 100644 (file)
index 0000000..365c682
--- /dev/null
@@ -0,0 +1,51 @@
+#! /usr/bin/perl
+#
+use warnings;
+use strict;
+
+use IO::Socket::INET;
+
+use FindBin;
+
+@ARGV == 1 or @ARGV == 2
+    or die "Usage: $FindBin::Script HOST:PORT [COUNT]\n";
+
+# Note that it's better to run the test over the wire, because for
+# localhost the task may become CPU bound.
+my $addr = $ARGV[0];
+my $count = $ARGV[1] || 10_000;
+
+my $sock = IO::Socket::INET->new(PeerAddr => $addr,
+                                 Timeout  => 3);
+die "$!\n" unless $sock;
+
+
+# By running 'noreply' test first we also ensure there are no reply
+# packets left in the network.
+foreach my $noreply (1, 0) {
+    use Time::HiRes qw(gettimeofday tv_interval);
+
+    print "'noreply' is ", $noreply ? "enabled" : "disabled", ":\n";
+    my $param = $noreply ? 'noreply' : '';
+    my $start = [gettimeofday];
+    foreach (1 .. $count) {
+        print $sock "add foo 0 0 1 $param\r\n1\r\n";
+        scalar<$sock> unless $noreply;
+        print $sock "set foo 0 0 1 $param\r\n1\r\n";
+        scalar<$sock> unless $noreply;
+        print $sock "replace foo 0 0 1 $param\r\n1\r\n";
+        scalar<$sock> unless $noreply;
+        print $sock "append foo 0 0 1 $param\r\n1\r\n";
+        scalar<$sock> unless $noreply;
+        print $sock "prepend foo 0 0 1 $param\r\n1\r\n";
+        scalar<$sock> unless $noreply;
+        print $sock "incr foo 1 $param\r\n";
+        scalar<$sock> unless $noreply;
+        print $sock "decr foo 1 $param\r\n";
+        scalar<$sock> unless $noreply;
+        print $sock "delete foo $param\r\n";
+        scalar<$sock> unless $noreply;
+    }
+    my $end = [gettimeofday];
+    printf("update commands: %.2f secs\n\n", tv_interval($start, $end));
+}
diff --git a/memcached/devtools/clean-whitespace.pl b/memcached/devtools/clean-whitespace.pl
new file mode 100755 (executable)
index 0000000..95481ef
--- /dev/null
@@ -0,0 +1,32 @@
+#!/usr/bin/perl
+use strict;
+use FindBin qw($Bin);
+chdir "$Bin/.." or die;
+
+my @exempted = qw(Makefile.am ChangeLog doc/Makefile.am);
+push(@exempted, glob("doc/*.xml"));
+push(@exempted, glob("doc/xml2rfc/*.xsl"));
+push(@exempted, glob("m4/*backport*m4"));
+my %exempted_hash = map { $_ => 1 } @exempted;
+
+my @stuff = split /\0/, `git ls-files -z -c -m -o --exclude-standard`;
+my @files = grep { ! $exempted_hash{$_} } @stuff;
+
+unless (@files) {
+    warn "ERROR: You don't seem to be running this from a git checkout\n";
+    exit;
+}
+
+foreach my $f (@files) {
+    open(my $fh, $f) or die;
+    my $before = do { local $/; <$fh>; };
+    close ($fh);
+    my $after = $before;
+    $after =~ s/\t/    /g;
+    $after =~ s/ +$//mg;
+    $after .= "\n" unless $after =~ /\n$/;
+    next if $after eq $before;
+    open(my $fh, ">$f") or die;
+    print $fh $after;
+    close($fh);
+}
diff --git a/memcached/doc/CONTRIBUTORS b/memcached/doc/CONTRIBUTORS
new file mode 100644 (file)
index 0000000..b87e5c6
--- /dev/null
@@ -0,0 +1,45 @@
+MEMCACHED CONTRIBUTORS
+
+This file contains a list of people who have contributed code and
+effort to the memcached project.  If you don't see your name mentioned
+send email to the memcached mailing list so you can be immortalized.
+
+Also see the ChangeLog for even more people who have helped over the
+years by submitting fixes, patches and reporting bugs.
+
+
+Major authors:
+--------------
+
+Brad Fitzpatrick <brad@danga.com>  -- maintainer, original implementations
+
+Anatoly Vorobey <mellon@pobox.com> -- lots of the modern server code
+
+Steven Grimm <sgrimm@facebook.com> -- iov writing (less CPU), UDP mode,
+                                        non-2.0 slab mantissas, multithread, ...
+
+Other Contributors
+------------------
+
+Evan Martin <evan@danga.com>
+Nathan Neulinger <nneul@umr.edu>
+Eric Hodel <drbrain@segment7.net>
+Michael Johnson <ahze@ahze.net>
+Paul Querna <chip@corelands.com>
+Jamie McCarthy <jamie@mccarthy.vg>
+Philip Neustrom <philipn@gmail.com>
+Andrew O'Brien <andrewo@oriel.com.au>
+Josh Rotenberg <joshrotenberg@gmail.com>
+Robin H. Johnson <robbat2@gentoo.org>
+Tim Yardley <liquid@haveheart.com>
+Paolo Borelli <paolo.borelli@gmail.com>
+Eli Bingham <eli@pandora.com>
+Jean-Francois Bustarret <jfbustarret@wat.tv>
+Paul G <paul-lists@perforge.com>
+Paul Lindner <lindner@inuus.com>
+Dormando <dormando@rydia.net>
+Dustin Sallings <dustin@spy.net>
+Chris Goffinet <goffinet@yahoo-inc.com>
+Tomash Brechko <tomash.brechko@gmail.com>
+Brian Aker <brian@tangent.org>
+Trond Norbye <trond.norbye@sun.com>
diff --git a/memcached/doc/Doxyfile b/memcached/doc/Doxyfile
new file mode 100644 (file)
index 0000000..e1b4afb
--- /dev/null
@@ -0,0 +1,1258 @@
+# Doxyfile 1.5.2
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project
+#
+# All text after a hash (#) is considered a comment and will be ignored
+# The format is:
+#       TAG = value [value, ...]
+# For lists items can also be appended using:
+#       TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (" ")
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the config file that
+# follow. The default is UTF-8 which is also the encoding used for all text before
+# the first occurrence of this tag. Doxygen uses libiconv (or the iconv built into
+# libc) for the transcoding. See http://www.gnu.org/software/libiconv for the list of
+# possible encodings.
+
+DOXYFILE_ENCODING      = UTF-8
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded
+# by quotes) that should identify the project.
+
+PROJECT_NAME           = memcached
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number.
+# This could be handy for archiving the generated documentation or
+# if some version control system is used.
+
+PROJECT_NUMBER         = 0.8
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
+# base path where the generated documentation will be put.
+# If a relative path is entered, it will be relative to the location
+# where doxygen was started. If left blank the current directory will be used.
+
+OUTPUT_DIRECTORY       = doxygen
+
+# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create
+# 4096 sub-directories (in 2 levels) under the output directory of each output
+# format and will distribute the generated files over these directories.
+# Enabling this option can be useful when feeding doxygen a huge amount of
+# source files, where putting all generated files in the same directory would
+# otherwise cause performance problems for the file system.
+
+CREATE_SUBDIRS         = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# The default language is English, other supported languages are:
+# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional,
+# Croatian, Czech, Danish, Dutch, Finnish, French, German, Greek, Hungarian,
+# Italian, Japanese, Japanese-en (Japanese with English messages), Korean,
+# Korean-en, Lithuanian, Norwegian, Polish, Portuguese, Romanian, Russian,
+# Serbian, Slovak, Slovene, Spanish, Swedish, and Ukrainian.
+
+OUTPUT_LANGUAGE        = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will
+# include brief member descriptions after the members that are listed in
+# the file and class documentation (similar to JavaDoc).
+# Set to NO to disable this.
+
+BRIEF_MEMBER_DESC      = YES
+
+# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend
+# the brief description of a member or function before the detailed description.
+# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+
+REPEAT_BRIEF           = NO
+
+# This tag implements a quasi-intelligent brief description abbreviator
+# that is used to form the text in various listings. Each string
+# in this list, if found as the leading text of the brief description, will be
+# stripped from the text and the result after processing the whole list, is
+# used as the annotated text. Otherwise, the brief description is used as-is.
+# If left blank, the following values are used ("$name" is automatically
+# replaced with the name of the entity): "The $name class" "The $name widget"
+# "The $name file" "is" "provides" "specifies" "contains"
+# "represents" "a" "an" "the"
+
+ABBREVIATE_BRIEF       =
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# Doxygen will generate a detailed section even if there is only a brief
+# description.
+
+ALWAYS_DETAILED_SEC    = YES
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+
+INLINE_INHERITED_MEMB  = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full
+# path before files name in the file list and in the header files. If set
+# to NO the shortest path that makes the file name unique will be used.
+
+FULL_PATH_NAMES        = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag
+# can be used to strip a user-defined part of the path. Stripping is
+# only done if one of the specified strings matches the left-hand part of
+# the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the
+# path to strip.
+
+STRIP_FROM_PATH        =
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of
+# the path mentioned in the documentation of a class, which tells
+# the reader which header file to include in order to use a class.
+# If left blank only the name of the header file containing the class
+# definition is used. Otherwise one should specify the include paths that
+# are normally passed to the compiler using the -I flag.
+
+STRIP_FROM_INC_PATH    =
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter
+# (but less readable) file names. This can be useful is your file systems
+# doesn't support long names like on DOS, Mac, or CD-ROM.
+
+SHORT_NAMES            = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen
+# will interpret the first line (until the first dot) of a JavaDoc-style
+# comment as the brief description. If set to NO, the JavaDoc
+# comments will behave just like the Qt-style comments (thus requiring an
+# explicit @brief command for a brief description.
+
+JAVADOC_AUTOBRIEF      = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen
+# treat a multi-line C++ special comment block (i.e. a block of //! or ///
+# comments) as a brief description. This used to be the default behaviour.
+# The new default is to treat a multi-line C++ comment block as a detailed
+# description. Set this tag to YES if you prefer the old behaviour instead.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the DETAILS_AT_TOP tag is set to YES then Doxygen
+# will output the detailed description near the top, like JavaDoc.
+# If set to NO, the detailed description appears after the member
+# documentation.
+
+DETAILS_AT_TOP         = NO
+
+# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented
+# member inherits the documentation from any documented member that it
+# re-implements.
+
+INHERIT_DOCS           = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce
+# a new page for each member. If set to NO, the documentation of a member will
+# be part of the file/class/namespace that contains it.
+
+SEPARATE_MEMBER_PAGES  = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab.
+# Doxygen uses this value to replace tabs by spaces in code fragments.
+
+TAB_SIZE               = 8
+
+# This tag can be used to specify a number of aliases that acts
+# as commands in the documentation. An alias has the form "name=value".
+# For example adding "sideeffect=\par Side Effects:\n" will allow you to
+# put the command \sideeffect (or @sideeffect) in the documentation, which
+# will result in a user-defined paragraph with heading "Side Effects:".
+# You can put \n's in the value part of an alias to insert newlines.
+
+ALIASES                =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C
+# sources only. Doxygen will then generate output that is more tailored for C.
+# For instance, some of the names that are used will be different. The list
+# of all members will be omitted, etc.
+
+OPTIMIZE_OUTPUT_FOR_C  = NO
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java
+# sources only. Doxygen will then generate output that is more tailored for Java.
+# For instance, namespaces will be presented as packages, qualified scopes
+# will look different, etc.
+
+OPTIMIZE_OUTPUT_JAVA   = NO
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want to
+# include (a tag file for) the STL sources as input, then you should
+# set this tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string); v.s.
+# func(std::string) {}). This also make the inheritance and collaboration
+# diagrams that involve STL classes more complete and accurate.
+
+BUILTIN_STL_SUPPORT    = NO
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+
+CPP_CLI_SUPPORT        = NO
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES, then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+
+DISTRIBUTE_GROUP_DOC   = NO
+
+# Set the SUBGROUPING tag to YES (the default) to allow class member groups of
+# the same type (for instance a group of public functions) to be put as a
+# subgroup of that type (e.g. under the Public Functions section). Set it to
+# NO to prevent subgrouping. Alternatively, this can be done per class using
+# the \nosubgrouping command.
+
+SUBGROUPING            = YES
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
+# documentation are documented, even if no documentation was available.
+# Private class members and static file members will be hidden unless
+# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
+
+EXTRACT_ALL            = YES
+
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class
+# will be included in the documentation.
+
+EXTRACT_PRIVATE        = NO
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file
+# will be included in the documentation.
+
+EXTRACT_STATIC         = NO
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs)
+# defined locally in source files will be included in the documentation.
+# If set to NO only classes defined in header files are included.
+
+EXTRACT_LOCAL_CLASSES  = YES
+
+# This flag is only useful for Objective-C code. When set to YES local
+# methods, which are defined in the implementation section but not in
+# the interface are included in the documentation.
+# If set to NO (the default) only methods in the interface are included.
+
+EXTRACT_LOCAL_METHODS  = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all
+# undocumented members of documented classes, files or namespaces.
+# If set to NO (the default) these members will be included in the
+# various overviews, but no documentation section is generated.
+# This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_MEMBERS     = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy.
+# If set to NO (the default) these classes will be included in the various
+# overviews. This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_CLASSES     = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all
+# friend (class|struct|union) declarations.
+# If set to NO (the default) these declarations will be included in the
+# documentation.
+
+HIDE_FRIEND_COMPOUNDS  = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any
+# documentation blocks found inside the body of a function.
+# If set to NO (the default) these blocks will be appended to the
+# function's detailed documentation block.
+
+HIDE_IN_BODY_DOCS      = NO
+
+# The INTERNAL_DOCS tag determines if documentation
+# that is typed after a \internal command is included. If the tag is set
+# to NO (the default) then the documentation will be excluded.
+# Set it to YES to include the internal documentation.
+
+INTERNAL_DOCS          = YES
+
+# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate
+# file names in lower-case letters. If set to YES upper-case letters are also
+# allowed. This is useful if you have classes or files whose names only differ
+# in case and if your file system supports case sensitive file names. Windows
+# and Mac users are advised to set this option to NO.
+
+CASE_SENSE_NAMES       = YES
+
+# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen
+# will show members with their full class and namespace scopes in the
+# documentation. If set to YES the scope will be hidden.
+
+HIDE_SCOPE_NAMES       = NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen
+# will put a list of the files that are included by a file in the documentation
+# of that file.
+
+SHOW_INCLUDE_FILES     = YES
+
+# If the INLINE_INFO tag is set to YES (the default) then a tag [inline]
+# is inserted in the documentation for inline members.
+
+INLINE_INFO            = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen
+# will sort the (detailed) documentation of file and class members
+# alphabetically by member name. If set to NO the members will appear in
+# declaration order.
+
+SORT_MEMBER_DOCS       = NO
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the
+# brief documentation of file, namespace and class members alphabetically
+# by member name. If set to NO (the default) the members will appear in
+# declaration order.
+
+SORT_BRIEF_DOCS        = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be
+# sorted by fully-qualified names, including namespaces. If set to
+# NO (the default), the class list will be sorted only by class name,
+# not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the
+# alphabetical list.
+
+SORT_BY_SCOPE_NAME     = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or
+# disable (NO) the todo list. This list is created by putting \todo
+# commands in the documentation.
+
+GENERATE_TODOLIST      = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or
+# disable (NO) the test list. This list is created by putting \test
+# commands in the documentation.
+
+GENERATE_TESTLIST      = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or
+# disable (NO) the bug list. This list is created by putting \bug
+# commands in the documentation.
+
+GENERATE_BUGLIST       = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or
+# disable (NO) the deprecated list. This list is created by putting
+# \deprecated commands in the documentation.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional
+# documentation sections, marked by \if sectionname ... \endif.
+
+ENABLED_SECTIONS       =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines
+# the initial value of a variable or define consists of for it to appear in
+# the documentation. If the initializer consists of more lines than specified
+# here it will be hidden. Use a value of 0 to hide initializers completely.
+# The appearance of the initializer of individual variables and defines in the
+# documentation can be controlled using \showinitializer or \hideinitializer
+# command in the documentation regardless of this setting.
+
+MAX_INITIALIZER_LINES  = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated
+# at the bottom of the documentation of classes and structs. If set to YES the
+# list will mention the files that were used to generate the documentation.
+
+SHOW_USED_FILES        = YES
+
+# If the sources in your project are distributed over multiple directories
+# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy
+# in the documentation. The default is NO.
+
+SHOW_DIRECTORIES       = NO
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from the
+# version control system). Doxygen will invoke the program by executing (via
+# popen()) the command <command> <input-file>, where <command> is the value of
+# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file
+# provided by doxygen. Whatever the program writes to standard output
+# is used as the file version. See the manual for examples.
+
+FILE_VERSION_FILTER    =
+
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated
+# by doxygen. Possible values are YES and NO. If left blank NO is used.
+
+QUIET                  = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated by doxygen. Possible values are YES and NO. If left blank
+# NO is used.
+
+WARNINGS               = YES
+
+# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings
+# for undocumented members. If EXTRACT_ALL is set to YES then this flag will
+# automatically be disabled.
+
+WARN_IF_UNDOCUMENTED   = YES
+
+# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as not documenting some
+# parameters in a documented function, or documenting parameters that
+# don't exist or using markup commands wrongly.
+
+WARN_IF_DOC_ERROR      = YES
+
+# This WARN_NO_PARAMDOC option can be abled to get warnings for
+# functions that are documented, but have no documentation for their parameters
+# or return value. If set to NO (the default) doxygen will only warn about
+# wrong or incomplete parameter documentation, but not about the absence of
+# documentation.
+
+WARN_NO_PARAMDOC       = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that
+# doxygen can produce. The string should contain the $file, $line, and $text
+# tags, which will be replaced by the file and line number from which the
+# warning originated and the warning text. Optionally the format may contain
+# $version, which will be replaced by the version of the file (if it could
+# be obtained via FILE_VERSION_FILTER)
+
+WARN_FORMAT            = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning
+# and error messages should be written. If left blank the output is written
+# to stderr.
+
+WARN_LOGFILE           =
+
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag can be used to specify the files and/or directories that contain
+# documented source files. You may enter file names like "myfile.cpp" or
+# directories like "/usr/src/myproject". Separate the files or directories
+# with spaces.
+
+INPUT                  = ..
+
+# This tag can be used to specify the character encoding of the source files that
+# doxygen parses. Internally doxygen uses the UTF-8 encoding, which is also the default
+# input encoding. Doxygen uses libiconv (or the iconv built into libc) for the transcoding.
+# See http://www.gnu.org/software/libiconv for the list of possible encodings.
+
+INPUT_ENCODING         = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank the following patterns are tested:
+# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx
+# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py
+
+FILE_PATTERNS          = *.h \
+                         *.c
+
+# The RECURSIVE tag can be used to turn specify whether or not subdirectories
+# should be searched for input files as well. Possible values are YES and NO.
+# If left blank NO is used.
+
+RECURSIVE              = NO
+
+# The EXCLUDE tag can be used to specify files and/or directories that should
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+
+EXCLUDE                = config.h
+
+# The EXCLUDE_SYMLINKS tag can be used select whether or not files or
+# directories that are symbolic links (a Unix filesystem feature) are excluded
+# from the input.
+
+EXCLUDE_SYMLINKS       = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories. Note that the wildcards are matched
+# against the file with absolute path, so to exclude all test directories
+# for example use the pattern */test/*
+
+EXCLUDE_PATTERNS       =
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the output.
+# The symbol name can be a fully qualified name, a word, or if the wildcard * is used,
+# a substring. Examples: ANamespace, AClass, AClass::ANamespace, ANamespace::*Test
+
+EXCLUDE_SYMBOLS        =
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or
+# directories that contain example code fragments that are included (see
+# the \include command).
+
+EXAMPLE_PATH           =
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank all files are included.
+
+EXAMPLE_PATTERNS       =
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude
+# commands irrespective of the value of the RECURSIVE tag.
+# Possible values are YES and NO. If left blank NO is used.
+
+EXAMPLE_RECURSIVE      = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or
+# directories that contain image that are included in the documentation (see
+# the \image command).
+
+IMAGE_PATH             =
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command <filter> <input-file>, where <filter>
+# is the value of the INPUT_FILTER tag, and <input-file> is the name of an
+# input file. Doxygen will then use the output that the filter program writes
+# to standard output.  If FILTER_PATTERNS is specified, this tag will be
+# ignored.
+
+INPUT_FILTER           =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis.  Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match.  The filters are a list of the form:
+# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further
+# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER
+# is applied to all files.
+
+FILTER_PATTERNS        =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER) will be used to filter the input files when producing source
+# files to browse (i.e. when SOURCE_BROWSER is set to YES).
+
+FILTER_SOURCE_FILES    = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will
+# be generated. Documented entities will be cross-referenced with these sources.
+# Note: To get rid of all source code in the generated output, make sure also
+# VERBATIM_HEADERS is set to NO.
+
+SOURCE_BROWSER         = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body
+# of functions and classes directly in the documentation.
+
+INLINE_SOURCES         = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct
+# doxygen to hide any special comment blocks from generated source code
+# fragments. Normal C and C++ comments will always remain visible.
+
+STRIP_CODE_COMMENTS    = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES (the default)
+# then for each documented function all documented
+# functions referencing it will be listed.
+
+REFERENCED_BY_RELATION = YES
+
+# If the REFERENCES_RELATION tag is set to YES (the default)
+# then for each documented function all documented entities
+# called/used by that function will be listed.
+
+REFERENCES_RELATION    = YES
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES (the default)
+# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from
+# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will
+# link to the source code.  Otherwise they will link to the documentstion.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code
+# will point to the HTML generated by the htags(1) tool instead of doxygen
+# built-in source browser. The htags tool is part of GNU's global source
+# tagging system (see http://www.gnu.org/software/global/global.html). You
+# will need version 4.8.6 or higher.
+
+USE_HTAGS              = NO
+
+# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen
+# will generate a verbatim copy of the header file for each class for
+# which an include is specified. Set to NO to disable this.
+
+VERBATIM_HEADERS       = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index
+# of all compounds will be generated. Enable this if the project
+# contains a lot of classes, structs, unions or interfaces.
+
+ALPHABETICAL_INDEX     = NO
+
+# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then
+# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns
+# in which this list will be split (can be a number in the range [1..20])
+
+COLS_IN_ALPHA_INDEX    = 5
+
+# In case all classes in a project start with a common prefix, all
+# classes will be put under the same header in the alphabetical index.
+# The IGNORE_PREFIX tag can be used to specify one or more prefixes that
+# should be ignored while generating the index headers.
+
+IGNORE_PREFIX          =
+
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES (the default) Doxygen will
+# generate HTML output.
+
+GENERATE_HTML          = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `html' will be used as the default path.
+
+HTML_OUTPUT            = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for
+# each generated HTML page (for example: .htm,.php,.asp). If it is left blank
+# doxygen will generate files with .html extension.
+
+HTML_FILE_EXTENSION    = .html
+
+# The HTML_HEADER tag can be used to specify a personal HTML header for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard header.
+
+HTML_HEADER            =
+
+# The HTML_FOOTER tag can be used to specify a personal HTML footer for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard footer.
+
+HTML_FOOTER            =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading
+# style sheet that is used by each HTML page. It can be used to
+# fine-tune the look of the HTML output. If the tag is left blank doxygen
+# will generate a default style sheet. Note that doxygen will try to copy
+# the style sheet file to the HTML output directory, so don't put your own
+# stylesheet in the HTML output directory as well, or it will be erased!
+
+HTML_STYLESHEET        =
+
+# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes,
+# files or namespaces will be aligned in HTML using tables. If set to
+# NO a bullet list will be used.
+
+HTML_ALIGN_MEMBERS     = YES
+
+# If the GENERATE_HTMLHELP tag is set to YES, additional index files
+# will be generated that can be used as input for tools like the
+# Microsoft HTML help workshop to generate a compressed HTML help file (.chm)
+# of the generated HTML documentation.
+
+GENERATE_HTMLHELP      = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can
+# be used to specify the file name of the resulting .chm file. You
+# can add a path in front of the file if the result should not be
+# written to the html output directory.
+
+CHM_FILE               =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can
+# be used to specify the location (absolute path including file name) of
+# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run
+# the HTML help compiler on the generated index.hhp.
+
+HHC_LOCATION           =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag
+# controls if a separate .chi index file is generated (YES) or that
+# it should be included in the master .chm file (NO).
+
+GENERATE_CHI           = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag
+# controls whether a binary table of contents is generated (YES) or a
+# normal table of contents (NO) in the .chm file.
+
+BINARY_TOC             = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members
+# to the contents of the HTML help documentation and to the tree view.
+
+TOC_EXPAND             = NO
+
+# The DISABLE_INDEX tag can be used to turn on/off the condensed index at
+# top of each HTML page. The value NO (the default) enables the index and
+# the value YES disables it.
+
+DISABLE_INDEX          = NO
+
+# This tag can be used to set the number of enum values (range [1..20])
+# that doxygen will group on one line in the generated HTML documentation.
+
+ENUM_VALUES_PER_LINE   = 4
+
+# If the GENERATE_TREEVIEW tag is set to YES, a side panel will be
+# generated containing a tree-like index structure (just like the one that
+# is generated for HTML Help). For this to work a browser that supports
+# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+,
+# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are
+# probably better off using the HTML help feature.
+
+GENERATE_TREEVIEW      = NO
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be
+# used to set the initial width (in pixels) of the frame in which the tree
+# is shown.
+
+TREEVIEW_WIDTH         = 250
+
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will
+# generate Latex output.
+
+GENERATE_LATEX         = NO
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `latex' will be used as the default path.
+
+LATEX_OUTPUT           = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked. If left blank `latex' will be used as the default command name.
+
+LATEX_CMD_NAME         = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to
+# generate index for LaTeX. If left blank `makeindex' will be used as the
+# default command name.
+
+MAKEINDEX_CMD_NAME     = makeindex
+
+# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact
+# LaTeX documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_LATEX          = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used
+# by the printer. Possible values are: a4, a4wide, letter, legal and
+# executive. If left blank a4wide will be used.
+
+PAPER_TYPE             = a4wide
+
+# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX
+# packages that should be included in the LaTeX output.
+
+EXTRA_PACKAGES         =
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for
+# the generated latex document. The header should contain everything until
+# the first chapter. If it is left blank doxygen will generate a
+# standard header. Notice: only use this tag if you know what you are doing!
+
+LATEX_HEADER           =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated
+# is prepared for conversion to pdf (using ps2pdf). The pdf file will
+# contain links (just like the HTML output) instead of page references
+# This makes the output suitable for online browsing using a pdf viewer.
+
+PDF_HYPERLINKS         = NO
+
+# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of
+# plain latex in the generated Makefile. Set this option to YES to get a
+# higher quality PDF documentation.
+
+USE_PDFLATEX           = NO
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode.
+# command to the generated LaTeX files. This will instruct LaTeX to keep
+# running if errors occur, instead of asking the user for help.
+# This option is also used when generating formulas in HTML.
+
+LATEX_BATCHMODE        = NO
+
+# If LATEX_HIDE_INDICES is set to YES then doxygen will not
+# include the index chapters (such as File Index, Compound Index, etc.)
+# in the output.
+
+LATEX_HIDE_INDICES     = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output
+# The RTF output is optimized for Word 97 and may not look very pretty with
+# other RTF readers or editors.
+
+GENERATE_RTF           = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `rtf' will be used as the default path.
+
+RTF_OUTPUT             = rtf
+
+# If the COMPACT_RTF tag is set to YES Doxygen generates more compact
+# RTF documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_RTF            = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated
+# will contain hyperlink fields. The RTF file will
+# contain links (just like the HTML output) instead of page references.
+# This makes the output suitable for online browsing using WORD or other
+# programs which support those fields.
+# Note: wordpad (write) and others do not support links.
+
+RTF_HYPERLINKS         = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's
+# config file, i.e. a series of assignments. You only have to provide
+# replacements, missing definitions are set to their default value.
+
+RTF_STYLESHEET_FILE    =
+
+# Set optional variables used in the generation of an rtf document.
+# Syntax is similar to doxygen's config file.
+
+RTF_EXTENSIONS_FILE    =
+
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES (the default) Doxygen will
+# generate man pages
+
+GENERATE_MAN           = YES
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `man' will be used as the default path.
+
+MAN_OUTPUT             = man
+
+# The MAN_EXTENSION tag determines the extension that is added to
+# the generated man pages (default is the subroutine's section .3)
+
+MAN_EXTENSION          = .3
+
+# If the MAN_LINKS tag is set to YES and Doxygen generates man output,
+# then it will generate one additional man file for each entity
+# documented in the real man page(s). These additional files
+# only source the real man page, but without them the man command
+# would be unable to find the correct page. The default is NO.
+
+MAN_LINKS              = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES Doxygen will
+# generate an XML file that captures the structure of
+# the code including all documentation.
+
+GENERATE_XML           = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `xml' will be used as the default path.
+
+XML_OUTPUT             = xml
+
+# The XML_SCHEMA tag can be used to specify an XML schema,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_SCHEMA             =
+
+# The XML_DTD tag can be used to specify an XML DTD,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_DTD                =
+
+# If the XML_PROGRAMLISTING tag is set to YES Doxygen will
+# dump the program listings (including syntax highlighting
+# and cross-referencing information) to the XML output. Note that
+# enabling this will significantly increase the size of the XML output.
+
+XML_PROGRAMLISTING     = YES
+
+#---------------------------------------------------------------------------
+# configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will
+# generate an AutoGen Definitions (see autogen.sf.net) file
+# that captures the structure of the code including all
+# documentation. Note that this feature is still experimental
+# and incomplete at the moment.
+
+GENERATE_AUTOGEN_DEF   = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES Doxygen will
+# generate a Perl module file that captures the structure of
+# the code including all documentation. Note that this
+# feature is still experimental and incomplete at the
+# moment.
+
+GENERATE_PERLMOD       = NO
+
+# If the PERLMOD_LATEX tag is set to YES Doxygen will generate
+# the necessary Makefile rules, Perl scripts and LaTeX code to be able
+# to generate PDF and DVI output from the Perl module output.
+
+PERLMOD_LATEX          = NO
+
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be
+# nicely formatted so it can be parsed by a human reader.  This is useful
+# if you want to understand what is going on.  On the other hand, if this
+# tag is set to NO the size of the Perl module output will be much smaller
+# and Perl will parse it just the same.
+
+PERLMOD_PRETTY         = YES
+
+# The names of the make variables in the generated doxyrules.make file
+# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX.
+# This is useful so different doxyrules.make files included by the same
+# Makefile don't overwrite each other's variables.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will
+# evaluate all C-preprocessor directives found in the sources and include
+# files.
+
+ENABLE_PREPROCESSING   = YES
+
+# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro
+# names in the source code. If set to NO (the default) only conditional
+# compilation will be performed. Macro expansion can be done in a controlled
+# way by setting EXPAND_ONLY_PREDEF to YES.
+
+MACRO_EXPANSION        = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES
+# then the macro expansion is limited to the macros specified with the
+# PREDEFINED and EXPAND_AS_DEFINED tags.
+
+EXPAND_ONLY_PREDEF     = NO
+
+# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files
+# in the INCLUDE_PATH (see below) will be search if a #include is found.
+
+SEARCH_INCLUDES        = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by
+# the preprocessor.
+
+INCLUDE_PATH           =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will
+# be used.
+
+INCLUDE_FILE_PATTERNS  =
+
+# The PREDEFINED tag can be used to specify one or more macro names that
+# are defined before the preprocessor is started (similar to the -D option of
+# gcc). The argument of the tag is a list of macros of the form: name
+# or name=definition (no spaces). If the definition and the = are
+# omitted =1 is assumed. To prevent a macro definition from being
+# undefined via #undef or recursively expanded use the := operator
+# instead of the = operator.
+
+PREDEFINED             =
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then
+# this tag can be used to specify a list of macro names that should be expanded.
+# The macro definition that is found in the sources will be used.
+# Use the PREDEFINED tag if you want to use a different macro definition.
+
+EXPAND_AS_DEFINED      =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then
+# doxygen's preprocessor will remove all function-like macros that are alone
+# on a line, have an all uppercase name, and do not end with a semicolon. Such
+# function macros are typically used for boiler-plate code, and will confuse
+# the parser if not removed.
+
+SKIP_FUNCTION_MACROS   = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES option can be used to specify one or more tagfiles.
+# Optionally an initial location of the external documentation
+# can be added for each tagfile. The format of a tag file without
+# this location is as follows:
+#   TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+#   TAGFILES = file1=loc1 "file2 = loc2" ...
+# where "loc1" and "loc2" can be relative or absolute paths or
+# URLs. If a location is present for each tag, the installdox tool
+# does not have to be run to correct the links.
+# Note that each tag file must have a unique name
+# (where the name does NOT include the path)
+# If a tag file is not located in the directory in which doxygen
+# is run, you must also specify the path to the tagfile here.
+
+TAGFILES               =
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create
+# a tag file that is based on the input files it reads.
+
+GENERATE_TAGFILE       =
+
+# If the ALLEXTERNALS tag is set to YES all external classes will be listed
+# in the class index. If set to NO only the inherited external classes
+# will be listed.
+
+ALLEXTERNALS           = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed
+# in the modules index. If set to NO, only the current project's groups will
+# be listed.
+
+EXTERNAL_GROUPS        = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script
+# interpreter (i.e. the result of `which perl').
+
+PERL_PATH              = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will
+# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base
+# or super classes. Setting the tag to NO turns the diagrams off. Note that
+# this option is superseded by the HAVE_DOT option below. This is only a
+# fallback. It is recommended to install and use dot, since it yields more
+# powerful graphs.
+
+CLASS_DIAGRAMS         = YES
+
+# You can define message sequence charts within doxygen comments using the \msc
+# command. Doxygen will then run the mscgen tool (see http://www.mcternan.me.uk/mscgen/) to
+# produce the chart and insert it in the documentation. The MSCGEN_PATH tag allows you to
+# specify the directory where the mscgen tool resides. If left empty the tool is assumed to
+# be found in the default search path.
+
+MSCGEN_PATH            =
+
+# If set to YES, the inheritance and collaboration graphs will hide
+# inheritance and usage relations if the target is undocumented
+# or is not a class.
+
+HIDE_UNDOC_RELATIONS   = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz, a graph visualization
+# toolkit from AT&T and Lucent Bell Labs. The other options in this section
+# have no effect if this option is set to NO (the default)
+
+HAVE_DOT               = NO
+
+# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect inheritance relations. Setting this tag to YES will force the
+# the CLASS_DIAGRAMS tag to NO.
+
+CLASS_GRAPH            = YES
+
+# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect implementation dependencies (inheritance, containment, and
+# class references variables) of the class with other documented classes.
+
+COLLABORATION_GRAPH    = YES
+
+# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for groups, showing the direct groups dependencies
+
+GROUP_GRAPHS           = YES
+
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+
+UML_LOOK               = NO
+
+# If set to YES, the inheritance and collaboration graphs will show the
+# relations between templates and their instances.
+
+TEMPLATE_RELATIONS     = NO
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT
+# tags are set to YES then doxygen will generate a graph for each documented
+# file showing the direct and indirect include dependencies of the file with
+# other documented files.
+
+INCLUDE_GRAPH          = YES
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and
+# HAVE_DOT tags are set to YES then doxygen will generate a graph for each
+# documented header file showing the documented files that directly or
+# indirectly include this file.
+
+INCLUDED_BY_GRAPH      = YES
+
+# If the CALL_GRAPH and HAVE_DOT tags are set to YES then doxygen will
+# generate a call dependency graph for every global function or class method.
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable call graphs for selected
+# functions only using the \callgraph command.
+
+CALL_GRAPH             = NO
+
+# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then doxygen will
+# generate a caller dependency graph for every global function or class method.
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable caller graphs for selected
+# functions only using the \callergraph command.
+
+CALLER_GRAPH           = NO
+
+# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen
+# will graphical hierarchy of all classes instead of a textual one.
+
+GRAPHICAL_HIERARCHY    = YES
+
+# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES
+# then doxygen will show the dependencies a directory has on other directories
+# in a graphical way. The dependency relations are determined by the #include
+# relations between the files in the directories.
+
+DIRECTORY_GRAPH        = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot. Possible values are png, jpg, or gif
+# If left blank png will be used.
+
+DOT_IMAGE_FORMAT       = png
+
+# The tag DOT_PATH can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+
+DOT_PATH               =
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the
+# \dotfile command).
+
+DOTFILE_DIRS           =
+
+# The MAX_DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of
+# nodes that will be shown in the graph. If the number of nodes in a graph
+# becomes larger than this value, doxygen will truncate the graph, which is
+# visualized by representing a node as a red box. Note that doxygen will always
+# show the root nodes and its direct children regardless of this setting.
+
+DOT_GRAPH_MAX_NODES    = 50
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
+# background. This is disabled by default, which results in a white background.
+# Warning: Depending on the platform used, enabling this option may lead to
+# badly anti-aliased labels on the edges of a graph (i.e. they become hard to
+# read).
+
+DOT_TRANSPARENT        = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10)
+# support this, this feature is disabled by default.
+
+DOT_MULTI_TARGETS      = NO
+
+# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will
+# generate a legend page explaining the meaning of the various boxes and
+# arrows in the dot generated graphs.
+
+GENERATE_LEGEND        = YES
+
+# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will
+# remove the intermediate dot files that are used to generate
+# the various graphs.
+
+DOT_CLEANUP            = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to the search engine
+#---------------------------------------------------------------------------
+
+# The SEARCHENGINE tag specifies whether or not a search engine should be
+# used. If set to NO the values of all tags below this one will be ignored.
+
+SEARCHENGINE           = NO
diff --git a/memcached/doc/Makefile.am b/memcached/doc/Makefile.am
new file mode 100644 (file)
index 0000000..f9f4f46
--- /dev/null
@@ -0,0 +1,20 @@
+man_MANS = memcached.1
+
+EXTRA_DIST = *.txt
+
+BUILT_SOURCES=
+
+if BUILD_SPECIFICATIONS
+BUILT_SOURCES += protocol-binary.txt protocol-binary-range.txt
+MOSTLYCLEANFILES = protocol-binary.txt protocol-binary-range.txt
+endif
+
+%.txt: %.full
+       @XML2RFC@ $< $@
+
+%.chk: %.xml xml2rfc/rfc2629-refchk.xsl
+       @XSLTPROC@ xml2rfc/rfc2629-refchk.xsl $< >$@
+
+%.full: %.xml xml2rfc/rfc2629-noinc.xsl
+       @XSLTPROC@ xml2rfc/rfc2629-noinc.xsl $< >$@
+
diff --git a/memcached/doc/memcached.1 b/memcached/doc/memcached.1
new file mode 100644 (file)
index 0000000..b6f633d
--- /dev/null
@@ -0,0 +1,153 @@
+.TH MEMCACHED 1 "April 11, 2005"
+.SH NAME
+memcached \- high-performance memory object caching system
+.SH SYNOPSIS
+.B memcached
+.RI [ options ]
+.br
+.SH DESCRIPTION
+This manual page documents briefly the
+.B memcached
+memory object caching daemon.
+.PP
+.B memcached
+is a flexible memory object caching daemon designed to alleviate database load
+in dynamic web applications by storing objects in memory.  It's based on
+libevent to scale to any size needed, and is specifically optimized to avoid
+swapping and always use non-blocking I/O.
+.br
+.SH OPTIONS
+These programs follow the usual GNU command line syntax. A summary of options
+is included below.
+.TP
+.B \-s <file>
+Unix socket path to listen on (disables network support).
+.TP
+.B \-a <perms>
+Permissions (in octal format) for Unix socket created with \-s option.
+.TP
+.B \-l <ip_addr>
+Listen on <ip_addr>; default to INADDR_ANY. This is an important option to
+consider as there is no other way to secure the installation. Binding to an
+internal or firewalled network interface is suggested.
+.TP
+.B \-d
+Run memcached as a daemon.
+.TP
+.B \-u <username>
+Assume the identity of <username> (only when run as root).
+.TP
+.B \-m <num>
+Use <num> MB memory max to use for object storage; the default is 64 megabytes.
+.TP
+.B \-c <num>
+Use <num> max simultaneous connections; the default is 1024.
+.TP
+.B \-R <num>
+This option seeks to prevent client starvation by setting a limit to the
+number of sequential requests the server will process from an individual
+client connection. Once a connection has exceeded this value, the server will
+attempt to process I/O on other connections before handling any further
+request from this connection. The default value for this option is 20.
+.TP
+.B \-k
+Lock down all paged memory. This is a somewhat dangerous option with large
+caches, so consult the README and memcached homepage for configuration
+suggestions.
+.TP
+.B \-p <num>
+Listen on TCP port <num>, the default is port 11211.
+.TP
+.B \-U <num>
+Listen on UDP port <num>, the default is port 11211, 0 is off.
+.TP
+.B \-M
+Disable automatic removal of items from the cache when out of memory.
+Additions will not be possible until adequate space is freed up.
+.TP
+.B \-r
+Raise the core file size limit to the maximum allowable.
+.TP
+.B \-f <factor>
+Use <factor> as the multiplier for computing the sizes of memory chunks that
+items are stored in. A lower value may result in less wasted memory depending
+on the total amount of memory available and the distribution of item sizes.
+The default is 1.25.
+.TP
+.B \-n <size>
+Allocate a minimum of <size> bytes for the item key, value, and flags. The
+default is 48. If you have a lot of small keys and values, you can get a
+significant memory efficiency gain with a lower value. If you use a high
+chunk growth factor (\-f option), on the other hand, you may want to increase
+the size to allow a bigger percentage of your items to fit in the most densely
+packed (smallest) chunks.
+.TP
+.B \-C
+Disable the use of CAS (and reduce the per-item size by 8 bytes).
+.TP
+.B \-h
+Show the version of memcached and a summary of options.
+.TP
+.B \-v
+Be verbose during the event loop; print out errors and warnings.
+.TP
+.B \-vv
+Be even more verbose; same as \-v but also print client commands and
+responses.
+.TP
+.B \-i
+Print memcached and libevent licenses.
+.TP
+.B \-P <filename>
+Print pidfile to <filename>, only used under \-d option.
+.TP
+.B \-t <threads>
+Number of threads to use to process incoming requests. This option is only
+meaningful if memcached was compiled with thread support enabled. It is
+typically not useful to set this higher than the number of CPU cores on the
+memcached server. The default is 4.
+.TP
+.B \-D <char>
+Use <char> as the delimiter between key prefixes and IDs. This is used for
+per-prefix stats reporting. The default is ":" (colon). If this option is
+specified, stats collection is turned on automatically; if not, then it may
+be turned on by sending the "stats detail on" command to the server.
+.TP
+.B \-L
+Try to use large memory pages (if available). Increasing the memory page size
+could reduce the number of TLB misses and improve the performance. In order to
+get large pages from the OS, memcached will allocate the total item-cache in
+one large chunk. Only available if supported on your OS.
+.TP
+.B \-B <proto>
+Specify the binding protocol to use.  By default, the server will
+autonegotiate client connections.  By using this option, you can
+specify the protocol clients must speak.  Possible options are "auto"
+(the default, autonegotiation behavior), "ascii" and "binary".
+.TP
+.B \-I <size>
+Override the default size of each slab page. Default is 1mb. Default is 1m,
+minimum is 1k, max is 128m. Adjusting this value changes the item size limit.
+Beware that this also increases the number of slabs (use -v to view), and the
+overal memory usage of memcached.
+.TP
+.B \-o <options>
+Comma separated list of extended or experimental options. See -h or wiki for
+up to date list.
+.br
+.SH LICENSE
+The memcached daemon is copyright Danga Interactive and is distributed under
+the BSD license. Note that daemon clients are licensed separately.
+.br
+.SH SEE ALSO
+The README file that comes with memcached
+.br
+.B http://www.danga.com/memcached
+.SH AUTHOR
+The memcached daemon was written by Anatoly Vorobey
+.B <mellon@pobox.com>
+and Brad Fitzpatrick
+.B <brad@danga.com>
+and the rest of the crew of Danga Interactive
+.B http://www.danga.com
+.br
diff --git a/memcached/doc/protocol-binary-range.xml b/memcached/doc/protocol-binary-range.xml
new file mode 100644 (file)
index 0000000..02711a8
--- /dev/null
@@ -0,0 +1,226 @@
+<?xml version="1.0" encoding="US-ASCII"?>
+<!DOCTYPE rfc SYSTEM "xml2rfc/rfc2629.dtd">
+<?xml-stylesheet type='text/xsl' href='xml2rfc/rfc2629.xslt'?>
+<?rfc toc="yes"?>
+<?rfc strict="yes"?>
+<?rfc symrefs="yes"?>
+<?rfc sortrefs="yes" ?>
+<?rfc compact="yes" ?>
+<?rfc subcompact="yes" ?>
+<rfc category="info" docName="draft-stone-memcache-udp-01" ipr="none">
+
+  <front>
+
+    <title abbrev="Memcache Over UDP"> Memcache Binary Protocol: Extensions for UDP </title>
+
+    <author fullname="Aaron Stone" surname="Aaron Stone" role="editor">
+      <organization>Six Apart, Ltd.</organization>
+      <address>
+        <postal>
+          <street>548 4th Street</street>
+          <city>San Francisco</city>
+          <region>CA</region>
+          <code>94107</code>
+          <country>USA</country>
+        </postal>
+        <email>aaron@serendipity.palo-alto.ca.us</email>
+      </address>
+    </author>
+
+    <date day="14" month="December" year="2007" />
+
+    <area>Applications</area>
+
+    <keyword>memcache memcached cache udp</keyword>
+
+    <abstract>
+      <t>
+      This memo explains extensions to the memcache binary protocol for use in a UDP environment.
+      </t>
+
+      <t>
+      Memcache is a high performance key-value cache. It is intentionally a
+      dumb cache, optimized for speed only. Applications using memcache do
+      not rely on it for data -- a persistent database with guaranteed reliability
+      is strongly recommended -- but applications can run much faster when
+      cached data is available in memcache.
+      </t>
+    </abstract>
+  </front>
+
+  <middle>
+    <section anchor="introduction" title="Introduction">
+      <t>
+      Memcache is a high performance key-value cache. It is intentionally a
+      dumb cache, optimized for speed only. Applications using memcache do
+      not rely on it for data -- a persistent database with guaranteed reliability
+      is strongly recommended -- but applications can run much faster when
+      cached data is available in memcache.
+      </t>
+      <t>
+      Sites may find that, due to their network architecture or application usage patterns,
+      the stateless <xref target="UDP"/> protocol better suits their needs. This document
+      provides extensions and descriptions of use of the <xref target="MEMCACHE">memcache protocol</xref>
+      in a UDP environment.
+      </t>
+      <t>
+      It is a goal of this document to provide sufficient information in each UDP packet
+      as to avoid any requirement for statefulness on the part of the server nor significant
+      caching of outstanding packets on the part of the client.
+      </t>
+      <section anchor="conventions" title="Conventions Used In This Document">
+        <t>The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
+        "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
+        document are to be interpreted as described in <xref target="KEYWORDS"/>.
+        </t>
+      </section>
+    </section>
+
+    <section anchor="values" title="Defined Values">
+      <section anchor="value-magic" title="Magic Byte">
+        <t>
+        The magic bytes remains the same as in <xref target="MEMCACHE"/>.
+        </t>
+      </section>
+
+      <section anchor="value-status" title="Response Status">
+        <t>
+        Additional status values:
+        <list hangIndent="8" style="hanging">
+          <t hangText="0x0004">Value is larger than a single response packet</t>
+        </list>
+        </t>
+      </section>
+
+      <section anchor="value-opcodes" title="Command Opcodes">
+        <t>
+        Additional opcode values:
+        <list hangIndent="8" style="hanging">
+          <t hangText="0x0C">Get Range</t>
+          <t hangText="0x0D">Set Range</t>
+        </list>
+        </t>
+      </section>
+
+      <section anchor="value-types" title="Data Types">
+        <t>
+        There are no new data types in this extension.
+        </t>
+      </section>
+    </section>
+
+    <section anchor="commands" title="Commands">
+
+      <section anchor="command-get" title="Get Response">
+        <t>
+        This section extends the behavior of the Get and GetQ commands as described in
+        <xref target="MEMCACHE" x:sec="command-get"/>.
+        </t>
+
+        <t>
+        When a Get or GetQ request is made via UDP, and the value of the key for which
+        the request was made is larger than can be placed into a single UDP packet (noting
+        that the protocol header must also be counted), a Get Range response packet
+        MUST be sent instead of the Get response packet. In this instance:
+        <list style="numbers">
+          <t>The Status field of the response header MUST be 0x0004.</t>
+          <t>The Offset field of the GetR response extras MUST be 0.</t>
+          <t>The Length field of the GetR response extras, and the data contained in
+             the Value field of the packet, SHOULD be the maximum
+             allowed length of a UDP packet, less the space required by the header
+             and extras; however it MAY be any amount below this maximum.</t>
+          <t>The Total value length field of the response extras MUST be the
+             actual length of the complete value.</t>
+        </list>
+        </t>
+
+        <t>
+        The client, upon receipt of a Get Range response bearing Status 0x004
+        and a Message ID corresponding to its Get request, shall then know that
+        it has received only the first portion of the value. The client MAY choose
+        to request the remaining portion of the value by sending one or more Get Range requests.
+        </t>
+      </section>
+
+      <section anchor="command-getr-request" title="Get Range Request">
+        <t>
+         The Get Range request is primarily intended for use over a UDP transport
+         to request byte ranges of the value for a key. In the event that the Data version
+         check fails to match that of the key, an error MUST be returned.
+       </t>
+        <t>
+      <figure>
+        <preamble>Extra data for get range request:</preamble>
+          <artwork>
+Byte/     0       |       1       |       2       |       3       |
+   /              |               |               |               |
+  |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|
+  +---------------+---------------+---------------+---------------+
+ 0| Flags                                                         |
+  +---------------+---------------+---------------+---------------+
+ 4| Data version check                                            |
+  |                                                               |
+  +---------------+---------------+---------------+---------------+
+12| Offset                                                        |
+  +---------------+---------------+---------------+---------------+
+16| Length                                                        |
+  +---------------+---------------+---------------+---------------+
+Total 20 bytes
+      </artwork></figure>
+        </t>
+      </section>
+
+      <section anchor="command-getr-response" title="Get Range Response">
+        <t>
+         The Get Range request is primarily intended for use over a UDP transport
+         to indicate the location of the bytes of the value for a key contained in
+         a given packet. A client receives enough information in each Get Range
+         extras to construct an appropriately sized buffer in its own memory and
+         blindly insert the contents of the packet at the given byte offset.
+       </t>
+        <t>
+      <figure>
+        <preamble>Extra data for get range response:</preamble>
+          <artwork>
+Byte/     0       |       1       |       2       |       3       |
+   /              |               |               |               |
+  |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|
+  +---------------+---------------+---------------+---------------+
+ 0| Flags                                                         |
+  +---------------+---------------+---------------+---------------+
+ 4| Data version check                                            |
+  |                                                               |
+  +---------------+---------------+---------------+---------------+
+12| Offset                                                        |
+  +---------------+---------------+---------------+---------------+
+16| Length                                                        |
+  +---------------+---------------+---------------+---------------+
+20| Total value length                                            |
+  +---------------+---------------+---------------+---------------+
+Total 24 bytes
+      </artwork></figure>
+        </t>
+      </section>
+
+    </section>
+
+    <section anchor="security" title="Security Considerations">
+      <t>
+      This document does not introduce any new security considerations
+      beyond those discussed in <xref target="MEMCACHE" x:sec="security"/>.
+      </t>
+    </section>
+
+  </middle>
+
+  <back>
+    <references title="Normative References">
+      <dwdrfc-ref anchor='UDP' src='http://xml.resource.org/public/rfc/bibxml/reference.RFC.0768.xml'/>
+      <dwdrfc-ref anchor='KEYWORDS' src='http://xml.resource.org/public/rfc/bibxml/reference.RFC.2119.xml'/>
+      <!-- FIXME: Get a draft reference for the base document. -->
+      <dwdrfc-ref anchor='MEMCACHE' src='http://xml.resource.org/public/rfc/bibxml/reference.RFC.2119.xml'/>
+    </references>
+  </back>
+
+</rfc>
+
diff --git a/memcached/doc/protocol-binary.xml b/memcached/doc/protocol-binary.xml
new file mode 100644 (file)
index 0000000..2dfbc58
--- /dev/null
@@ -0,0 +1,1461 @@
+<?xml version="1.0" encoding="US-ASCII"?>
+<!DOCTYPE rfc SYSTEM "xml2rfc/rfc2629.dtd">
+<?xml-stylesheet type='text/xsl' href='xml2rfc/rfc2629.xslt'?>
+<?rfc toc="yes"?>
+<?rfc strict="yes"?>
+<?rfc symrefs="yes"?>
+<?rfc sortrefs="yes" ?>
+<?rfc compact="yes" ?>
+<?rfc subcompact="yes" ?>
+<rfc category="info" docName="draft-stone-memcache-binary-01" ipr="none">
+  <front>
+    <title> Memcache Binary Protocol </title>
+
+    <author fullname="Aaron Stone" surname="Stone" role="editor">
+      <organization>Six Apart, Ltd.</organization>
+      <address>
+        <postal>
+          <street>548 4th Street</street>
+          <city>San Francisco</city>
+          <region>CA</region>
+          <code>94107</code>
+          <country>USA</country>
+        </postal>
+        <email>aaron@serendipity.palo-alto.ca.us</email>
+      </address>
+    </author>
+    <author fullname="Trond Norbye" surname="Norbye" role="editor">
+      <organization>Sun Microsystems, INC</organization>
+      <address>
+        <postal>
+          <street>Haakon VII g. 7B</street>
+          <city>Trondheim</city>
+          <code>NO-7485 Trondheim</code>
+          <country>Norway</country>
+        </postal>
+        <email>trond.norbye@sun.com</email>
+      </address>
+    </author>
+    <date day="28" month="August" year="2008" />
+    <area>Applications</area>
+    <keyword>memcache memcached cache</keyword>
+    <abstract>
+      <t>
+        This memo explains the memcache binary protocol for informational
+        purposes.
+      </t>
+      <t>
+        Memcache is a high performance key-value cache. It is intentionally a
+        dumb cache, optimized for speed only. Applications using memcache do
+        not rely on it for data -- a persistent database with guaranteed
+        reliability is strongly recommended -- but applications can run much
+        faster when cached data is available in memcache.
+      </t>
+    </abstract>
+  </front>
+
+  <middle>
+    <section anchor="introduction" title="Introduction">
+      <t>
+        Memcache is a high performance key-value cache. It is intentionally a
+        dumb cache, optimized for speed only. Applications using memcache should
+        not rely on it for data -- a persistent database with guaranteed
+        reliability is strongly recommended -- but applications can run much
+        faster when cached data is available in memcache.
+      </t>
+      <t>
+        Memcache was originally written to make
+        <xref target="LJ">LiveJournal</xref> faster. It now powers all of
+        the fastest web sites that you love.
+      </t>
+      <section anchor="conventions" title="Conventions Used In This Document">
+        <t>
+          The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
+          "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
+          document are to be interpreted as described in
+          <xref target="KEYWORDS"/>.
+        </t>
+      </section>
+    </section>
+
+    <section anchor="packet" title="Packet Structure">
+      <figure>
+        <preamble>General format of a packet:</preamble>
+        <artwork>
+  Byte/     0       |       1       |       2       |       3       |
+     /              |               |               |               |
+    |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|
+    +---------------+---------------+---------------+---------------+
+   0/ HEADER                                                        /
+    /                                                               /
+    /                                                               /
+    /                                                               /
+    +---------------+---------------+---------------+---------------+
+  24/ COMMAND-SPECIFIC EXTRAS (as needed)                           /
+   +/  (note length in the extras length header field)              /
+    +---------------+---------------+---------------+---------------+
+   m/ Key (as needed)                                               /
+   +/  (note length in key length header field)                     /
+    +---------------+---------------+---------------+---------------+
+   n/ Value (as needed)                                             /
+   +/  (note length is total body length header field, minus        /
+   +/   sum of the extras and key length body fields)               /
+    +---------------+---------------+---------------+---------------+
+    Total 24 bytes
+        </artwork>
+      </figure>
+
+      <figure>
+        <preamble>Request header:</preamble>
+        <artwork>
+  Byte/     0       |       1       |       2       |       3       |
+     /              |               |               |               |
+    |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|
+    +---------------+---------------+---------------+---------------+
+   0| Magic         | Opcode        | Key length                    |
+    +---------------+---------------+---------------+---------------+
+   4| Extras length | Data type     | Reserved                      |
+    +---------------+---------------+---------------+---------------+
+   8| Total body length                                             |
+    +---------------+---------------+---------------+---------------+
+  12| Opaque                                                        |
+    +---------------+---------------+---------------+---------------+
+  16| CAS                                                           |
+    |                                                               |
+    +---------------+---------------+---------------+---------------+
+    Total 24 bytes
+        </artwork>
+      </figure>
+
+      <figure>
+        <preamble>Response header:</preamble>
+        <artwork>
+  Byte/     0       |       1       |       2       |       3       |
+     /              |               |               |               |
+    |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|
+    +---------------+---------------+---------------+---------------+
+   0| Magic         | Opcode        | Key Length                    |
+    +---------------+---------------+---------------+---------------+
+   4| Extras length | Data type     | Status                        |
+    +---------------+---------------+---------------+---------------+
+   8| Total body length                                             |
+    +---------------+---------------+---------------+---------------+
+  12| Opaque                                                        |
+    +---------------+---------------+---------------+---------------+
+  16| CAS                                                           |
+    |                                                               |
+    +---------------+---------------+---------------+---------------+
+    Total 24 bytes
+        </artwork>
+      </figure>
+
+      <t>
+        Header fields:
+        <list hangIndent="20" style="hanging">
+          <t hangText="Magic">Magic number.</t>
+          <t hangText="Opcode">Command code.</t>
+          <t hangText="Key length">Length in bytes of the text key that follows the command extras.</t>
+          <t hangText="Status">Status of the response (non-zero on error).</t>
+          <t hangText="Extras length">Length in bytes of the command extras.</t>
+          <t hangText="Data type">Reserved for future use (Sean is using this soon).</t>
+          <t hangText="Reserved">Really reserved for future use (up for grabs).</t>
+          <t hangText="Total body length">Length in bytes of extra + key + value.</t>
+          <t hangText="Opaque">Will be copied back to you in the response.</t>
+          <t hangText="CAS">Data version check.</t>
+        </list>
+      </t>
+    </section>
+
+    <section anchor="values" title="Defined Values">
+      <section anchor="value-magic" title="Magic Byte">
+        <t>
+        <list hangIndent="8" style="hanging">
+          <t hangText="0x80">Request packet for this protocol version</t>
+          <t hangText="0x81">Response packet for this protocol version</t>
+        </list>
+        </t>
+
+        <t>
+          Magic byte / version. For each version of the protocol, we'll use a
+          different request/response value pair. This is useful for protocol
+          analyzers to distinguish the nature of the packet from the direction
+          which it is moving. Note, it is common to run a memcached instance on
+         a host that also runs an application server. Such a host will both
+         send and receive memcache packets.
+        </t>
+
+        <t>
+          The version should hopefully correspond only to different meanings of
+          the command byte. In an ideal world, we will not change the header
+          format. As reserved bytes are given defined meaning, the protocol
+          version / magic byte values should be incremented.
+        </t>
+
+        <t>
+          Traffic analysis tools are encouraged to identify memcache packets
+          and provide detailed interpretation if the magic bytes are recognized
+          and otherwise to provide a generic breakdown of the packet. Note, that
+          the key and value positions can always be identified even if the magic
+          byte or command opcode are not recognized.
+        </t>
+      </section>
+
+      <section anchor="value-status" title="Response Status">
+        <t>
+        Possible values of this two-byte field:
+        <list hangIndent="8" style="hanging">
+          <t hangText="0x0000">No error</t>
+          <t hangText="0x0001">Key not found</t>
+          <t hangText="0x0002">Key exists</t>
+          <t hangText="0x0003">Value too large</t>
+          <t hangText="0x0004">Invalid arguments</t>
+          <t hangText="0x0005">Item not stored</t>
+          <t hangText="0x0006">Incr/Decr on non-numeric value.</t>
+          <t hangText="0x0081">Unknown command</t>
+          <t hangText="0x0082">Out of memory</t>
+        </list>
+        </t>
+      </section>
+
+      <section anchor="value-opcodes" title="Command Opcodes">
+        <t>
+        Possible values of the one-byte field:
+        <list hangIndent="8" style="hanging">
+          <t hangText="0x00">Get</t>
+          <t hangText="0x01">Set</t>
+          <t hangText="0x02">Add</t>
+          <t hangText="0x03">Replace</t>
+          <t hangText="0x04">Delete</t>
+          <t hangText="0x05">Increment</t>
+          <t hangText="0x06">Decrement</t>
+          <t hangText="0x07">Quit</t>
+          <t hangText="0x08">Flush</t>
+          <t hangText="0x09">GetQ</t>
+          <t hangText="0x0A">No-op</t>
+          <t hangText="0x0B">Version</t>
+          <t hangText="0x0C">GetK</t>
+          <t hangText="0x0D">GetKQ</t>
+          <t hangText="0x0E">Append</t>
+          <t hangText="0x0F">Prepend</t>
+         <t hangText="0x10">Stat</t>
+          <t hangText="0x11">SetQ</t>
+          <t hangText="0x12">AddQ</t>
+          <t hangText="0x13">ReplaceQ</t>
+          <t hangText="0x14">DeleteQ</t>
+          <t hangText="0x15">IncrementQ</t>
+          <t hangText="0x16">DecrementQ</t>
+          <t hangText="0x17">QuitQ</t>
+          <t hangText="0x18">FlushQ</t>
+          <t hangText="0x19">AppendQ</t>
+          <t hangText="0x1A">PrependQ</t>
+        </list>
+        </t>
+             <t>
+               As a convention all of the commands ending with "Q" for
+               Quiet.  A quiet version of a command will omit responses
+               that are considered uninteresting.  Whether a given response
+               is interesting is dependent upon the command.  See the
+               descriptions of the
+               <xref target="command-get">set commands</xref>
+               and <xref target="command-set">set commands</xref> for
+               examples of commands that include quiet variants.
+             </t>
+      </section>
+
+      <section anchor="value-types" title="Data Types">
+        <t>
+        Possible values of the one-byte field:
+        <list hangIndent="8" style="hanging">
+          <t hangText="0x00">Raw bytes</t>
+        </list>
+        </t>
+      </section>
+    </section>
+
+    <section title="Commands">
+      <section anchor="command-introduction" title="Introduction">
+        <t>
+        All communication is initiated by a request from the client,
+        and the server will respond to each request with zero or
+       multiple packets for each request. If the status code of a response
+       packet is non-nil, the body of the packet will contain a textual error
+       message. If the status code is nil, the command opcode will define the
+       layout of the body of the message.
+        </t>
+        <section anchor="command-introduction-example" title="Example">
+            <t>
+                The following figure illustrates the packet layout for
+                a packet with an error message.
+            </t>
+          <figure>
+            <preamble>Packet layout:</preamble>
+            <artwork>
+
+  Byte/     0       |       1       |       2       |       3       |
+     /              |               |               |               |
+    |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|
+    +---------------+---------------+---------------+---------------+
+   0| 0x81          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+   4| 0x00          | 0x00          | 0x00          | 0x01          |
+    +---------------+---------------+---------------+---------------+
+   8| 0x00          | 0x00          | 0x00          | 0x09          |
+    +---------------+---------------+---------------+---------------+
+  12| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+  16| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+  20| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+  24| 0x4e ('N')    | 0x6f ('o')    | 0x74 ('t')    | 0x20 (' ')    |
+    +---------------+---------------+---------------+---------------+
+  28| 0x66 ('f')    | 0x6f ('o')    | 0x75 ('u')    | 0x6e ('n')    |
+    +---------------+---------------+---------------+---------------+
+  32| 0x64 ('d')    |
+    +---------------+
+    Total 33 bytes (24 byte header, and 9 bytes value)
+
+Field        (offset) (value)
+Magic        (0)    : 0x81
+Opcode       (1)    : 0x00
+Key length   (2,3)  : 0x0000
+Extra length (4)    : 0x00
+Data type    (5)    : 0x00
+Status       (6,7)  : 0x0001
+Total body   (8-11) : 0x00000009
+Opaque       (12-15): 0x00000000
+CAS          (16-23): 0x0000000000000000
+Extras              : None
+Key                 : None
+Value        (24-32): The textual string "Not found"
+            </artwork>
+          </figure>
+        </section>
+      </section>
+
+      <section anchor="command-get" title="Get, Get Quietly, Get Key, Get Key Quietly">
+        <t>
+            Request:
+        </t>
+        <t>
+        <list style="empty">
+          <t>MUST NOT have extras.</t>
+          <t>MUST have key.</t>
+          <t>MUST NOT have value.</t>
+        </list>
+        </t>
+
+        <t>
+            Response (if found):
+        </t>
+        <t>
+        <list style="empty">
+          <t>MUST have extras.</t>
+          <t>MAY have key.</t>
+          <t>MAY have value.</t>
+        </list>
+        </t>
+
+        <t>
+        <list style="symbols">
+          <t>4 byte flags</t>
+        </list>
+        </t>
+
+        <t>
+        <figure>
+          <preamble>Extra data for the get commands:</preamble>
+          <artwork>
+  Byte/     0       |       1       |       2       |       3       |
+     /              |               |               |               |
+    |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|
+    +---------------+---------------+---------------+---------------+
+   0| Flags                                                         |
+    +---------------+---------------+---------------+---------------+
+
+    Total 4 bytes
+          </artwork>
+        </figure>
+        </t>
+
+        <t>
+          The get command gets a single key. The getq command is both mum
+          on cache miss and quiet, holding its response until a non-quiet
+          command is issued. Getk and getkq differs from get and getq by
+          adding the key into the response packet.
+        </t>
+
+        <t>
+          You're not guaranteed a response to a getq/getkq cache hit until
+          you send a non-getq/getkq command later, which uncorks the
+          server and bundles up IOs to send to the client in one go.
+        </t>
+
+        <t>
+          Clients should implement multi-get (still important for
+          reducing network roundtrips!) as n pipelined requests, the
+          first n-1 being getq/getkq, the last being a regular
+          get/getk.  That way you're guaranteed to get a response, and
+          you know when the server's done.  You can also do the naive
+          thing and send n pipelined get/getks, but then you could potentially
+          get back a lot of "NOT_FOUND" error code packets.
+          Alternatively, you can send 'n' getq/getkqs, followed by a
+          'noop' command.
+        </t>
+
+        <section anchor="command-get-example" title="Example">
+          <t>
+           To request the data associated with the key "Hello" the
+           following fields must be specified in the packet.
+          </t>
+          <figure>
+            <preamble>get request:</preamble>
+            <artwork>
+  Byte/     0       |       1       |       2       |       3       |
+     /              |               |               |               |
+    |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|
+    +---------------+---------------+---------------+---------------+
+   0| 0x80          | 0x00          | 0x00          | 0x05          |
+    +---------------+---------------+---------------+---------------+
+   4| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+   8| 0x00          | 0x00          | 0x00          | 0x05          |
+    +---------------+---------------+---------------+---------------+
+  12| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+  16| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+  20| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+  24| 0x48 ('H')    | 0x65 ('e')    | 0x6c ('l')    | 0x6c ('l')    |
+    +---------------+---------------+---------------+---------------+
+  28| 0x6f ('o')    |
+    +---------------+
+
+    Total 29 bytes (24 byte header, and 5 bytes key)
+
+Field        (offset) (value)
+Magic        (0)    : 0x80
+Opcode       (1)    : 0x00
+Key length   (2,3)  : 0x0005
+Extra length (4)    : 0x00
+Data type    (5)    : 0x00
+Reserved     (6,7)  : 0x0000
+Total body   (8-11) : 0x00000005
+Opaque       (12-15): 0x00000000
+CAS          (16-23): 0x0000000000000000
+Extras              : None
+Key          (24-29): The textual string: "Hello"
+Value               : None
+            </artwork>
+          </figure>
+          <t>If the item exist on the server the following packet is returned,
+          otherwise a packet with status code != 0 will be returned (see
+           <xref target="command-introduction">Introduction</xref>)
+          </t>
+          <figure>
+            <preamble>get/getq response:</preamble>
+            <artwork>
+
+  Byte/     0       |       1       |       2       |       3       |
+     /              |               |               |               |
+    |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|
+    +---------------+---------------+---------------+---------------+
+   0| 0x81          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+   4| 0x04          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+   8| 0x00          | 0x00          | 0x00          | 0x09          |
+    +---------------+---------------+---------------+---------------+
+  12| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+  16| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+  20| 0x00          | 0x00          | 0x00          | 0x01          |
+    +---------------+---------------+---------------+---------------+
+  24| 0xde          | 0xad          | 0xbe          | 0xef          |
+    +---------------+---------------+---------------+---------------+
+  28| 0x57 ('W')    | 0x6f ('o')    | 0x72 ('r')    | 0x6c ('l')    |
+    +---------------+---------------+---------------+---------------+
+  32| 0x64 ('d')    |
+    +---------------+
+
+    Total 33 bytes (24 byte header, 4 byte extras and 5 byte value)
+
+Field        (offset) (value)
+Magic        (0)    : 0x81
+Opcode       (1)    : 0x00
+Key length   (2,3)  : 0x0000
+Extra length (4)    : 0x04
+Data type    (5)    : 0x00
+Status       (6,7)  : 0x0000
+Total body   (8-11) : 0x00000009
+Opaque       (12-15): 0x00000000
+CAS          (16-23): 0x0000000000000001
+Extras              :
+  Flags      (24-27): 0xdeadbeef
+Key                 : None
+Value        (28-32): The textual string "World"
+            </artwork>
+          </figure>
+          <figure>
+            <preamble>getk/getkq response:</preamble>
+            <artwork>
+
+  Byte/     0       |       1       |       2       |       3       |
+     /              |               |               |               |
+    |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|
+    +---------------+---------------+---------------+---------------+
+   0| 0x81          | 0x00          | 0x00          | 0x05          |
+    +---------------+---------------+---------------+---------------+
+   4| 0x04          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+   8| 0x00          | 0x00          | 0x00          | 0x09          |
+    +---------------+---------------+---------------+---------------+
+  12| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+  16| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+  20| 0x00          | 0x00          | 0x00          | 0x01          |
+    +---------------+---------------+---------------+---------------+
+  24| 0xde          | 0xad          | 0xbe          | 0xef          |
+    +---------------+---------------+---------------+---------------+
+  28| 0x48 ('H')    | 0x65 ('e')    | 0x6c ('l')    | 0x6c ('l')    |
+    +---------------+---------------+---------------+---------------+
+  32| 0x6f ('o')    | 0x57 ('W')    | 0x6f ('o')    | 0x72 ('r')    |
+    +---------------+---------------+---------------+---------------+
+  36| 0x6c ('l')    | 0x64 ('d')    |
+    +---------------+---------------+
+
+    Total 38 bytes (24 byte header, 4 byte extras, 5 byte key
+                    and 5 byte value)
+
+Field        (offset) (value)
+Magic        (0)    : 0x81
+Opcode       (1)    : 0x00
+Key length   (2,3)  : 0x0005
+Extra length (4)    : 0x04
+Data type    (5)    : 0x00
+Status       (6,7)  : 0x0000
+Total body   (8-11) : 0x00000009
+Opaque       (12-15): 0x00000000
+CAS          (16-23): 0x0000000000000001
+Extras              :
+  Flags      (24-27): 0xdeadbeef
+Key          (28-32): The textual string: "Hello"
+Value        (33-37): The textual string: "World"
+            </artwork>
+          </figure>
+        </section>
+      </section>
+
+      <section anchor="command-set" title="Set, Add, Replace">
+        <t>
+        <list style="empty">
+          <t>MUST have extras.</t>
+          <t>MUST have key.</t>
+          <t>MUST have value.</t>
+        </list>
+        </t>
+
+        <t>
+          <list style="symbols">
+            <t>4 byte flags</t>
+            <t>4 byte expiration time</t>
+          </list>
+        </t>
+
+        <figure>
+          <preamble>Extra data for set/add/replace:</preamble>
+          <artwork>
+  Byte/     0       |       1       |       2       |       3       |
+     /              |               |               |               |
+    |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|
+    +---------------+---------------+---------------+---------------+
+   0| Flags                                                         |
+    +---------------+---------------+---------------+---------------+
+   4| Expiration                                                    |
+    +---------------+---------------+---------------+---------------+
+    Total 8 bytes
+          </artwork>
+        </figure>
+
+        <t>
+        If the Data Version Check (CAS) is nonzero, the requested
+        operation MUST only succeed if the item exists and has a CAS value
+        identical to the provided value.
+        </t>
+
+        <t>
+        Add MUST fail if the item already exist.
+        </t>
+
+        <t>
+        Replace MUST fail if the item doesn't exist.
+        </t>
+
+        <t>
+        Set should store the data unconditionally if the item exists
+        or not.
+        </t>
+
+        <t>
+          Quiet mutations only return responses on failure.  Success
+          is considered the general case and is suppressed when in
+          quiet mode, but errors should not be allowed to go
+          unnoticed.
+        </t>
+
+        <section anchor="command-set-example" title="Example">
+            <t>The following figure shows an add-command for
+                <list style="empty">
+                  <t>Key: "Hello"</t>
+                  <t>Value: "World"</t>
+                  <t>Flags: 0xdeadbeef</t>
+                  <t>Expiry: in two hours</t>
+                </list>
+            </t>
+           <figure>
+             <preamble>Add request:</preamble>
+             <artwork>
+
+  Byte/     0       |       1       |       2       |       3       |
+     /              |               |               |               |
+    |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|
+    +---------------+---------------+---------------+---------------+
+   0| 0x80          | 0x02          | 0x00          | 0x05          |
+    +---------------+---------------+---------------+---------------+
+   4| 0x08          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+   8| 0x00          | 0x00          | 0x00          | 0x12          |
+    +---------------+---------------+---------------+---------------+
+  12| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+  16| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+  20| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+  24| 0xde          | 0xad          | 0xbe          | 0xef          |
+    +---------------+---------------+---------------+---------------+
+  28| 0x00          | 0x00          | 0x0e          | 0x10          |
+    +---------------+---------------+---------------+---------------+
+  32| 0x48 ('H')    | 0x65 ('e')    | 0x6c ('l')    | 0x6c ('l')    |
+    +---------------+---------------+---------------+---------------+
+  36| 0x6f ('o')    | 0x57 ('W')    | 0x6f ('o')    | 0x72 ('r')    |
+    +---------------+---------------+---------------+---------------+
+  40| 0x6c ('l')    | 0x64 ('d')    |
+    +---------------+---------------+
+
+    Total 42 bytes (24 byte header, 8 byte extras, 5 byte key and
+                    5 byte value)
+
+Field        (offset) (value)
+Magic        (0)    : 0x80
+Opcode       (1)    : 0x02
+Key length   (2,3)  : 0x0005
+Extra length (4)    : 0x08
+Data type    (5)    : 0x00
+Reserved     (6,7)  : 0x0000
+Total body   (8-11) : 0x00000012
+Opaque       (12-15): 0x00000000
+CAS          (16-23): 0x0000000000000000
+Extras              :
+  Flags      (24-27): 0xdeadbeef
+  Expiry     (28-31): 0x00000e10
+Key          (32-36): The textual string "Hello"
+Value        (37-41): The textual string "World"
+             </artwork>
+           </figure>
+           <t>
+        The response-packet contains no extra data, and the result of the
+        operation is signaled through the status code. If the command
+        succeeds, the CAS value for the item is returned in the CAS-field
+        of the packet.
+           </t>
+           <figure>
+             <preamble>Successful add response:</preamble>
+             <artwork>
+
+  Byte/     0       |       1       |       2       |       3       |
+     /              |               |               |               |
+    |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|
+    +---------------+---------------+---------------+---------------+
+   0| 0x81          | 0x02          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+   4| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+   8| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+  12| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+  16| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+  20| 0x00          | 0x00          | 0x00          | 0x01          |
+    +---------------+---------------+---------------+---------------+
+
+    Total 24 bytes
+
+Field        (offset) (value)
+Magic        (0)    : 0x81
+Opcode       (1)    : 0x02
+Key length   (2,3)  : 0x0000
+Extra length (4)    : 0x00
+Data type    (5)    : 0x00
+Status       (6,7)  : 0x0000
+Total body   (8-11) : 0x00000000
+Opaque       (12-15): 0x00000000
+CAS          (16-23): 0x0000000000000001
+Extras              : None
+Key                 : None
+Value               : None
+             </artwork>
+           </figure>
+         </section>
+      </section>
+
+      <section anchor="command-delete" title="Delete">
+        <t>
+        <list style="empty">
+          <t>MUST NOT have extras.</t>
+          <t>MUST have key.</t>
+          <t>MUST NOT have value.</t>
+        </list>
+        </t>
+
+        <t>
+         Delete the item with the specific key.
+        </t>
+
+        <section anchor="command-delete-example" title="Example">
+          <t>The following figure shows a delete message for the
+            item "Hello".</t>
+          <figure>
+            <preamble>Delete request:</preamble>
+            <artwork>
+  Byte/     0       |       1       |       2       |       3       |
+     /              |               |               |               |
+    |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|
+    +---------------+---------------+---------------+---------------+
+   0| 0x80          | 0x04          | 0x00          | 0x05          |
+    +---------------+---------------+---------------+---------------+
+   4| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+   8| 0x00          | 0x00          | 0x00          | 0x05          |
+    +---------------+---------------+---------------+---------------+
+  12| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+  16| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+  20| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+  24| 0x48 ('H')    | 0x65 ('e')    | 0x6c ('l')    | 0x6c ('l')    |
+    +---------------+---------------+---------------+---------------+
+  28| 0x6f ('o')    |
+    +---------------+
+
+    Total 29 bytes (24 byte header, 5 byte value)
+
+Field        (offset) (value)
+Magic        (0)    : 0x80
+Opcode       (1)    : 0x04
+Key length   (2,3)  : 0x0005
+Extra length (4)    : 0x00
+Data type    (5)    : 0x00
+Reserved     (6,7)  : 0x0000
+Total body   (8-11) : 0x00000005
+Opaque       (12-15): 0x00000000
+CAS          (16-23): 0x0000000000000000
+Extras              : None
+Key                 : The textual string "Hello"
+Value               : None
+            </artwork>
+          </figure>
+          <t>
+            The response-packet contains no extra data, and the result of the
+            operation is signaled through the status code.
+          </t>
+        </section>
+      </section>
+
+      <section anchor="command-incr" title="Increment, Decrement">
+        <t>
+          <list style="empty">
+            <t>MUST have extras.</t>
+            <t>MUST have key.</t>
+            <t>MUST NOT have value.</t>
+          </list>
+        </t>
+
+        <t>
+          <list style="symbols">
+            <t>8 byte value to add / subtract</t>
+            <t>8 byte initial value (unsigned)</t>
+            <t>4 byte expiration time</t>
+          </list>
+        </t>
+        <figure>
+          <preamble>Extra data for incr/decr:</preamble>
+          <artwork>
+  Byte/     0       |       1       |       2       |       3       |
+     /              |               |               |               |
+    |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|
+    +---------------+---------------+---------------+---------------+
+   0| Amount to add                                                 |
+    |                                                               |
+    +---------------+---------------+---------------+---------------+
+   8| Initial value                                                 |
+    |                                                               |
+    +---------------+---------------+---------------+---------------+
+  16| Expiration                                                    |
+    +---------------+---------------+---------------+---------------+
+    Total 20 bytes
+          </artwork>
+        </figure>
+
+        <t>
+          These commands will either add or remove the specified
+          amount to the requested counter.
+        </t>
+        <t>
+          If the counter does not exist, one of two things may happen:
+        </t>
+        <t>
+          <list style="numbers">
+          <t>If the expiration value is all one-bits (0xffffffff), the
+             operation will fail with NOT_FOUND.</t>
+          <t>For all other expiration values, the operation will succeed
+             by seeding the value for this key with the provided initial
+             value to expire with the provided expiration time. The flags
+             will be set to zero.</t>
+          </list>
+        </t>
+        <figure>
+          <preamble>incr/decr response body:</preamble>
+          <artwork>
+  Byte/     0       |       1       |       2       |       3       |
+     /              |               |               |               |
+    |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|
+    +---------------+---------------+---------------+---------------+
+   0| 64-bit unsigned response.                                     |
+    |                                                               |
+    +---------------+---------------+---------------+---------------+
+    Total 8 bytes
+          </artwork>
+        </figure>
+        <section anchor="command-incr-example" title="Example">
+            <t>The following figure shows an incr-command for
+                <list style="empty">
+                  <t>Key: "counter"</t>
+                  <t>Delta: 0x01</t>
+                  <t>Initial: 0x00</t>
+                  <t>Expiry: in two hours</t>
+                </list>
+            </t>
+            <figure>
+              <preamble>Increment request:</preamble>
+              <artwork>
+
+  Byte/     0       |       1       |       2       |       3       |
+     /              |               |               |               |
+    |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|
+    +---------------+---------------+---------------+---------------+
+   0| 0x80          | 0x05          | 0x00          | 0x07          |
+    +---------------+---------------+---------------+---------------+
+   4| 0x14          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+   8| 0x00          | 0x00          | 0x00          | 0x1b          |
+    +---------------+---------------+---------------+---------------+
+  12| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+  16| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+  20| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+  24| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+  28| 0x00          | 0x00          | 0x00          | 0x01          |
+    +---------------+---------------+---------------+---------------+
+  32| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+  36| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+  40| 0x00          | 0x00          | 0x0e          | 0x10          |
+    +---------------+---------------+---------------+---------------+
+  44| 0x63 ('c')    | 0x6f ('o')    | 0x75 ('u')    | 0x6e ('n')    |
+    +---------------+---------------+---------------+---------------+
+  48| 0x74 ('t')    | 0x65 ('e')    | 0x72 ('r')    |
+    +---------------+---------------+---------------+
+    Total 51 bytes (24 byte header, 20 byte extras, 7 byte key)
+
+Field        (offset) (value)
+Magic        (0)    : 0x80
+Opcode       (1)    : 0x05
+Key length   (2,3)  : 0x0007
+Extra length (4)    : 0x14
+Data type    (5)    : 0x00
+Reserved     (6,7)  : 0x0000
+Total body   (8-11) : 0x0000001b
+Opaque       (12-15): 0x00000000
+CAS          (16-23): 0x0000000000000000
+Extras              :
+  delta      (24-31): 0x0000000000000001
+  initial    (32-39): 0x0000000000000000
+  exipration (40-43): 0x00000e10
+Key                 : Textual string "counter"
+Value               : None
+              </artwork>
+            </figure>
+            <t>
+             If the key doesn't exist, the server will respond with the
+             initial value. If not the incremented value will be returned.
+             Let's assume that the key didn't exist, so the initial value
+             is returned.
+            </t>
+            <figure>
+              <preamble>Increment response:</preamble>
+              <artwork>
+  Byte/     0       |       1       |       2       |       3       |
+     /              |               |               |               |
+    |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|
+    +---------------+---------------+---------------+---------------+
+   0| 0x81          | 0x05          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+   4| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+   8| 0x00          | 0x00          | 0x00          | 0x08          |
+    +---------------+---------------+---------------+---------------+
+  12| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+  16| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+  20| 0x00          | 0x00          | 0x00          | 0x05          |
+    +---------------+---------------+---------------+---------------+
+  24| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+  28| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+    Total 32 bytes (24 byte header, 8 byte value)
+
+Field        (offset) (value)
+Magic        (0)    : 0x81
+Opcode       (1)    : 0x05
+Key length   (2,3)  : 0x0000
+Extra length (4)    : 0x00
+Data type    (5)    : 0x00
+Status       (6,7)  : 0x0000
+Total body   (8-11) : 0x00000008
+Opaque       (12-15): 0x00000000
+CAS          (16-23): 0x0000000000000005
+Extras              : None
+Key                 : None
+Value               : 0x0000000000000000
+              </artwork>
+            </figure>
+          </section>
+      </section>
+
+      <section anchor="command-quit" title="quit">
+        <t>
+        <list style="empty">
+          <t>MUST NOT have extras.</t>
+          <t>MUST NOT have key.</t>
+          <t>MUST NOT have value.</t>
+        </list>
+        </t>
+
+        <t>
+        Close the connection to the server.
+        </t>
+
+        <section anchor="command-quit-example" title="Example">
+          <figure>
+            <preamble>Quit request:</preamble>
+            <artwork>
+  Byte/     0       |       1       |       2       |       3       |
+     /              |               |               |               |
+    |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|
+    +---------------+---------------+---------------+---------------+
+   0| 0x80          | 0x07          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+   4| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+   8| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+  12| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+  16| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+  20| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+    Total 24 bytes
+
+Field        (offset) (value)
+Magic        (0)    : 0x80
+Opcode       (1)    : 0x07
+Key length   (2,3)  : 0x0000
+Extra length (4)    : 0x00
+Data type    (5)    : 0x00
+Reserved     (6,7)  : 0x0000
+Total body   (8-11) : 0x00000000
+Opaque       (12-15): 0x00000000
+CAS          (16-23): 0x0000000000000000
+Extras              : None
+Key                 : None
+Value               : None
+            </artwork>
+          </figure>
+          <t>
+            The response-packet contains no extra data, and the result of the
+            operation is signaled through the status code. The server will
+            then close the connection.
+          </t>
+        </section>
+      </section>
+
+      <section anchor="command-flush" title="Flush">
+        <t>
+          <list style="empty">
+            <t>MAY have extras.</t>
+            <t>MUST NOT have key.</t>
+            <t>MUST NOT have value.</t>
+          </list>
+        </t>
+
+        <t>
+          <list style="symbols">
+            <t>4 byte expiration time</t>
+          </list>
+        </t>
+        <figure>
+          <preamble>Extra data for flush:</preamble>
+          <artwork>
+  Byte/     0       |       1       |       2       |       3       |
+     /              |               |               |               |
+    |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|
+    +---------------+---------------+---------------+---------------+
+   0| Expiration                                                    |
+    +---------------+---------------+---------------+---------------+
+  Total 4 bytes
+          </artwork>
+        </figure>
+        <t>
+            Flush the items in the cache now or some time in the future as
+            specified by the expiration field. See the documentation of the
+            textual protocol for the full description on how to specify the
+            expiration time.
+        </t>
+        <section anchor="command-flush-example" title="Example">
+        <t>
+            To flush the cache (delete all items) in two hours, the set
+            the following values in the request
+        </t>
+        <figure>
+          <preamble>Flush request:</preamble>
+          <artwork>
+  Byte/     0       |       1       |       2       |       3       |
+     /              |               |               |               |
+    |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|
+    +---------------+---------------+---------------+---------------+
+   0| 0x80          | 0x08          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+   4| 0x04          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+   8| 0x00          | 0x00          | 0x00          | 0x04          |
+    +---------------+---------------+---------------+---------------+
+  12| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+  16| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+  20| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+  24| 0x00          | 0x00          | 0x0e          | 0x10          |
+    +---------------+---------------+---------------+---------------+
+    Total 28 bytes (24 byte header, 4 byte body)
+
+Field        (offset) (value)
+Magic        (0)    : 0x80
+Opcode       (1)    : 0x08
+Key length   (2,3)  : 0x0000
+Extra length (4)    : 0x04
+Data type    (5)    : 0x00
+Reserved     (6,7)  : 0x0000
+Total body   (8-11) : 0x00000004
+Opaque       (12-15): 0x00000000
+CAS          (16-23): 0x0000000000000000
+Extras              :
+   Expiry    (24-27): 0x000e10
+Key                 : None
+Value               : None
+          </artwork>
+        </figure>
+          <t>
+            The response-packet contains no extra data, and the result of the
+            operation is signaled through the status code.
+          </t>
+        </section>
+      </section>
+      <section anchor="command-noop" title="noop">
+        <t>
+        <list style="empty">
+          <t>MUST NOT have extras.</t>
+          <t>MUST NOT have key.</t>
+          <t>MUST NOT have value.</t>
+        </list>
+        </t>
+
+        <t>
+        Used as a keep alive. Flushes outstanding getq/getkq's.
+        </t>
+        <section anchor="command-noop-example" title="Example">
+          <figure>
+            <preamble>Noop request:</preamble>
+            <artwork>
+  Byte/     0       |       1       |       2       |       3       |
+     /              |               |               |               |
+    |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|
+    +---------------+---------------+---------------+---------------+
+   0| 0x80          | 0x0a          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+   4| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+   8| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+  12| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+  16| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+  20| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+    Total 24 bytes
+
+Field        (offset) (value)
+Magic        (0)    : 0x80
+Opcode       (1)    : 0x0a
+Key length   (2,3)  : 0x0000
+Extra length (4)    : 0x00
+Data type    (5)    : 0x00
+Reserved     (6,7)  : 0x0000
+Total body   (8-11) : 0x00000000
+Opaque       (12-15): 0x00000000
+CAS          (16-23): 0x0000000000000000
+Extras              : None
+Key                 : None
+Value               : None
+            </artwork>
+          </figure>
+          <t>
+            The response-packet contains no extra data, and the result of the
+            operation is signaled through the status code.
+          </t>
+        </section>
+      </section>
+
+      <section anchor="command-version" title="version">
+        <t>
+        <list style="empty">
+          <t>MUST NOT have extras.</t>
+          <t>MUST NOT have key.</t>
+          <t>MUST NOT have value.</t>
+        </list>
+        </t>
+
+        <t>
+          Request the server version.
+        </t>
+        <t>
+         The server responds with a packet containing the version string
+         in the body with the following format: "x.y.z"
+        </t>
+        <section anchor="command-version-example" title="Example">
+          <figure>
+            <preamble>Version request:</preamble>
+            <artwork>
+  Byte/     0       |       1       |       2       |       3       |
+     /              |               |               |               |
+    |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|
+    +---------------+---------------+---------------+---------------+
+   0| 0x80          | 0x0b          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+   4| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+   8| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+  12| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+  16| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+  20| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+    Total 24 bytes
+
+Field        (offset) (value)
+Magic        (0)    : 0x80
+Opcode       (1)    : 0x0b
+Key length   (2,3)  : 0x0000
+Extra length (4)    : 0x00
+Data type    (5)    : 0x00
+Reserved     (6,7)  : 0x0000
+Total body   (8-11) : 0x00000000
+Opaque       (12-15): 0x00000000
+CAS          (16-23): 0x0000000000000000
+Extras              : None
+            </artwork>
+          </figure>
+          <figure>
+            <preamble>Version response:</preamble>
+            <artwork>
+  Byte/     0       |       1       |       2       |       3       |
+     /              |               |               |               |
+    |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|
+    +---------------+---------------+---------------+---------------+
+   0| 0x81          | 0x0b          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+   4| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+   8| 0x00          | 0x00          | 0x00          | 0x05          |
+    +---------------+---------------+---------------+---------------+
+  12| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+  16| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+  20| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+  24| 0x31 ('1')    | 0x2e ('.')    | 0x33 ('3')    | 0x2e ('.')    |
+    +---------------+---------------+---------------+---------------+
+  28| 0x31 ('1')    |
+    +---------------+
+    Total 29 bytes (24 byte header, 5 byte body)
+
+Field        (offset) (value)
+Magic        (0)    : 0x81
+Opcode       (1)    : 0x0b
+Key length   (2,3)  : 0x0000
+Extra length (4)    : 0x00
+Data type    (5)    : 0x00
+Status       (6,7)  : 0x0000
+Total body   (8-11) : 0x00000005
+Opaque       (12-15): 0x00000000
+CAS          (16-23): 0x0000000000000000
+Extras              : None
+Key                 : None
+Value               : Textual string "1.3.1"
+            </artwork>
+          </figure>
+        </section>
+      </section>
+
+      <section anchor="command-append" title="Append, Prepend">
+        <t>
+        <list style="empty">
+          <t>MUST NOT have extras.</t>
+          <t>MUST have key.</t>
+          <t>MUST have value.</t>
+        </list>
+        </t>
+
+        <t>
+          These commands will either append or prepend the specified
+          value to the requested key.
+        </t>
+
+        <section anchor="command-append-example" title="Example">
+          <t>The following example appends '!' to the 'Hello' key.</t>
+          <figure>
+            <preamble>Append request:</preamble>
+            <artwork>
+  Byte/     0       |       1       |       2       |       3       |
+     /              |               |               |               |
+    |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|
+    +---------------+---------------+---------------+---------------+
+   0| 0x80          | 0x0e          | 0x00          | 0x05          |
+    +---------------+---------------+---------------+---------------+
+   4| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+   8| 0x00          | 0x00          | 0x00          | 0x06          |
+    +---------------+---------------+---------------+---------------+
+  12| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+  16| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+  20| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+  24| 0x48 ('H')    | 0x65 ('e')    | 0x6c ('l')    | 0x6c ('l')    |
+    +---------------+---------------+---------------+---------------+
+  28| 0x6f ('o')    | 0x21 ('!')    |
+    +---------------+---------------+
+    Total 30 bytes (24 byte header, 5 byte key, 1 byte value)
+
+Field        (offset) (value)
+Magic        (0)    : 0x80
+Opcode       (1)    : 0x0e
+Key length   (2,3)  : 0x0005
+Extra length (4)    : 0x00
+Data type    (5)    : 0x00
+Reserved     (6,7)  : 0x0000
+Total body   (8-11) : 0x00000006
+Opaque       (12-15): 0x00000000
+CAS          (16-23): 0x0000000000000000
+Extras              : None
+Key          (24-28): The textual string "Hello"
+Value        (29)   : "!"
+            </artwork>
+          </figure>
+          <t>
+            The response-packet contains no extra data, and the result of the
+            operation is signaled through the status code.
+          </t>
+        </section>
+      </section>
+
+      <section anchor="command-stat" title="Stat">
+        <t>
+        <list style="empty">
+          <t>MUST NOT have extras.</t>
+          <t>MAY have key.</t>
+          <t>MUST NOT have value.</t>
+        </list>
+        </t>
+
+        <t>
+         Request server statistics. Without a key specified the server will
+          respond with a "default" set of statistics information. Each piece
+          of statistical information is returned in its own packet (key
+          contains the name of the statistical item and the body contains the
+          value in ASCII format). The sequence of return packets is terminated
+          with a packet that contains no key and no value.
+        </t>
+        <section anchor="command-stat-example" title="Example">
+            <t>The following example requests all statistics from the server</t>
+          <figure>
+            <preamble>Stat request:</preamble>
+            <artwork>
+  Byte/     0       |       1       |       2       |       3       |
+     /              |               |               |               |
+    |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|
+    +---------------+---------------+---------------+---------------+
+   0| 0x80          | 0x10          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+   4| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+   8| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+  12| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+  16| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+  20| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+    Total 24 bytes
+
+Field        (offset) (value)
+Magic        (0)    : 0x80
+Opcode       (1)    : 0x10
+Key length   (2,3)  : 0x0000
+Extra length (4)    : 0x00
+Data type    (5)    : 0x00
+Reserved     (6,7)  : 0x0000
+Total body   (8-11) : 0x00000000
+Opaque       (12-15): 0x00000000
+CAS          (16-23): 0x0000000000000000
+Extras              : None
+Key                 : None
+Value               : None
+            </artwork>
+          </figure>
+          <t>
+              The server will send each value in a separate packet with
+              an "empty" packet (no key / no value) to terminate the sequence.
+              Each of the response packets look like the following example:
+         </t>
+          <figure>
+            <preamble>Stat response:</preamble>
+            <artwork>
+  Byte/     0       |       1       |       2       |       3       |
+     /              |               |               |               |
+    |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|
+    +---------------+---------------+---------------+---------------+
+   0| 0x81          | 0x10          | 0x00          | 0x03          |
+    +---------------+---------------+---------------+---------------+
+   4| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+   8| 0x00          | 0x00          | 0x00          | 0x07          |
+    +---------------+---------------+---------------+---------------+
+  12| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+  16| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+  20| 0x00          | 0x00          | 0x00          | 0x00          |
+    +---------------+---------------+---------------+---------------+
+  24| 0x70 ('p')    | 0x69 ('i')    | 0x64 ('d')    | 0x33 ('3')    |
+    +---------------+---------------+---------------+---------------+
+  28| 0x30 ('0')    | 0x37 ('7')    | 0x38 ('8')    |
+    +---------------+---------------+---------------+
+    Total 31 bytes (24 byte header, 3 byte key, 4 byte body)
+
+Field        (offset) (value)
+Magic        (0)    : 0x81
+Opcode       (1)    : 0x10
+Key length   (2,3)  : 0x0003
+Extra length (4)    : 0x00
+Data type    (5)    : 0x00
+Status       (6,7)  : 0x0000
+Total body   (8-11) : 0x00000007
+Opaque       (12-15): 0x00000000
+CAS          (16-23): 0x0000000000000000
+Exstras             : None
+Key                 : The textual string "pid"
+Value               : The textual string "3078"
+            </artwork>
+          </figure>
+        </section>
+      </section>
+    </section>
+    <section anchor="security" title="Security Considerations">
+      <t>
+      Memcache has no authentication or security layers whatsoever. It is
+      RECOMMENDED that memcache be deployed strictly on closed, protected,
+      back-end networks within a single data center, within a single cluster of
+      servers, or even on a single host, providing shared caching for multiple
+      applications. Memcache MUST NOT be made available on a public network.
+      </t>
+    </section>
+
+  </middle>
+
+  <back>
+    <references title="Normative References">
+      <reference anchor="LJ">
+        <front>
+          <title>LJ NEEDS MOAR SPEED</title>
+          <author fullname="Brad Fitzpatrick">
+            <organization>Danga Interactive</organization>
+          </author>
+          <date day="5" month="10" year="1999" />
+          <abstract>
+            <t>http://www.livejournal.com/</t>
+          </abstract>
+        </front>
+      </reference>
+      <dwdrfc-ref anchor="KEYWORDS" src='http://xml.resource.org/public/rfc/bibxml/reference.RFC.2119.xml'/>
+    </references>
+
+    <section anchor="acknowledgments" title="Acknowledgments">
+      <t>
+      Thanks to Brad Fitzpatrick, Anatoly Vorobey, Steven Grimm, and Dustin
+      Sallings, for their work on the memcached server.
+      </t>
+
+      <t>
+      Thanks to Sean Chittenden, Jonathan Steinert, Brian Aker, Evan Martin,
+      Nathan Neulinger, Eric Hodel, Michael Johnson, Paul Querna, Jamie
+      McCarthy, Philip Neustrom, Andrew O'Brien, Josh Rotenberg, Robin H.
+      Johnson, Tim Yardley, Paolo Borelli, Eli Bingham, Jean-Francois
+      Bustarret, Paul G, Paul Lindner, Alan Kasindorf, Chris Goffinet, Tomash
+      Brechko, and others for their work reporting bugs and maintaining
+      memcached client libraries and bindings in many languages.
+      </t>
+    </section>
+  </back>
+
+</rfc>
+
diff --git a/memcached/doc/protocol.txt b/memcached/doc/protocol.txt
new file mode 100644 (file)
index 0000000..1a844f0
--- /dev/null
@@ -0,0 +1,774 @@
+Protocol
+--------
+
+Clients of memcached communicate with server through TCP connections.
+(A UDP interface is also available; details are below under "UDP
+protocol.") A given running memcached server listens on some
+(configurable) port; clients connect to that port, send commands to
+the server, read responses, and eventually close the connection.
+
+There is no need to send any command to end the session. A client may
+just close the connection at any moment it no longer needs it. Note,
+however, that clients are encouraged to cache their connections rather
+than reopen them every time they need to store or retrieve data.  This
+is because memcached is especially designed to work very efficiently
+with a very large number (many hundreds, more than a thousand if
+necessary) of open connections. Caching connections will eliminate the
+overhead associated with establishing a TCP connection (the overhead
+of preparing for a new connection on the server side is insignificant
+compared to this).
+
+There are two kinds of data sent in the memcache protocol: text lines
+and unstructured data.  Text lines are used for commands from clients
+and responses from servers. Unstructured data is sent when a client
+wants to store or retrieve data. The server will transmit back
+unstructured data in exactly the same way it received it, as a byte
+stream. The server doesn't care about byte order issues in
+unstructured data and isn't aware of them. There are no limitations on
+characters that may appear in unstructured data; however, the reader
+of such data (either a client or a server) will always know, from a
+preceding text line, the exact length of the data block being
+transmitted.
+
+Text lines are always terminated by \r\n. Unstructured data is _also_
+terminated by \r\n, even though \r, \n or any other 8-bit characters
+may also appear inside the data. Therefore, when a client retrieves
+data from a server, it must use the length of the data block (which it
+will be provided with) to determine where the data block ends, and not
+the fact that \r\n follows the end of the data block, even though it
+does.
+
+Keys
+----
+
+Data stored by memcached is identified with the help of a key. A key
+is a text string which should uniquely identify the data for clients
+that are interested in storing and retrieving it.  Currently the
+length limit of a key is set at 250 characters (of course, normally
+clients wouldn't need to use such long keys); the key must not include
+control characters or whitespace.
+
+Commands
+--------
+
+There are three types of commands.
+
+Storage commands (there are six: "set", "add", "replace", "append"
+"prepend" and "cas") ask the server to store some data identified by a
+key. The client sends a command line, and then a data block; after
+that the client expects one line of response, which will indicate
+success or failure.
+
+Retrieval commands (there are two: "get" and "gets") ask the server to
+retrieve data corresponding to a set of keys (one or more keys in one
+request). The client sends a command line, which includes all the
+requested keys; after that for each item the server finds it sends to
+the client one response line with information about the item, and one
+data block with the item's data; this continues until the server
+finished with the "END" response line.
+
+All other commands don't involve unstructured data. In all of them,
+the client sends one command line, and expects (depending on the
+command) either one line of response, or several lines of response
+ending with "END" on the last line.
+
+A command line always starts with the name of the command, followed by
+parameters (if any) delimited by whitespace. Command names are
+lower-case and are case-sensitive.
+
+Expiration times
+----------------
+
+Some commands involve a client sending some kind of expiration time
+(relative to an item or to an operation requested by the client) to
+the server. In all such cases, the actual value sent may either be
+Unix time (number of seconds since January 1, 1970, as a 32-bit
+value), or a number of seconds starting from current time. In the
+latter case, this number of seconds may not exceed 60*60*24*30 (number
+of seconds in 30 days); if the number sent by a client is larger than
+that, the server will consider it to be real Unix time value rather
+than an offset from current time.
+
+
+Error strings
+-------------
+
+Each command sent by a client may be answered with an error string
+from the server. These error strings come in three types:
+
+- "ERROR\r\n"
+
+  means the client sent a nonexistent command name.
+
+- "CLIENT_ERROR <error>\r\n"
+
+  means some sort of client error in the input line, i.e. the input
+  doesn't conform to the protocol in some way. <error> is a
+  human-readable error string.
+
+- "SERVER_ERROR <error>\r\n"
+
+  means some sort of server error prevents the server from carrying
+  out the command. <error> is a human-readable error string. In cases
+  of severe server errors, which make it impossible to continue
+  serving the client (this shouldn't normally happen), the server will
+  close the connection after sending the error line. This is the only
+  case in which the server closes a connection to a client.
+
+
+In the descriptions of individual commands below, these error lines
+are not again specifically mentioned, but clients must allow for their
+possibility.
+
+
+Storage commands
+----------------
+
+First, the client sends a command line which looks like this:
+
+<command name> <key> <flags> <exptime> <bytes> [noreply]\r\n
+cas <key> <flags> <exptime> <bytes> <cas unique> [noreply]\r\n
+
+- <command name> is "set", "add", "replace", "append" or "prepend"
+
+  "set" means "store this data".
+
+  "add" means "store this data, but only if the server *doesn't* already
+  hold data for this key".
+
+  "replace" means "store this data, but only if the server *does*
+  already hold data for this key".
+
+  "append" means "add this data to an existing key after existing data".
+
+  "prepend" means "add this data to an existing key before existing data".
+
+  The append and prepend commands do not accept flags or exptime.
+  They update existing data portions, and ignore new flag and exptime
+  settings.
+
+  "cas" is a check and set operation which means "store this data but
+  only if no one else has updated since I last fetched it."
+
+- <key> is the key under which the client asks to store the data
+
+- <flags> is an arbitrary 16-bit unsigned integer (written out in
+  decimal) that the server stores along with the data and sends back
+  when the item is retrieved. Clients may use this as a bit field to
+  store data-specific information; this field is opaque to the server.
+  Note that in memcached 1.2.1 and higher, flags may be 32-bits, instead
+  of 16, but you might want to restrict yourself to 16 bits for
+  compatibility with older versions.
+
+- <exptime> is expiration time. If it's 0, the item never expires
+  (although it may be deleted from the cache to make place for other
+  items). If it's non-zero (either Unix time or offset in seconds from
+  current time), it is guaranteed that clients will not be able to
+  retrieve this item after the expiration time arrives (measured by
+  server time).
+
+- <bytes> is the number of bytes in the data block to follow, *not*
+  including the delimiting \r\n. <bytes> may be zero (in which case
+  it's followed by an empty data block).
+
+- <cas unique> is a unique 64-bit value of an existing entry.
+  Clients should use the value returned from the "gets" command
+  when issuing "cas" updates.
+
+- "noreply" optional parameter instructs the server to not send the
+  reply.  NOTE: if the request line is malformed, the server can't
+  parse "noreply" option reliably.  In this case it may send the error
+  to the client, and not reading it on the client side will break
+  things.  Client should construct only valid requests.
+
+After this line, the client sends the data block:
+
+<data block>\r\n
+
+- <data block> is a chunk of arbitrary 8-bit data of length <bytes>
+  from the previous line.
+
+After sending the command line and the data blockm the client awaits
+the reply, which may be:
+
+- "STORED\r\n", to indicate success.
+
+- "NOT_STORED\r\n" to indicate the data was not stored, but not
+because of an error. This normally means that the
+condition for an "add" or a "replace" command wasn't met.
+
+- "EXISTS\r\n" to indicate that the item you are trying to store with
+a "cas" command has been modified since you last fetched it.
+
+- "NOT_FOUND\r\n" to indicate that the item you are trying to store
+with a "cas" command did not exist.
+
+
+Retrieval command:
+------------------
+
+The retrieval commands "get" and "gets" operates like this:
+
+get <key>*\r\n
+gets <key>*\r\n
+
+- <key>* means one or more key strings separated by whitespace.
+
+After this command, the client expects zero or more items, each of
+which is received as a text line followed by a data block. After all
+the items have been transmitted, the server sends the string
+
+"END\r\n"
+
+to indicate the end of response.
+
+Each item sent by the server looks like this:
+
+VALUE <key> <flags> <bytes> [<cas unique>]\r\n
+<data block>\r\n
+
+- <key> is the key for the item being sent
+
+- <flags> is the flags value set by the storage command
+
+- <bytes> is the length of the data block to follow, *not* including
+  its delimiting \r\n
+
+- <cas unique> is a unique 64-bit integer that uniquely identifies
+  this specific item.
+
+- <data block> is the data for this item.
+
+If some of the keys appearing in a retrieval request are not sent back
+by the server in the item list this means that the server does not
+hold items with such keys (because they were never stored, or stored
+but deleted to make space for more items, or expired, or explicitly
+deleted by a client).
+
+
+Deletion
+--------
+
+The command "delete" allows for explicit deletion of items:
+
+delete <key> [noreply]\r\n
+
+- <key> is the key of the item the client wishes the server to delete
+
+- "noreply" optional parameter instructs the server to not send the
+  reply.  See the note in Storage commands regarding malformed
+  requests.
+
+The response line to this command can be one of:
+
+- "DELETED\r\n" to indicate success
+
+- "NOT_FOUND\r\n" to indicate that the item with this key was not
+  found.
+
+See the "flush_all" command below for immediate invalidation
+of all existing items.
+
+
+Increment/Decrement
+-------------------
+
+Commands "incr" and "decr" are used to change data for some item
+in-place, incrementing or decrementing it. The data for the item is
+treated as decimal representation of a 64-bit unsigned integer.  If
+the current data value does not conform to such a representation, the
+incr/decr commands return an error (memcached <= 1.2.6 treated the
+bogus value as if it were 0, leading to confusing). Also, the item
+must already exist for incr/decr to work; these commands won't pretend
+that a non-existent key exists with value 0; instead, they will fail.
+
+The client sends the command line:
+
+incr <key> <value> [noreply]\r\n
+
+or
+
+decr <key> <value> [noreply]\r\n
+
+- <key> is the key of the item the client wishes to change
+
+- <value> is the amount by which the client wants to increase/decrease
+the item. It is a decimal representation of a 64-bit unsigned integer.
+
+- "noreply" optional parameter instructs the server to not send the
+  reply.  See the note in Storage commands regarding malformed
+  requests.
+
+The response will be one of:
+
+- "NOT_FOUND\r\n" to indicate the item with this value was not found
+
+- <value>\r\n , where <value> is the new value of the item's data,
+  after the increment/decrement operation was carried out.
+
+Note that underflow in the "decr" command is caught: if a client tries
+to decrease the value below 0, the new value will be 0.  Overflow in
+the "incr" command will wrap around the 64 bit mark.
+
+Note also that decrementing a number such that it loses length isn't
+guaranteed to decrement its returned length.  The number MAY be
+space-padded at the end, but this is purely an implementation
+optimization, so you also shouldn't rely on that.
+
+Touch
+-----
+
+The "touch" command is used to update the expiration time of an existing item
+without fetching it.
+
+touch <key> <exptime> [noreply]\r\n
+
+- <key> is the key of the item the client wishes the server to delete
+
+- <exptime> is expiration time. Works the same as with the update commands
+  (set/add/etc). This replaces the existing expiration time. If an existing
+  item were to expire in 10 seconds, but then was touched with an
+  expiration time of "20", the item would then expire in 20 seconds.
+
+- "noreply" optional parameter instructs the server to not send the
+  reply.  See the note in Storage commands regarding malformed
+  requests.
+
+The response line to this command can be one of:
+
+- "TOUCHED\r\n" to indicate success
+
+- "NOT_FOUND\r\n" to indicate that the item with this key was not
+  found.
+
+Slabs Reassign
+--------------
+
+NOTE: This command is subject to change as of this writing.
+
+The slabs reassign command is used to redistribute memory once a running
+instance has hit its limit. It might be desireable to have memory laid out
+differently than was automatically assigned after the server started.
+
+slabs reassign <source class> <dest class>\r\n
+
+- <source class> is an id number for the slab class to steal a page from
+
+- <dest class> is an id number for the slab class to move a page to
+
+The response line could be one of:
+
+- "OK" to indicate the page has been scheduled to move
+
+- "BUSY [message]" to indicate a page is already being processed, try again
+  later.
+
+- "BADCLASS [message]" a bad class id was specified
+
+- "NOSPARE [message]" source class has no spare pages
+
+- "NOTFULL [message]" dest class must be full to move new pages to it
+
+- "UNSAFE [message]" source class cannot move a page right now
+
+- "SAME [message]" must specify different source/dest ids.
+
+Slabs Automove
+--------------
+
+NOTE: This command is subject to change as of this writing.
+
+The slabs automove command enables a background thread which decides on its
+own when to move memory between slab classes. Its implementation and options
+will likely be in flux for several versions. See the wiki/mailing list for
+more details.
+
+The automover can be enabled or disabled at runtime with this command.
+
+slabs automove <1|0>
+
+- 1|0 is the indicator on whether to enable the slabs automover or not.
+
+The response should always be "OK\r\n"
+
+Statistics
+----------
+
+The command "stats" is used to query the server about statistics it
+maintains and other internal data. It has two forms. Without
+arguments:
+
+stats\r\n
+
+it causes the server to output general-purpose statistics and
+settings, documented below.  In the other form it has some arguments:
+
+stats <args>\r\n
+
+Depending on <args>, various internal data is sent by the server. The
+kinds of arguments and the data sent are not documented in this vesion
+of the protocol, and are subject to change for the convenience of
+memcache developers.
+
+
+General-purpose statistics
+--------------------------
+
+Upon receiving the "stats" command without arguments, the server sents
+a number of lines which look like this:
+
+STAT <name> <value>\r\n
+
+The server terminates this list with the line
+
+END\r\n
+
+In each line of statistics, <name> is the name of this statistic, and
+<value> is the data.  The following is the list of all names sent in
+response to the "stats" command, together with the type of the value
+sent for this name, and the meaning of the value.
+
+In the type column below, "32u" means a 32-bit unsigned integer, "64u"
+means a 64-bit unsigned integer. '32u.32u' means two 32-bit unsigned
+integers separated by a colon (treat this as a floating point number).
+
+|-----------------------+---------+-------------------------------------------|
+| Name                  | Type    | Meaning                                   |
+|-----------------------+---------+-------------------------------------------|
+| pid                   | 32u     | Process id of this server process         |
+| uptime                | 32u     | Number of secs since the server started   |
+| time                  | 32u     | current UNIX time according to the server |
+| version               | string  | Version string of this server             |
+| pointer_size          | 32      | Default size of pointers on the host OS   |
+|                       |         | (generally 32 or 64)                      |
+| rusage_user           | 32u.32u | Accumulated user time for this process    |
+|                       |         | (seconds:microseconds)                    |
+| rusage_system         | 32u.32u | Accumulated system time for this process  |
+|                       |         | (seconds:microseconds)                    |
+| curr_items            | 32u     | Current number of items stored            |
+| total_items           | 32u     | Total number of items stored since        |
+|                       |         | the server started                        |
+| bytes                 | 64u     | Current number of bytes used              |
+|                       |         | to store items                            |
+| curr_connections      | 32u     | Number of open connections                |
+| total_connections     | 32u     | Total number of connections opened since  |
+|                       |         | the server started running                |
+| connection_structures | 32u     | Number of connection structures allocated |
+|                       |         | by the server                             |
+| reserved_fds          | 32u     | Number of misc fds used internally        |
+| cmd_get               | 64u     | Cumulative number of retrieval reqs       |
+| cmd_set               | 64u     | Cumulative number of storage reqs         |
+| cmd_flush             | 64u     | Cumulative number of flush reqs           |
+| cmd_touch             | 64u     | Cumulative number of touch reqs           |
+| get_hits              | 64u     | Number of keys that have been requested   |
+|                       |         | and found present                         |
+| get_misses            | 64u     | Number of items that have been requested  |
+|                       |         | and not found                             |
+| delete_misses         | 64u     | Number of deletions reqs for missing keys |
+| delete_hits           | 64u     | Number of deletion reqs resulting in      |
+|                       |         | an item being removed.                    |
+| incr_misses           | 64u     | Number of incr reqs against missing keys. |
+| incr_hits             | 64u     | Number of successful incr reqs.           |
+| decr_misses           | 64u     | Number of decr reqs against missing keys. |
+| decr_hits             | 64u     | Number of successful decr reqs.           |
+| cas_misses            | 64u     | Number of CAS reqs against missing keys.  |
+| cas_hits              | 64u     | Number of successful CAS reqs.            |
+| cas_badval            | 64u     | Number of CAS reqs for which a key was    |
+|                       |         | found, but the CAS value did not match.   |
+| touch_hits            | 64u     | Numer of keys that have been touched with |
+|                       |         | a new expiration time                     |
+| touch_misses          | 64u     | Numer of items that have been touched and |
+|                       |         | not found                                 |
+| auth_cmds             | 64u     | Number of authentication commands         |
+|                       |         | handled, success or failure.              |
+| auth_errors           | 64u     | Number of failed authentications.         |
+| evictions             | 64u     | Number of valid items removed from cache  |
+|                       |         | to free memory for new items              |
+| reclaimed             | 64u     | Number of times an entry was stored using |
+|                       |         | memory from an expired entry              |
+| bytes_read            | 64u     | Total number of bytes read by this server |
+|                       |         | from network                              |
+| bytes_written         | 64u     | Total number of bytes sent by this server |
+|                       |         | to network                                |
+| limit_maxbytes        | 32u     | Number of bytes this server is allowed to |
+|                       |         | use for storage.                          |
+| threads               | 32u     | Number of worker threads requested.       |
+|                       |         | (see doc/threads.txt)                     |
+| conn_yields           | 64u     | Number of times any connection yielded to |
+|                       |         | another due to hitting the -R limit.      |
+| hash_power_level      | 32u     | Current size multiplier for hash table    |
+| hash_bytes            | 64u     | Bytes currently used by hash tables       |
+| hash_is_expanding     | bool    | Indicates if the hash table is being      |
+|                       |         | grown to a new size                       |
+| expired_unfetched     | 64u     | Items pulled from LRU that were never     |
+|                       |         | touched by get/incr/append/etc before     |
+|                       |         | expiring                                  |
+| evicted_unfetched     | 64u     | Items evicted from LRU that were never    |
+|                       |         | touched by get/incr/append/etc.           |
+| slab_reassign_running | bool    | If a slab page is being moved             |
+| slabs_moved           | 64u     | Total slab pages moved                    |
+|-----------------------+---------+-------------------------------------------|
+
+Settings statistics
+-------------------
+CAVEAT: This section describes statistics which are subject to change in the
+future.
+
+The "stats" command with the argument of "settings" returns details of
+the settings of the running memcached.  This is primarily made up of
+the results of processing commandline options.
+
+Note that these are not guaranteed to return in any specific order and
+this list may not be exhaustive.  Otherwise, this returns like any
+other stats command.
+
+|-------------------+----------+----------------------------------------------|
+| Name              | Type     | Meaning                                      |
+|-------------------+----------+----------------------------------------------|
+| maxbytes          | size_t   | Maximum number of bytes allows in this cache |
+| maxconns          | 32       | Maximum number of clients allowed.           |
+| tcpport           | 32       | TCP listen port.                             |
+| udpport           | 32       | UDP listen port.                             |
+| inter             | string   | Listen interface.                            |
+| verbosity         | 32       | 0 = none, 1 = some, 2 = lots                 |
+| oldest            | 32u      | Age of the oldest honored object.            |
+| evictions         | on/off   | When off, LRU evictions are disabled.        |
+| domain_socket     | string   | Path to the domain socket (if any).          |
+| umask             | 32 (oct) | umask for the creation of the domain socket. |
+| growth_factor     | float    | Chunk size growth factor.                    |
+| chunk_size        | 32       | Minimum space allocated for key+value+flags. |
+| num_threads       | 32       | Number of threads (including dispatch).      |
+| stat_key_prefix   | char     | Stats prefix separator character.            |
+| detail_enabled    | bool     | If yes, stats detail is enabled.             |
+| reqs_per_event    | 32       | Max num IO ops processed within an event.    |
+| cas_enabled       | bool     | When no, CAS is not enabled for this server. |
+| tcp_backlog       | 32       | TCP listen backlog.                          |
+| auth_enabled_sasl | yes/no   | SASL auth requested and enabled.             |
+| item_size_max     | size_t   | maximum item size                            |
+| maxconns_fast     | bool     | If fast disconnects are enabled              |
+| hashpower_init    | 32       | Starting size multiplier for hash table      |
+| slab_reassign     | bool     | Whether slab page reassignment is allowed    |
+| slab_automove     | bool     | Whether slab page automover is enabled       |
+|-------------------+----------+----------------------------------------------|
+
+
+Item statistics
+---------------
+CAVEAT: This section describes statistics which are subject to change in the
+future.
+
+The "stats" command with the argument of "items" returns information about
+item storage per slab class. The data is returned in the format:
+
+STAT items:<slabclass>:<stat> <value>\r\n
+
+The server terminates this list with the line
+
+END\r\n
+
+The slabclass aligns with class ids used by the "stats slabs" command. Where
+"stats slabs" describes size and memory usage, "stats items" shows higher
+level information.
+
+The following item values are defined as of writing.
+
+Name                   Meaning
+------------------------------
+number                 Number of items presently stored in this class. Expired
+                       items are not automatically excluded.
+age                    Age of the oldest item in the LRU.
+evicted                Number of times an item had to be evicted from the LRU
+                       before it expired.
+evicted_nonzero        Number of times an item which had an explicit expire
+                       time set had to be evicted from the LRU before it
+                       expired.
+evicted_time           Seconds since the last access for the most recent item
+                       evicted from this class. Use this to judge how
+                       recently active your evicted data is.
+outofmemory            Number of times the underlying slab class was unable to
+                       store a new item. This means you are running with -M or
+                       an eviction failed.
+tailrepairs            Number of times we self-healed a slab with a refcount
+                       leak. If this counter is increasing a lot, please
+                       report your situation to the developers.
+reclaimed              Number of times an entry was stored using memory from
+                       an expired entry.
+expired_unfetched      Number of expired items reclaimed from the LRU which
+                       were never touched after being set.
+evicted_unfetched      Number of valid items evicted from the LRU which were
+                       never touched after being set.
+
+Note this will only display information about slabs which exist, so an empty
+cache will return an empty set.
+
+
+Item size statistics
+--------------------
+CAVEAT: This section describes statistics which are subject to change in the
+future.
+
+The "stats" command with the argument of "sizes" returns information about the
+general size and count of all items stored in the cache.
+WARNING: This command WILL lock up your cache! It iterates over *every item*
+and examines the size. While the operation is fast, if you have many items
+you could prevent memcached from serving requests for several seconds.
+
+The data is returned in the following format:
+
+<size> <count>\r\n
+
+The server terminates this list with the line
+
+END\r\n
+
+'size' is an approximate size of the item, within 32 bytes.
+'count' is the amount of items that exist within that 32-byte range.
+
+This is essentially a display of all of your items if there was a slab class
+for every 32 bytes. You can use this to determine if adjusting the slab growth
+factor would save memory overhead. For example: generating more classes in the
+lower range could allow items to fit more snugly into their slab classes, if
+most of your items are less than 200 bytes in size.
+
+
+Slab statistics
+---------------
+CAVEAT: This section describes statistics which are subject to change in the
+future.
+
+The "stats" command with the argument of "slabs" returns information about
+each of the slabs created by memcached during runtime. This includes per-slab
+information along with some totals. The data is returned in the format:
+
+STAT <slabclass>:<stat> <value>\r\n
+STAT <stat> <value>\r\n
+
+The server terminates this list with the line
+
+END\r\n
+
+|-----------------+----------------------------------------------------------|
+| Name            | Meaning                                                  |
+|-----------------+----------------------------------------------------------|
+| chunk_size      | The amount of space each chunk uses. One item will use   |
+|                 | one chunk of the appropriate size.                       |
+| chunks_per_page | How many chunks exist within one page. A page by         |
+|                 | default is less than or equal to one megabyte in size.   |
+|                 | Slabs are allocated by page, then broken into chunks.    |
+| total_pages     | Total number of pages allocated to the slab class.       |
+| total_chunks    | Total number of chunks allocated to the slab class.      |
+| get_hits        | Total number of get requests serviced by this class.     |
+| cmd_set         | Total number of set requests storing data in this class. |
+| delete_hits     | Total number of successful deletes from this class.      |
+| incr_hits       | Total number of incrs modifying this class.              |
+| decr_hits       | Total number of decrs modifying this class.              |
+| cas_hits        | Total number of CAS commands modifying this class.       |
+| cas_badval      | Total number of CAS commands that failed to modify a     |
+|                 | value due to a bad CAS id.                               |
+| touch_hits      | Total number of touches serviced by this class.          |
+| used_chunks     | How many chunks have been allocated to items.            |
+| free_chunks     | Chunks not yet allocated to items, or freed via delete.  |
+| free_chunks_end | Number of free chunks at the end of the last allocated   |
+|                 | page.                                                    |
+| mem_requested   | Number of bytes requested to be stored in this slab[*].  |
+| active_slabs    | Total number of slab classes allocated.                  |
+| total_malloced  | Total amount of memory allocated to slab pages.          |
+|-----------------+----------------------------------------------------------|
+
+* Items are stored in a slab that is the same size or larger than the
+  item.  mem_requested shows the size of all items within a
+  slab. (total_chunks * chunk_size) - mem_requested shows memory
+  wasted in a slab class.  If you see a lot of waste, consider tuning
+  the slab factor.
+
+Other commands
+--------------
+
+"flush_all" is a command with an optional numeric argument. It always
+succeeds, and the server sends "OK\r\n" in response (unless "noreply"
+is given as the last parameter). Its effect is to invalidate all
+existing items immediately (by default) or after the expiration
+specified.  After invalidation none of the items will be returned in
+response to a retrieval command (unless it's stored again under the
+same key *after* flush_all has invalidated the items). flush_all
+doesn't actually free all the memory taken up by existing items; that
+will happen gradually as new items are stored. The most precise
+definition of what flush_all does is the following: it causes all
+items whose update time is earlier than the time at which flush_all
+was set to be executed to be ignored for retrieval purposes.
+
+The intent of flush_all with a delay, was that in a setting where you
+have a pool of memcached servers, and you need to flush all content,
+you have the option of not resetting all memcached servers at the
+same time (which could e.g. cause a spike in database load with all
+clients suddenly needing to recreate content that would otherwise
+have been found in the memcached daemon).
+
+The delay option allows you to have them reset in e.g. 10 second
+intervals (by passing 0 to the first, 10 to the second, 20 to the
+third, etc. etc.).
+
+
+"version" is a command with no arguments:
+
+version\r\n
+
+In response, the server sends
+
+"VERSION <version>\r\n", where <version> is the version string for the
+server.
+
+"verbosity" is a command with a numeric argument. It always succeeds,
+and the server sends "OK\r\n" in response (unless "noreply" is given
+as the last parameter). Its effect is to set the verbosity level of
+the logging output.
+
+"quit" is a command with no arguments:
+
+quit\r\n
+
+Upon receiving this command, the server closes the
+connection. However, the client may also simply close the connection
+when it no longer needs it, without issuing this command.
+
+
+UDP protocol
+------------
+
+For very large installations where the number of clients is high enough
+that the number of TCP connections causes scaling difficulties, there is
+also a UDP-based interface. The UDP interface does not provide guaranteed
+delivery, so should only be used for operations that aren't required to
+succeed; typically it is used for "get" requests where a missing or
+incomplete response can simply be treated as a cache miss.
+
+Each UDP datagram contains a simple frame header, followed by data in the
+same format as the TCP protocol described above. In the current
+implementation, requests must be contained in a single UDP datagram, but
+responses may span several datagrams. (The only common requests that would
+span multiple datagrams are huge multi-key "get" requests and "set"
+requests, both of which are more suitable to TCP transport for reliability
+reasons anyway.)
+
+The frame header is 8 bytes long, as follows (all values are 16-bit integers
+in network byte order, high byte first):
+
+0-1 Request ID
+2-3 Sequence number
+4-5 Total number of datagrams in this message
+6-7 Reserved for future use; must be 0
+
+The request ID is supplied by the client. Typically it will be a
+monotonically increasing value starting from a random seed, but the client
+is free to use whatever request IDs it likes. The server's response will
+contain the same ID as the incoming request. The client uses the request ID
+to differentiate between responses to outstanding requests if there are
+several pending from the same server; any datagrams with an unknown request
+ID are probably delayed responses to an earlier request and should be
+discarded.
+
+The sequence number ranges from 0 to n-1, where n is the total number of
+datagrams in the message. The client should concatenate the payloads of the
+datagrams for a given response in sequence number order; the resulting byte
+stream will contain a complete response in the same format as the TCP
+protocol (including terminating \r\n sequences).
diff --git a/memcached/doc/readme.txt b/memcached/doc/readme.txt
new file mode 100644 (file)
index 0000000..6a9d0c8
--- /dev/null
@@ -0,0 +1 @@
+To build the documentation you need xml2rfc ( http://xml.resource.org/ ).
diff --git a/memcached/doc/xml2rfc/reference.RFC.0768.xml b/memcached/doc/xml2rfc/reference.RFC.0768.xml
new file mode 100644 (file)
index 0000000..4452c34
--- /dev/null
@@ -0,0 +1,22 @@
+<?xml version='1.0' encoding='UTF-8'?>
+
+<reference anchor='RFC0768'>
+
+<front>
+<title>User Datagram Protocol</title>
+<author initials='J.' surname='Postel' fullname='J. Postel'>
+<organization>University of Southern California (USC)/Information Sciences Institute</organization>
+<address>
+<postal>
+<street>4676 Admiralty Way</street>
+<city>Marina del Rey</city>
+<region>CA</region>
+<code>90291</code>
+<country>US</country></postal>
+<phone>+1 213 822 1511</phone></address></author>
+<date year='1980' day='28' month='August' /></front>
+
+<seriesInfo name='STD' value='6' />
+<seriesInfo name='RFC' value='768' />
+<format type='TXT' octets='5896' target='ftp://ftp.isi.edu/in-notes/rfc768.txt' />
+</reference>
diff --git a/memcached/doc/xml2rfc/rfc2629-noinc.xsl b/memcached/doc/xml2rfc/rfc2629-noinc.xsl
new file mode 100644 (file)
index 0000000..99b73a8
--- /dev/null
@@ -0,0 +1,106 @@
+<?xml version="1.0" encoding="utf-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+                version="1.0">
+
+  <xsl:output method="xml"/>
+
+  <xsl:template name='noinc'>
+    <xsl:apply-templates mode='noinc'/>
+  </xsl:template>
+
+  <xsl:template match="*" mode='noinc'>
+    <xsl:copy>
+      <xsl:apply-templates select='@*' mode='noinc'/>
+      <xsl:apply-templates mode='noinc'/>
+    </xsl:copy>
+  </xsl:template>
+  <xsl:template match='@*' mode='noinc'>
+    <xsl:attribute name='{name()}'>
+      <xsl:value-of select='.'/>
+    </xsl:attribute>
+  </xsl:template>
+
+  <xsl:template match='processing-instruction("rfc")' mode='noinc'>
+    <xsl:choose>
+      <xsl:when test='substring-before(.,"=") = "include"'>
+        <xsl:call-template name='include-pi'>
+          <xsl:with-param name='include-href'>
+            <xsl:value-of select="translate( substring-after( ., '=' ), '&quot; ', '' )"/><xsl:text>.xml</xsl:text>
+          </xsl:with-param>
+        </xsl:call-template>
+      </xsl:when>
+      <xsl:otherwise>
+        <xsl:copy-of select='.'/>
+      </xsl:otherwise>
+    </xsl:choose>
+  </xsl:template>
+
+  <xsl:template name='include-pi'>
+    <xsl:param name='include-href'/>
+    <xsl:apply-templates select="document( $include-href )" mode='noinc'/>
+  </xsl:template>
+
+  <xsl:template match='dwdrfc-ref' mode='noinc'>
+       <xsl:param name='include-href'>
+          <xsl:choose>
+            <xsl:when test='starts-with( @src, "http://" )'>
+              <xsl:value-of select='@src'/>
+            </xsl:when>
+            <xsl:otherwise>
+              <xsl:text>http://xml.resource.org/public/rfc/</xsl:text>
+              <xsl:value-of select='@src'/>
+              <xsl:text>.xml</xsl:text>
+            </xsl:otherwise>
+          </xsl:choose>
+       </xsl:param>
+        <reference anchor="{@anchor}">
+          <xsl:apply-templates select="document( $include-href )" mode="refrename"/>
+       </reference>
+  </xsl:template>
+
+       <xsl:template match='*' mode='refrename'>
+       <xsl:apply-templates mode='refrename'/>
+       </xsl:template>
+
+       <xsl:template match='reference' mode='refrename'>
+       <xsl:apply-templates mode='noinc'/>
+       </xsl:template>
+
+  <xsl:template match='/'>
+    <xsl:call-template name='noinc'/>
+  </xsl:template>
+
+  <xsl:template name='output'>
+    <xsl:param name='foo'/>
+    <xsl:copy-of select='$foo'/>
+    <xsl:apply-templates select='$foo'/>
+  </xsl:template>
+
+  <xsl:template match='*' mode='output'>
+    <element>
+      <xsl:value-of select='name()'/>
+      <xsl:apply-templates mode='output'/>
+    </element>
+  </xsl:template>
+  <xsl:template match='text()' mode='output'/>
+
+  <!-- Reference checking attributes stripped here. -->
+  <xsl:template match='references' mode='noinc'>
+    <xsl:element name='references'>
+      <xsl:attribute name='title'>
+        <xsl:value-of select='@title'/>
+      </xsl:attribute>
+      <xsl:apply-templates mode='noinc'/>
+    </xsl:element>
+  </xsl:template>
+
+  <xsl:template match='xref' mode='noinc'>
+    <xsl:element name='xref'>
+      <xsl:attribute name='target'>
+        <xsl:value-of select='@target'/>
+      </xsl:attribute>
+      <xsl:apply-templates mode='noinc'/>
+    </xsl:element>
+  </xsl:template>
+
+</xsl:stylesheet>
diff --git a/memcached/doc/xml2rfc/rfc2629-other.ent b/memcached/doc/xml2rfc/rfc2629-other.ent
new file mode 100644 (file)
index 0000000..0da80b7
--- /dev/null
@@ -0,0 +1,61 @@
+<!-- rfc2629-other.ent
+
+     Character entity set for RFC-2629 source xml documents.
+     There is no need to invoke this directly from
+     the source document itself; just invoke the DTD.
+
+     These are unknown to (X)HTML, so any converter to these
+     formats must either substitute these entities
+     or invoke this file from the output file.
+
+     Conversion to txt or nroff format will replace
+     these entities by the parenthesized text.
+
+     Typical invocation:
+       <!ENTITY % rfc2629-other
+                PUBLIC "-//IETF//ENTITIES Other for RFC 2629//EN"
+                       "http://xml.resource.org/authoring/rfc2629-other.ent">
+       %rfc2629-other;
+-->
+
+<!-- Magical -->
+<!--     rfc.number            (automatically expanded to content
+                                of number="..." attribute
+                                of <rfc> element, or to "XXXX")                       -->
+
+<!-- ASCII -->
+<!ENTITY excl     "&#33;"><!-- U+0021 EXCLAMATION MARK                  ("!")         -->
+<!ENTITY num      "&#35;"><!-- U+0023 NUMBER SIGN                       ("#")         -->
+<!ENTITY dollar   "&#36;"><!-- U+0024 DOLLAR SIGN                       ("$")         -->
+<!ENTITY percnt   "&#37;"><!-- U+0025 PERCENT SIGN                      ("%")         -->
+<!ENTITY lpar     "&#40;"><!-- U+0028 LEFT PARENTHESIS                  ("(")         -->
+<!ENTITY rpar     "&#41;"><!-- U+0029 RIGHT PARENTHESIS                 (")")         -->
+<!ENTITY ast      "&#42;"><!-- U+002A ASTERISK                          ("*")         -->
+<!ENTITY plus     "&#43;"><!-- U+002B PLUS SIGN                         ("+")         -->
+<!ENTITY comma    "&#44;"><!-- U+002C COMMA                             (",")         -->
+<!ENTITY hyphen   "&#45;"><!-- U+002D HYPHEN-MINUS                      ("-")         -->
+<!ENTITY period   "&#46;"><!-- U+002E FULL STOP                         (".")         -->
+<!ENTITY sol      "&#47;"><!-- U+002F SOLIDUS                           ("/")         -->
+<!ENTITY colon    "&#58;"><!-- U+003A COLON                             (":")         -->
+<!ENTITY semi     "&#59;"><!-- U+003B SEMICOLON                         (";")         -->
+<!ENTITY equals   "&#61;"><!-- U+003D EQUALS SIGN                       ("=")         -->
+<!ENTITY quest    "&#63;"><!-- U+003F QUESTION MARK                     ("?")         -->
+<!ENTITY commat   "&#64;"><!-- U+0040 COMMERCIAL AT                     ("@")         -->
+<!ENTITY lsqb     "&#91;"><!-- U+005B LEFT SQUARE BRACKET               ("[")         -->
+<!ENTITY bsol     "&#92;"><!-- U+005C REVERSE SOLIDUS                   ("\\")        -->
+<!ENTITY rsqb     "&#93;"><!-- U+005D RIGHT SQUARE BRACKET              ("]")         -->
+<!ENTITY circ     "&#94;"><!-- U+005E CIRCUMFLEX ACCENT                 ("^")         -->
+<!ENTITY lowbar   "&#95;"><!-- U+005F LOW LINE                          ("_")         -->
+<!ENTITY grave    "&#96;"><!-- U+0060 GRAVE ACCENT                      ("`")         -->
+<!ENTITY lcub    "&#123;"><!-- U+007B LEFT CURLY BRACKET                ("{")         -->
+<!ENTITY verbar  "&#124;"><!-- U+007C VERTICAL LINE                     ("|")         -->
+<!ENTITY rcub    "&#125;"><!-- U+007D RIGHT CURLY BRACKET               ("}")         -->
+
+<!-- Useful Unicode -->
+<!ENTITY Zcaron  "&#381;"><!-- U+017D LATIN CAPITAL LETTER Z WITH CARON ("Z")         -->
+<!ENTITY zcaron  "&#382;"><!-- U+017E LATIN SMALL LETTER Z WITH CARON   ("z")         -->
+<!ENTITY dash   "&#8208;"><!-- U+2010 HYPHEN                            ("-")         -->
+<!ENTITY nbhy   "&#8209;"><!-- U+2011 NON-BREAKING HYPHEN               (special "-") -->
+<!ENTITY wj     "&#8288;"><!-- U+2060 WORD JOINER                       (special "")  -->
+
+<!-- EOF -->
diff --git a/memcached/doc/xml2rfc/rfc2629-refchk.xsl b/memcached/doc/xml2rfc/rfc2629-refchk.xsl
new file mode 100644 (file)
index 0000000..0cea58e
--- /dev/null
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="utf-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+                version="1.0">
+
+  <xsl:output method="xml"/>
+
+  <xsl:template match='/'>
+    <refcheck>
+      <xsl:apply-templates select='//xref' mode='missing'/>
+      <xsl:apply-templates select='//reference' mode='orphans'/>
+      <xsl:apply-templates select='//references[@dwdrfc-type = "norm"]' mode='normcheck'/>
+      <xsl:apply-templates select='//xref[@dwdrfc-type = "norm"]' mode='normrefs'/>
+    </refcheck>
+  </xsl:template>
+
+  <xsl:template match='xref' mode='missing'>
+    <xsl:param name='refname'>
+      <xsl:value-of select='@target'/>
+    </xsl:param>
+    <xsl:param name='reftgt'>
+      <xsl:value-of select='//*[@anchor = $refname]/@anchor'/>
+    </xsl:param>
+    <xsl:choose>
+      <xsl:when test='$reftgt = $refname'/>
+      <xsl:otherwise>
+        <missing><xsl:value-of select='$refname'/></missing><xsl:text>
+      </xsl:text>
+    </xsl:otherwise>
+  </xsl:choose>
+  </xsl:template>
+
+  <xsl:template match='xref' mode='normrefs'>
+    <xsl:param name='refname'>
+      <xsl:value-of select='@target'/>
+    </xsl:param>
+    <xsl:param name='reftgt'>
+      <xsl:value-of select='//references[@dwdrfc-type = "norm"]/*[@anchor = $refname]/@anchor'/>
+    </xsl:param>
+    <xsl:choose>
+      <xsl:when test='$reftgt = $refname'/>
+      <xsl:otherwise>
+        <missing-norm><xsl:value-of select='$refname'/></missing-norm><xsl:text>
+      </xsl:text>
+    </xsl:otherwise>
+  </xsl:choose>
+  </xsl:template>
+
+  <xsl:template match='reference' mode='orphans'>
+    <xsl:param name='refname'>
+      <xsl:value-of select='@anchor'/>
+    </xsl:param>
+    <xsl:param name='reftgt'>
+      <xsl:value-of select='//xref[@target = $refname]/@target'/>
+    </xsl:param>
+    <xsl:if test='$reftgt != $refname'>
+      <orphan><xsl:value-of select='$refname'/></orphan><xsl:text>
+    </xsl:text>
+  </xsl:if>
+</xsl:template>
+
+<xsl:template match='references' mode='normcheck'>
+  <xsl:apply-templates mode='normcheck'/>
+</xsl:template>
+
+<xsl:template match='*' mode='normcheck'>
+  <!-- Need to find at least one normative reference -->
+  <xsl:param name='refname'>
+    <xsl:value-of select='@anchor'/>
+  </xsl:param>
+  <xsl:param name='reftgt'>
+    <xsl:value-of select='//xref[@dwdrfc-type = "norm" and @target = $refname]/@target'/>
+  </xsl:param>
+  <xsl:if test='$refname != $reftgt'>
+    <normchk><xsl:value-of select='$refname'/></normchk><xsl:text>
+</xsl:text>
+  </xsl:if>
+</xsl:template>
+
+<xsl:template match='text()' mode='normcheck'/>
+
+</xsl:stylesheet>
diff --git a/memcached/doc/xml2rfc/rfc2629-xhtml.ent b/memcached/doc/xml2rfc/rfc2629-xhtml.ent
new file mode 100644 (file)
index 0000000..3d4a07d
--- /dev/null
@@ -0,0 +1,165 @@
+<!-- rfc2629-xhtml.ent
+
+     Character entity set for RFC-2629 source xml documents.
+     There is no need to invoke this directly from
+     the source document itself; just invoke the DTD.
+
+     These are known to (X)HTML, so any converter to these
+     formats can just leave them as is without having
+     to invoke this file from the output file.
+
+     Conversion to txt or nroff format will replace
+     these entities by the parenthesized text.
+
+     Typical invocation:
+       <!ENTITY % rfc2629-xhtml
+                PUBLIC "-//IETF//ENTITIES XHTML subset for RFC 2629//EN"
+                       "http://xml.resource.org/authoring/rfc2629-xhtml.ent">
+       %rfc2629-xhtml;
+-->
+
+<!-- All of ISO Latin 1 -->
+<!ENTITY nbsp    "&#160;"><!-- U+00A0 NO-BREAK SPACE                             (special " ")        -->
+<!ENTITY iexcl   "&#161;"><!-- U+00A1 INVERTED EXCLAMATION MARK                  ("!")                -->
+<!ENTITY cent    "&#162;"><!-- U+00A2 CENT SIGN                                  ("[cents]")          -->
+<!ENTITY pound   "&#163;"><!-- U+00A3 POUND SIGN                                 ("GBP")              -->
+<!ENTITY curren  "&#164;"><!-- U+00A4 CURRENCY SIGN                              ("[currency units]") -->
+<!ENTITY yen     "&#165;"><!-- U+00A5 YEN SIGN                                   ("JPY")              -->
+<!ENTITY brvbar  "&#166;"><!-- U+00A6 BROKEN BAR                                 ("|")                -->
+<!ENTITY sect    "&#167;"><!-- U+00A7 SECTION SIGN                               ("S.")               -->
+<!ENTITY uml     "&#168;"><!-- U+00A8 DIAERESIS                                  ('"')                -->
+<!ENTITY copy    "&#169;"><!-- U+00A9 COPYRIGHT SIGN                             ("(C)")              -->
+<!ENTITY ordf    "&#170;"><!-- U+00AA FEMININE ORDINAL INDICATOR                 ("a")                -->
+<!ENTITY laquo   "&#171;"><!-- U+00AB LEFT-POINTING DOUBLE ANGLE QUOTATION MARK  ("<<")               -->
+<!ENTITY not     "&#172;"><!-- U+00AC NOT SIGN                                   ("[not]")            -->
+<!ENTITY shy     "&#173;"><!-- U+00AD SOFT HYPHEN                                (ignored "")         -->
+<!ENTITY reg     "&#174;"><!-- U+00AE REGISTERED SIGN                            ("(R)")              -->
+<!ENTITY macr    "&#175;"><!-- U+00AF MACRON                                     ("_")                -->
+<!ENTITY deg     "&#176;"><!-- U+00B0 DEGREE SIGN                                ("o")                -->
+<!ENTITY plusmn  "&#177;"><!-- U+00B1 PLUS-MINUS SIGN                            ("+/-")              -->
+<!ENTITY sup2    "&#178;"><!-- U+00B2 SUPERSCRIPT TWO                            ("^2")               -->
+<!ENTITY sup3    "&#179;"><!-- U+00B3 SUPERSCRIPT THREE                          ("^3")               -->
+<!ENTITY acute   "&#180;"><!-- U+00B4 ACUTE ACCENT                               ("'")                -->
+<!ENTITY micro   "&#181;"><!-- U+00B5 MICRO SIGN                                 ("[micro]")          -->
+<!ENTITY para    "&#182;"><!-- U+00B6 PILCROW SIGN                               ("P.")               -->
+<!ENTITY middot  "&#183;"><!-- U+00B7 MIDDLE DOT                                 (".")                -->
+<!ENTITY cedil   "&#184;"><!-- U+00B8 CEDILLA                                    (",")                -->
+<!ENTITY sup1    "&#185;"><!-- U+00B9 SUPERSCRIPT ONE                            ("^1")               -->
+<!ENTITY ordm    "&#186;"><!-- U+00BA MASCULINE ORDINAL INDICATOR                ("o")                -->
+<!ENTITY raquo   "&#187;"><!-- U+00BB RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK (">>")               -->
+<!ENTITY frac14  "&#188;"><!-- U+00BC VULGAR FRACTION ONE QUARTER                ("1/4")              -->
+<!ENTITY frac12  "&#189;"><!-- U+00BD VULGAR FRACTION ONE HALF                   ("1/2")              -->
+<!ENTITY frac34  "&#190;"><!-- U+00BE VULGAR FRACTION THREE QUARTERS             ("3/4")              -->
+<!ENTITY iquest  "&#191;"><!-- U+00BF INVERTED QUESTION MARK                     ("?")                -->
+<!ENTITY Agrave  "&#192;"><!-- U+00C0 LATIN CAPITAL LETTER A WITH GRAVE          ("A")                -->
+<!ENTITY Aacute  "&#193;"><!-- U+00C1 LATIN CAPITAL LETTER A WITH ACUTE          ("A")                -->
+<!ENTITY Acirc   "&#194;"><!-- U+00C2 LATIN CAPITAL LETTER A WITH CIRCUMFLEX     ("A")                -->
+<!ENTITY Atilde  "&#195;"><!-- U+00C3 LATIN CAPITAL LETTER A WITH TILDE          ("A")                -->
+<!ENTITY Auml    "&#196;"><!-- U+00C4 LATIN CAPITAL LETTER A WITH DIAERESIS      ("Ae")               -->
+<!ENTITY Aring   "&#197;"><!-- U+00C5 LATIN CAPITAL LETTER A WITH RING ABOVE     ("Ae")               -->
+<!ENTITY AElig   "&#198;"><!-- U+00C6 LATIN CAPITAL LETTER AE                    ("AE")               -->
+<!ENTITY Ccedil  "&#199;"><!-- U+00C7 LATIN CAPITAL LETTER C WITH CEDILLA        ("C")                -->
+<!ENTITY Egrave  "&#200;"><!-- U+00C8 LATIN CAPITAL LETTER E WITH GRAVE          ("E")                -->
+<!ENTITY Eacute  "&#201;"><!-- U+00C9 LATIN CAPITAL LETTER E WITH ACUTE          ("E")                -->
+<!ENTITY Ecirc   "&#202;"><!-- U+00CA LATIN CAPITAL LETTER E WITH CIRCUMFLEX     ("E")                -->
+<!ENTITY Euml    "&#203;"><!-- U+00CB LATIN CAPITAL LETTER E WITH DIAERESIS      ("E")                -->
+<!ENTITY Igrave  "&#204;"><!-- U+00CC LATIN CAPITAL LETTER I WITH GRAVE          ("I")                -->
+<!ENTITY Iacute  "&#205;"><!-- U+00CD LATIN CAPITAL LETTER I WITH ACUTE          ("I")                -->
+<!ENTITY Icirc   "&#206;"><!-- U+00CE LATIN CAPITAL LETTER I WITH CIRCUMFLEX     ("I")                -->
+<!ENTITY Iuml    "&#207;"><!-- U+00CF LATIN CAPITAL LETTER I WITH DIAERESIS      ("I")                -->
+<!ENTITY ETH     "&#208;"><!-- U+00D0 LATIN CAPITAL LETTER ETH                   ("[ETH]")            -->
+<!ENTITY Ntilde  "&#209;"><!-- U+00D1 LATIN CAPITAL LETTER N WITH TILDE          ("N")                -->
+<!ENTITY Ograve  "&#210;"><!-- U+00D2 LATIN CAPITAL LETTER O WITH GRAVE          ("O")                -->
+<!ENTITY Oacute  "&#211;"><!-- U+00D3 LATIN CAPITAL LETTER O WITH ACUTE          ("O")                -->
+<!ENTITY Ocirc   "&#212;"><!-- U+00D4 LATIN CAPITAL LETTER O WITH CIRCUMFLEX     ("O")                -->
+<!ENTITY Otilde  "&#213;"><!-- U+00D5 LATIN CAPITAL LETTER O WITH TILDE          ("O")                -->
+<!ENTITY Ouml    "&#214;"><!-- U+00D6 LATIN CAPITAL LETTER O WITH DIAERESIS      ("Oe")               -->
+<!ENTITY times   "&#215;"><!-- U+00D7 MULTIPLICATION SIGN                        ("x")                -->
+<!ENTITY Oslash  "&#216;"><!-- U+00D8 LATIN CAPITAL LETTER O WITH STROKE         ("Oe")               -->
+<!ENTITY Ugrave  "&#217;"><!-- U+00D9 LATIN CAPITAL LETTER U WITH GRAVE          ("U")                -->
+<!ENTITY Uacute  "&#218;"><!-- U+00DA LATIN CAPITAL LETTER U WITH ACUTE          ("U")                -->
+<!ENTITY Ucirc   "&#219;"><!-- U+00DB LATIN CAPITAL LETTER U WITH CIRCUMFLEX     ("U")                -->
+<!ENTITY Uuml    "&#220;"><!-- U+00DC LATIN CAPITAL LETTER U WITH DIAERESIS      ("Ue")               -->
+<!ENTITY Yacute  "&#221;"><!-- U+00DD LATIN CAPITAL LETTER Y WITH ACUTE          ("Y")                -->
+<!ENTITY THORN   "&#222;"><!-- U+00DE LATIN CAPITAL LETTER THORN                 ("[THORN]")          -->
+<!ENTITY szlig   "&#223;"><!-- U+00DF LATIN SMALL LETTER SHARP S                 ("ss")               -->
+<!ENTITY agrave  "&#224;"><!-- U+00E0 LATIN SMALL LETTER A WITH GRAVE            ("a")                -->
+<!ENTITY aacute  "&#225;"><!-- U+00E1 LATIN SMALL LETTER A WITH ACUTE            ("a")                -->
+<!ENTITY acirc   "&#226;"><!-- U+00E2 LATIN SMALL LETTER A WITH CIRCUMFLEX       ("a")                -->
+<!ENTITY atilde  "&#227;"><!-- U+00E3 LATIN SMALL LETTER A WITH TILDE            ("a")                -->
+<!ENTITY auml    "&#228;"><!-- U+00E4 LATIN SMALL LETTER A WITH DIAERESIS        ("ae")               -->
+<!ENTITY aring   "&#229;"><!-- U+00E5 LATIN SMALL LETTER A WITH RING ABOVE       ("ae")               -->
+<!ENTITY aelig   "&#230;"><!-- U+00E6 LATIN SMALL LETTER AE                      ("ae")               -->
+<!ENTITY ccedil  "&#231;"><!-- U+00E7 LATIN SMALL LETTER C WITH CEDILLA          ("c")                -->
+<!ENTITY egrave  "&#232;"><!-- U+00E8 LATIN SMALL LETTER E WITH GRAVE            ("e")                -->
+<!ENTITY eacute  "&#233;"><!-- U+00E9 LATIN SMALL LETTER E WITH ACUTE            ("e")                -->
+<!ENTITY ecirc   "&#234;"><!-- U+00EA LATIN SMALL LETTER E WITH CIRCUMFLEX       ("e")                -->
+<!ENTITY euml    "&#235;"><!-- U+00EB LATIN SMALL LETTER E WITH DIAERESIS        ("e")                -->
+<!ENTITY igrave  "&#236;"><!-- U+00EC LATIN SMALL LETTER I WITH GRAVE            ("i")                -->
+<!ENTITY iacute  "&#237;"><!-- U+00ED LATIN SMALL LETTER I WITH ACUTE            ("i")                -->
+<!ENTITY icirc   "&#238;"><!-- U+00EE LATIN SMALL LETTER I WITH CIRCUMFLEX       ("i")                -->
+<!ENTITY iuml    "&#239;"><!-- U+00EF LATIN SMALL LETTER I WITH DIAERESIS        ("i")                -->
+<!ENTITY eth     "&#240;"><!-- U+00F0 LATIN SMALL LETTER ETH                     ("[eth]")            -->
+<!ENTITY ntilde  "&#241;"><!-- U+00F1 LATIN SMALL LETTER N WITH TILDE            ("n")                -->
+<!ENTITY ograve  "&#242;"><!-- U+00F2 LATIN SMALL LETTER O WITH GRAVE            ("o")                -->
+<!ENTITY oacute  "&#243;"><!-- U+00F3 LATIN SMALL LETTER O WITH ACUTE            ("o")                -->
+<!ENTITY ocirc   "&#244;"><!-- U+00F4 LATIN SMALL LETTER O WITH CIRCUMFLEX       ("o")                -->
+<!ENTITY otilde  "&#245;"><!-- U+00F5 LATIN SMALL LETTER O WITH TILDE            ("o")                -->
+<!ENTITY ouml    "&#246;"><!-- U+00F6 LATIN SMALL LETTER O WITH DIAERESIS        ("oe")               -->
+<!ENTITY divide  "&#247;"><!-- U+00F7 DIVISION SIGN                              ("/")                -->
+<!ENTITY oslash  "&#248;"><!-- U+00F8 LATIN SMALL LETTER O WITH STROKE           ("oe")               -->
+<!ENTITY ugrave  "&#249;"><!-- U+00F9 LATIN SMALL LETTER U WITH GRAVE            ("u")                -->
+<!ENTITY uacute  "&#250;"><!-- U+00FA LATIN SMALL LETTER U WITH ACUTE            ("u")                -->
+<!ENTITY ucirc   "&#251;"><!-- U+00FB LATIN SMALL LETTER U WITH CIRCUMFLEX       ("u")                -->
+<!ENTITY uuml    "&#252;"><!-- U+00FC LATIN SMALL LETTER U WITH DIAERESIS        ("ue")               -->
+<!ENTITY yacute  "&#253;"><!-- U+00FD LATIN SMALL LETTER Y WITH ACUTE            ("y")                -->
+<!ENTITY thorn   "&#254;"><!-- U+00FE LATIN SMALL LETTER THORN                   ("[thorn]")          -->
+<!ENTITY yuml    "&#255;"><!-- U+00FF LATIN SMALL LETTER Y WITH DIAERESIS        ("y")                -->
+
+<!-- Some of ISO Latin 9 and 10 -->
+<!ENTITY OElig   "&#338;"><!-- U+0152 LATIN CAPITAL LIGATURE OE                  ("OE")               -->
+<!ENTITY oelig   "&#339;"><!-- U+0153 LATIN SMALL LIGATURE OE                    ("oe")               -->
+<!ENTITY Scaron  "&#352;"><!-- U+0160 LATIN CAPITAL LETTER S WITH CARON          ("S")                -->
+<!ENTITY scaron  "&#353;"><!-- U+0161 LATIN SMALL LETTER S WITH CARON            ("s")                -->
+<!ENTITY Yuml    "&#376;"><!-- U+0178 LATIN CAPITAL LETTER Y WITH DIAERESIS      ("Y")                -->
+
+<!-- Other Unicode (including some characters from the windows-1252 repertoire) -->
+<!ENTITY fnof    "&#402;"><!-- U+0192 LATIN SMALL LETTER F WITH HOOK             ("f")                -->
+<!ENTITY tilde   "&#732;"><!-- U+02DC SMALL TILDE                                ("~")                -->
+<!ENTITY ensp   "&#8194;"><!-- U+2002 EN SPACE                                   (" ")                -->
+<!ENTITY emsp   "&#8195;"><!-- U+2003 EM SPACE                                   (" ")                -->
+<!ENTITY thinsp "&#8201;"><!-- U+2009 THIN SPACE                                 (" ")                -->
+<!ENTITY ndash  "&#8211;"><!-- U+2013 EN DASH                                    ("-")                -->
+<!ENTITY mdash  "&#8212;"><!-- U+2014 EM DASH                                    ("-\u002D")          -->
+<!ENTITY lsquo  "&#8216;"><!-- U+2018 LEFT SINGLE QUOTATION MARK                 ("'")                -->
+<!ENTITY rsquo  "&#8217;"><!-- U+2019 RIGHT SINGLE QUOTATION MARK                ("'")                -->
+<!ENTITY sbquo  "&#8218;"><!-- U+201A SINGLE LOW-9 QUOTATION MARK                ("'")                -->
+<!ENTITY ldquo  "&#8220;"><!-- U+201C LEFT DOUBLE QUOTATION MARK                 ('"')                -->
+<!ENTITY rdquo  "&#8221;"><!-- U+201D RIGHT DOUBLE QUOTATION MARK                ('"')                -->
+<!ENTITY bdquo  "&#8222;"><!-- U+201E DOUBLE LOW-9 QUOTATION MARK                ('"')                -->
+<!ENTITY dagger "&#8224;"><!-- U+2020 DAGGER                                     ("*!*")              -->
+<!ENTITY Dagger "&#8225;"><!-- U+2021 DOUBLE DAGGER                              ("*!!*")             -->
+<!ENTITY bull   "&#8226;"><!-- U+2022 BULLET                                     ("o")                -->
+<!ENTITY hellip "&#8230;"><!-- U+2026 HORIZONTAL ELLIPSIS                        ("...")              -->
+<!ENTITY permil "&#8242;"><!-- U+2030 PER MILLE SIGN                             ("[/1000]")          -->
+<!ENTITY prime  "&#8242;"><!-- U+2032 PRIME                                      ("'")                -->
+<!ENTITY Prime  "&#8243;"><!-- U+2033 DOUBLE PRIME                               ('"')                -->
+<!ENTITY lsaquo "&#8249;"><!-- U+2039 SINGLE LEFT-POINTING ANGLE QUOTATION MARK  ("<")                -->
+<!ENTITY rsaquo "&#8250;"><!-- U+203A SINGLE RIGHT-POINTING ANGLE QUOTATION MARK (">")                -->
+<!ENTITY frasl  "&#8260;"><!-- U+2044 FRACTION SLASH                             ("/")                -->
+<!ENTITY euro   "&#8364;"><!-- U+20AC EURO SIGN                                  ("EUR")              -->
+<!ENTITY trade  "&#8482;"><!-- U+2122 TRADE MARK SIGN                            ("[TM]")             -->
+<!ENTITY larr   "&#8592;"><!-- U+2190 LEFTWARDS ARROW                            ("<-\u002D")         -->
+<!ENTITY rarr   "&#8594;"><!-- U+2192 RIGHTWARDS ARROW                           ("\u002D->")         -->
+<!ENTITY harr   "&#8596;"><!-- U+2194 LEFT RIGHT ARROW                           ("<->")              -->
+<!ENTITY lArr   "&#8656;"><!-- U+21D0 LEFTWARDS DOUBLE ARROW                     ("<==")              -->
+<!ENTITY rArr   "&#8658;"><!-- U+21D2 RIGHTWARDS DOUBLE ARROW                    ("==>")              -->
+<!ENTITY hArr   "&#8660;"><!-- U+21D4 LEFT RIGHT DOUBLE ARROW                    ("<=>")              -->
+<!ENTITY minus  "&#8722;"><!-- U+2212 MINUS SIGN                                 ("-")                -->
+<!ENTITY lowast "&#8727;"><!-- U+2217 ASTERISK OPERATOR                          ("*")                -->
+<!ENTITY le     "&#8804;"><!-- U+2264 LESS-THAN OR EQUAL TO                      ("<=")               -->
+<!ENTITY ge     "&#8805;"><!-- U+2265 GREATER-THAN OR EQUAL TO                   (">=")               -->
+<!ENTITY lang   "&#9001;"><!-- U+2329 LEFT-POINTING ANGLE BRACKET                ("<")                -->
+<!ENTITY rang   "&#9002;"><!-- U+232A RIGHT-POINTING ANGLE BRACKET               (">")                -->
+
+<!-- EOF -->
diff --git a/memcached/doc/xml2rfc/rfc2629.dtd b/memcached/doc/xml2rfc/rfc2629.dtd
new file mode 100644 (file)
index 0000000..bc30260
--- /dev/null
@@ -0,0 +1,304 @@
+<!--
+  revised DTD for the RFC document series, draft of 2007-03-29
+  -->
+
+
+<!--
+  Typical invocation:
+      <!DOCTYPE rfc PUBLIC "-//IETF//DTD RFC 2629//EN"
+                           "http://xml.resource.org/authoring/rfc2629.dtd" [
+        ... dtd subset ...
+      ]>
+    or
+      <!DOCTYPE rfc SYSTEM "rfc2629.dtd" [
+        ... dtd subset ...
+      ]>
+  -->
+
+
+<!--
+  Contents
+
+    Character entities
+
+    DTD data types
+
+    The top-level
+
+    Front matter
+
+    The Body
+
+    Back matter
+  -->
+
+
+<!--
+  Character entities
+  -->
+
+
+<!ENTITY % rfc2629-xhtml
+         PUBLIC "-//IETF//ENTITIES XHTML subset for RFC 2629//EN"
+                "rfc2629-xhtml.ent">
+%rfc2629-xhtml;
+
+<!ENTITY % rfc2629-other
+         PUBLIC "-//IETF//ENTITIES Other for RFC 2629//EN"
+                "rfc2629-other.ent">
+%rfc2629-other;
+
+
+<!--
+  DTD data types:
+
+        entity        description
+        ======        ===============================================
+        NUMBER        [0-9]+
+        NUMBERS       a comma-separated list of NUMBER
+
+        DAY           the day of the month, e.g., "1"
+        MONTH         the month of the year, e.g., "January"
+        YEAR          a four-digit year, e.g., "1999"
+
+        URI           e.g., "http://invisible.net/"
+
+        ATEXT/CTEXT   printable ASCII text (no line-terminators)
+
+        TEXT          character data
+  -->
+
+
+<!ENTITY % NUMBER     "CDATA">
+<!ENTITY % NUMBERS    "CDATA">
+
+<!ENTITY % DAY        "CDATA">
+<!ENTITY % MONTH      "CDATA">
+<!ENTITY % YEAR       "CDATA">
+
+<!ENTITY % URI        "CDATA">
+
+<!ENTITY % ATEXT      "CDATA">
+<!ENTITY % CTEXT      "#PCDATA">
+
+<!ENTITY % TEXT       "#PCDATA">
+
+<!ENTITY   rfc.number "XXXX">
+
+
+<!--
+  The top-level
+  -->
+
+
+<!--
+  attributes for the "rfc" element are supplied by the RFC
+  editor. when preparing drafts, authors should leave them blank.
+
+  the "seriesNo" attribute is used if the category is, e.g., BCP.
+  -->
+<!ELEMENT rfc         (front,middle,back?)>
+<!ATTLIST rfc
+          number      %NUMBER;           #IMPLIED
+          obsoletes   %NUMBERS;          ""
+          updates     %NUMBERS;          ""
+          category    (std|bcp|info|exp|historic)
+                                         #IMPLIED
+          seriesNo    %NUMBER;           #IMPLIED
+          ipr         (full2026|noDerivativeWorks2026|none
+                      |full3667|noModification3667|noDerivatives3667
+                      |full3978|noModification3978|noDerivatives3978)
+                                         #IMPLIED
+          iprExtract  IDREF              #IMPLIED
+          submissionType
+                      (IETF|independent) "IETF"
+          docName     %ATEXT;            #IMPLIED
+          xml:lang    %ATEXT;            "en">
+
+
+<!--
+  Front matter
+  -->
+
+
+<!ELEMENT front       (title,author+,date,area*,workgroup*,keyword*,
+                       abstract?,note*)>
+
+<!-- the "abbrev" attribute is used for headers, etc. -->
+<!ELEMENT title       (%CTEXT;)>
+<!ATTLIST title
+          abbrev      %ATEXT;            #IMPLIED>
+
+<!ELEMENT author      (organization,address?)>
+<!ATTLIST author
+          initials    %ATEXT;            #IMPLIED
+          surname     %ATEXT;            #IMPLIED
+          fullname    %ATEXT;            #IMPLIED
+          role        (editor)           #IMPLIED>
+
+<!ELEMENT organization
+                      (%CTEXT;)>
+<!ATTLIST organization
+          abbrev      %ATEXT;            #IMPLIED>
+
+<!ELEMENT address     (postal?,phone?,facsimile?,email?,uri?)>
+
+<!-- this content model should be more strict:
+     at most one of each the city, region, code, and country
+     elements may be present -->
+<!ELEMENT postal      (street+,(city|region|code|country)*)>
+<!ELEMENT street      (%CTEXT;)>
+<!ELEMENT city        (%CTEXT;)>
+<!ELEMENT region      (%CTEXT;)>
+<!ELEMENT code        (%CTEXT;)>
+<!ELEMENT country     (%CTEXT;)>
+<!ELEMENT phone       (%CTEXT;)>
+<!ELEMENT facsimile   (%CTEXT;)>
+<!ELEMENT email       (%CTEXT;)>
+<!ELEMENT uri         (%CTEXT;)>
+
+<!ELEMENT date        EMPTY>
+<!ATTLIST date
+          day         %DAY;              #IMPLIED
+          month       %MONTH;            #IMPLIED
+          year        %YEAR;             #IMPLIED>
+
+<!-- meta-data... -->
+<!ELEMENT area        (%CTEXT;)>
+<!ELEMENT workgroup   (%CTEXT;)>
+<!ELEMENT keyword     (%CTEXT;)>
+
+<!ELEMENT abstract    (t)+>
+<!ELEMENT note        (t)+>
+<!ATTLIST note
+          title       %ATEXT;            #REQUIRED>
+
+
+<!--
+  The body
+  -->
+
+
+<!-- later on, may be (section+,appendix*,section*) -->
+<!ELEMENT middle      (section+)>
+
+<!ELEMENT section     ((t|figure|texttable|iref)*,section*)>
+<!ATTLIST section
+          anchor      ID                 #IMPLIED
+          title       %ATEXT;            #REQUIRED
+          toc         (include|exclude|default)
+                                         "default">
+
+<!--
+<!ELEMENT appendix    ((t|figure|texttable|iref)*,appendix*)>
+<!ATTLIST appendix
+          anchor      ID                 #IMPLIED
+          title       %ATEXT;            #REQUIRED
+          toc         (include|exclude|default)
+                                         "default">
+  -->
+
+<!-- use of <figure/> is deprecated... -->
+<!ELEMENT t           (%TEXT;|list|figure|xref|eref|iref|cref|spanx|vspace)*>
+<!ATTLIST t
+          anchor      ID                 #IMPLIED
+          hangText    %ATEXT;            #IMPLIED>
+
+<!-- the value of the style attribute is inherited from the closest
+     parent -->
+<!ELEMENT list        (t+)>
+<!ATTLIST list
+          style       %ATEXT;            #IMPLIED
+          hangIndent  %NUMBER;           #IMPLIED
+          counter     %ATEXT;            #IMPLIED>
+
+<!ELEMENT xref        (%CTEXT;)>
+<!ATTLIST xref
+          target      IDREF              #REQUIRED
+          pageno      (true|false)       "false"
+          format      (counter|title|none|default)
+                                         "default">
+
+<!ELEMENT eref        (%CTEXT;)>
+<!ATTLIST eref
+          target      %URI;              #REQUIRED>
+
+<!ELEMENT iref        EMPTY>
+<!ATTLIST iref
+          item        %ATEXT;            #REQUIRED
+          subitem     %ATEXT;            ""
+          primary    (true|false)       "false">
+
+<!ELEMENT cref        (%CTEXT;)>
+<!ATTLIST cref
+          anchor      ID                 #IMPLIED
+          source      %ATEXT;            #IMPLIED>
+
+<!ELEMENT spanx       (%CTEXT;)>
+<!ATTLIST spanx
+          style       %ATEXT;            "emph">
+
+<!ELEMENT vspace      EMPTY>
+<!ATTLIST vspace
+          blankLines  %NUMBER;           "0">
+
+<!ELEMENT figure      (iref*,preamble?,artwork,postamble?)>
+<!ATTLIST figure
+          anchor      ID                 #IMPLIED
+          title       %ATEXT;            ""
+          src         %URI;              #IMPLIED
+          align       (left|center|right) "left"
+          alt         %ATEXT;            ""
+          width       %ATEXT;            ""
+          height      %ATEXT;            "">
+
+<!ELEMENT preamble    (%TEXT;|xref|eref|iref|cref|spanx)*>
+<!ELEMENT artwork     (%TEXT;)*>
+<!ATTLIST artwork
+          xml:space   (default|preserve) "preserve"
+          name        %ATEXT;            ""
+          type        %ATEXT;            ""
+          src         %URI;              #IMPLIED
+          align       (left|center|right) "left"
+          alt         %ATEXT;            ""
+          width       %ATEXT;            ""
+          height      %ATEXT;            "">
+
+<!ELEMENT postamble   (%TEXT;|xref|eref|iref|cref|spanx)*>
+
+<!ELEMENT texttable   (preamble?,ttcol+,c*,postamble?)>
+<!ATTLIST texttable
+          anchor      ID                 #IMPLIED
+          title       %ATEXT;            "">
+<!ELEMENT ttcol       (%CTEXT;)>
+<!ATTLIST ttcol
+          width       %ATEXT;           #IMPLIED
+          align       (left|center|right) "left">
+<!ELEMENT c           (%TEXT;|xref|eref|iref|cref|spanx)*>
+
+
+<!--
+  Back matter
+  -->
+
+
+<!-- sections, if present, are appendices -->
+<!ELEMENT back        (references*,section*)>
+
+<!ELEMENT references  (reference+)>
+<!ATTLIST references
+          title       %ATEXT;            "References">
+<!ELEMENT reference   (front,seriesInfo*,format*,annotation*)>
+<!ATTLIST reference
+          anchor      ID                 #IMPLIED
+          target      %URI;              #IMPLIED>
+<!ELEMENT seriesInfo  EMPTY>
+<!ATTLIST seriesInfo
+          name        %ATEXT;            #REQUIRED
+          value       %ATEXT;            #REQUIRED>
+<!ELEMENT format      EMPTY>
+<!ATTLIST format
+          target      %URI;              #IMPLIED
+          type        %ATEXT;            #REQUIRED
+          octets      %NUMBER;           #IMPLIED>
+<!ELEMENT annotation  (%TEXT;|xref|eref|iref|cref|spanx)*>
diff --git a/memcached/globals.c b/memcached/globals.c
new file mode 100644 (file)
index 0000000..a01e370
--- /dev/null
@@ -0,0 +1,25 @@
+#include "memcached.h"
+
+/*
+ * This file contains global variables shared across the rest of the
+ * memcached codebase.  These were originally in memcached.c but had
+ * to be removed to make the rest of the object files linkable into
+ * the test infrastructure.
+ *
+ */
+
+/*
+ * We keep the current time of day in a global variable that's updated by a
+ * timer event. This saves us a bunch of time() system calls (we really only
+ * need to get the time once a second, whereas there can be tens of thousands
+ * of requests a second) and allows us to use server-start-relative timestamps
+ * rather than absolute UNIX timestamps, a space savings on systems where
+ * sizeof(time_t) > sizeof(unsigned int).
+ */
+volatile rel_time_t current_time;
+
+/** exported globals **/
+struct stats stats;
+struct settings settings;
+struct slab_rebalance slab_rebal;
+volatile int slab_rebalance_signal;
diff --git a/memcached/hash.c b/memcached/hash.c
new file mode 100644 (file)
index 0000000..6c9b9b6
--- /dev/null
@@ -0,0 +1,437 @@
+/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Hash table
+ *
+ * The hash function used here is by Bob Jenkins, 1996:
+ *    <http://burtleburtle.net/bob/hash/doobs.html>
+ *       "By Bob Jenkins, 1996.  bob_jenkins@burtleburtle.net.
+ *       You may use this code any way you wish, private, educational,
+ *       or commercial.  It's free."
+ *
+ */
+#include "memcached.h"
+
+/*
+ * Since the hash function does bit manipulation, it needs to know
+ * whether it's big or little-endian. ENDIAN_LITTLE and ENDIAN_BIG
+ * are set in the configure script.
+ */
+#if defined(ENDIAN_BIG) && ENDIAN_BIG == 1
+# define HASH_LITTLE_ENDIAN 0
+# define HASH_BIG_ENDIAN 1
+#else
+# if defined(ENDIAN_LITTLE) && ENDIAN_LITTLE == 1
+#  define HASH_LITTLE_ENDIAN 1
+#  define HASH_BIG_ENDIAN 0
+# else
+#  define HASH_LITTLE_ENDIAN 0
+#  define HASH_BIG_ENDIAN 0
+# endif
+#endif
+
+#define rot(x,k) (((x)<<(k)) ^ ((x)>>(32-(k))))
+
+/*
+-------------------------------------------------------------------------------
+mix -- mix 3 32-bit values reversibly.
+
+This is reversible, so any information in (a,b,c) before mix() is
+still in (a,b,c) after mix().
+
+If four pairs of (a,b,c) inputs are run through mix(), or through
+mix() in reverse, there are at least 32 bits of the output that
+are sometimes the same for one pair and different for another pair.
+This was tested for:
+* pairs that differed by one bit, by two bits, in any combination
+  of top bits of (a,b,c), or in any combination of bottom bits of
+  (a,b,c).
+* "differ" is defined as +, -, ^, or ~^.  For + and -, I transformed
+  the output delta to a Gray code (a^(a>>1)) so a string of 1's (as
+  is commonly produced by subtraction) look like a single 1-bit
+  difference.
+* the base values were pseudorandom, all zero but one bit set, or
+  all zero plus a counter that starts at zero.
+
+Some k values for my "a-=c; a^=rot(c,k); c+=b;" arrangement that
+satisfy this are
+    4  6  8 16 19  4
+    9 15  3 18 27 15
+   14  9  3  7 17  3
+Well, "9 15 3 18 27 15" didn't quite get 32 bits diffing
+for "differ" defined as + with a one-bit base and a two-bit delta.  I
+used http://burtleburtle.net/bob/hash/avalanche.html to choose
+the operations, constants, and arrangements of the variables.
+
+This does not achieve avalanche.  There are input bits of (a,b,c)
+that fail to affect some output bits of (a,b,c), especially of a.  The
+most thoroughly mixed value is c, but it doesn't really even achieve
+avalanche in c.
+
+This allows some parallelism.  Read-after-writes are good at doubling
+the number of bits affected, so the goal of mixing pulls in the opposite
+direction as the goal of parallelism.  I did what I could.  Rotates
+seem to cost as much as shifts on every machine I could lay my hands
+on, and rotates are much kinder to the top and bottom bits, so I used
+rotates.
+-------------------------------------------------------------------------------
+*/
+#define mix(a,b,c) \
+{ \
+  a -= c;  a ^= rot(c, 4);  c += b; \
+  b -= a;  b ^= rot(a, 6);  a += c; \
+  c -= b;  c ^= rot(b, 8);  b += a; \
+  a -= c;  a ^= rot(c,16);  c += b; \
+  b -= a;  b ^= rot(a,19);  a += c; \
+  c -= b;  c ^= rot(b, 4);  b += a; \
+}
+
+/*
+-------------------------------------------------------------------------------
+final -- final mixing of 3 32-bit values (a,b,c) into c
+
+Pairs of (a,b,c) values differing in only a few bits will usually
+produce values of c that look totally different.  This was tested for
+* pairs that differed by one bit, by two bits, in any combination
+  of top bits of (a,b,c), or in any combination of bottom bits of
+  (a,b,c).
+* "differ" is defined as +, -, ^, or ~^.  For + and -, I transformed
+  the output delta to a Gray code (a^(a>>1)) so a string of 1's (as
+  is commonly produced by subtraction) look like a single 1-bit
+  difference.
+* the base values were pseudorandom, all zero but one bit set, or
+  all zero plus a counter that starts at zero.
+
+These constants passed:
+ 14 11 25 16 4 14 24
+ 12 14 25 16 4 14 24
+and these came close:
+  4  8 15 26 3 22 24
+ 10  8 15 26 3 22 24
+ 11  8 15 26 3 22 24
+-------------------------------------------------------------------------------
+*/
+#define final(a,b,c) \
+{ \
+  c ^= b; c -= rot(b,14); \
+  a ^= c; a -= rot(c,11); \
+  b ^= a; b -= rot(a,25); \
+  c ^= b; c -= rot(b,16); \
+  a ^= c; a -= rot(c,4);  \
+  b ^= a; b -= rot(a,14); \
+  c ^= b; c -= rot(b,24); \
+}
+
+#if HASH_LITTLE_ENDIAN == 1
+uint32_t hash(
+  const void *key,       /* the key to hash */
+  size_t      length,    /* length of the key */
+  const uint32_t    initval)   /* initval */
+{
+  uint32_t a,b,c;                                          /* internal state */
+  union { const void *ptr; size_t i; } u;     /* needed for Mac Powerbook G4 */
+
+  /* Set up the internal state */
+  a = b = c = 0xdeadbeef + ((uint32_t)length) + initval;
+
+  u.ptr = key;
+  if (HASH_LITTLE_ENDIAN && ((u.i & 0x3) == 0)) {
+    const uint32_t *k = key;                           /* read 32-bit chunks */
+#ifdef VALGRIND
+    const uint8_t  *k8;
+#endif /* ifdef VALGRIND */
+
+    /*------ all but last block: aligned reads and affect 32 bits of (a,b,c) */
+    while (length > 12)
+    {
+      a += k[0];
+      b += k[1];
+      c += k[2];
+      mix(a,b,c);
+      length -= 12;
+      k += 3;
+    }
+
+    /*----------------------------- handle the last (probably partial) block */
+    /*
+     * "k[2]&0xffffff" actually reads beyond the end of the string, but
+     * then masks off the part it's not allowed to read.  Because the
+     * string is aligned, the masked-off tail is in the same word as the
+     * rest of the string.  Every machine with memory protection I've seen
+     * does it on word boundaries, so is OK with this.  But VALGRIND will
+     * still catch it and complain.  The masking trick does make the hash
+     * noticably faster for short strings (like English words).
+     */
+#ifndef VALGRIND
+
+    switch(length)
+    {
+    case 12: c+=k[2]; b+=k[1]; a+=k[0]; break;
+    case 11: c+=k[2]&0xffffff; b+=k[1]; a+=k[0]; break;
+    case 10: c+=k[2]&0xffff; b+=k[1]; a+=k[0]; break;
+    case 9 : c+=k[2]&0xff; b+=k[1]; a+=k[0]; break;
+    case 8 : b+=k[1]; a+=k[0]; break;
+    case 7 : b+=k[1]&0xffffff; a+=k[0]; break;
+    case 6 : b+=k[1]&0xffff; a+=k[0]; break;
+    case 5 : b+=k[1]&0xff; a+=k[0]; break;
+    case 4 : a+=k[0]; break;
+    case 3 : a+=k[0]&0xffffff; break;
+    case 2 : a+=k[0]&0xffff; break;
+    case 1 : a+=k[0]&0xff; break;
+    case 0 : return c;  /* zero length strings require no mixing */
+    default:
+             abort();
+    }
+
+#else /* make valgrind happy */
+
+    k8 = (const uint8_t *)k;
+    switch(length)
+    {
+    case 12: c+=k[2]; b+=k[1]; a+=k[0]; break;
+    case 11: c+=((uint32_t)k8[10])<<16;  /* fall through */
+    case 10: c+=((uint32_t)k8[9])<<8;    /* fall through */
+    case 9 : c+=k8[8];                   /* fall through */
+    case 8 : b+=k[1]; a+=k[0]; break;
+    case 7 : b+=((uint32_t)k8[6])<<16;   /* fall through */
+    case 6 : b+=((uint32_t)k8[5])<<8;    /* fall through */
+    case 5 : b+=k8[4];                   /* fall through */
+    case 4 : a+=k[0]; break;
+    case 3 : a+=((uint32_t)k8[2])<<16;   /* fall through */
+    case 2 : a+=((uint32_t)k8[1])<<8;    /* fall through */
+    case 1 : a+=k8[0]; break;
+    case 0 : return c;  /* zero length strings require no mixing */
+    }
+
+#endif /* !valgrind */
+
+  } else if (HASH_LITTLE_ENDIAN && ((u.i & 0x1) == 0)) {
+    const uint16_t *k = key;                           /* read 16-bit chunks */
+    const uint8_t  *k8;
+
+    /*--------------- all but last block: aligned reads and different mixing */
+    while (length > 12)
+    {
+      a += k[0] + (((uint32_t)k[1])<<16);
+      b += k[2] + (((uint32_t)k[3])<<16);
+      c += k[4] + (((uint32_t)k[5])<<16);
+      mix(a,b,c);
+      length -= 12;
+      k += 6;
+    }
+
+    /*----------------------------- handle the last (probably partial) block */
+    k8 = (const uint8_t *)k;
+    switch(length)
+    {
+    case 12: c+=k[4]+(((uint32_t)k[5])<<16);
+             b+=k[2]+(((uint32_t)k[3])<<16);
+             a+=k[0]+(((uint32_t)k[1])<<16);
+             break;
+    case 11: c+=((uint32_t)k8[10])<<16;     /* @fallthrough */
+    case 10: c+=k[4];                       /* @fallthrough@ */
+             b+=k[2]+(((uint32_t)k[3])<<16);
+             a+=k[0]+(((uint32_t)k[1])<<16);
+             break;
+    case 9 : c+=k8[8];                      /* @fallthrough */
+    case 8 : b+=k[2]+(((uint32_t)k[3])<<16);
+             a+=k[0]+(((uint32_t)k[1])<<16);
+             break;
+    case 7 : b+=((uint32_t)k8[6])<<16;      /* @fallthrough */
+    case 6 : b+=k[2];
+             a+=k[0]+(((uint32_t)k[1])<<16);
+             break;
+    case 5 : b+=k8[4];                      /* @fallthrough */
+    case 4 : a+=k[0]+(((uint32_t)k[1])<<16);
+             break;
+    case 3 : a+=((uint32_t)k8[2])<<16;      /* @fallthrough */
+    case 2 : a+=k[0];
+             break;
+    case 1 : a+=k8[0];
+             break;
+    case 0 : return c;  /* zero length strings require no mixing */
+    default:
+             abort();
+    }
+
+  } else {                        /* need to read the key one byte at a time */
+    const uint8_t *k = key;
+
+    /*--------------- all but the last block: affect some 32 bits of (a,b,c) */
+    while (length > 12)
+    {
+      a += k[0];
+      a += ((uint32_t)k[1])<<8;
+      a += ((uint32_t)k[2])<<16;
+      a += ((uint32_t)k[3])<<24;
+      b += k[4];
+      b += ((uint32_t)k[5])<<8;
+      b += ((uint32_t)k[6])<<16;
+      b += ((uint32_t)k[7])<<24;
+      c += k[8];
+      c += ((uint32_t)k[9])<<8;
+      c += ((uint32_t)k[10])<<16;
+      c += ((uint32_t)k[11])<<24;
+      mix(a,b,c);
+      length -= 12;
+      k += 12;
+    }
+
+    /*-------------------------------- last block: affect all 32 bits of (c) */
+    switch(length)                   /* all the case statements fall through */
+    {
+    case 12: c+=((uint32_t)k[11])<<24;
+    case 11: c+=((uint32_t)k[10])<<16;
+    case 10: c+=((uint32_t)k[9])<<8;
+    case 9 : c+=k[8];
+    case 8 : b+=((uint32_t)k[7])<<24;
+    case 7 : b+=((uint32_t)k[6])<<16;
+    case 6 : b+=((uint32_t)k[5])<<8;
+    case 5 : b+=k[4];
+    case 4 : a+=((uint32_t)k[3])<<24;
+    case 3 : a+=((uint32_t)k[2])<<16;
+    case 2 : a+=((uint32_t)k[1])<<8;
+    case 1 : a+=k[0];
+             break;
+    case 0 : return c;  /* zero length strings require no mixing */
+    default:
+             abort();
+    }
+  }
+
+  final(a,b,c);
+  return c;             /* zero length strings require no mixing */
+}
+
+#elif HASH_BIG_ENDIAN == 1
+/*
+ * hashbig():
+ * This is the same as hashword() on big-endian machines.  It is different
+ * from hashlittle() on all machines.  hashbig() takes advantage of
+ * big-endian byte ordering.
+ */
+uint32_t hash( const void *key, size_t length, const uint32_t initval)
+{
+  uint32_t a,b,c;
+  union { const void *ptr; size_t i; } u; /* to cast key to (size_t) happily */
+
+  /* Set up the internal state */
+  a = b = c = 0xdeadbeef + ((uint32_t)length) + initval;
+
+  u.ptr = key;
+  if (HASH_BIG_ENDIAN && ((u.i & 0x3) == 0)) {
+    const uint32_t *k = key;                           /* read 32-bit chunks */
+#ifdef VALGRIND
+    const uint8_t  *k8;
+#endif /* ifdef VALGRIND */
+
+    /*------ all but last block: aligned reads and affect 32 bits of (a,b,c) */
+    while (length > 12)
+    {
+      a += k[0];
+      b += k[1];
+      c += k[2];
+      mix(a,b,c);
+      length -= 12;
+      k += 3;
+    }
+
+    /*----------------------------- handle the last (probably partial) block */
+    /*
+     * "k[2]<<8" actually reads beyond the end of the string, but
+     * then shifts out the part it's not allowed to read.  Because the
+     * string is aligned, the illegal read is in the same word as the
+     * rest of the string.  Every machine with memory protection I've seen
+     * does it on word boundaries, so is OK with this.  But VALGRIND will
+     * still catch it and complain.  The masking trick does make the hash
+     * noticably faster for short strings (like English words).
+     */
+#ifndef VALGRIND
+
+    switch(length)
+    {
+    case 12: c+=k[2]; b+=k[1]; a+=k[0]; break;
+    case 11: c+=k[2]&0xffffff00; b+=k[1]; a+=k[0]; break;
+    case 10: c+=k[2]&0xffff0000; b+=k[1]; a+=k[0]; break;
+    case 9 : c+=k[2]&0xff000000; b+=k[1]; a+=k[0]; break;
+    case 8 : b+=k[1]; a+=k[0]; break;
+    case 7 : b+=k[1]&0xffffff00; a+=k[0]; break;
+    case 6 : b+=k[1]&0xffff0000; a+=k[0]; break;
+    case 5 : b+=k[1]&0xff000000; a+=k[0]; break;
+    case 4 : a+=k[0]; break;
+    case 3 : a+=k[0]&0xffffff00; break;
+    case 2 : a+=k[0]&0xffff0000; break;
+    case 1 : a+=k[0]&0xff000000; break;
+    case 0 : return c;              /* zero length strings require no mixing */
+    }
+
+#else  /* make valgrind happy */
+
+    k8 = (const uint8_t *)k;
+    switch(length)                   /* all the case statements fall through */
+    {
+    case 12: c+=k[2]; b+=k[1]; a+=k[0]; break;
+    case 11: c+=((uint32_t)k8[10])<<8;  /* fall through */
+    case 10: c+=((uint32_t)k8[9])<<16;  /* fall through */
+    case 9 : c+=((uint32_t)k8[8])<<24;  /* fall through */
+    case 8 : b+=k[1]; a+=k[0]; break;
+    case 7 : b+=((uint32_t)k8[6])<<8;   /* fall through */
+    case 6 : b+=((uint32_t)k8[5])<<16;  /* fall through */
+    case 5 : b+=((uint32_t)k8[4])<<24;  /* fall through */
+    case 4 : a+=k[0]; break;
+    case 3 : a+=((uint32_t)k8[2])<<8;   /* fall through */
+    case 2 : a+=((uint32_t)k8[1])<<16;  /* fall through */
+    case 1 : a+=((uint32_t)k8[0])<<24; break;
+    case 0 : return c;
+    }
+
+#endif /* !VALGRIND */
+
+  } else {                        /* need to read the key one byte at a time */
+    const uint8_t *k = key;
+
+    /*--------------- all but the last block: affect some 32 bits of (a,b,c) */
+    while (length > 12)
+    {
+      a += ((uint32_t)k[0])<<24;
+      a += ((uint32_t)k[1])<<16;
+      a += ((uint32_t)k[2])<<8;
+      a += ((uint32_t)k[3]);
+      b += ((uint32_t)k[4])<<24;
+      b += ((uint32_t)k[5])<<16;
+      b += ((uint32_t)k[6])<<8;
+      b += ((uint32_t)k[7]);
+      c += ((uint32_t)k[8])<<24;
+      c += ((uint32_t)k[9])<<16;
+      c += ((uint32_t)k[10])<<8;
+      c += ((uint32_t)k[11]);
+      mix(a,b,c);
+      length -= 12;
+      k += 12;
+    }
+
+    /*-------------------------------- last block: affect all 32 bits of (c) */
+    switch(length)                   /* all the case statements fall through */
+    {
+    case 12: c+=k[11];
+    case 11: c+=((uint32_t)k[10])<<8;
+    case 10: c+=((uint32_t)k[9])<<16;
+    case 9 : c+=((uint32_t)k[8])<<24;
+    case 8 : b+=k[7];
+    case 7 : b+=((uint32_t)k[6])<<8;
+    case 6 : b+=((uint32_t)k[5])<<16;
+    case 5 : b+=((uint32_t)k[4])<<24;
+    case 4 : a+=k[3];
+    case 3 : a+=((uint32_t)k[2])<<8;
+    case 2 : a+=((uint32_t)k[1])<<16;
+    case 1 : a+=((uint32_t)k[0])<<24;
+             break;
+    case 0 : return c;
+    }
+  }
+
+  final(a,b,c);
+  return c;
+}
+#else /* HASH_XXX_ENDIAN == 1 */
+#error Must define HASH_BIG_ENDIAN or HASH_LITTLE_ENDIAN
+#endif /* HASH_XXX_ENDIAN == 1 */
diff --git a/memcached/hash.h b/memcached/hash.h
new file mode 100644 (file)
index 0000000..aa02c58
--- /dev/null
@@ -0,0 +1,15 @@
+#ifndef HASH_H
+#define    HASH_H
+
+#ifdef    __cplusplus
+extern "C" {
+#endif
+
+uint32_t hash(const void *key, size_t length, const uint32_t initval);
+
+#ifdef    __cplusplus
+}
+#endif
+
+#endif    /* HASH_H */
+
diff --git a/memcached/include.am b/memcached/include.am
new file mode 100644 (file)
index 0000000..c1d6325
--- /dev/null
@@ -0,0 +1,40 @@
+# vim:ft=automake
+
+noinst_HEADERS+= memcached/assoc.h
+noinst_HEADERS+= memcached/cache.h 
+noinst_HEADERS+= memcached/hash.h
+noinst_HEADERS+= memcached/items.h
+noinst_HEADERS+= memcached/memcached.h
+noinst_HEADERS+= memcached/protocol_binary.h
+noinst_HEADERS+= memcached/sasl_defs.h
+noinst_HEADERS+= memcached/slabs.h
+noinst_HEADERS+= memcached/stats.h
+noinst_HEADERS+= memcached/trace.h
+noinst_HEADERS+= memcached/util.h
+
+memcached_memcached_SOURCES=
+memcached_memcached_CFLAGS=
+memcached_memcached_LDADD=
+
+memcached_memcached_SOURCES+=
+memcached_memcached_SOURCES+= memcached/assoc.c 
+memcached_memcached_SOURCES+= memcached/cache.c 
+memcached_memcached_SOURCES+= memcached/daemon.c
+memcached_memcached_SOURCES+= memcached/hash.c
+memcached_memcached_SOURCES+= memcached/items.c
+memcached_memcached_SOURCES+= memcached/memcached.c
+memcached_memcached_SOURCES+= memcached/slabs.c
+memcached_memcached_SOURCES+= memcached/stats.c 
+memcached_memcached_SOURCES+= memcached/thread.c 
+memcached_memcached_SOURCES+= memcached/trace.h 
+memcached_memcached_SOURCES+= memcached/util.c 
+
+memcached_memcached_CFLAGS+= -DHAVE_CONFIG_H -std=c99
+memcached_memcached_CFLAGS+= ${PTHREAD_CFLAGS}
+memcached_memcached_CFLAGS+= -DRMEMCACHED_VERSION="\"1.4.13\""
+memcached_memcached_CFLAGS+= -DRMEMCACHED_PACKAGE="\"memcached\""
+
+memcached_memcached_LDADD+= $(LIBEVENT_LDFLAGS)
+memcached_memcached_LDADD+= ${PTHREAD_LIBS}
+
+noinst_PROGRAMS+= memcached/memcached
diff --git a/memcached/items.c b/memcached/items.c
new file mode 100644 (file)
index 0000000..58fc871
--- /dev/null
@@ -0,0 +1,575 @@
+/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+#include "memcached.h"
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/signal.h>
+#include <sys/resource.h>
+#include <fcntl.h>
+#include <netinet/in.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <assert.h>
+
+/* Forward Declarations */
+static void item_link_q(item *it);
+static void item_unlink_q(item *it);
+
+/*
+ * We only reposition items in the LRU queue if they haven't been repositioned
+ * in this many seconds. That saves us from churning on frequently-accessed
+ * items.
+ */
+#define ITEM_UPDATE_INTERVAL 60
+
+#define LARGEST_ID POWER_LARGEST
+typedef struct {
+    uint64_t evicted;
+    uint64_t evicted_nonzero;
+    rel_time_t evicted_time;
+    uint64_t reclaimed;
+    uint64_t outofmemory;
+    uint64_t tailrepairs;
+    uint64_t expired_unfetched;
+    uint64_t evicted_unfetched;
+} itemstats_t;
+
+static item *heads[LARGEST_ID];
+static item *tails[LARGEST_ID];
+static itemstats_t itemstats[LARGEST_ID];
+static unsigned int sizes[LARGEST_ID];
+
+void item_stats_reset(void) {
+    mutex_lock(&cache_lock);
+    memset(itemstats, 0, sizeof(itemstats));
+    pthread_mutex_unlock(&cache_lock);
+}
+
+
+/* Get the next CAS id for a new item. */
+uint64_t get_cas_id(void) {
+    static uint64_t cas_id = 0;
+    return ++cas_id;
+}
+
+/* Enable this for reference-count debugging. */
+#if 0
+# define DEBUG_REFCNT(it,op) \
+                fprintf(stderr, "item %x refcnt(%c) %d %c%c%c\n", \
+                        it, op, it->refcount, \
+                        (it->it_flags & ITEM_LINKED) ? 'L' : ' ', \
+                        (it->it_flags & ITEM_SLABBED) ? 'S' : ' ')
+#else
+# define DEBUG_REFCNT(it,op) while(0)
+#endif
+
+/**
+ * Generates the variable-sized part of the header for an object.
+ *
+ * key     - The key
+ * nkey    - The length of the key
+ * flags   - key flags
+ * nbytes  - Number of bytes to hold value and addition CRLF terminator
+ * suffix  - Buffer for the "VALUE" line suffix (flags, size).
+ * nsuffix - The length of the suffix is stored here.
+ *
+ * Returns the total size of the header.
+ */
+static size_t item_make_header(const uint8_t nkey, const int flags, const int nbytes,
+                     char *suffix, uint8_t *nsuffix) {
+    /* suffix is defined at 40 chars elsewhere.. */
+    *nsuffix = (uint8_t) snprintf(suffix, 40, " %d %d\r\n", flags, nbytes - 2);
+    return sizeof(item) + nkey + *nsuffix + nbytes;
+}
+
+/*@null@*/
+item *do_item_alloc(char *key, const size_t nkey, const int flags, const rel_time_t exptime, const int nbytes) {
+    uint8_t nsuffix;
+    item *it = NULL;
+    char suffix[40];
+    size_t ntotal = item_make_header(nkey + 1, flags, nbytes, suffix, &nsuffix);
+    if (settings.use_cas) {
+        ntotal += sizeof(uint64_t);
+    }
+
+    unsigned int id = slabs_clsid(ntotal);
+    if (id == 0)
+        return 0;
+
+    mutex_lock(&cache_lock);
+    /* do a quick check if we have any expired items in the tail.. */
+    item *search;
+    rel_time_t oldest_live = settings.oldest_live;
+
+    search = tails[id];
+    if (search != NULL && (refcount_incr(&search->refcount) == 2)) {
+        if ((search->exptime != 0 && search->exptime < current_time)
+            || (search->time <= oldest_live && oldest_live <= current_time)) {  // dead by flush
+            STATS_LOCK();
+            stats.reclaimed++;
+            STATS_UNLOCK();
+            itemstats[id].reclaimed++;
+            if ((search->it_flags & ITEM_FETCHED) == 0) {
+                STATS_LOCK();
+                stats.expired_unfetched++;
+                STATS_UNLOCK();
+                itemstats[id].expired_unfetched++;
+            }
+            it = search;
+            slabs_adjust_mem_requested(it->slabs_clsid, ITEM_ntotal(it), ntotal);
+            do_item_unlink_nolock(it, hash(ITEM_key(it), it->nkey, 0));
+            /* Initialize the item block: */
+            it->slabs_clsid = 0;
+        } else if ((it = slabs_alloc(ntotal, id)) == NULL) {
+            if (settings.evict_to_free == 0) {
+                itemstats[id].outofmemory++;
+                pthread_mutex_unlock(&cache_lock);
+                return NULL;
+            }
+            itemstats[id].evicted++;
+            itemstats[id].evicted_time = current_time - search->time;
+            if (search->exptime != 0)
+                itemstats[id].evicted_nonzero++;
+            if ((search->it_flags & ITEM_FETCHED) == 0) {
+                STATS_LOCK();
+                stats.evicted_unfetched++;
+                STATS_UNLOCK();
+                itemstats[id].evicted_unfetched++;
+            }
+            STATS_LOCK();
+            stats.evictions++;
+            STATS_UNLOCK();
+            it = search;
+            slabs_adjust_mem_requested(it->slabs_clsid, ITEM_ntotal(it), ntotal);
+            do_item_unlink_nolock(it, hash(ITEM_key(it), it->nkey, 0));
+            /* Initialize the item block: */
+            it->slabs_clsid = 0;
+        } else {
+            refcount_decr(&search->refcount);
+        }
+    } else {
+        /* If the LRU is empty or locked, attempt to allocate memory */
+        it = slabs_alloc(ntotal, id);
+        if (search != NULL)
+            refcount_decr(&search->refcount);
+    }
+
+    if (it == NULL) {
+        itemstats[id].outofmemory++;
+        /* Last ditch effort. There was a very rare bug which caused
+         * refcount leaks. We leave this just in case they ever happen again.
+         * We can reasonably assume no item can stay locked for more than
+         * three hours, so if we find one in the tail which is that old,
+         * free it anyway.
+         */
+        if (search != NULL &&
+            search->refcount != 2 &&
+            search->time + TAIL_REPAIR_TIME < current_time) {
+            itemstats[id].tailrepairs++;
+            search->refcount = 1;
+            do_item_unlink_nolock(search, hash(ITEM_key(search), search->nkey, 0));
+        }
+        pthread_mutex_unlock(&cache_lock);
+        return NULL;
+    }
+
+    assert(it->slabs_clsid == 0);
+    assert(it != heads[id]);
+
+    /* Item initialization can happen outside of the lock; the item's already
+     * been removed from the slab LRU.
+     */
+    it->refcount = 1;     /* the caller will have a reference */
+    pthread_mutex_unlock(&cache_lock);
+    it->next = it->prev = it->h_next = 0;
+    it->slabs_clsid = id;
+
+    DEBUG_REFCNT(it, '*');
+    it->it_flags = settings.use_cas ? ITEM_CAS : 0;
+    it->nkey = nkey;
+    it->nbytes = nbytes;
+    memcpy(ITEM_key(it), key, nkey);
+    it->exptime = exptime;
+    memcpy(ITEM_suffix(it), suffix, (size_t)nsuffix);
+    it->nsuffix = nsuffix;
+    return it;
+}
+
+void item_free(item *it) {
+    size_t ntotal = ITEM_ntotal(it);
+    unsigned int clsid;
+    assert((it->it_flags & ITEM_LINKED) == 0);
+    assert(it != heads[it->slabs_clsid]);
+    assert(it != tails[it->slabs_clsid]);
+    assert(it->refcount == 0);
+
+    /* so slab size changer can tell later if item is already free or not */
+    clsid = it->slabs_clsid;
+    it->slabs_clsid = 0;
+    DEBUG_REFCNT(it, 'F');
+    slabs_free(it, ntotal, clsid);
+}
+
+/**
+ * Returns true if an item will fit in the cache (its size does not exceed
+ * the maximum for a cache entry.)
+ */
+bool item_size_ok(const size_t nkey, const int flags, const int nbytes) {
+    char prefix[40];
+    uint8_t nsuffix;
+
+    size_t ntotal = item_make_header(nkey + 1, flags, nbytes,
+                                     prefix, &nsuffix);
+    if (settings.use_cas) {
+        ntotal += sizeof(uint64_t);
+    }
+
+    return slabs_clsid(ntotal) != 0;
+}
+
+static void item_link_q(item *it) { /* item is the new head */
+    item **head, **tail;
+    assert(it->slabs_clsid < LARGEST_ID);
+    assert((it->it_flags & ITEM_SLABBED) == 0);
+
+    head = &heads[it->slabs_clsid];
+    tail = &tails[it->slabs_clsid];
+    assert(it != *head);
+    assert((*head && *tail) || (*head == 0 && *tail == 0));
+    it->prev = 0;
+    it->next = *head;
+    if (it->next) it->next->prev = it;
+    *head = it;
+    if (*tail == 0) *tail = it;
+    sizes[it->slabs_clsid]++;
+    return;
+}
+
+static void item_unlink_q(item *it) {
+    item **head, **tail;
+    assert(it->slabs_clsid < LARGEST_ID);
+    head = &heads[it->slabs_clsid];
+    tail = &tails[it->slabs_clsid];
+
+    if (*head == it) {
+        assert(it->prev == 0);
+        *head = it->next;
+    }
+    if (*tail == it) {
+        assert(it->next == 0);
+        *tail = it->prev;
+    }
+    assert(it->next != it);
+    assert(it->prev != it);
+
+    if (it->next) it->next->prev = it->prev;
+    if (it->prev) it->prev->next = it->next;
+    sizes[it->slabs_clsid]--;
+    return;
+}
+
+int do_item_link(item *it, const uint32_t hv) {
+    MEMCACHED_ITEM_LINK(ITEM_key(it), it->nkey, it->nbytes);
+    assert((it->it_flags & (ITEM_LINKED|ITEM_SLABBED)) == 0);
+    mutex_lock(&cache_lock);
+    it->it_flags |= ITEM_LINKED;
+    it->time = current_time;
+
+    STATS_LOCK();
+    stats.curr_bytes += ITEM_ntotal(it);
+    stats.curr_items += 1;
+    stats.total_items += 1;
+    STATS_UNLOCK();
+
+    /* Allocate a new CAS ID on link. */
+    ITEM_set_cas(it, (settings.use_cas) ? get_cas_id() : 0);
+    assoc_insert(it, hv);
+    item_link_q(it);
+    refcount_incr(&it->refcount);
+    pthread_mutex_unlock(&cache_lock);
+
+    return 1;
+}
+
+void do_item_unlink(item *it, const uint32_t hv) {
+    MEMCACHED_ITEM_UNLINK(ITEM_key(it), it->nkey, it->nbytes);
+    mutex_lock(&cache_lock);
+    if ((it->it_flags & ITEM_LINKED) != 0) {
+        it->it_flags &= ~ITEM_LINKED;
+        STATS_LOCK();
+        stats.curr_bytes -= ITEM_ntotal(it);
+        stats.curr_items -= 1;
+        STATS_UNLOCK();
+        assoc_delete(ITEM_key(it), it->nkey, hv);
+        item_unlink_q(it);
+        do_item_remove(it);
+    }
+    pthread_mutex_unlock(&cache_lock);
+}
+
+/* FIXME: Is it necessary to keep this copy/pasted code? */
+void do_item_unlink_nolock(item *it, const uint32_t hv) {
+    MEMCACHED_ITEM_UNLINK(ITEM_key(it), it->nkey, it->nbytes);
+    if ((it->it_flags & ITEM_LINKED) != 0) {
+        it->it_flags &= ~ITEM_LINKED;
+        STATS_LOCK();
+        stats.curr_bytes -= ITEM_ntotal(it);
+        stats.curr_items -= 1;
+        STATS_UNLOCK();
+        assoc_delete(ITEM_key(it), it->nkey, hv);
+        item_unlink_q(it);
+        do_item_remove(it);
+    }
+}
+
+void do_item_remove(item *it) {
+    MEMCACHED_ITEM_REMOVE(ITEM_key(it), it->nkey, it->nbytes);
+    assert((it->it_flags & ITEM_SLABBED) == 0);
+
+    if (refcount_decr(&it->refcount) == 0) {
+        item_free(it);
+    }
+}
+
+void do_item_update(item *it) {
+    MEMCACHED_ITEM_UPDATE(ITEM_key(it), it->nkey, it->nbytes);
+    if (it->time < current_time - ITEM_UPDATE_INTERVAL) {
+        assert((it->it_flags & ITEM_SLABBED) == 0);
+
+        mutex_lock(&cache_lock);
+        if ((it->it_flags & ITEM_LINKED) != 0) {
+            item_unlink_q(it);
+            it->time = current_time;
+            item_link_q(it);
+        }
+        pthread_mutex_unlock(&cache_lock);
+    }
+}
+
+int do_item_replace(item *it, item *new_it, const uint32_t hv) {
+    MEMCACHED_ITEM_REPLACE(ITEM_key(it), it->nkey, it->nbytes,
+                           ITEM_key(new_it), new_it->nkey, new_it->nbytes);
+    assert((it->it_flags & ITEM_SLABBED) == 0);
+
+    do_item_unlink(it, hv);
+    return do_item_link(new_it, hv);
+}
+
+#ifndef __INTEL_COMPILER
+#pragma GCC diagnostic ignored "-Wshadow"
+#endif
+
+/*@null@*/
+char *do_item_cachedump(const unsigned int slabs_clsid, const unsigned int limit, unsigned int *bytes) {
+    unsigned int memlimit = 2 * 1024 * 1024;   /* 2MB max response size */
+    char *buffer;
+    unsigned int bufcurr;
+    item *it;
+    unsigned int len;
+    unsigned int shown = 0;
+    char key_temp[KEY_MAX_LENGTH + 1];
+    char temp[512];
+
+    it = heads[slabs_clsid];
+
+    buffer = malloc((size_t)memlimit);
+    if (buffer == 0) return NULL;
+    bufcurr = 0;
+
+    while (it != NULL && (limit == 0 || shown < limit)) {
+        assert(it->nkey <= KEY_MAX_LENGTH);
+        /* Copy the key since it may not be null-terminated in the struct */
+        strncpy(key_temp, ITEM_key(it), it->nkey);
+        key_temp[it->nkey] = 0x00; /* terminate */
+        len = snprintf(temp, sizeof(temp), "ITEM %s [%d b; %lu s]\r\n",
+                       key_temp, it->nbytes - 2,
+                       (unsigned long)it->exptime + process_started);
+        if (bufcurr + len + 6 > memlimit)  /* 6 is END\r\n\0 */
+            break;
+        memcpy(buffer + bufcurr, temp, len);
+        bufcurr += len;
+        shown++;
+        it = it->next;
+    }
+
+    memcpy(buffer + bufcurr, "END\r\n", 6);
+    bufcurr += 5;
+
+    *bytes = bufcurr;
+    return buffer;
+}
+
+void item_stats_evictions(uint64_t *evicted) {
+    int i;
+    mutex_lock(&cache_lock);
+    for (i = 0; i < LARGEST_ID; i++) {
+        evicted[i] = itemstats[i].evicted;
+    }
+    pthread_mutex_unlock(&cache_lock);
+}
+
+void do_item_stats(ADD_STAT add_stats, void *c) {
+    int i;
+    for (i = 0; i < LARGEST_ID; i++) {
+        if (tails[i] != NULL) {
+            const char *fmt = "items:%d:%s";
+            char key_str[STAT_KEY_LEN];
+            char val_str[STAT_VAL_LEN];
+            int klen = 0, vlen = 0;
+            if (tails[i] == NULL) {
+                /* We removed all of the items in this slab class */
+                continue;
+            }
+            APPEND_NUM_FMT_STAT(fmt, i, "number", "%u", sizes[i]);
+            APPEND_NUM_FMT_STAT(fmt, i, "age", "%u", current_time - tails[i]->time);
+            APPEND_NUM_FMT_STAT(fmt, i, "evicted",
+                                "%llu", (unsigned long long)itemstats[i].evicted);
+            APPEND_NUM_FMT_STAT(fmt, i, "evicted_nonzero",
+                                "%llu", (unsigned long long)itemstats[i].evicted_nonzero);
+            APPEND_NUM_FMT_STAT(fmt, i, "evicted_time",
+                                "%u", itemstats[i].evicted_time);
+            APPEND_NUM_FMT_STAT(fmt, i, "outofmemory",
+                                "%llu", (unsigned long long)itemstats[i].outofmemory);
+            APPEND_NUM_FMT_STAT(fmt, i, "tailrepairs",
+                                "%llu", (unsigned long long)itemstats[i].tailrepairs);
+            APPEND_NUM_FMT_STAT(fmt, i, "reclaimed",
+                                "%llu", (unsigned long long)itemstats[i].reclaimed);
+            APPEND_NUM_FMT_STAT(fmt, i, "expired_unfetched",
+                                "%llu", (unsigned long long)itemstats[i].expired_unfetched);
+            APPEND_NUM_FMT_STAT(fmt, i, "evicted_unfetched",
+                                "%llu", (unsigned long long)itemstats[i].evicted_unfetched);
+        }
+    }
+
+    /* getting here means both ascii and binary terminators fit */
+    add_stats(NULL, 0, NULL, 0, c);
+}
+
+/** dumps out a list of objects of each size, with granularity of 32 bytes */
+/*@null@*/
+void do_item_stats_sizes(ADD_STAT add_stats, void *c) {
+
+    /* max 1MB object, divided into 32 bytes size buckets */
+    const int num_buckets = 32768;
+    unsigned int *histogram = calloc(num_buckets, sizeof(int));
+
+    if (histogram != NULL) {
+        int i;
+
+        /* build the histogram */
+        for (i = 0; i < LARGEST_ID; i++) {
+            item *iter = heads[i];
+            while (iter) {
+                int ntotal = ITEM_ntotal(iter);
+                int bucket = ntotal / 32;
+                if ((ntotal % 32) != 0) bucket++;
+                if (bucket < num_buckets) histogram[bucket]++;
+                iter = iter->next;
+            }
+        }
+
+        /* write the buffer */
+        for (i = 0; i < num_buckets; i++) {
+            if (histogram[i] != 0) {
+                char key[8];
+                snprintf(key, sizeof(key), "%d", i * 32);
+                APPEND_STAT(key, "%u", histogram[i]);
+            }
+        }
+        free(histogram);
+    }
+    add_stats(NULL, 0, NULL, 0, c);
+}
+
+/** wrapper around assoc_find which does the lazy expiration logic */
+item *do_item_get(const char *key, const size_t nkey, const uint32_t hv) {
+    mutex_lock(&cache_lock);
+    item *it = assoc_find(key, nkey, hv);
+    if (it != NULL) {
+        refcount_incr(&it->refcount);
+        /* Optimization for slab reassignment. prevents popular items from
+         * jamming in busy wait. Can only do this here to satisfy lock order
+         * of item_lock, cache_lock, slabs_lock. */
+        if (slab_rebalance_signal &&
+            ((void *)it >= slab_rebal.slab_start && (void *)it < slab_rebal.slab_end)) {
+            do_item_unlink_nolock(it, hv);
+            do_item_remove(it);
+            it = NULL;
+        }
+    }
+    pthread_mutex_unlock(&cache_lock);
+    int was_found = 0;
+
+    if (settings.verbose > 2) {
+        if (it == NULL) {
+            fprintf(stderr, "> NOT FOUND %s", key);
+        } else {
+            fprintf(stderr, "> FOUND KEY %s", ITEM_key(it));
+            was_found++;
+        }
+    }
+
+    if (it != NULL) {
+        if (settings.oldest_live != 0 && settings.oldest_live <= current_time &&
+            it->time <= settings.oldest_live) {
+            do_item_unlink(it, hv);
+            do_item_remove(it);
+            it = NULL;
+            if (was_found) {
+                fprintf(stderr, " -nuked by flush");
+            }
+        } else if (it->exptime != 0 && it->exptime <= current_time) {
+            do_item_unlink(it, hv);
+            do_item_remove(it);
+            it = NULL;
+            if (was_found) {
+                fprintf(stderr, " -nuked by expire");
+            }
+        } else {
+            it->it_flags |= ITEM_FETCHED;
+            DEBUG_REFCNT(it, '+');
+        }
+    }
+
+    if (settings.verbose > 2)
+        fprintf(stderr, "\n");
+
+    return it;
+}
+
+item *do_item_touch(const char *key, size_t nkey, uint32_t exptime,
+                    const uint32_t hv) {
+    item *it = do_item_get(key, nkey, hv);
+    if (it != NULL) {
+        it->exptime = exptime;
+    }
+    return it;
+}
+
+/* expires items that are more recent than the oldest_live setting. */
+void do_item_flush_expired(void) {
+    int i;
+    item *iter, *next;
+    if (settings.oldest_live == 0)
+        return;
+    for (i = 0; i < LARGEST_ID; i++) {
+        /* The LRU is sorted in decreasing time order, and an item's timestamp
+         * is never newer than its last access time, so we only need to walk
+         * back until we hit an item older than the oldest_live time.
+         * The oldest_live checking will auto-expire the remaining items.
+         */
+        for (iter = heads[i]; iter != NULL; iter = next) {
+            if (iter->time >= settings.oldest_live) {
+                next = iter->next;
+                if ((iter->it_flags & ITEM_SLABBED) == 0) {
+                    do_item_unlink_nolock(iter, hash(ITEM_key(iter), iter->nkey, 0));
+                }
+            } else {
+                /* We've hit the first old item. Continue to the next queue. */
+                break;
+            }
+        }
+    }
+}
diff --git a/memcached/items.h b/memcached/items.h
new file mode 100644 (file)
index 0000000..2ec142d
--- /dev/null
@@ -0,0 +1,27 @@
+/* See items.c */
+uint64_t get_cas_id(void);
+
+/*@null@*/
+item *do_item_alloc(char *key, const size_t nkey, const int flags, const rel_time_t exptime, const int nbytes);
+void item_free(item *it);
+bool item_size_ok(const size_t nkey, const int flags, const int nbytes);
+
+int  do_item_link(item *it, const uint32_t hv);     /** may fail if transgresses limits */
+void do_item_unlink(item *it, const uint32_t hv);
+void do_item_unlink_nolock(item *it, const uint32_t hv);
+void do_item_remove(item *it);
+void do_item_update(item *it);   /** update LRU time to current and reposition */
+int  do_item_replace(item *it, item *new_it, const uint32_t hv);
+
+/*@null@*/
+char *do_item_cachedump(const unsigned int slabs_clsid, const unsigned int limit, unsigned int *bytes);
+void do_item_stats(ADD_STAT add_stats, void *c);
+/*@null@*/
+void do_item_stats_sizes(ADD_STAT add_stats, void *c);
+void do_item_flush_expired(void);
+
+item *do_item_get(const char *key, const size_t nkey, const uint32_t hv);
+item *do_item_touch(const char *key, const size_t nkey, uint32_t exptime, const uint32_t hv);
+void item_stats_reset(void);
+extern pthread_mutex_t cache_lock;
+void item_stats_evictions(uint64_t *evicted);
diff --git a/memcached/m4/c99-backport.m4 b/memcached/m4/c99-backport.m4
new file mode 100644 (file)
index 0000000..0812584
--- /dev/null
@@ -0,0 +1,188 @@
+# AC_PROG_CC_C99 ([ACTION-IF-AVAILABLE], [ACTION-IF-UNAVAILABLE])
+# ----------------------------------------------------------------
+# If the C compiler is not in ISO C99 mode by default, try to add an
+# option to output variable CC to make it so.  This macro tries
+# various options that select ISO C99 on some system or another.  It
+# considers the compiler to be in ISO C99 mode if it handles _Bool,
+# // comments, flexible array members, inline, long long int, mixed
+# code and declarations, named initialization of structs, restrict,
+# va_copy, varargs macros, variable declarations in for loops and
+# variable length arrays.
+AC_DEFUN([AC_PROG_CC_C99],
+[AC_C_STD_TRY([c99],
+[[#include <stdarg.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <wchar.h>
+#include <stdio.h>
+
+// Check varargs macros.  These examples are taken from C99 6.10.3.5.
+#define debug(...) fprintf (stderr, __VA_ARGS__)
+#define showlist(...) puts (#__VA_ARGS__)
+#define report(test,...) ((test) ? puts (#test) : printf (__VA_ARGS__))
+static void
+test_varargs_macros (void)
+{
+  int x = 1234;
+  int y = 5678;
+  debug ("Flag");
+  debug ("X = %d\n", x);
+  showlist (The first, second, and third items.);
+  report (x>y, "x is %d but y is %d", x, y);
+}
+
+// Check long long types.
+#define BIG64 18446744073709551615ull
+#define BIG32 4294967295ul
+#define BIG_OK (BIG64 / BIG32 == 4294967297ull && BIG64 % BIG32 == 0)
+#if !BIG_OK
+  your preprocessor is broken;
+#endif
+#if BIG_OK
+#else
+  your preprocessor is broken;
+#endif
+static long long int bignum = -9223372036854775807LL;
+static unsigned long long int ubignum = BIG64;
+
+struct incomplete_array
+{
+  int datasize;
+  double data[];
+};
+
+struct named_init {
+  int number;
+  const wchar_t *name;
+  double average;
+};
+
+typedef const char *ccp;
+
+static inline int
+test_restrict (ccp restrict text)
+{
+  // See if C++-style comments work.
+  // Iterate through items via the restricted pointer.
+  // Also check for declarations in for loops.
+  for (unsigned int i = 0; *(text+i) != '\0'; ++i)
+    continue;
+  return 0;
+}
+
+// Check varargs and va_copy.
+static void
+test_varargs (const char *format, ...)
+{
+  va_list args;
+  va_start (args, format);
+  va_list args_copy;
+  va_copy (args_copy, args);
+
+  const char *str;
+  int number;
+  float fnumber;
+
+  while (*format)
+    {
+      switch (*format++)
+       {
+       case 's': // string
+         str = va_arg (args_copy, const char *);
+         break;
+       case 'd': // int
+         number = va_arg (args_copy, int);
+         break;
+       case 'f': // float
+         fnumber = va_arg (args_copy, double);
+         break;
+       default:
+         break;
+       }
+    }
+  va_end (args_copy);
+  va_end (args);
+}
+]],
+[[
+  // Check bool.
+  _Bool success = false;
+
+  // Check restrict.
+  if (test_restrict ("String literal") == 0)
+    success = true;
+  char *restrict newvar = "Another string";
+
+  // Check varargs.
+  test_varargs ("s, d' f .", "string", 65, 34.234);
+  test_varargs_macros ();
+
+  // Check flexible array members.
+  struct incomplete_array *ia =
+    malloc (sizeof (struct incomplete_array) + (sizeof (double) * 10));
+  ia->datasize = 10;
+  for (int i = 0; i < ia->datasize; ++i)
+    ia->data[i] = i * 1.234;
+
+  // Check named initializers.
+  struct named_init ni = {
+    .number = 34,
+    .name = L"Test wide string",
+    .average = 543.34343,
+  };
+
+  ni.number = 58;
+
+  int dynamic_array[ni.number];
+  dynamic_array[ni.number - 1] = 543;
+
+  // work around unused variable warnings
+  return (!success || bignum == 0LL || ubignum == 0uLL || newvar[0] == 'x'
+         || dynamic_array[ni.number - 1] != 543);
+]],
+dnl Try
+dnl GCC                -std=gnu99 (unused restrictive modes: -std=c99 -std=iso9899:1999)
+dnl AIX                -qlanglvl=extc99 (unused restrictive mode: -qlanglvl=stdc99)
+dnl Intel ICC  -c99
+dnl IRIX       -c99
+dnl Solaris    (unused because it causes the compiler to assume C99 semantics for
+dnl            library functions, and this is invalid before Solaris 10: -xc99)
+dnl Tru64      -c99
+dnl with extended modes being tried first.
+[[-std=gnu99 -c99 -qlanglvl=extc99]], [$1], [$2])[]dnl
+])# AC_PROG_CC_C99
+
+# AC_C_STD_TRY(STANDARD, TEST-PROLOGUE, TEST-BODY, OPTION-LIST,
+#              ACTION-IF-AVAILABLE, ACTION-IF-UNAVAILABLE)
+# --------------------------------------------------------------
+# Check whether the C compiler accepts features of STANDARD (e.g `c89', `c99')
+# by trying to compile a program of TEST-PROLOGUE and TEST-BODY.  If this fails,
+# try again with each compiler option in the space-separated OPTION-LIST; if one
+# helps, append it to CC.  If eventually successful, run ACTION-IF-AVAILABLE,
+# else ACTION-IF-UNAVAILABLE.
+AC_DEFUN([AC_C_STD_TRY],
+[AC_MSG_CHECKING([for $CC option to accept ISO ]m4_translit($1, [c], [C]))
+AC_CACHE_VAL(ac_cv_prog_cc_$1,
+[ac_cv_prog_cc_$1=no
+ac_save_CC=$CC
+AC_LANG_CONFTEST([AC_LANG_PROGRAM([$2], [$3])])
+for ac_arg in '' $4
+do
+  CC="$ac_save_CC $ac_arg"
+  _AC_COMPILE_IFELSE([], [ac_cv_prog_cc_$1=$ac_arg])
+  test "x$ac_cv_prog_cc_$1" != "xno" && break
+done
+rm -f conftest.$ac_ext
+CC=$ac_save_CC
+])# AC_CACHE_VAL
+case "x$ac_cv_prog_cc_$1" in
+  x)
+    AC_MSG_RESULT([none needed]) ;;
+  xno)
+    AC_MSG_RESULT([unsupported]) ;;
+  *)
+    CC="$CC $ac_cv_prog_cc_$1"
+    AC_MSG_RESULT([$ac_cv_prog_cc_$1]) ;;
+esac
+AS_IF([test "x$ac_cv_prog_cc_$1" != xno], [$5], [$6])
+])# AC_C_STD_TRY
diff --git a/memcached/memcached.c b/memcached/memcached.c
new file mode 100644 (file)
index 0000000..7d053cc
--- /dev/null
@@ -0,0 +1,5292 @@
+/* -*- 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>
+ */
+#include "memcached.h"
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <signal.h>
+#include <sys/resource.h>
+#include <sys/uio.h>
+#include <ctype.h>
+#include <stdarg.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
+/* need this to get IOV_MAX on some platforms. */
+#ifndef __need_IOV_MAX
+#define __need_IOV_MAX
+#endif
+#include <pwd.h>
+#include <sys/mman.h>
+#include <fcntl.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <assert.h>
+#include <limits.h>
+#include <sysexits.h>
+#include <stddef.h>
+
+/* FreeBSD 4.x doesn't have IOV_MAX exposed. */
+#ifndef IOV_MAX
+#if defined(__FreeBSD__) || defined(__APPLE__)
+# define IOV_MAX 1024
+#endif
+#endif
+
+/*
+ * forward declarations
+ */
+static void drive_machine(conn *c);
+static int new_socket(struct addrinfo *ai);
+static int try_read_command(conn *c);
+
+enum try_read_result {
+    READ_DATA_RECEIVED,
+    READ_NO_DATA_RECEIVED,
+    READ_ERROR,            /** an error occured (on the socket) (or client closed connection) */
+    READ_MEMORY_ERROR      /** failed to allocate more memory */
+};
+
+static enum try_read_result try_read_network(conn *c);
+static enum try_read_result try_read_udp(conn *c);
+
+static void conn_set_state(conn *c, enum conn_states state);
+
+/* stats */
+static void stats_init(void);
+static void server_stats(ADD_STAT add_stats, conn *c);
+static void process_stat_settings(ADD_STAT add_stats, void *c);
+
+
+/* defaults */
+static void settings_init(void);
+
+/* event handling, network IO */
+static void event_handler(const int fd, const short which, void *arg);
+static void conn_close(conn *c);
+static void conn_init(void);
+static bool update_event(conn *c, const int new_flags);
+static void complete_nread(conn *c);
+static void process_command(conn *c, char *command);
+static void write_and_free(conn *c, char *buf, int bytes);
+static int ensure_iov_space(conn *c);
+static int add_iov(conn *c, const void *buf, int len);
+static int add_msghdr(conn *c);
+
+
+static void conn_free(conn *c);
+
+/** exported globals **/
+struct stats stats;
+struct settings settings;
+time_t process_started;     /* when the process was started */
+
+struct slab_rebalance slab_rebal;
+volatile int slab_rebalance_signal;
+
+/** file scope variables **/
+static conn *listen_conn = NULL;
+static struct event_base *main_base;
+
+enum transmit_result {
+    TRANSMIT_COMPLETE,   /** All done writing. */
+    TRANSMIT_INCOMPLETE, /** More data remaining to write. */
+    TRANSMIT_SOFT_ERROR, /** Can't write any more right now. */
+    TRANSMIT_HARD_ERROR  /** Can't write (c->state is set to conn_closing) */
+};
+
+static enum transmit_result transmit(conn *c);
+
+/* This reduces the latency without adding lots of extra wiring to be able to
+ * notify the listener thread of when to listen again.
+ * Also, the clock timer could be broken out into its own thread and we
+ * can block the listener via a condition.
+ */
+static volatile bool allow_new_conns = true;
+static struct event maxconnsevent;
+#ifndef __INTEL_COMPILER
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#endif
+static void maxconns_handler(const int fd, const short which, void *arg) {
+    struct timeval t = {.tv_sec = 0, .tv_usec = 10000};
+
+    if (fd == -42 || allow_new_conns == false) {
+        /* reschedule in 10ms if we need to keep polling */
+        evtimer_set(&maxconnsevent, maxconns_handler, 0);
+        event_base_set(main_base, &maxconnsevent);
+        evtimer_add(&maxconnsevent, &t);
+    } else {
+        evtimer_del(&maxconnsevent);
+        accept_new_conns(true);
+    }
+}
+
+#define REALTIME_MAXDELTA 60*60*24*30
+
+/*
+ * given time value that's either unix time or delta from current unix time, return
+ * unix time. Use the fact that delta can't exceed one month (and real time value can't
+ * be that low).
+ */
+static rel_time_t realtime(const time_t exptime) {
+    /* no. of seconds in 30 days - largest possible delta exptime */
+
+    if (exptime == 0) return 0; /* 0 means never expire */
+
+    if (exptime > REALTIME_MAXDELTA) {
+        /* if item expiration is at/before the server started, give it an
+           expiration time of 1 second after the server started.
+           (because 0 means don't expire).  without this, we'd
+           underflow and wrap around to some large value way in the
+           future, effectively making items expiring in the past
+           really expiring never */
+        if (exptime <= process_started)
+            return (rel_time_t)1;
+        return (rel_time_t)(exptime - process_started);
+    } else {
+        return (rel_time_t)(exptime + current_time);
+    }
+}
+
+static 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 = stats.evictions = stats.reclaimed = 0;
+    stats.touch_cmds = stats.touch_misses = stats.touch_hits = stats.rejected_conns = 0;
+    stats.curr_bytes = stats.listen_disabled_num = 0;
+    stats.hash_power_level = stats.hash_bytes = stats.hash_is_expanding = 0;
+    stats.expired_unfetched = stats.evicted_unfetched = 0;
+    stats.slabs_moved = 0;
+    stats.accepting_conns = true; /* assuming we start in this state. */
+    stats.slab_reassign_running = false;
+
+    /* make the time we started always be 2 seconds before we really
+       did, so time(0) - time.started is never zero.  if so, things
+       like 'settings.oldest_live' which act as booleans as well as
+       values are now false in boolean context... */
+    process_started = time(0) - 2;
+    stats_prefix_init();
+}
+
+static void stats_reset(void) {
+    STATS_LOCK();
+    stats.total_items = stats.total_conns = 0;
+    stats.rejected_conns = 0;
+    stats.evictions = 0;
+    stats.reclaimed = 0;
+    stats.listen_disabled_num = 0;
+    stats_prefix_clear();
+    STATS_UNLOCK();
+    threadlocal_stats_reset();
+    item_stats_reset();
+}
+
+static void settings_init(void) {
+    settings.use_cas = true;
+    settings.access = 0700;
+    settings.port = 11211;
+    settings.udpport = 11211;
+    /* By default this string should be NULL for getaddrinfo() */
+    settings.inter = NULL;
+    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 */
+    settings.socketpath = NULL;       /* by default, not using a unix socket */
+    settings.factor = 1.25;
+    settings.chunk_size = 48;         /* space for a modest key and value */
+    settings.num_threads = 4;         /* N workers */
+    settings.num_threads_per_udp = 0;
+    settings.prefix_delimiter = ':';
+    settings.detail_enabled = 0;
+    settings.reqs_per_event = 20;
+    settings.backlog = 1024;
+    settings.binding_protocol = negotiating_prot;
+    settings.item_size_max = 1024 * 1024; /* The famous 1MB upper limit. */
+    settings.maxconns_fast = false;
+    settings.hashpower_init = 0;
+    settings.slab_reassign = false;
+    settings.slab_automove = false;
+}
+
+/*
+ * Adds a message header to a connection.
+ *
+ * Returns 0 on success, -1 on out-of-memory.
+ */
+static int add_msghdr(conn *c)
+{
+    struct msghdr *msg;
+
+    assert(c != NULL);
+
+    if (c->msgsize == c->msgused) {
+        msg = realloc(c->msglist, c->msgsize * 2 * sizeof(struct msghdr));
+        if (! msg)
+            return -1;
+        c->msglist = msg;
+        c->msgsize *= 2;
+    }
+
+    msg = c->msglist + c->msgused;
+
+    /* this wipes msg_iovlen, msg_control, msg_controllen, and
+       msg_flags, the last 3 of which aren't defined on solaris: */
+    memset(msg, 0, sizeof(struct msghdr));
+
+    msg->msg_iov = &c->iov[c->iovused];
+
+    if (c->request_addr_size > 0) {
+        msg->msg_name = &c->request_addr;
+        msg->msg_namelen = c->request_addr_size;
+    }
+
+    c->msgbytes = 0;
+    c->msgused++;
+
+    if (IS_UDP(c->transport)) {
+        /* Leave room for the UDP header, which we'll fill in later. */
+        return add_iov(c, NULL, UDP_HEADER_SIZE);
+    }
+
+    return 0;
+}
+
+
+/*
+ * Free list management for connections.
+ */
+
+static conn **freeconns;
+static int freetotal;
+static int freecurr;
+/* Lock for connection freelist */
+static pthread_mutex_t conn_lock = PTHREAD_MUTEX_INITIALIZER;
+
+
+static void conn_init(void) {
+    freetotal = 200;
+    freecurr = 0;
+    if ((freeconns = calloc(freetotal, sizeof(conn *))) == NULL) {
+        fprintf(stderr, "Failed to allocate connection structures\n");
+    }
+    return;
+}
+
+/*
+ * Returns a connection from the freelist, if any.
+ */
+conn *conn_from_freelist() {
+    conn *c;
+
+    pthread_mutex_lock(&conn_lock);
+    if (freecurr > 0) {
+        c = freeconns[--freecurr];
+    } else {
+        c = NULL;
+    }
+    pthread_mutex_unlock(&conn_lock);
+
+    return c;
+}
+
+/*
+ * Adds a connection to the freelist. 0 = success.
+ */
+bool conn_add_to_freelist(conn *c) {
+    bool ret = true;
+    pthread_mutex_lock(&conn_lock);
+    if (freecurr < freetotal) {
+        freeconns[freecurr++] = c;
+        ret = false;
+    } else {
+        /* try to enlarge free connections array */
+        size_t newsize = freetotal * 2;
+        conn **new_freeconns = realloc(freeconns, sizeof(conn *) * newsize);
+        if (new_freeconns) {
+            freetotal = newsize;
+            freeconns = new_freeconns;
+            freeconns[freecurr++] = c;
+            ret = false;
+        }
+    }
+    pthread_mutex_unlock(&conn_lock);
+    return ret;
+}
+
+static const char *prot_text(enum protocol prot) {
+    const char *rv = "unknown";
+    switch(prot) {
+        case ascii_prot:
+            rv = "ascii";
+            break;
+        case binary_prot:
+            rv = "binary";
+            break;
+        case negotiating_prot:
+            rv = "auto-negotiate";
+            break;
+        default:
+            abort();
+    }
+    return rv;
+}
+
+conn *conn_new(const int sfd, enum conn_states init_state,
+                const int event_flags,
+                const int read_buffer_size, enum network_transport transport,
+                struct event_base *base) {
+    conn *c = conn_from_freelist();
+
+    if (NULL == c) {
+        if (!(c = (conn *)calloc(1, sizeof(conn)))) {
+            fprintf(stderr, "calloc()\n");
+            return NULL;
+        }
+        MEMCACHED_CONN_CREATE(c);
+
+        c->rbuf = c->wbuf = 0;
+        c->ilist = 0;
+        c->suffixlist = 0;
+        c->iov = 0;
+        c->msglist = 0;
+        c->hdrbuf = 0;
+
+        c->rsize = read_buffer_size;
+        c->wsize = DATA_BUFFER_SIZE;
+        c->isize = ITEM_LIST_INITIAL;
+        c->suffixsize = SUFFIX_LIST_INITIAL;
+        c->iovsize = IOV_LIST_INITIAL;
+        c->msgsize = MSG_LIST_INITIAL;
+        c->hdrsize = 0;
+
+        c->rbuf = (char *)malloc((size_t)c->rsize);
+        c->wbuf = (char *)malloc((size_t)c->wsize);
+        c->ilist = (item **)malloc(sizeof(item *) * c->isize);
+        c->suffixlist = (char **)malloc(sizeof(char *) * c->suffixsize);
+        c->iov = (struct iovec *)malloc(sizeof(struct iovec) * c->iovsize);
+        c->msglist = (struct msghdr *)malloc(sizeof(struct msghdr) * c->msgsize);
+
+        if (c->rbuf == 0 || c->wbuf == 0 || c->ilist == 0 || c->iov == 0 ||
+                c->msglist == 0 || c->suffixlist == 0) {
+            conn_free(c);
+            fprintf(stderr, "malloc()\n");
+            return NULL;
+        }
+
+        STATS_LOCK();
+        stats.conn_structs++;
+        STATS_UNLOCK();
+    }
+
+    c->transport = transport;
+    c->protocol = settings.binding_protocol;
+
+    /* unix socket mode doesn't need this, so zeroed out.  but why
+     * is this done for every command?  presumably for UDP
+     * mode.  */
+    if (!settings.socketpath) {
+        c->request_addr_size = sizeof(c->request_addr);
+    } else {
+        c->request_addr_size = 0;
+    }
+
+    if (settings.verbose > 1) {
+        if (init_state == conn_listening) {
+            fprintf(stderr, "<%d server listening (%s)\n", sfd,
+                prot_text(c->protocol));
+        } else if (IS_UDP(transport)) {
+            fprintf(stderr, "<%d server listening (udp)\n", sfd);
+        } else if (c->protocol == negotiating_prot) {
+            fprintf(stderr, "<%d new auto-negotiating client connection\n",
+                    sfd);
+        } else if (c->protocol == ascii_prot) {
+            fprintf(stderr, "<%d new ascii client connection.\n", sfd);
+        } else if (c->protocol == binary_prot) {
+            fprintf(stderr, "<%d new binary client connection.\n", sfd);
+        } else {
+            fprintf(stderr, "<%d new unknown (%d) client connection\n",
+                sfd, c->protocol);
+            assert(false);
+        }
+    }
+
+    c->sfd = sfd;
+    c->state = init_state;
+    c->rlbytes = 0;
+    c->cmd = -1;
+    c->rbytes = c->wbytes = 0;
+    c->wcurr = c->wbuf;
+    c->rcurr = c->rbuf;
+    c->ritem = 0;
+    c->icurr = c->ilist;
+    c->suffixcurr = c->suffixlist;
+    c->ileft = 0;
+    c->suffixleft = 0;
+    c->iovused = 0;
+    c->msgcurr = 0;
+    c->msgused = 0;
+
+    c->write_and_go = init_state;
+    c->write_and_free = 0;
+    c->item = 0;
+
+    c->noreply = false;
+
+    event_set(&c->event, sfd, event_flags, event_handler, (void *)c);
+    event_base_set(base, &c->event);
+    c->ev_flags = event_flags;
+
+    if (event_add(&c->event, 0) == -1) {
+        if (conn_add_to_freelist(c)) {
+            conn_free(c);
+        }
+        perror("event_add");
+        return NULL;
+    }
+
+    STATS_LOCK();
+    stats.curr_conns++;
+    stats.total_conns++;
+    STATS_UNLOCK();
+
+    MEMCACHED_CONN_ALLOCATE(c->sfd);
+
+    return c;
+}
+
+static void conn_cleanup(conn *c) {
+    assert(c != NULL);
+
+    if (c->item) {
+        item_remove(c->item);
+        c->item = 0;
+    }
+
+    if (c->ileft != 0) {
+        for (; c->ileft > 0; c->ileft--,c->icurr++) {
+            item_remove(*(c->icurr));
+        }
+    }
+
+    if (c->suffixleft != 0) {
+        for (; c->suffixleft > 0; c->suffixleft--, c->suffixcurr++) {
+            cache_free(c->thread->suffix_cache, *(c->suffixcurr));
+        }
+    }
+
+    if (c->write_and_free) {
+        free(c->write_and_free);
+        c->write_and_free = 0;
+    }
+
+    if (c->sasl_conn) {
+        assert(settings.sasl);
+        sasl_dispose(&c->sasl_conn);
+        c->sasl_conn = NULL;
+    }
+
+    if (IS_UDP(c->transport)) {
+        conn_set_state(c, conn_read);
+    }
+}
+
+/*
+ * Frees a connection.
+ */
+void conn_free(conn *c) {
+    if (c) {
+        MEMCACHED_CONN_DESTROY(c);
+        if (c->hdrbuf)
+            free(c->hdrbuf);
+        if (c->msglist)
+            free(c->msglist);
+        if (c->rbuf)
+            free(c->rbuf);
+        if (c->wbuf)
+            free(c->wbuf);
+        if (c->ilist)
+            free(c->ilist);
+        if (c->suffixlist)
+            free(c->suffixlist);
+        if (c->iov)
+            free(c->iov);
+        free(c);
+    }
+}
+
+static void conn_close(conn *c) {
+    assert(c != NULL);
+
+    /* delete the event, the socket and the conn */
+    event_del(&c->event);
+
+    if (settings.verbose > 1)
+        fprintf(stderr, "<%d connection closed.\n", c->sfd);
+
+    MEMCACHED_CONN_RELEASE(c->sfd);
+    close(c->sfd);
+    pthread_mutex_lock(&conn_lock);
+    allow_new_conns = true;
+    pthread_mutex_unlock(&conn_lock);
+    conn_cleanup(c);
+
+    /* if the connection has big buffers, just free it */
+    if (c->rsize > READ_BUFFER_HIGHWAT || conn_add_to_freelist(c)) {
+        conn_free(c);
+    }
+
+    STATS_LOCK();
+    stats.curr_conns--;
+    STATS_UNLOCK();
+
+    return;
+}
+
+/*
+ * Shrinks a connection's buffers if they're too big.  This prevents
+ * periodic large "get" requests from permanently chewing lots of server
+ * memory.
+ *
+ * This should only be called in between requests since it can wipe output
+ * buffers!
+ */
+static void conn_shrink(conn *c) {
+    assert(c != NULL);
+
+    if (IS_UDP(c->transport))
+        return;
+
+    if (c->rsize > READ_BUFFER_HIGHWAT && c->rbytes < DATA_BUFFER_SIZE) {
+        char *newbuf;
+
+        if (c->rcurr != c->rbuf)
+            memmove(c->rbuf, c->rcurr, (size_t)c->rbytes);
+
+        newbuf = (char *)realloc((void *)c->rbuf, DATA_BUFFER_SIZE);
+
+        if (newbuf) {
+            c->rbuf = newbuf;
+            c->rsize = DATA_BUFFER_SIZE;
+        }
+        /* TODO check other branch... */
+        c->rcurr = c->rbuf;
+    }
+
+    if (c->isize > ITEM_LIST_HIGHWAT) {
+        item **newbuf = (item**) realloc((void *)c->ilist, ITEM_LIST_INITIAL * sizeof(c->ilist[0]));
+        if (newbuf) {
+            c->ilist = newbuf;
+            c->isize = ITEM_LIST_INITIAL;
+        }
+    /* TODO check error condition? */
+    }
+
+    if (c->msgsize > MSG_LIST_HIGHWAT) {
+        struct msghdr *newbuf = (struct msghdr *) realloc((void *)c->msglist, MSG_LIST_INITIAL * sizeof(c->msglist[0]));
+        if (newbuf) {
+            c->msglist = newbuf;
+            c->msgsize = MSG_LIST_INITIAL;
+        }
+    /* TODO check error condition? */
+    }
+
+    if (c->iovsize > IOV_LIST_HIGHWAT) {
+        struct iovec *newbuf = (struct iovec *) realloc((void *)c->iov, IOV_LIST_INITIAL * sizeof(c->iov[0]));
+        if (newbuf) {
+            c->iov = newbuf;
+            c->iovsize = IOV_LIST_INITIAL;
+        }
+    /* TODO check return value */
+    }
+}
+
+/**
+ * Convert a state name to a human readable form.
+ */
+static const char *state_text(enum conn_states state) {
+    const char* const statenames[] = { "conn_listening",
+                                       "conn_new_cmd",
+                                       "conn_waiting",
+                                       "conn_read",
+                                       "conn_parse_cmd",
+                                       "conn_write",
+                                       "conn_nread",
+                                       "conn_swallow",
+                                       "conn_closing",
+                                       "conn_mwrite" };
+    return statenames[state];
+}
+
+#ifndef __INTEL_COMPILER
+#pragma GCC diagnostic ignored "-Wtype-limits"
+#endif
+/*
+ * Sets a connection's current state in the state machine. Any special
+ * processing that needs to happen on certain state transitions can
+ * happen here.
+ */
+static void conn_set_state(conn *c, enum conn_states state) {
+    assert(c != NULL);
+    assert(state >= conn_listening && state < conn_max_state);
+
+    if (state != c->state) {
+        if (settings.verbose > 2) {
+            fprintf(stderr, "%d: going from %s to %s\n",
+                    c->sfd, state_text(c->state),
+                    state_text(state));
+        }
+
+        if (state == conn_write || state == conn_mwrite) {
+            MEMCACHED_PROCESS_COMMAND_END(c->sfd, c->wbuf, c->wbytes);
+        }
+        c->state = state;
+    }
+}
+
+/*
+ * Ensures that there is room for another struct iovec in a connection's
+ * iov list.
+ *
+ * Returns 0 on success, -1 on out-of-memory.
+ */
+static int ensure_iov_space(conn *c) {
+    assert(c != NULL);
+
+    if (c->iovused >= c->iovsize) {
+        int i, iovnum;
+        struct iovec *new_iov = (struct iovec *)realloc(c->iov,
+                                (c->iovsize * 2) * sizeof(struct iovec));
+        if (! new_iov)
+            return -1;
+        c->iov = new_iov;
+        c->iovsize *= 2;
+
+        /* Point all the msghdr structures at the new list. */
+        for (i = 0, iovnum = 0; i < c->msgused; i++) {
+            c->msglist[i].msg_iov = &c->iov[iovnum];
+            iovnum += c->msglist[i].msg_iovlen;
+        }
+    }
+
+    return 0;
+}
+
+
+/*
+ * Adds data to the list of pending data that will be written out to a
+ * connection.
+ *
+ * Returns 0 on success, -1 on out-of-memory.
+ */
+
+static int add_iov(conn *c, const void *buf, int len) {
+    struct msghdr *m;
+    int leftover;
+    bool limit_to_mtu;
+
+    assert(c != NULL);
+
+    do {
+        m = &c->msglist[c->msgused - 1];
+
+        /*
+         * Limit UDP packets, and the first payloads of TCP replies, to
+         * UDP_MAX_PAYLOAD_SIZE bytes.
+         */
+        limit_to_mtu = IS_UDP(c->transport) || (1 == c->msgused);
+
+        /* We may need to start a new msghdr if this one is full. */
+        if (m->msg_iovlen == IOV_MAX ||
+            (limit_to_mtu && c->msgbytes >= UDP_MAX_PAYLOAD_SIZE)) {
+            add_msghdr(c);
+            m = &c->msglist[c->msgused - 1];
+        }
+
+        if (ensure_iov_space(c) != 0)
+            return -1;
+
+        /* If the fragment is too big to fit in the datagram, split it up */
+        if (limit_to_mtu && len + c->msgbytes > UDP_MAX_PAYLOAD_SIZE) {
+            leftover = len + c->msgbytes - UDP_MAX_PAYLOAD_SIZE;
+            len -= leftover;
+        } else {
+            leftover = 0;
+        }
+
+        m = &c->msglist[c->msgused - 1];
+        m->msg_iov[m->msg_iovlen].iov_base = (void *)buf;
+        m->msg_iov[m->msg_iovlen].iov_len = len;
+
+        c->msgbytes += len;
+        c->iovused++;
+        m->msg_iovlen++;
+
+        buf = ((char *)buf) + len;
+        len = leftover;
+    } while (leftover > 0);
+
+    return 0;
+}
+
+
+/*
+ * Constructs a set of UDP headers and attaches them to the outgoing messages.
+ */
+static int build_udp_headers(conn *c) {
+    int i;
+    unsigned char *hdr;
+
+    assert(c != NULL);
+
+    if (c->msgused > c->hdrsize) {
+        void *new_hdrbuf;
+        if (c->hdrbuf)
+            new_hdrbuf = realloc(c->hdrbuf, c->msgused * 2 * UDP_HEADER_SIZE);
+        else
+            new_hdrbuf = malloc(c->msgused * 2 * UDP_HEADER_SIZE);
+        if (! new_hdrbuf)
+            return -1;
+        c->hdrbuf = (unsigned char *)new_hdrbuf;
+        c->hdrsize = c->msgused * 2;
+    }
+
+    hdr = c->hdrbuf;
+    for (i = 0; i < c->msgused; i++) {
+        c->msglist[i].msg_iov[0].iov_base = (void*)hdr;
+        c->msglist[i].msg_iov[0].iov_len = UDP_HEADER_SIZE;
+        *hdr++ = c->request_id / 256;
+        *hdr++ = c->request_id % 256;
+        *hdr++ = i / 256;
+        *hdr++ = i % 256;
+        *hdr++ = c->msgused / 256;
+        *hdr++ = c->msgused % 256;
+        *hdr++ = 0;
+        *hdr++ = 0;
+        assert((void *) hdr == (caddr_t)c->msglist[i].msg_iov[0].iov_base + UDP_HEADER_SIZE);
+    }
+
+    return 0;
+}
+
+
+#ifndef __INTEL_COMPILER
+#pragma GCC diagnostic ignored "-Wsign-compare"
+#endif
+static void out_string(conn *c, const char *str) {
+    size_t len;
+
+    assert(c != NULL);
+
+    if (c->noreply) {
+        if (settings.verbose > 1)
+            fprintf(stderr, ">%d NOREPLY %s\n", c->sfd, str);
+        c->noreply = false;
+        conn_set_state(c, conn_new_cmd);
+        return;
+    }
+
+    if (settings.verbose > 1)
+        fprintf(stderr, ">%d %s\n", c->sfd, str);
+
+    /* Nuke a partial output... */
+    c->msgcurr = 0;
+    c->msgused = 0;
+    c->iovused = 0;
+    add_msghdr(c);
+
+    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);
+    }
+
+    memcpy(c->wbuf, str, len);
+    memcpy(c->wbuf + len, "\r\n", 2);
+    c->wbytes = len + 2;
+    c->wcurr = c->wbuf;
+
+    conn_set_state(c, conn_write);
+    c->write_and_go = conn_new_cmd;
+    return;
+}
+
+/*
+ * we get here after reading the value in set/add/replace commands. The command
+ * has been stored in c->cmd, and the item is ready in c->item.
+ */
+static void complete_nread_ascii(conn *c) {
+    assert(c != NULL);
+
+    item *it = c->item;
+    int comm = c->cmd;
+    enum store_item_type ret;
+
+    pthread_mutex_lock(&c->thread->stats.mutex);
+    c->thread->stats.slab_stats[it->slabs_clsid].set_cmds++;
+    pthread_mutex_unlock(&c->thread->stats.mutex);
+
+    if (strncmp(ITEM_data(it) + it->nbytes - 2, "\r\n", 2) != 0) {
+        out_string(c, "CLIENT_ERROR bad data chunk");
+    } else {
+      ret = store_item(it, comm, c);
+
+#ifdef ENABLE_DTRACE
+      uint64_t cas = ITEM_get_cas(it);
+      switch (c->cmd) {
+      case NREAD_ADD:
+          MEMCACHED_COMMAND_ADD(c->sfd, ITEM_key(it), it->nkey,
+                                (ret == 1) ? it->nbytes : -1, cas);
+          break;
+      case NREAD_REPLACE:
+          MEMCACHED_COMMAND_REPLACE(c->sfd, ITEM_key(it), it->nkey,
+                                    (ret == 1) ? it->nbytes : -1, cas);
+          break;
+      case NREAD_APPEND:
+          MEMCACHED_COMMAND_APPEND(c->sfd, ITEM_key(it), it->nkey,
+                                   (ret == 1) ? it->nbytes : -1, cas);
+          break;
+      case NREAD_PREPEND:
+          MEMCACHED_COMMAND_PREPEND(c->sfd, ITEM_key(it), it->nkey,
+                                    (ret == 1) ? it->nbytes : -1, cas);
+          break;
+      case NREAD_SET:
+          MEMCACHED_COMMAND_SET(c->sfd, ITEM_key(it), it->nkey,
+                                (ret == 1) ? it->nbytes : -1, cas);
+          break;
+      case NREAD_CAS:
+          MEMCACHED_COMMAND_CAS(c->sfd, ITEM_key(it), it->nkey, it->nbytes,
+                                cas);
+          break;
+      }
+#endif
+
+      switch (ret) {
+      case STORED:
+          out_string(c, "STORED");
+          break;
+      case EXISTS:
+          out_string(c, "EXISTS");
+          break;
+      case NOT_FOUND:
+          out_string(c, "NOT_FOUND");
+          break;
+      case NOT_STORED:
+          out_string(c, "NOT_STORED");
+          break;
+      default:
+          out_string(c, "SERVER_ERROR Unhandled storage type.");
+      }
+
+    }
+
+    item_remove(c->item);       /* release the c->item reference */
+    c->item = 0;
+}
+
+/**
+ * get a pointer to the start of the request struct for the current command
+ */
+static void* binary_get_request(conn *c) {
+    char *ret = c->rcurr;
+    ret -= (sizeof(c->binary_header) + c->binary_header.request.keylen +
+            c->binary_header.request.extlen);
+
+    assert(ret >= c->rbuf);
+    return ret;
+}
+
+/**
+ * get a pointer to the key in this request
+ */
+static char* binary_get_key(conn *c) {
+    return c->rcurr - (c->binary_header.request.keylen);
+}
+
+static void add_bin_header(conn *c, uint16_t err, uint8_t hdr_len, uint16_t key_len, uint32_t body_len) {
+    protocol_binary_response_header* header;
+
+    assert(c);
+
+    c->msgcurr = 0;
+    c->msgused = 0;
+    c->iovused = 0;
+    if (add_msghdr(c) != 0) {
+        /* XXX:  out_string is inappropriate here */
+        out_string(c, "SERVER_ERROR out of memory");
+        return;
+    }
+
+    header = (protocol_binary_response_header *)c->wbuf;
+
+    header->response.magic = (uint8_t)PROTOCOL_BINARY_RES;
+    header->response.opcode = c->binary_header.request.opcode;
+    header->response.keylen = (uint16_t)htons(key_len);
+
+    header->response.extlen = (uint8_t)hdr_len;
+    header->response.datatype = (uint8_t)PROTOCOL_BINARY_RAW_BYTES;
+    header->response.status = (uint16_t)htons(err);
+
+    header->response.bodylen = htonl(body_len);
+    header->response.opaque = c->opaque;
+    header->response.cas = htonll(c->cas);
+
+    if (settings.verbose > 1) {
+        int ii;
+        fprintf(stderr, ">%d Writing bin response:", c->sfd);
+        for (ii = 0; ii < sizeof(header->bytes); ++ii) {
+            if (ii % 4 == 0) {
+                fprintf(stderr, "\n>%d  ", c->sfd);
+            }
+            fprintf(stderr, " 0x%02x", header->bytes[ii]);
+        }
+        fprintf(stderr, "\n");
+    }
+
+    add_iov(c, c->wbuf, sizeof(header->response));
+}
+
+static void write_bin_error(conn *c, protocol_binary_response_status err, int swallow) {
+    const char *errstr = "Unknown error";
+    size_t len;
+
+    switch (err) {
+    case PROTOCOL_BINARY_RESPONSE_ENOMEM:
+        errstr = "Out of memory";
+        break;
+    case PROTOCOL_BINARY_RESPONSE_UNKNOWN_COMMAND:
+        errstr = "Unknown command";
+        break;
+    case PROTOCOL_BINARY_RESPONSE_KEY_ENOENT:
+        errstr = "Not found";
+        break;
+    case PROTOCOL_BINARY_RESPONSE_EINVAL:
+        errstr = "Invalid arguments";
+        break;
+    case PROTOCOL_BINARY_RESPONSE_KEY_EEXISTS:
+        errstr = "Data exists for key.";
+        break;
+    case PROTOCOL_BINARY_RESPONSE_E2BIG:
+        errstr = "Too large.";
+        break;
+    case PROTOCOL_BINARY_RESPONSE_DELTA_BADVAL:
+        errstr = "Non-numeric server-side value for incr or decr";
+        break;
+    case PROTOCOL_BINARY_RESPONSE_NOT_STORED:
+        errstr = "Not stored.";
+        break;
+    case PROTOCOL_BINARY_RESPONSE_AUTH_ERROR:
+        errstr = "Auth failure.";
+        break;
+    case PROTOCOL_BINARY_RESPONSE_AUTH_CONTINUE:
+        assert(false);
+    case PROTOCOL_BINARY_RESPONSE_SUCCESS:
+        assert(false);
+    default:
+        assert(false);
+        errstr = "UNHANDLED ERROR";
+        fprintf(stderr, ">%d UNHANDLED ERROR: %d\n", c->sfd, err);
+    }
+
+    if (settings.verbose > 1) {
+        fprintf(stderr, ">%d Writing an error: %s\n", c->sfd, errstr);
+    }
+
+    len = strlen(errstr);
+    add_bin_header(c, err, 0, 0, len);
+    if (len > 0) {
+        add_iov(c, errstr, len);
+    }
+    conn_set_state(c, conn_mwrite);
+    if(swallow > 0) {
+        c->sbytes = swallow;
+        c->write_and_go = conn_swallow;
+    } else {
+        c->write_and_go = conn_new_cmd;
+    }
+}
+
+/* Form and send a response to a command over the binary protocol */
+static void write_bin_response(conn *c, const void *d, int hlen, int keylen, int dlen) {
+    if (!c->noreply || c->cmd == PROTOCOL_BINARY_CMD_GET ||
+        c->cmd == PROTOCOL_BINARY_CMD_GETK) {
+        add_bin_header(c, 0, hlen, keylen, dlen);
+        if(dlen > 0) {
+            add_iov(c, d, dlen);
+        }
+        conn_set_state(c, conn_mwrite);
+        c->write_and_go = conn_new_cmd;
+    } else {
+        conn_set_state(c, conn_new_cmd);
+    }
+}
+
+static void complete_incr_bin(conn *c) {
+    item *it;
+    char *key;
+    size_t nkey;
+    /* Weird magic in add_delta forces me to pad here */
+    char tmpbuf[INCR_MAX_STORAGE_LEN];
+    uint64_t cas = 0;
+
+    protocol_binary_response_incr* rsp = (protocol_binary_response_incr*)c->wbuf;
+    protocol_binary_request_incr* req = binary_get_request(c);
+
+    assert(c != NULL);
+    assert(c->wsize >= sizeof(*rsp));
+
+    /* fix byteorder in the request */
+    req->message.body.delta = ntohll(req->message.body.delta);
+    req->message.body.initial = ntohll(req->message.body.initial);
+    req->message.body.expiration = ntohl(req->message.body.expiration);
+    key = binary_get_key(c);
+    nkey = c->binary_header.request.keylen;
+
+    if (settings.verbose > 1) {
+        int i;
+        fprintf(stderr, "incr ");
+
+        for (i = 0; i < nkey; i++) {
+            fprintf(stderr, "%c", key[i]);
+        }
+        fprintf(stderr, " %lld, %llu, %d\n",
+                (long long)req->message.body.delta,
+                (long long)req->message.body.initial,
+                req->message.body.expiration);
+    }
+
+    if (c->binary_header.request.cas != 0) {
+        cas = c->binary_header.request.cas;
+    }
+    switch(add_delta(c, key, nkey, c->cmd == PROTOCOL_BINARY_CMD_INCREMENT,
+                     req->message.body.delta, tmpbuf,
+                     &cas)) {
+    case OK:
+        rsp->message.body.value = htonll(strtoull(tmpbuf, NULL, 10));
+        if (cas) {
+            c->cas = cas;
+        }
+        write_bin_response(c, &rsp->message.body, 0, 0,
+                           sizeof(rsp->message.body.value));
+        break;
+    case NON_NUMERIC:
+        write_bin_error(c, PROTOCOL_BINARY_RESPONSE_DELTA_BADVAL, 0);
+        break;
+    case EOM:
+        write_bin_error(c, PROTOCOL_BINARY_RESPONSE_ENOMEM, 0);
+        break;
+    case DELTA_ITEM_NOT_FOUND:
+        if (req->message.body.expiration != 0xffffffff) {
+            /* Save some room for the response */
+            rsp->message.body.value = htonll(req->message.body.initial);
+            it = item_alloc(key, nkey, 0, realtime(req->message.body.expiration),
+                            INCR_MAX_STORAGE_LEN);
+
+            if (it != NULL) {
+                snprintf(ITEM_data(it), INCR_MAX_STORAGE_LEN, "%llu",
+                         (unsigned long long)req->message.body.initial);
+
+                if (store_item(it, NREAD_ADD, c)) {
+                    c->cas = ITEM_get_cas(it);
+                    write_bin_response(c, &rsp->message.body, 0, 0, sizeof(rsp->message.body.value));
+                } else {
+                    write_bin_error(c, PROTOCOL_BINARY_RESPONSE_NOT_STORED, 0);
+                }
+                item_remove(it);         /* release our reference */
+            } else {
+                write_bin_error(c, PROTOCOL_BINARY_RESPONSE_ENOMEM, 0);
+            }
+        } else {
+            pthread_mutex_lock(&c->thread->stats.mutex);
+            if (c->cmd == PROTOCOL_BINARY_CMD_INCREMENT) {
+                c->thread->stats.incr_misses++;
+            } else {
+                c->thread->stats.decr_misses++;
+            }
+            pthread_mutex_unlock(&c->thread->stats.mutex);
+
+            write_bin_error(c, PROTOCOL_BINARY_RESPONSE_KEY_ENOENT, 0);
+        }
+        break;
+    case DELTA_ITEM_CAS_MISMATCH:
+        write_bin_error(c, PROTOCOL_BINARY_RESPONSE_KEY_EEXISTS, 0);
+        break;
+
+    default:
+        assert(0);
+        abort();
+    }
+}
+
+static void complete_update_bin(conn *c) {
+    protocol_binary_response_status eno = PROTOCOL_BINARY_RESPONSE_EINVAL;
+    enum store_item_type ret = NOT_STORED;
+    assert(c != NULL);
+
+    item *it = c->item;
+
+    pthread_mutex_lock(&c->thread->stats.mutex);
+    c->thread->stats.slab_stats[it->slabs_clsid].set_cmds++;
+    pthread_mutex_unlock(&c->thread->stats.mutex);
+
+    /* We don't actually receive the trailing two characters in the bin
+     * protocol, so we're going to just set them here */
+    *(ITEM_data(it) + it->nbytes - 2) = '\r';
+    *(ITEM_data(it) + it->nbytes - 1) = '\n';
+
+    ret = store_item(it, c->cmd, c);
+
+#ifdef ENABLE_DTRACE
+    uint64_t cas = ITEM_get_cas(it);
+    switch (c->cmd) {
+    case NREAD_ADD:
+        MEMCACHED_COMMAND_ADD(c->sfd, ITEM_key(it), it->nkey,
+                              (ret == STORED) ? it->nbytes : -1, cas);
+        break;
+    case NREAD_REPLACE:
+        MEMCACHED_COMMAND_REPLACE(c->sfd, ITEM_key(it), it->nkey,
+                                  (ret == STORED) ? it->nbytes : -1, cas);
+        break;
+    case NREAD_APPEND:
+        MEMCACHED_COMMAND_APPEND(c->sfd, ITEM_key(it), it->nkey,
+                                 (ret == STORED) ? it->nbytes : -1, cas);
+        break;
+    case NREAD_PREPEND:
+        MEMCACHED_COMMAND_PREPEND(c->sfd, ITEM_key(it), it->nkey,
+                                 (ret == STORED) ? it->nbytes : -1, cas);
+        break;
+    case NREAD_SET:
+        MEMCACHED_COMMAND_SET(c->sfd, ITEM_key(it), it->nkey,
+                              (ret == STORED) ? it->nbytes : -1, cas);
+        break;
+    }
+#endif
+
+    switch (ret) {
+    case STORED:
+        /* Stored */
+        write_bin_response(c, NULL, 0, 0, 0);
+        break;
+    case EXISTS:
+        write_bin_error(c, PROTOCOL_BINARY_RESPONSE_KEY_EEXISTS, 0);
+        break;
+    case NOT_FOUND:
+        write_bin_error(c, PROTOCOL_BINARY_RESPONSE_KEY_ENOENT, 0);
+        break;
+    case NOT_STORED:
+        if (c->cmd == NREAD_ADD) {
+            eno = PROTOCOL_BINARY_RESPONSE_KEY_EEXISTS;
+        } else if(c->cmd == NREAD_REPLACE) {
+            eno = PROTOCOL_BINARY_RESPONSE_KEY_ENOENT;
+        } else {
+            eno = PROTOCOL_BINARY_RESPONSE_NOT_STORED;
+        }
+        write_bin_error(c, eno, 0);
+    default:
+        assert(false);
+        abort();
+    }
+
+    item_remove(c->item);       /* release the c->item reference */
+    c->item = 0;
+}
+
+static void process_bin_touch(conn *c) {
+    item *it;
+
+    protocol_binary_response_get* rsp = (protocol_binary_response_get*)c->wbuf;
+    char* key = binary_get_key(c);
+    size_t nkey = c->binary_header.request.keylen;
+    protocol_binary_request_touch *t = (void *)&c->binary_header;
+    uint32_t exptime = ntohl(t->message.body.expiration);
+
+    if (settings.verbose > 1) {
+        int ii;
+        /* May be GAT/GATQ/etc */
+        fprintf(stderr, "<%d TOUCH ", c->sfd);
+        for (ii = 0; ii < nkey; ++ii) {
+            fprintf(stderr, "%c", key[ii]);
+        }
+        fprintf(stderr, "\n");
+    }
+
+    it = item_touch(key, nkey, realtime(exptime));
+
+    if (it) {
+        /* the length has two unnecessary bytes ("\r\n") */
+        uint16_t keylen = 0;
+        uint32_t bodylen = sizeof(rsp->message.body) + (it->nbytes - 2);
+
+        item_update(it);
+        pthread_mutex_lock(&c->thread->stats.mutex);
+        c->thread->stats.touch_cmds++;
+        c->thread->stats.slab_stats[it->slabs_clsid].touch_hits++;
+        pthread_mutex_unlock(&c->thread->stats.mutex);
+
+        MEMCACHED_COMMAND_TOUCH(c->sfd, ITEM_key(it), it->nkey,
+                                it->nbytes, ITEM_get_cas(it));
+
+        if (c->cmd == PROTOCOL_BINARY_CMD_TOUCH) {
+            bodylen -= it->nbytes - 2;
+        } else if (c->cmd == PROTOCOL_BINARY_CMD_GATK) {
+            bodylen += nkey;
+            keylen = nkey;
+        }
+
+        add_bin_header(c, 0, sizeof(rsp->message.body), keylen, bodylen);
+        rsp->message.header.response.cas = htonll(ITEM_get_cas(it));
+
+        // add the flags
+        rsp->message.body.flags = htonl(strtoul(ITEM_suffix(it), NULL, 10));
+        add_iov(c, &rsp->message.body, sizeof(rsp->message.body));
+
+        if (c->cmd == PROTOCOL_BINARY_CMD_GATK) {
+            add_iov(c, ITEM_key(it), nkey);
+        }
+
+        /* Add the data minus the CRLF */
+        if (c->cmd != PROTOCOL_BINARY_CMD_TOUCH) {
+            add_iov(c, ITEM_data(it), it->nbytes - 2);
+        }
+
+        conn_set_state(c, conn_mwrite);
+        c->write_and_go = conn_new_cmd;
+        /* Remember this command so we can garbage collect it later */
+        c->item = it;
+    } else {
+        pthread_mutex_lock(&c->thread->stats.mutex);
+        c->thread->stats.touch_cmds++;
+        c->thread->stats.touch_misses++;
+        pthread_mutex_unlock(&c->thread->stats.mutex);
+
+        MEMCACHED_COMMAND_TOUCH(c->sfd, key, nkey, -1, 0);
+
+        if (c->noreply) {
+            conn_set_state(c, conn_new_cmd);
+        } else {
+            if (c->cmd == PROTOCOL_BINARY_CMD_GATK) {
+                char *ofs = c->wbuf + sizeof(protocol_binary_response_header);
+                add_bin_header(c, PROTOCOL_BINARY_RESPONSE_KEY_ENOENT,
+                        0, nkey, nkey);
+                memcpy(ofs, key, nkey);
+                add_iov(c, ofs, nkey);
+                conn_set_state(c, conn_mwrite);
+                c->write_and_go = conn_new_cmd;
+            } else {
+                write_bin_error(c, PROTOCOL_BINARY_RESPONSE_KEY_ENOENT, 0);
+            }
+        }
+    }
+
+    if (settings.detail_enabled) {
+        stats_prefix_record_get(key, nkey, NULL != it);
+    }
+}
+
+static void process_bin_get(conn *c) {
+    item *it;
+
+    protocol_binary_response_get* rsp = (protocol_binary_response_get*)c->wbuf;
+    char* key = binary_get_key(c);
+    size_t nkey = c->binary_header.request.keylen;
+
+    if (settings.verbose > 1) {
+        int ii;
+        fprintf(stderr, "<%d GET ", c->sfd);
+        for (ii = 0; ii < nkey; ++ii) {
+            fprintf(stderr, "%c", key[ii]);
+        }
+        fprintf(stderr, "\n");
+    }
+
+    it = item_get(key, nkey);
+    if (it) {
+        /* the length has two unnecessary bytes ("\r\n") */
+        uint16_t keylen = 0;
+        uint32_t bodylen = sizeof(rsp->message.body) + (it->nbytes - 2);
+
+        item_update(it);
+        pthread_mutex_lock(&c->thread->stats.mutex);
+        c->thread->stats.get_cmds++;
+        c->thread->stats.slab_stats[it->slabs_clsid].get_hits++;
+        pthread_mutex_unlock(&c->thread->stats.mutex);
+
+        MEMCACHED_COMMAND_GET(c->sfd, ITEM_key(it), it->nkey,
+                              it->nbytes, ITEM_get_cas(it));
+
+        if (c->cmd == PROTOCOL_BINARY_CMD_GETK) {
+            bodylen += nkey;
+            keylen = nkey;
+        }
+        add_bin_header(c, 0, sizeof(rsp->message.body), keylen, bodylen);
+        rsp->message.header.response.cas = htonll(ITEM_get_cas(it));
+
+        // add the flags
+        rsp->message.body.flags = htonl(strtoul(ITEM_suffix(it), NULL, 10));
+        add_iov(c, &rsp->message.body, sizeof(rsp->message.body));
+
+        if (c->cmd == PROTOCOL_BINARY_CMD_GETK) {
+            add_iov(c, ITEM_key(it), nkey);
+        }
+
+        /* Add the data minus the CRLF */
+        add_iov(c, ITEM_data(it), it->nbytes - 2);
+        conn_set_state(c, conn_mwrite);
+        c->write_and_go = conn_new_cmd;
+        /* Remember this command so we can garbage collect it later */
+        c->item = it;
+    } else {
+        pthread_mutex_lock(&c->thread->stats.mutex);
+        c->thread->stats.get_cmds++;
+        c->thread->stats.get_misses++;
+        pthread_mutex_unlock(&c->thread->stats.mutex);
+
+        MEMCACHED_COMMAND_GET(c->sfd, key, nkey, -1, 0);
+
+        if (c->noreply) {
+            conn_set_state(c, conn_new_cmd);
+        } else {
+            if (c->cmd == PROTOCOL_BINARY_CMD_GETK) {
+                char *ofs = c->wbuf + sizeof(protocol_binary_response_header);
+                add_bin_header(c, PROTOCOL_BINARY_RESPONSE_KEY_ENOENT,
+                        0, nkey, nkey);
+                memcpy(ofs, key, nkey);
+                add_iov(c, ofs, nkey);
+                conn_set_state(c, conn_mwrite);
+                c->write_and_go = conn_new_cmd;
+            } else {
+                write_bin_error(c, PROTOCOL_BINARY_RESPONSE_KEY_ENOENT, 0);
+            }
+        }
+    }
+
+    if (settings.detail_enabled) {
+        stats_prefix_record_get(key, nkey, NULL != it);
+    }
+}
+
+#ifndef __INTEL_COMPILER
+#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
+#endif
+static void append_bin_stats(const char *key, const uint16_t klen,
+                             const char *val, const uint32_t vlen,
+                             conn *c) {
+    char *buf = c->stats.buffer + c->stats.offset;
+    uint32_t bodylen = klen + vlen;
+    protocol_binary_response_header header = {
+        .response.magic = (uint8_t)PROTOCOL_BINARY_RES,
+        .response.opcode = (uint8_t)PROTOCOL_BINARY_CMD_STAT,
+        .response.keylen = (uint16_t)htons(klen),
+        .response.extlen = (uint8_t)0,
+        .response.datatype = (uint8_t)PROTOCOL_BINARY_RAW_BYTES,
+        .response.status = (uint16_t)0,
+        .response.bodylen = htonl(bodylen),
+        .response.opaque = c->opaque,
+        .response.cas = (uint64_t)0
+    };
+
+    memcpy(buf, header.bytes, sizeof(header.response));
+    buf += sizeof(header.response);
+
+    if (klen > 0) {
+        memcpy(buf, key, klen);
+        buf += klen;
+
+        if (vlen > 0) {
+            memcpy(buf, val, vlen);
+        }
+    }
+
+    c->stats.offset += sizeof(header.response) + bodylen;
+}
+
+static void append_ascii_stats(const char *key, const uint16_t klen,
+                               const char *val, const uint32_t vlen,
+                               conn *c) {
+    char *pos = c->stats.buffer + c->stats.offset;
+    uint32_t nbytes = 0;
+    int remaining = c->stats.size - c->stats.offset;
+    int room = remaining - 1;
+
+    if (klen == 0 && vlen == 0) {
+        nbytes = snprintf(pos, room, "END\r\n");
+    } else if (vlen == 0) {
+        nbytes = snprintf(pos, room, "STAT %s\r\n", key);
+    } else {
+        nbytes = snprintf(pos, room, "STAT %s %s\r\n", key, val);
+    }
+
+    c->stats.offset += nbytes;
+}
+
+static bool grow_stats_buf(conn *c, size_t needed) {
+    size_t nsize = c->stats.size;
+    size_t available = nsize - c->stats.offset;
+    bool rv = true;
+
+    /* Special case: No buffer -- need to allocate fresh */
+    if (c->stats.buffer == NULL) {
+        nsize = 1024;
+        available = c->stats.size = c->stats.offset = 0;
+    }
+
+    while (needed > available) {
+        assert(nsize > 0);
+        nsize = nsize << 1;
+        available = nsize - c->stats.offset;
+    }
+
+    if (nsize != c->stats.size) {
+        char *ptr = realloc(c->stats.buffer, nsize);
+        if (ptr) {
+            c->stats.buffer = ptr;
+            c->stats.size = nsize;
+        } else {
+            rv = false;
+        }
+    }
+
+    return rv;
+}
+
+static void append_stats(const char *key, const uint16_t klen,
+                  const char *val, const uint32_t vlen,
+                  const void *cookie)
+{
+    /* value without a key is invalid */
+    if (klen == 0 && vlen > 0) {
+        return ;
+    }
+
+    conn *c = (conn*)cookie;
+
+    if (c->protocol == binary_prot) {
+        size_t needed = vlen + klen + sizeof(protocol_binary_response_header);
+        if (!grow_stats_buf(c, needed)) {
+            return ;
+        }
+        append_bin_stats(key, klen, val, vlen, c);
+    } else {
+        size_t needed = vlen + klen + 10; // 10 == "STAT = \r\n"
+        if (!grow_stats_buf(c, needed)) {
+            return ;
+        }
+        append_ascii_stats(key, klen, val, vlen, c);
+    }
+
+    assert(c->stats.offset <= c->stats.size);
+}
+
+static void process_bin_stat(conn *c) {
+    char *subcommand = binary_get_key(c);
+    size_t nkey = c->binary_header.request.keylen;
+
+    if (settings.verbose > 1) {
+        int ii;
+        fprintf(stderr, "<%d STATS ", c->sfd);
+        for (ii = 0; ii < nkey; ++ii) {
+            fprintf(stderr, "%c", subcommand[ii]);
+        }
+        fprintf(stderr, "\n");
+    }
+
+    if (nkey == 0) {
+        /* request all statistics */
+        server_stats(&append_stats, c);
+        (void)get_stats(NULL, 0, &append_stats, c);
+    } else if (strncmp(subcommand, "reset", 5) == 0) {
+        stats_reset();
+    } else if (strncmp(subcommand, "settings", 8) == 0) {
+        process_stat_settings(&append_stats, c);
+    } else if (strncmp(subcommand, "detail", 6) == 0) {
+        char *subcmd_pos = subcommand + 6;
+        if (strncmp(subcmd_pos, " dump", 5) == 0) {
+            int len;
+            char *dump_buf = stats_prefix_dump(&len);
+            if (dump_buf == NULL || len <= 0) {
+                write_bin_error(c, PROTOCOL_BINARY_RESPONSE_ENOMEM, 0);
+                return ;
+            } else {
+                append_stats("detailed", strlen("detailed"), dump_buf, len, c);
+                free(dump_buf);
+            }
+        } else if (strncmp(subcmd_pos, " on", 3) == 0) {
+            settings.detail_enabled = 1;
+        } else if (strncmp(subcmd_pos, " off", 4) == 0) {
+            settings.detail_enabled = 0;
+        } else {
+            write_bin_error(c, PROTOCOL_BINARY_RESPONSE_KEY_ENOENT, 0);
+            return;
+        }
+    } else {
+        if (get_stats(subcommand, nkey, &append_stats, c)) {
+            if (c->stats.buffer == NULL) {
+                write_bin_error(c, PROTOCOL_BINARY_RESPONSE_ENOMEM, 0);
+            } else {
+                write_and_free(c, c->stats.buffer, c->stats.offset);
+                c->stats.buffer = NULL;
+            }
+        } else {
+            write_bin_error(c, PROTOCOL_BINARY_RESPONSE_KEY_ENOENT, 0);
+        }
+
+        return;
+    }
+
+    /* Append termination package and start the transfer */
+    append_stats(NULL, 0, NULL, 0, c);
+    if (c->stats.buffer == NULL) {
+        write_bin_error(c, PROTOCOL_BINARY_RESPONSE_ENOMEM, 0);
+    } else {
+        write_and_free(c, c->stats.buffer, c->stats.offset);
+        c->stats.buffer = NULL;
+    }
+}
+
+static void bin_read_key(conn *c, enum bin_substates next_substate, int extra) {
+    assert(c);
+    c->substate = next_substate;
+    c->rlbytes = c->keylen + extra;
+
+    /* Ok... do we have room for the extras and the key in the input buffer? */
+    ptrdiff_t offset = c->rcurr + sizeof(protocol_binary_request_header) - c->rbuf;
+    if (c->rlbytes > c->rsize - offset) {
+        size_t nsize = c->rsize;
+        size_t size = c->rlbytes + sizeof(protocol_binary_request_header);
+
+        while (size > nsize) {
+            nsize *= 2;
+        }
+
+        if (nsize != c->rsize) {
+            if (settings.verbose > 1) {
+                fprintf(stderr, "%d: Need to grow buffer from %lu to %lu\n",
+                        c->sfd, (unsigned long)c->rsize, (unsigned long)nsize);
+            }
+            char *newm = realloc(c->rbuf, nsize);
+            if (newm == NULL) {
+                if (settings.verbose) {
+                    fprintf(stderr, "%d: Failed to grow buffer.. closing connection\n",
+                            c->sfd);
+                }
+                conn_set_state(c, conn_closing);
+                return;
+            }
+
+            c->rbuf= newm;
+            /* rcurr should point to the same offset in the packet */
+            c->rcurr = c->rbuf + offset - sizeof(protocol_binary_request_header);
+            c->rsize = nsize;
+        }
+        if (c->rbuf != c->rcurr) {
+            memmove(c->rbuf, c->rcurr, c->rbytes);
+            c->rcurr = c->rbuf;
+            if (settings.verbose > 1) {
+                fprintf(stderr, "%d: Repack input buffer\n", c->sfd);
+            }
+        }
+    }
+
+    /* preserve the header in the buffer.. */
+    c->ritem = c->rcurr + sizeof(protocol_binary_request_header);
+    conn_set_state(c, conn_nread);
+}
+
+/* Just write an error message and disconnect the client */
+static void handle_binary_protocol_error(conn *c) {
+    write_bin_error(c, PROTOCOL_BINARY_RESPONSE_EINVAL, 0);
+    if (settings.verbose) {
+        fprintf(stderr, "Protocol error (opcode %02x), close connection %d\n",
+                c->binary_header.request.opcode, c->sfd);
+    }
+    c->write_and_go = conn_closing;
+}
+
+static void init_sasl_conn(conn *c) {
+    assert(c);
+    /* should something else be returned? */
+    if (!settings.sasl)
+        return;
+
+    if (!c->sasl_conn) {
+        int result=sasl_server_new("memcached",
+                                   NULL,
+                                   my_sasl_hostname[0] ? my_sasl_hostname : NULL,
+                                   NULL, NULL,
+                                   NULL, 0, &c->sasl_conn);
+        if (result != SASL_OK) {
+            if (settings.verbose) {
+                fprintf(stderr, "Failed to initialize SASL conn.\n");
+            }
+            c->sasl_conn = NULL;
+        }
+    }
+}
+
+static void bin_list_sasl_mechs(conn *c) {
+    // Guard against a disabled SASL.
+    if (!settings.sasl) {
+        write_bin_error(c, PROTOCOL_BINARY_RESPONSE_UNKNOWN_COMMAND,
+                        c->binary_header.request.bodylen
+                        - c->binary_header.request.keylen);
+        return;
+    }
+
+    init_sasl_conn(c);
+    const char *result_string = NULL;
+    unsigned int string_length = 0;
+    int result=sasl_listmech(c->sasl_conn, NULL,
+                             "",   /* What to prepend the string with */
+                             " ",  /* What to separate mechanisms with */
+                             "",   /* What to append to the string */
+                             &result_string, &string_length,
+                             NULL);
+    if (result != SASL_OK) {
+        /* Perhaps there's a better error for this... */
+        if (settings.verbose) {
+            fprintf(stderr, "Failed to list SASL mechanisms.\n");
+        }
+        write_bin_error(c, PROTOCOL_BINARY_RESPONSE_AUTH_ERROR, 0);
+        return;
+    }
+    write_bin_response(c, (char*)result_string, 0, 0, string_length);
+}
+
+static void process_bin_sasl_auth(conn *c) {
+    // Guard for handling disabled SASL on the server.
+    if (!settings.sasl) {
+        write_bin_error(c, PROTOCOL_BINARY_RESPONSE_UNKNOWN_COMMAND,
+                        c->binary_header.request.bodylen
+                        - c->binary_header.request.keylen);
+        return;
+    }
+
+    assert(c->binary_header.request.extlen == 0);
+
+    int nkey = c->binary_header.request.keylen;
+    int vlen = c->binary_header.request.bodylen - nkey;
+
+    if (nkey > MAX_SASL_MECH_LEN) {
+        write_bin_error(c, PROTOCOL_BINARY_RESPONSE_EINVAL, vlen);
+        c->write_and_go = conn_swallow;
+        return;
+    }
+
+    char *key = binary_get_key(c);
+    assert(key);
+
+    item *it = item_alloc(key, nkey, 0, 0, vlen);
+
+    if (it == 0) {
+        write_bin_error(c, PROTOCOL_BINARY_RESPONSE_ENOMEM, vlen);
+        c->write_and_go = conn_swallow;
+        return;
+    }
+
+    c->item = it;
+    c->ritem = ITEM_data(it);
+    c->rlbytes = vlen;
+    conn_set_state(c, conn_nread);
+    c->substate = bin_reading_sasl_auth_data;
+}
+
+static void process_bin_complete_sasl_auth(conn *c) {
+    assert(settings.sasl);
+    const char *out = NULL;
+    unsigned int outlen = 0;
+
+    assert(c->item);
+    init_sasl_conn(c);
+
+    int nkey = c->binary_header.request.keylen;
+    int vlen = c->binary_header.request.bodylen - nkey;
+
+    char mech[nkey+1];
+    memcpy(mech, ITEM_key((item*)c->item), nkey);
+    mech[nkey] = 0x00;
+
+    if (settings.verbose)
+        fprintf(stderr, "mech:  ``%s'' with %d bytes of data\n", mech, vlen);
+
+    const char *challenge = vlen == 0 ? NULL : ITEM_data((item*) c->item);
+
+    int result=-1;
+
+    switch (c->cmd) {
+    case PROTOCOL_BINARY_CMD_SASL_AUTH:
+        result = sasl_server_start(c->sasl_conn, mech,
+                                   challenge, vlen,
+                                   &out, &outlen);
+        break;
+    case PROTOCOL_BINARY_CMD_SASL_STEP:
+        result = sasl_server_step(c->sasl_conn,
+                                  challenge, vlen,
+                                  &out, &outlen);
+        break;
+    default:
+        assert(false); /* CMD should be one of the above */
+        /* This code is pretty much impossible, but makes the compiler
+           happier */
+        if (settings.verbose) {
+            fprintf(stderr, "Unhandled command %d with challenge %s\n",
+                    c->cmd, challenge);
+        }
+        break;
+    }
+
+    item_unlink(c->item);
+
+    if (settings.verbose) {
+        fprintf(stderr, "sasl result code:  %d\n", result);
+    }
+
+    switch(result) {
+    case SASL_OK:
+        write_bin_response(c, "Authenticated", 0, 0, strlen("Authenticated"));
+        pthread_mutex_lock(&c->thread->stats.mutex);
+        c->thread->stats.auth_cmds++;
+        pthread_mutex_unlock(&c->thread->stats.mutex);
+        break;
+    case SASL_CONTINUE:
+        add_bin_header(c, PROTOCOL_BINARY_RESPONSE_AUTH_CONTINUE, 0, 0, outlen);
+        if(outlen > 0) {
+            add_iov(c, out, outlen);
+        }
+        conn_set_state(c, conn_mwrite);
+        c->write_and_go = conn_new_cmd;
+        break;
+    default:
+        if (settings.verbose)
+            fprintf(stderr, "Unknown sasl response:  %d\n", result);
+        write_bin_error(c, PROTOCOL_BINARY_RESPONSE_AUTH_ERROR, 0);
+        pthread_mutex_lock(&c->thread->stats.mutex);
+        c->thread->stats.auth_cmds++;
+        c->thread->stats.auth_errors++;
+        pthread_mutex_unlock(&c->thread->stats.mutex);
+    }
+}
+
+static bool authenticated(conn *c) {
+    assert(settings.sasl);
+    bool rv = false;
+
+    switch (c->cmd) {
+    case PROTOCOL_BINARY_CMD_SASL_LIST_MECHS: /* FALLTHROUGH */
+    case PROTOCOL_BINARY_CMD_SASL_AUTH:       /* FALLTHROUGH */
+    case PROTOCOL_BINARY_CMD_SASL_STEP:       /* FALLTHROUGH */
+    case PROTOCOL_BINARY_CMD_VERSION:         /* FALLTHROUGH */
+        rv = true;
+        break;
+    default:
+        if (c->sasl_conn) {
+            const void *uname = NULL;
+            sasl_getprop(c->sasl_conn, SASL_USERNAME, &uname);
+            rv = uname != NULL;
+        }
+    }
+
+    if (settings.verbose > 1) {
+        fprintf(stderr, "authenticated() in cmd 0x%02x is %s\n",
+                c->cmd, rv ? "true" : "false");
+    }
+
+    return rv;
+}
+
+static void dispatch_bin_command(conn *c) {
+    int protocol_error = 0;
+
+    int extlen = c->binary_header.request.extlen;
+    int keylen = c->binary_header.request.keylen;
+    uint32_t bodylen = c->binary_header.request.bodylen;
+
+    if (settings.sasl && !authenticated(c)) {
+        write_bin_error(c, PROTOCOL_BINARY_RESPONSE_AUTH_ERROR, 0);
+        c->write_and_go = conn_closing;
+        return;
+    }
+
+    MEMCACHED_PROCESS_COMMAND_START(c->sfd, c->rcurr, c->rbytes);
+    c->noreply = true;
+
+    /* binprot supports 16bit keys, but internals are still 8bit */
+    if (keylen > KEY_MAX_LENGTH) {
+        handle_binary_protocol_error(c);
+        return;
+    }
+
+    switch (c->cmd) {
+    case PROTOCOL_BINARY_CMD_SETQ:
+        c->cmd = PROTOCOL_BINARY_CMD_SET;
+        break;
+    case PROTOCOL_BINARY_CMD_ADDQ:
+        c->cmd = PROTOCOL_BINARY_CMD_ADD;
+        break;
+    case PROTOCOL_BINARY_CMD_REPLACEQ:
+        c->cmd = PROTOCOL_BINARY_CMD_REPLACE;
+        break;
+    case PROTOCOL_BINARY_CMD_DELETEQ:
+        c->cmd = PROTOCOL_BINARY_CMD_DELETE;
+        break;
+    case PROTOCOL_BINARY_CMD_INCREMENTQ:
+        c->cmd = PROTOCOL_BINARY_CMD_INCREMENT;
+        break;
+    case PROTOCOL_BINARY_CMD_DECREMENTQ:
+        c->cmd = PROTOCOL_BINARY_CMD_DECREMENT;
+        break;
+    case PROTOCOL_BINARY_CMD_QUITQ:
+        c->cmd = PROTOCOL_BINARY_CMD_QUIT;
+        break;
+    case PROTOCOL_BINARY_CMD_FLUSHQ:
+        c->cmd = PROTOCOL_BINARY_CMD_FLUSH;
+        break;
+    case PROTOCOL_BINARY_CMD_APPENDQ:
+        c->cmd = PROTOCOL_BINARY_CMD_APPEND;
+        break;
+    case PROTOCOL_BINARY_CMD_PREPENDQ:
+        c->cmd = PROTOCOL_BINARY_CMD_PREPEND;
+        break;
+    case PROTOCOL_BINARY_CMD_GETQ:
+        c->cmd = PROTOCOL_BINARY_CMD_GET;
+        break;
+    case PROTOCOL_BINARY_CMD_GETKQ:
+        c->cmd = PROTOCOL_BINARY_CMD_GETK;
+        break;
+    case PROTOCOL_BINARY_CMD_GATQ:
+        c->cmd = PROTOCOL_BINARY_CMD_GAT;
+        break;
+    case PROTOCOL_BINARY_CMD_GATKQ:
+        c->cmd = PROTOCOL_BINARY_CMD_GAT;
+        break;
+    default:
+        c->noreply = false;
+    }
+
+    switch (c->cmd) {
+        case PROTOCOL_BINARY_CMD_VERSION:
+            if (extlen == 0 && keylen == 0 && bodylen == 0) {
+                write_bin_response(c, VERSION, 0, 0, strlen(VERSION));
+            } else {
+                protocol_error = 1;
+            }
+            break;
+        case PROTOCOL_BINARY_CMD_FLUSH:
+            if (keylen == 0 && bodylen == extlen && (extlen == 0 || extlen == 4)) {
+                bin_read_key(c, bin_read_flush_exptime, extlen);
+            } else {
+                protocol_error = 1;
+            }
+            break;
+        case PROTOCOL_BINARY_CMD_NOOP:
+            if (extlen == 0 && keylen == 0 && bodylen == 0) {
+                write_bin_response(c, NULL, 0, 0, 0);
+            } else {
+                protocol_error = 1;
+            }
+            break;
+        case PROTOCOL_BINARY_CMD_SET: /* FALLTHROUGH */
+        case PROTOCOL_BINARY_CMD_ADD: /* FALLTHROUGH */
+        case PROTOCOL_BINARY_CMD_REPLACE:
+            if (extlen == 8 && keylen != 0 && bodylen >= (keylen + 8)) {
+                bin_read_key(c, bin_reading_set_header, 8);
+            } else {
+                protocol_error = 1;
+            }
+            break;
+        case PROTOCOL_BINARY_CMD_GETQ:  /* FALLTHROUGH */
+        case PROTOCOL_BINARY_CMD_GET:   /* FALLTHROUGH */
+        case PROTOCOL_BINARY_CMD_GETKQ: /* FALLTHROUGH */
+        case PROTOCOL_BINARY_CMD_GETK:
+            if (extlen == 0 && bodylen == keylen && keylen > 0) {
+                bin_read_key(c, bin_reading_get_key, 0);
+            } else {
+                protocol_error = 1;
+            }
+            break;
+        case PROTOCOL_BINARY_CMD_DELETE:
+            if (keylen > 0 && extlen == 0 && bodylen == keylen) {
+                bin_read_key(c, bin_reading_del_header, extlen);
+            } else {
+                protocol_error = 1;
+            }
+            break;
+        case PROTOCOL_BINARY_CMD_INCREMENT:
+        case PROTOCOL_BINARY_CMD_DECREMENT:
+            if (keylen > 0 && extlen == 20 && bodylen == (keylen + extlen)) {
+                bin_read_key(c, bin_reading_incr_header, 20);
+            } else {
+                protocol_error = 1;
+            }
+            break;
+        case PROTOCOL_BINARY_CMD_APPEND:
+        case PROTOCOL_BINARY_CMD_PREPEND:
+            if (keylen > 0 && extlen == 0) {
+                bin_read_key(c, bin_reading_set_header, 0);
+            } else {
+                protocol_error = 1;
+            }
+            break;
+        case PROTOCOL_BINARY_CMD_STAT:
+            if (extlen == 0) {
+                bin_read_key(c, bin_reading_stat, 0);
+            } else {
+                protocol_error = 1;
+            }
+            break;
+        case PROTOCOL_BINARY_CMD_QUIT:
+            if (keylen == 0 && extlen == 0 && bodylen == 0) {
+                write_bin_response(c, NULL, 0, 0, 0);
+                c->write_and_go = conn_closing;
+                if (c->noreply) {
+                    conn_set_state(c, conn_closing);
+                }
+            } else {
+                protocol_error = 1;
+            }
+            break;
+        case PROTOCOL_BINARY_CMD_SASL_LIST_MECHS:
+            if (extlen == 0 && keylen == 0 && bodylen == 0) {
+                bin_list_sasl_mechs(c);
+            } else {
+                protocol_error = 1;
+            }
+            break;
+        case PROTOCOL_BINARY_CMD_SASL_AUTH:
+        case PROTOCOL_BINARY_CMD_SASL_STEP:
+            if (extlen == 0 && keylen != 0) {
+                bin_read_key(c, bin_reading_sasl_auth, 0);
+            } else {
+                protocol_error = 1;
+            }
+            break;
+        case PROTOCOL_BINARY_CMD_TOUCH:
+        case PROTOCOL_BINARY_CMD_GAT:
+        case PROTOCOL_BINARY_CMD_GATQ:
+        case PROTOCOL_BINARY_CMD_GATK:
+        case PROTOCOL_BINARY_CMD_GATKQ:
+            if (extlen == 4 && keylen != 0) {
+                bin_read_key(c, bin_reading_touch_key, 4);
+            } else {
+                protocol_error = 1;
+            }
+            break;
+        default:
+            write_bin_error(c, PROTOCOL_BINARY_RESPONSE_UNKNOWN_COMMAND, bodylen);
+    }
+
+    if (protocol_error)
+        handle_binary_protocol_error(c);
+}
+
+static void process_bin_update(conn *c) {
+    char *key;
+    int nkey;
+    int vlen;
+    item *it;
+    protocol_binary_request_set* req = binary_get_request(c);
+
+    assert(c != NULL);
+
+    key = binary_get_key(c);
+    nkey = c->binary_header.request.keylen;
+
+    /* fix byteorder in the request */
+    req->message.body.flags = ntohl(req->message.body.flags);
+    req->message.body.expiration = ntohl(req->message.body.expiration);
+
+    vlen = c->binary_header.request.bodylen - (nkey + c->binary_header.request.extlen);
+
+    if (settings.verbose > 1) {
+        int ii;
+        if (c->cmd == PROTOCOL_BINARY_CMD_ADD) {
+            fprintf(stderr, "<%d ADD ", c->sfd);
+        } else if (c->cmd == PROTOCOL_BINARY_CMD_SET) {
+            fprintf(stderr, "<%d SET ", c->sfd);
+        } else {
+            fprintf(stderr, "<%d REPLACE ", c->sfd);
+        }
+        for (ii = 0; ii < nkey; ++ii) {
+            fprintf(stderr, "%c", key[ii]);
+        }
+
+        fprintf(stderr, " Value len is %d", vlen);
+        fprintf(stderr, "\n");
+    }
+
+    if (settings.detail_enabled) {
+        stats_prefix_record_set(key, nkey);
+    }
+
+    it = item_alloc(key, nkey, req->message.body.flags,
+            realtime(req->message.body.expiration), vlen+2);
+
+    if (it == 0) {
+        if (! item_size_ok(nkey, req->message.body.flags, vlen + 2)) {
+            write_bin_error(c, PROTOCOL_BINARY_RESPONSE_E2BIG, vlen);
+        } else {
+            write_bin_error(c, PROTOCOL_BINARY_RESPONSE_ENOMEM, vlen);
+        }
+
+        /* Avoid stale data persisting in cache because we failed alloc.
+         * Unacceptable for SET. Anywhere else too? */
+        if (c->cmd == PROTOCOL_BINARY_CMD_SET) {
+            it = item_get(key, nkey);
+            if (it) {
+                item_unlink(it);
+                item_remove(it);
+            }
+        }
+
+        /* swallow the data line */
+        c->write_and_go = conn_swallow;
+        return;
+    }
+
+    ITEM_set_cas(it, c->binary_header.request.cas);
+
+    switch (c->cmd) {
+        case PROTOCOL_BINARY_CMD_ADD:
+            c->cmd = NREAD_ADD;
+            break;
+        case PROTOCOL_BINARY_CMD_SET:
+            c->cmd = NREAD_SET;
+            break;
+        case PROTOCOL_BINARY_CMD_REPLACE:
+            c->cmd = NREAD_REPLACE;
+            break;
+        default:
+            assert(0);
+    }
+
+    if (ITEM_get_cas(it) != 0) {
+        c->cmd = NREAD_CAS;
+    }
+
+    c->item = it;
+    c->ritem = ITEM_data(it);
+    c->rlbytes = vlen;
+    conn_set_state(c, conn_nread);
+    c->substate = bin_read_set_value;
+}
+
+static void process_bin_append_prepend(conn *c) {
+    char *key;
+    int nkey;
+    int vlen;
+    item *it;
+
+    assert(c != NULL);
+
+    key = binary_get_key(c);
+    nkey = c->binary_header.request.keylen;
+    vlen = c->binary_header.request.bodylen - nkey;
+
+    if (settings.verbose > 1) {
+        fprintf(stderr, "Value len is %d\n", vlen);
+    }
+
+    if (settings.detail_enabled) {
+        stats_prefix_record_set(key, nkey);
+    }
+
+    it = item_alloc(key, nkey, 0, 0, vlen+2);
+
+    if (it == 0) {
+        if (! item_size_ok(nkey, 0, vlen + 2)) {
+            write_bin_error(c, PROTOCOL_BINARY_RESPONSE_E2BIG, vlen);
+        } else {
+            write_bin_error(c, PROTOCOL_BINARY_RESPONSE_ENOMEM, vlen);
+        }
+        /* swallow the data line */
+        c->write_and_go = conn_swallow;
+        return;
+    }
+
+    ITEM_set_cas(it, c->binary_header.request.cas);
+
+    switch (c->cmd) {
+        case PROTOCOL_BINARY_CMD_APPEND:
+            c->cmd = NREAD_APPEND;
+            break;
+        case PROTOCOL_BINARY_CMD_PREPEND:
+            c->cmd = NREAD_PREPEND;
+            break;
+        default:
+            assert(0);
+    }
+
+    c->item = it;
+    c->ritem = ITEM_data(it);
+    c->rlbytes = vlen;
+    conn_set_state(c, conn_nread);
+    c->substate = bin_read_set_value;
+}
+
+static void process_bin_flush(conn *c) {
+    time_t exptime = 0;
+    protocol_binary_request_flush* req = binary_get_request(c);
+
+    if (c->binary_header.request.extlen == sizeof(req->message.body)) {
+        exptime = ntohl(req->message.body.expiration);
+    }
+
+    if (exptime > 0) {
+        settings.oldest_live = realtime(exptime) - 1;
+    } else {
+        settings.oldest_live = current_time - 1;
+    }
+    item_flush_expired();
+
+    pthread_mutex_lock(&c->thread->stats.mutex);
+    c->thread->stats.flush_cmds++;
+    pthread_mutex_unlock(&c->thread->stats.mutex);
+
+    write_bin_response(c, NULL, 0, 0, 0);
+}
+
+static void process_bin_delete(conn *c) {
+    item *it;
+
+    protocol_binary_request_delete* req = binary_get_request(c);
+
+    char* key = binary_get_key(c);
+    size_t nkey = c->binary_header.request.keylen;
+
+    assert(c != NULL);
+
+    if (settings.verbose > 1) {
+        fprintf(stderr, "Deleting %s\n", key);
+    }
+
+    if (settings.detail_enabled) {
+        stats_prefix_record_delete(key, nkey);
+    }
+
+    it = item_get(key, nkey);
+    if (it) {
+        uint64_t cas = ntohll(req->message.header.request.cas);
+        if (cas == 0 || cas == ITEM_get_cas(it)) {
+            MEMCACHED_COMMAND_DELETE(c->sfd, ITEM_key(it), it->nkey);
+            pthread_mutex_lock(&c->thread->stats.mutex);
+            c->thread->stats.slab_stats[it->slabs_clsid].delete_hits++;
+            pthread_mutex_unlock(&c->thread->stats.mutex);
+            item_unlink(it);
+            write_bin_response(c, NULL, 0, 0, 0);
+        } else {
+            write_bin_error(c, PROTOCOL_BINARY_RESPONSE_KEY_EEXISTS, 0);
+        }
+        item_remove(it);      /* release our reference */
+    } else {
+        write_bin_error(c, PROTOCOL_BINARY_RESPONSE_KEY_ENOENT, 0);
+        pthread_mutex_lock(&c->thread->stats.mutex);
+        c->thread->stats.delete_misses++;
+        pthread_mutex_unlock(&c->thread->stats.mutex);
+    }
+}
+
+static void complete_nread_binary(conn *c) {
+    assert(c != NULL);
+    assert(c->cmd >= 0);
+
+    switch(c->substate) {
+    case bin_reading_set_header:
+        if (c->cmd == PROTOCOL_BINARY_CMD_APPEND ||
+                c->cmd == PROTOCOL_BINARY_CMD_PREPEND) {
+            process_bin_append_prepend(c);
+        } else {
+            process_bin_update(c);
+        }
+        break;
+    case bin_read_set_value:
+        complete_update_bin(c);
+        break;
+    case bin_reading_get_key:
+        process_bin_get(c);
+        break;
+    case bin_reading_touch_key:
+        process_bin_touch(c);
+        break;
+    case bin_reading_stat:
+        process_bin_stat(c);
+        break;
+    case bin_reading_del_header:
+        process_bin_delete(c);
+        break;
+    case bin_reading_incr_header:
+        complete_incr_bin(c);
+        break;
+    case bin_read_flush_exptime:
+        process_bin_flush(c);
+        break;
+    case bin_reading_sasl_auth:
+        process_bin_sasl_auth(c);
+        break;
+    case bin_reading_sasl_auth_data:
+        process_bin_complete_sasl_auth(c);
+        break;
+    case bin_reading_cas_header:
+        assert(0);
+    case bin_no_state:
+        assert(0);
+    default:
+        fprintf(stderr, "Not handling substate %d\n", c->substate);
+        assert(0);
+    }
+}
+
+static void reset_cmd_handler(conn *c) {
+    c->cmd = -1;
+    c->substate = bin_no_state;
+    if(c->item != NULL) {
+        item_remove(c->item);
+        c->item = NULL;
+    }
+    conn_shrink(c);
+    if (c->rbytes > 0) {
+        conn_set_state(c, conn_parse_cmd);
+    } else {
+        conn_set_state(c, conn_waiting);
+    }
+}
+
+static void complete_nread(conn *c) {
+    assert(c != NULL);
+    assert(c->protocol == ascii_prot
+           || c->protocol == binary_prot);
+
+    if (c->protocol == ascii_prot) {
+        complete_nread_ascii(c);
+    } else if (c->protocol == binary_prot) {
+        complete_nread_binary(c);
+    }
+}
+
+/*
+ * Stores an item in the cache according to the semantics of one of the set
+ * commands. In threaded mode, this is protected by the cache lock.
+ *
+ * Returns the state of storage.
+ */
+enum store_item_type do_store_item(item *it, int comm, conn *c, const uint32_t hv) {
+    char *key = ITEM_key(it);
+    item *old_it = do_item_get(key, it->nkey, hv);
+    enum store_item_type stored = NOT_STORED;
+
+    item *new_it = NULL;
+    int flags;
+
+    if (old_it != NULL && comm == NREAD_ADD) {
+        /* add only adds a nonexistent item, but promote to head of LRU */
+        do_item_update(old_it);
+    } else if (!old_it && (comm == NREAD_REPLACE
+        || comm == NREAD_APPEND || comm == NREAD_PREPEND))
+    {
+        /* replace only replaces an existing value; don't store */
+    } else if (comm == NREAD_CAS) {
+        /* validate cas operation */
+        if(old_it == NULL) {
+            // LRU expired
+            stored = NOT_FOUND;
+            pthread_mutex_lock(&c->thread->stats.mutex);
+            c->thread->stats.cas_misses++;
+            pthread_mutex_unlock(&c->thread->stats.mutex);
+        }
+        else if (ITEM_get_cas(it) == ITEM_get_cas(old_it)) {
+            // cas validates
+            // it and old_it may belong to different classes.
+            // I'm updating the stats for the one that's getting pushed out
+            pthread_mutex_lock(&c->thread->stats.mutex);
+            c->thread->stats.slab_stats[old_it->slabs_clsid].cas_hits++;
+            pthread_mutex_unlock(&c->thread->stats.mutex);
+
+            item_replace(old_it, it, hv);
+            stored = STORED;
+        } else {
+            pthread_mutex_lock(&c->thread->stats.mutex);
+            c->thread->stats.slab_stats[old_it->slabs_clsid].cas_badval++;
+            pthread_mutex_unlock(&c->thread->stats.mutex);
+
+            if(settings.verbose > 1) {
+                fprintf(stderr, "CAS:  failure: expected %llu, got %llu\n",
+                        (unsigned long long)ITEM_get_cas(old_it),
+                        (unsigned long long)ITEM_get_cas(it));
+            }
+            stored = EXISTS;
+        }
+    } else {
+        /*
+         * Append - combine new and old record into single one. Here it's
+         * atomic and thread-safe.
+         */
+        if (comm == NREAD_APPEND || comm == NREAD_PREPEND) {
+            /*
+             * Validate CAS
+             */
+            if (ITEM_get_cas(it) != 0) {
+                // CAS much be equal
+                if (ITEM_get_cas(it) != ITEM_get_cas(old_it)) {
+                    stored = EXISTS;
+                }
+            }
+
+            if (stored == NOT_STORED) {
+                /* we have it and old_it here - alloc memory to hold both */
+                /* flags was already lost - so recover them from ITEM_suffix(it) */
+
+                flags = (int) strtol(ITEM_suffix(old_it), (char **) NULL, 10);
+
+                new_it = item_alloc(key, it->nkey, flags, old_it->exptime, it->nbytes + old_it->nbytes - 2 /* CRLF */);
+
+                if (new_it == NULL) {
+                    /* SERVER_ERROR out of memory */
+                    if (old_it != NULL)
+                        do_item_remove(old_it);
+
+                    return NOT_STORED;
+                }
+
+                /* copy data from it and old_it to new_it */
+
+                if (comm == NREAD_APPEND) {
+                    memcpy(ITEM_data(new_it), ITEM_data(old_it), old_it->nbytes);
+                    memcpy(ITEM_data(new_it) + old_it->nbytes - 2 /* CRLF */, ITEM_data(it), it->nbytes);
+                } else {
+                    /* NREAD_PREPEND */
+                    memcpy(ITEM_data(new_it), ITEM_data(it), it->nbytes);
+                    memcpy(ITEM_data(new_it) + it->nbytes - 2 /* CRLF */, ITEM_data(old_it), old_it->nbytes);
+                }
+
+                it = new_it;
+            }
+        }
+
+        if (stored == NOT_STORED) {
+            if (old_it != NULL)
+                item_replace(old_it, it, hv);
+            else
+                do_item_link(it, hv);
+
+            c->cas = ITEM_get_cas(it);
+
+            stored = STORED;
+        }
+    }
+
+    if (old_it != NULL)
+        do_item_remove(old_it);         /* release our reference */
+    if (new_it != NULL)
+        do_item_remove(new_it);
+
+    if (stored == STORED) {
+        c->cas = ITEM_get_cas(it);
+    }
+
+    return stored;
+}
+
+typedef struct token_s {
+    char *value;
+    size_t length;
+} token_t;
+
+#define COMMAND_TOKEN 0
+#define SUBCOMMAND_TOKEN 1
+#define KEY_TOKEN 1
+
+#define MAX_TOKENS 8
+
+/*
+ * Tokenize the command string by replacing whitespace with '\0' and update
+ * the token array tokens with pointer to start of each token and length.
+ * Returns total number of tokens.  The last valid token is the terminal
+ * token (value points to the first unprocessed character of the string and
+ * length zero).
+ *
+ * Usage example:
+ *
+ *  while(tokenize_command(command, ncommand, tokens, max_tokens) > 0) {
+ *      for(int ix = 0; tokens[ix].length != 0; ix++) {
+ *          ...
+ *      }
+ *      ncommand = tokens[ix].value - command;
+ *      command  = tokens[ix].value;
+ *   }
+ */
+static size_t tokenize_command(char *command, token_t *tokens, const size_t max_tokens) {
+    char *s, *e;
+    size_t ntokens = 0;
+    size_t len = strlen(command);
+    unsigned int i = 0;
+
+    assert(command != NULL && tokens != NULL && max_tokens > 1);
+
+    s = e = command;
+    for (i = 0; i < len; i++) {
+        if (*e == ' ') {
+            if (s != e) {
+                tokens[ntokens].value = s;
+                tokens[ntokens].length = e - s;
+                ntokens++;
+                *e = '\0';
+                if (ntokens == max_tokens - 1) {
+                    e++;
+                    s = e; /* so we don't add an extra token */
+                    break;
+                }
+            }
+            s = e + 1;
+        }
+        e++;
+    }
+
+    if (s != e) {
+        tokens[ntokens].value = s;
+        tokens[ntokens].length = e - s;
+        ntokens++;
+    }
+
+    /*
+     * If we scanned the whole string, the terminal value pointer is null,
+     * otherwise it is the first unprocessed character.
+     */
+    tokens[ntokens].value =  *e == '\0' ? NULL : e;
+    tokens[ntokens].length = 0;
+    ntokens++;
+
+    return ntokens;
+}
+
+/* set up a connection to write a buffer then free it, used for stats */
+static void write_and_free(conn *c, char *buf, int bytes) {
+    if (buf) {
+        c->write_and_free = buf;
+        c->wcurr = buf;
+        c->wbytes = bytes;
+        conn_set_state(c, conn_write);
+        c->write_and_go = conn_new_cmd;
+    } else {
+        out_string(c, "SERVER_ERROR out of memory writing stats");
+    }
+}
+
+static inline bool set_noreply_maybe(conn *c, token_t *tokens, size_t ntokens)
+{
+    int noreply_index = ntokens - 2;
+
+    /*
+      NOTE: this function is not the first place where we are going to
+      send the reply.  We could send it instead from process_command()
+      if the request line has wrong number of tokens.  However parsing
+      malformed line for "noreply" option is not reliable anyway, so
+      it can't be helped.
+    */
+    if (tokens[noreply_index].value
+        && strcmp(tokens[noreply_index].value, "noreply") == 0) {
+        c->noreply = true;
+    }
+    return c->noreply;
+}
+
+void append_stat(const char *name, ADD_STAT add_stats, conn *c,
+                 const char *fmt, ...) {
+    char val_str[STAT_VAL_LEN];
+    int vlen;
+    va_list ap;
+
+    assert(name);
+    assert(add_stats);
+    assert(c);
+    assert(fmt);
+
+    va_start(ap, fmt);
+    vlen = vsnprintf(val_str, sizeof(val_str) - 1, fmt, ap);
+    va_end(ap);
+
+    add_stats(name, strlen(name), val_str, vlen, c);
+}
+
+inline static void process_stats_detail(conn *c, const char *command) {
+    assert(c != NULL);
+
+    if (strcmp(command, "on") == 0) {
+        settings.detail_enabled = 1;
+        out_string(c, "OK");
+    }
+    else if (strcmp(command, "off") == 0) {
+        settings.detail_enabled = 0;
+        out_string(c, "OK");
+    }
+    else if (strcmp(command, "dump") == 0) {
+        int len;
+        char *stats = stats_prefix_dump(&len);
+        write_and_free(c, stats, len);
+    }
+    else {
+        out_string(c, "CLIENT_ERROR usage: stats detail on|off|dump");
+    }
+}
+
+/* return server specific stats only */
+static void server_stats(ADD_STAT add_stats, conn *c) {
+    pid_t pid = getpid();
+    rel_time_t now = current_time;
+
+    struct thread_stats thread_stats;
+    threadlocal_stats_aggregate(&thread_stats);
+    struct slab_stats slab_stats;
+    slab_stats_aggregate(&thread_stats, &slab_stats);
+
+#ifndef WIN32
+    struct rusage usage;
+    getrusage(RUSAGE_SELF, &usage);
+#endif /* !WIN32 */
+
+    STATS_LOCK();
+
+    APPEND_STAT("pid", "%lu", (long)pid);
+    APPEND_STAT("uptime", "%u", now);
+    APPEND_STAT("time", "%ld", now + (long)process_started);
+    APPEND_STAT("version", "%s", VERSION);
+    APPEND_STAT("libevent", "%s", event_get_version());
+    APPEND_STAT("pointer_size", "%d", (int)(8 * sizeof(void *)));
+
+#ifndef WIN32
+    append_stat("rusage_user", add_stats, c, "%ld.%06ld",
+                (long)usage.ru_utime.tv_sec,
+                (long)usage.ru_utime.tv_usec);
+    append_stat("rusage_system", add_stats, c, "%ld.%06ld",
+                (long)usage.ru_stime.tv_sec,
+                (long)usage.ru_stime.tv_usec);
+#endif /* !WIN32 */
+
+    APPEND_STAT("curr_connections", "%u", stats.curr_conns - 1);
+    APPEND_STAT("total_connections", "%u", stats.total_conns);
+    if (settings.maxconns_fast) {
+        APPEND_STAT("rejected_connections", "%llu", (unsigned long long)stats.rejected_conns);
+    }
+    APPEND_STAT("connection_structures", "%u", stats.conn_structs);
+    APPEND_STAT("reserved_fds", "%u", stats.reserved_fds);
+    APPEND_STAT("cmd_get", "%llu", (unsigned long long)thread_stats.get_cmds);
+    APPEND_STAT("cmd_set", "%llu", (unsigned long long)slab_stats.set_cmds);
+    APPEND_STAT("cmd_flush", "%llu", (unsigned long long)thread_stats.flush_cmds);
+    APPEND_STAT("cmd_touch", "%llu", (unsigned long long)thread_stats.touch_cmds);
+    APPEND_STAT("get_hits", "%llu", (unsigned long long)slab_stats.get_hits);
+    APPEND_STAT("get_misses", "%llu", (unsigned long long)thread_stats.get_misses);
+    APPEND_STAT("delete_misses", "%llu", (unsigned long long)thread_stats.delete_misses);
+    APPEND_STAT("delete_hits", "%llu", (unsigned long long)slab_stats.delete_hits);
+    APPEND_STAT("incr_misses", "%llu", (unsigned long long)thread_stats.incr_misses);
+    APPEND_STAT("incr_hits", "%llu", (unsigned long long)slab_stats.incr_hits);
+    APPEND_STAT("decr_misses", "%llu", (unsigned long long)thread_stats.decr_misses);
+    APPEND_STAT("decr_hits", "%llu", (unsigned long long)slab_stats.decr_hits);
+    APPEND_STAT("cas_misses", "%llu", (unsigned long long)thread_stats.cas_misses);
+    APPEND_STAT("cas_hits", "%llu", (unsigned long long)slab_stats.cas_hits);
+    APPEND_STAT("cas_badval", "%llu", (unsigned long long)slab_stats.cas_badval);
+    APPEND_STAT("touch_hits", "%llu", (unsigned long long)slab_stats.touch_hits);
+    APPEND_STAT("touch_misses", "%llu", (unsigned long long)thread_stats.touch_misses);
+    APPEND_STAT("auth_cmds", "%llu", (unsigned long long)thread_stats.auth_cmds);
+    APPEND_STAT("auth_errors", "%llu", (unsigned long long)thread_stats.auth_errors);
+    APPEND_STAT("bytes_read", "%llu", (unsigned long long)thread_stats.bytes_read);
+    APPEND_STAT("bytes_written", "%llu", (unsigned long long)thread_stats.bytes_written);
+    APPEND_STAT("limit_maxbytes", "%llu", (unsigned long long)settings.maxbytes);
+    APPEND_STAT("accepting_conns", "%u", stats.accepting_conns);
+    APPEND_STAT("listen_disabled_num", "%llu", (unsigned long long)stats.listen_disabled_num);
+    APPEND_STAT("threads", "%d", settings.num_threads);
+    APPEND_STAT("conn_yields", "%llu", (unsigned long long)thread_stats.conn_yields);
+    APPEND_STAT("hash_power_level", "%u", stats.hash_power_level);
+    APPEND_STAT("hash_bytes", "%llu", (unsigned long long)stats.hash_bytes);
+    APPEND_STAT("hash_is_expanding", "%u", stats.hash_is_expanding);
+    APPEND_STAT("expired_unfetched", "%llu", stats.expired_unfetched);
+    APPEND_STAT("evicted_unfetched", "%llu", stats.evicted_unfetched);
+    if (settings.slab_reassign) {
+        APPEND_STAT("slab_reassign_running", "%u", stats.slab_reassign_running);
+        APPEND_STAT("slabs_moved", "%llu", stats.slabs_moved);
+    }
+    STATS_UNLOCK();
+}
+
+static void process_stat_settings(ADD_STAT add_stats, void *c) {
+    assert(add_stats);
+    APPEND_STAT("maxbytes", "%u", (unsigned int)settings.maxbytes);
+    APPEND_STAT("maxconns", "%d", settings.maxconns);
+    APPEND_STAT("tcpport", "%d", settings.port);
+    APPEND_STAT("udpport", "%d", settings.udpport);
+    APPEND_STAT("inter", "%s", settings.inter ? settings.inter : "NULL");
+    APPEND_STAT("verbosity", "%d", settings.verbose);
+    APPEND_STAT("oldest", "%lu", (unsigned long)settings.oldest_live);
+    APPEND_STAT("evictions", "%s", settings.evict_to_free ? "on" : "off");
+    APPEND_STAT("domain_socket", "%s",
+                settings.socketpath ? settings.socketpath : "NULL");
+    APPEND_STAT("umask", "%o", settings.access);
+    APPEND_STAT("growth_factor", "%.2f", settings.factor);
+    APPEND_STAT("chunk_size", "%d", settings.chunk_size);
+    APPEND_STAT("num_threads", "%d", settings.num_threads);
+    APPEND_STAT("num_threads_per_udp", "%d", settings.num_threads_per_udp);
+    APPEND_STAT("stat_key_prefix", "%c", settings.prefix_delimiter);
+    APPEND_STAT("detail_enabled", "%s",
+                settings.detail_enabled ? "yes" : "no");
+    APPEND_STAT("reqs_per_event", "%d", settings.reqs_per_event);
+    APPEND_STAT("cas_enabled", "%s", settings.use_cas ? "yes" : "no");
+    APPEND_STAT("tcp_backlog", "%d", settings.backlog);
+    APPEND_STAT("binding_protocol", "%s",
+                prot_text(settings.binding_protocol));
+    APPEND_STAT("auth_enabled_sasl", "%s", settings.sasl ? "yes" : "no");
+    APPEND_STAT("item_size_max", "%d", settings.item_size_max);
+    APPEND_STAT("maxconns_fast", "%s", settings.maxconns_fast ? "yes" : "no");
+    APPEND_STAT("hashpower_init", "%d", settings.hashpower_init);
+    APPEND_STAT("slab_reassign", "%s", settings.slab_reassign ? "yes" : "no");
+    APPEND_STAT("slab_automove", "%s", settings.slab_automove ? "yes" : "no");
+}
+
+static void process_stat(conn *c, token_t *tokens, const size_t ntokens) {
+    const char *subcommand = tokens[SUBCOMMAND_TOKEN].value;
+    assert(c != NULL);
+
+    if (ntokens < 2) {
+        out_string(c, "CLIENT_ERROR bad command line");
+        return;
+    }
+
+    if (ntokens == 2) {
+        server_stats(&append_stats, c);
+        (void)get_stats(NULL, 0, &append_stats, c);
+    } else if (strcmp(subcommand, "reset") == 0) {
+        stats_reset();
+        out_string(c, "RESET");
+        return ;
+    } else if (strcmp(subcommand, "detail") == 0) {
+        /* NOTE: how to tackle detail with binary? */
+        if (ntokens < 4)
+            process_stats_detail(c, "");  /* outputs the error message */
+        else
+            process_stats_detail(c, tokens[2].value);
+        /* Output already generated */
+        return ;
+    } else if (strcmp(subcommand, "settings") == 0) {
+        process_stat_settings(&append_stats, c);
+    } else if (strcmp(subcommand, "cachedump") == 0) {
+        char *buf;
+        unsigned int bytes, id, limit = 0;
+
+        if (ntokens < 5) {
+            out_string(c, "CLIENT_ERROR bad command line");
+            return;
+        }
+
+        if (!safe_strtoul(tokens[2].value, &id) ||
+            !safe_strtoul(tokens[3].value, &limit)) {
+            out_string(c, "CLIENT_ERROR bad command line format");
+            return;
+        }
+
+        if (id >= POWER_LARGEST) {
+            out_string(c, "CLIENT_ERROR Illegal slab id");
+            return;
+        }
+
+        buf = item_cachedump(id, limit, &bytes);
+        write_and_free(c, buf, bytes);
+        return ;
+    } else {
+        /* getting here means that the subcommand is either engine specific or
+           is invalid. query the engine and see. */
+        if (get_stats(subcommand, strlen(subcommand), &append_stats, c)) {
+            if (c->stats.buffer == NULL) {
+                out_string(c, "SERVER_ERROR out of memory writing stats");
+            } else {
+                write_and_free(c, c->stats.buffer, c->stats.offset);
+                c->stats.buffer = NULL;
+            }
+        } else {
+            out_string(c, "ERROR");
+        }
+        return ;
+    }
+
+    /* append terminator and start the transfer */
+    append_stats(NULL, 0, NULL, 0, c);
+
+    if (c->stats.buffer == NULL) {
+        out_string(c, "SERVER_ERROR out of memory writing stats");
+    } else {
+        write_and_free(c, c->stats.buffer, c->stats.offset);
+        c->stats.buffer = NULL;
+    }
+}
+
+#ifndef __INTEL_COMPILER
+#pragma GCC diagnostic ignored "-Wunused-but-set-parameter"
+#endif
+/* ntokens is overwritten here... shrug.. */
+static inline void process_get_command(conn *c, token_t *tokens, size_t ntokens, bool return_cas) {
+    char *key;
+    size_t nkey;
+    int i = 0;
+    item *it;
+    token_t *key_token = &tokens[KEY_TOKEN];
+    char *suffix;
+    assert(c != NULL);
+
+    do {
+        while(key_token->length != 0) {
+
+            key = key_token->value;
+            nkey = key_token->length;
+
+            if(nkey > KEY_MAX_LENGTH) {
+                out_string(c, "CLIENT_ERROR bad command line format");
+                return;
+            }
+
+            it = item_get(key, nkey);
+            if (settings.detail_enabled) {
+                stats_prefix_record_get(key, nkey, NULL != it);
+            }
+            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 {
+                        item_remove(it);
+                        break;
+                    }
+                }
+
+                /*
+                 * Construct the response. Each hit adds three elements to the
+                 * outgoing data list:
+                 *   "VALUE "
+                 *   key
+                 *   " " + flags + " " + data length + "\r\n" + data (with \r\n)
+                 */
+
+                if (return_cas)
+                {
+                  MEMCACHED_COMMAND_GET(c->sfd, ITEM_key(it), it->nkey,
+                                        it->nbytes, ITEM_get_cas(it));
+                  /* Goofy mid-flight realloc. */
+                  if (i >= c->suffixsize) {
+                    char **new_suffix_list = realloc(c->suffixlist,
+                                           sizeof(char *) * c->suffixsize * 2);
+                    if (new_suffix_list) {
+                        c->suffixsize *= 2;
+                        c->suffixlist  = new_suffix_list;
+                    } else {
+                        item_remove(it);
+                        break;
+                    }
+                  }
+
+                  suffix = cache_alloc(c->thread->suffix_cache);
+                  if (suffix == NULL) {
+                    out_string(c, "SERVER_ERROR out of memory making CAS suffix");
+                    item_remove(it);
+                    return;
+                  }
+                  *(c->suffixlist + i) = suffix;
+                  int suffix_len = snprintf(suffix, SUFFIX_SIZE,
+                                            " %llu\r\n",
+                                            (unsigned long long)ITEM_get_cas(it));
+                  if (add_iov(c, "VALUE ", 6) != 0 ||
+                      add_iov(c, ITEM_key(it), it->nkey) != 0 ||
+                      add_iov(c, ITEM_suffix(it), it->nsuffix - 2) != 0 ||
+                      add_iov(c, suffix, suffix_len) != 0 ||
+                      add_iov(c, ITEM_data(it), it->nbytes) != 0)
+                      {
+                          item_remove(it);
+                          break;
+                      }
+                }
+                else
+                {
+                  MEMCACHED_COMMAND_GET(c->sfd, ITEM_key(it), it->nkey,
+                                        it->nbytes, ITEM_get_cas(it));
+                  if (add_iov(c, "VALUE ", 6) != 0 ||
+                      add_iov(c, ITEM_key(it), it->nkey) != 0 ||
+                      add_iov(c, ITEM_suffix(it), it->nsuffix + it->nbytes) != 0)
+                      {
+                          item_remove(it);
+                          break;
+                      }
+                }
+
+
+                if (settings.verbose > 1)
+                    fprintf(stderr, ">%d sending key %s\n", c->sfd, ITEM_key(it));
+
+                /* item_get() has incremented it->refcount for us */
+                pthread_mutex_lock(&c->thread->stats.mutex);
+                c->thread->stats.slab_stats[it->slabs_clsid].get_hits++;
+                c->thread->stats.get_cmds++;
+                pthread_mutex_unlock(&c->thread->stats.mutex);
+                item_update(it);
+                *(c->ilist + i) = it;
+                i++;
+
+            } else {
+                pthread_mutex_lock(&c->thread->stats.mutex);
+                c->thread->stats.get_misses++;
+                c->thread->stats.get_cmds++;
+                pthread_mutex_unlock(&c->thread->stats.mutex);
+                MEMCACHED_COMMAND_GET(c->sfd, key, nkey, -1, 0);
+            }
+
+            key_token++;
+        }
+
+        /*
+         * If the command string hasn't been fully processed, get the next set
+         * of tokens.
+         */
+        if(key_token->value != NULL) {
+            ntokens = tokenize_command(key_token->value, tokens, MAX_TOKENS);
+            key_token = tokens;
+        }
+
+    } while(key_token->value != NULL);
+
+    c->icurr = c->ilist;
+    c->ileft = i;
+    if (return_cas) {
+        c->suffixcurr = c->suffixlist;
+        c->suffixleft = i;
+    }
+
+    if (settings.verbose > 1)
+        fprintf(stderr, ">%d END\n", c->sfd);
+
+    /*
+        If the loop was terminated because of out-of-memory, it is not
+        reliable to add END\r\n to the buffer, because it might not end
+        in \r\n. So we send SERVER_ERROR instead.
+    */
+    if (key_token->value != NULL || add_iov(c, "END\r\n", 5) != 0
+        || (IS_UDP(c->transport) && build_udp_headers(c) != 0)) {
+        out_string(c, "SERVER_ERROR out of memory writing get response");
+    }
+    else {
+        conn_set_state(c, conn_mwrite);
+        c->msgcurr = 0;
+    }
+
+    return;
+}
+
+static void process_update_command(conn *c, token_t *tokens, const size_t ntokens, int comm, bool handle_cas) {
+    char *key;
+    size_t nkey;
+    unsigned int flags;
+    int32_t exptime_int = 0;
+    time_t exptime;
+    int vlen;
+    uint64_t req_cas_id=0;
+    item *it;
+
+    assert(c != NULL);
+
+    set_noreply_maybe(c, tokens, ntokens);
+
+    if (tokens[KEY_TOKEN].length > KEY_MAX_LENGTH) {
+        out_string(c, "CLIENT_ERROR bad command line format");
+        return;
+    }
+
+    key = tokens[KEY_TOKEN].value;
+    nkey = tokens[KEY_TOKEN].length;
+
+    if (! (safe_strtoul(tokens[2].value, (uint32_t *)&flags)
+           && safe_strtol(tokens[3].value, &exptime_int)
+           && safe_strtol(tokens[4].value, (int32_t *)&vlen))) {
+        out_string(c, "CLIENT_ERROR bad command line format");
+        return;
+    }
+
+    /* Ubuntu 8.04 breaks when I pass exptime to safe_strtol */
+    exptime = exptime_int;
+
+    /* Negative exptimes can underflow and end up immortal. realtime() will
+       immediately expire values that are greater than REALTIME_MAXDELTA, but less
+       than process_started, so lets aim for that. */
+    if (exptime < 0)
+        exptime = REALTIME_MAXDELTA + 1;
+
+    // does cas value exist?
+    if (handle_cas) {
+        if (!safe_strtoull(tokens[5].value, &req_cas_id)) {
+            out_string(c, "CLIENT_ERROR bad command line format");
+            return;
+        }
+    }
+
+    vlen += 2;
+    if (vlen < 0 || vlen - 2 < 0) {
+        out_string(c, "CLIENT_ERROR bad command line format");
+        return;
+    }
+
+    if (settings.detail_enabled) {
+        stats_prefix_record_set(key, nkey);
+    }
+
+    it = item_alloc(key, nkey, flags, realtime(exptime), vlen);
+
+    if (it == 0) {
+        if (! item_size_ok(nkey, flags, vlen))
+            out_string(c, "SERVER_ERROR object too large for cache");
+        else
+            out_string(c, "SERVER_ERROR out of memory storing object");
+        /* swallow the data line */
+        c->write_and_go = conn_swallow;
+        c->sbytes = vlen;
+
+        /* Avoid stale data persisting in cache because we failed alloc.
+         * Unacceptable for SET. Anywhere else too? */
+        if (comm == NREAD_SET) {
+            it = item_get(key, nkey);
+            if (it) {
+                item_unlink(it);
+                item_remove(it);
+            }
+        }
+
+        return;
+    }
+    ITEM_set_cas(it, req_cas_id);
+
+    c->item = it;
+    c->ritem = ITEM_data(it);
+    c->rlbytes = it->nbytes;
+    c->cmd = comm;
+    conn_set_state(c, conn_nread);
+}
+
+static void process_touch_command(conn *c, token_t *tokens, const size_t ntokens) {
+    char *key;
+    size_t nkey;
+    int32_t exptime_int = 0;
+    item *it;
+
+    assert(c != NULL);
+
+    set_noreply_maybe(c, tokens, ntokens);
+
+    if (tokens[KEY_TOKEN].length > KEY_MAX_LENGTH) {
+        out_string(c, "CLIENT_ERROR bad command line format");
+        return;
+    }
+
+    key = tokens[KEY_TOKEN].value;
+    nkey = tokens[KEY_TOKEN].length;
+
+    if (!safe_strtol(tokens[2].value, &exptime_int)) {
+        out_string(c, "CLIENT_ERROR invalid exptime argument");
+        return;
+    }
+
+    it = item_touch(key, nkey, realtime(exptime_int));
+    if (it) {
+        item_update(it);
+        pthread_mutex_lock(&c->thread->stats.mutex);
+        c->thread->stats.touch_cmds++;
+        c->thread->stats.slab_stats[it->slabs_clsid].touch_hits++;
+        pthread_mutex_unlock(&c->thread->stats.mutex);
+
+        out_string(c, "TOUCHED");
+        item_remove(it);
+    } else {
+        pthread_mutex_lock(&c->thread->stats.mutex);
+        c->thread->stats.touch_cmds++;
+        c->thread->stats.touch_misses++;
+        pthread_mutex_unlock(&c->thread->stats.mutex);
+
+        out_string(c, "NOT_FOUND");
+    }
+}
+
+static void process_arithmetic_command(conn *c, token_t *tokens, const size_t ntokens, const bool incr) {
+    char temp[INCR_MAX_STORAGE_LEN];
+    uint64_t delta;
+    char *key;
+    size_t nkey;
+
+    assert(c != NULL);
+
+    set_noreply_maybe(c, tokens, ntokens);
+
+    if (tokens[KEY_TOKEN].length > KEY_MAX_LENGTH) {
+        out_string(c, "CLIENT_ERROR bad command line format");
+        return;
+    }
+
+    key = tokens[KEY_TOKEN].value;
+    nkey = tokens[KEY_TOKEN].length;
+
+    if (!safe_strtoull(tokens[2].value, &delta)) {
+        out_string(c, "CLIENT_ERROR invalid numeric delta argument");
+        return;
+    }
+
+    switch(add_delta(c, key, nkey, incr, delta, temp, NULL)) {
+    case OK:
+        out_string(c, temp);
+        break;
+    case NON_NUMERIC:
+        out_string(c, "CLIENT_ERROR cannot increment or decrement non-numeric value");
+        break;
+    case EOM:
+        out_string(c, "SERVER_ERROR out of memory");
+        break;
+    case DELTA_ITEM_NOT_FOUND:
+        pthread_mutex_lock(&c->thread->stats.mutex);
+        if (incr) {
+            c->thread->stats.incr_misses++;
+        } else {
+            c->thread->stats.decr_misses++;
+        }
+        pthread_mutex_unlock(&c->thread->stats.mutex);
+
+        out_string(c, "NOT_FOUND");
+        break;
+    case DELTA_ITEM_CAS_MISMATCH:
+        break; /* Should never get here */
+    default:
+        assert(false);
+        abort();
+    }
+}
+
+/*
+ * adds a delta value to a numeric item.
+ *
+ * c     connection requesting the operation
+ * it    item to adjust
+ * incr  true to increment value, false to decrement
+ * delta amount to adjust value by
+ * buf   buffer for response string
+ *
+ * returns a response string to send back to the client.
+ */
+enum delta_result_type do_add_delta(conn *c, const char *key, const size_t nkey,
+                                    const bool incr, const int64_t delta,
+                                    char *buf, uint64_t *cas,
+                                    const uint32_t hv) {
+    char *ptr;
+    uint64_t value;
+    int res;
+    item *it;
+
+    it = do_item_get(key, nkey, hv);
+    if (!it) {
+        return DELTA_ITEM_NOT_FOUND;
+    }
+
+    if (cas != NULL && *cas != 0 && ITEM_get_cas(it) != *cas) {
+        do_item_remove(it);
+        return DELTA_ITEM_CAS_MISMATCH;
+    }
+
+    ptr = ITEM_data(it);
+
+    if (!safe_strtoull(ptr, &value)) {
+        do_item_remove(it);
+        return NON_NUMERIC;
+    }
+
+    if (incr) {
+        value += delta;
+        MEMCACHED_COMMAND_INCR(c->sfd, ITEM_key(it), it->nkey, value);
+    } else {
+        if(delta > value) {
+            value = 0;
+        } else {
+            value -= delta;
+        }
+        MEMCACHED_COMMAND_DECR(c->sfd, ITEM_key(it), it->nkey, value);
+    }
+
+    pthread_mutex_lock(&c->thread->stats.mutex);
+    if (incr) {
+        c->thread->stats.slab_stats[it->slabs_clsid].incr_hits++;
+    } else {
+        c->thread->stats.slab_stats[it->slabs_clsid].decr_hits++;
+    }
+    pthread_mutex_unlock(&c->thread->stats.mutex);
+
+    snprintf(buf, INCR_MAX_STORAGE_LEN, "%llu", (unsigned long long)value);
+    res = strlen(buf);
+    if (res + 2 > it->nbytes || it->refcount != 1) { /* need to realloc */
+        item *new_it;
+        new_it = item_alloc(ITEM_key(it), it->nkey, atoi(ITEM_suffix(it) + 1), it->exptime, res + 2 );
+        if (new_it == 0) {
+            do_item_remove(it);
+            return EOM;
+        }
+        memcpy(ITEM_data(new_it), buf, res);
+        memcpy(ITEM_data(new_it) + res, "\r\n", 2);
+        item_replace(it, new_it, hv);
+        // Overwrite the older item's CAS with our new CAS since we're
+        // returning the CAS of the old item below.
+        ITEM_set_cas(it, (settings.use_cas) ? ITEM_get_cas(new_it) : 0);
+        do_item_remove(new_it);       /* release our reference */
+    } else { /* replace in-place */
+        /* When changing the value without replacing the item, we
+           need to update the CAS on the existing item. */
+        mutex_lock(&cache_lock); /* FIXME */
+        ITEM_set_cas(it, (settings.use_cas) ? get_cas_id() : 0);
+        pthread_mutex_unlock(&cache_lock);
+
+        memcpy(ITEM_data(it), buf, res);
+        memset(ITEM_data(it) + res, ' ', it->nbytes - res - 2);
+        do_item_update(it);
+    }
+
+    if (cas) {
+        *cas = ITEM_get_cas(it);    /* swap the incoming CAS value */
+    }
+    do_item_remove(it);         /* release our reference */
+    return OK;
+}
+
+static void process_delete_command(conn *c, token_t *tokens, const size_t ntokens) {
+    char *key;
+    size_t nkey;
+    item *it;
+
+    assert(c != NULL);
+
+    if (ntokens > 3) {
+        bool hold_is_zero = strcmp(tokens[KEY_TOKEN+1].value, "0") == 0;
+        bool sets_noreply = set_noreply_maybe(c, tokens, ntokens);
+        bool valid = (ntokens == 4 && (hold_is_zero || sets_noreply))
+            || (ntokens == 5 && hold_is_zero && sets_noreply);
+        if (!valid) {
+            out_string(c, "CLIENT_ERROR bad command line format.  "
+                       "Usage: delete <key> [noreply]");
+            return;
+        }
+    }
+
+
+    key = tokens[KEY_TOKEN].value;
+    nkey = tokens[KEY_TOKEN].length;
+
+    if(nkey > KEY_MAX_LENGTH) {
+        out_string(c, "CLIENT_ERROR bad command line format");
+        return;
+    }
+
+    if (settings.detail_enabled) {
+        stats_prefix_record_delete(key, nkey);
+    }
+
+    it = item_get(key, nkey);
+    if (it) {
+        MEMCACHED_COMMAND_DELETE(c->sfd, ITEM_key(it), it->nkey);
+
+        pthread_mutex_lock(&c->thread->stats.mutex);
+        c->thread->stats.slab_stats[it->slabs_clsid].delete_hits++;
+        pthread_mutex_unlock(&c->thread->stats.mutex);
+
+        item_unlink(it);
+        item_remove(it);      /* release our reference */
+        out_string(c, "DELETED");
+    } else {
+        pthread_mutex_lock(&c->thread->stats.mutex);
+        c->thread->stats.delete_misses++;
+        pthread_mutex_unlock(&c->thread->stats.mutex);
+
+        out_string(c, "NOT_FOUND");
+    }
+}
+
+static void process_verbosity_command(conn *c, token_t *tokens, const size_t ntokens) {
+    unsigned int level;
+
+    assert(c != NULL);
+
+    set_noreply_maybe(c, tokens, ntokens);
+
+    level = strtoul(tokens[1].value, NULL, 10);
+    settings.verbose = level > MAX_VERBOSITY_LEVEL ? MAX_VERBOSITY_LEVEL : level;
+    out_string(c, "OK");
+    return;
+}
+
+static void process_slabs_automove_command(conn *c, token_t *tokens, const size_t ntokens) {
+    unsigned int level;
+
+    assert(c != NULL);
+
+    set_noreply_maybe(c, tokens, ntokens);
+
+    level = strtoul(tokens[2].value, NULL, 10);
+    if (level == 0) {
+        settings.slab_automove = false;
+    } else if (level == 1) {
+        settings.slab_automove = true;
+    } else {
+        out_string(c, "ERROR");
+        return;
+    }
+    out_string(c, "OK");
+    return;
+}
+
+static void process_command(conn *c, char *command) {
+
+    token_t tokens[MAX_TOKENS];
+    size_t ntokens;
+    int comm;
+
+    assert(c != NULL);
+
+    MEMCACHED_PROCESS_COMMAND_START(c->sfd, c->rcurr, c->rbytes);
+
+    if (settings.verbose > 1)
+        fprintf(stderr, "<%d %s\n", c->sfd, command);
+
+    /*
+     * for commands set/add/replace, we build an item and read the data
+     * directly into it, then continue in nread_complete().
+     */
+
+    c->msgcurr = 0;
+    c->msgused = 0;
+    c->iovused = 0;
+    if (add_msghdr(c) != 0) {
+        out_string(c, "SERVER_ERROR out of memory preparing response");
+        return;
+    }
+
+    ntokens = tokenize_command(command, tokens, MAX_TOKENS);
+    if (ntokens >= 3 &&
+        ((strcmp(tokens[COMMAND_TOKEN].value, "get") == 0) ||
+         (strcmp(tokens[COMMAND_TOKEN].value, "bget") == 0))) {
+
+        process_get_command(c, tokens, ntokens, false);
+
+    } else if ((ntokens == 6 || ntokens == 7) &&
+               ((strcmp(tokens[COMMAND_TOKEN].value, "add") == 0 && (comm = NREAD_ADD)) ||
+                (strcmp(tokens[COMMAND_TOKEN].value, "set") == 0 && (comm = NREAD_SET)) ||
+                (strcmp(tokens[COMMAND_TOKEN].value, "replace") == 0 && (comm = NREAD_REPLACE)) ||
+                (strcmp(tokens[COMMAND_TOKEN].value, "prepend") == 0 && (comm = NREAD_PREPEND)) ||
+                (strcmp(tokens[COMMAND_TOKEN].value, "append") == 0 && (comm = NREAD_APPEND)) )) {
+
+        process_update_command(c, tokens, ntokens, comm, false);
+
+    } else if ((ntokens == 7 || ntokens == 8) && (strcmp(tokens[COMMAND_TOKEN].value, "cas") == 0 && (comm = NREAD_CAS))) {
+
+        process_update_command(c, tokens, ntokens, comm, true);
+
+    } else if ((ntokens == 4 || ntokens == 5) && (strcmp(tokens[COMMAND_TOKEN].value, "incr") == 0)) {
+
+        process_arithmetic_command(c, tokens, ntokens, 1);
+
+    } else if (ntokens >= 3 && (strcmp(tokens[COMMAND_TOKEN].value, "gets") == 0)) {
+
+        process_get_command(c, tokens, ntokens, true);
+
+    } else if ((ntokens == 4 || ntokens == 5) && (strcmp(tokens[COMMAND_TOKEN].value, "decr") == 0)) {
+
+        process_arithmetic_command(c, tokens, ntokens, 0);
+
+    } else if (ntokens >= 3 && ntokens <= 5 && (strcmp(tokens[COMMAND_TOKEN].value, "delete") == 0)) {
+
+        process_delete_command(c, tokens, ntokens);
+
+    } else if ((ntokens == 4 || ntokens == 5) && (strcmp(tokens[COMMAND_TOKEN].value, "touch") == 0)) {
+
+        process_touch_command(c, tokens, ntokens);
+
+    } else if (ntokens >= 2 && (strcmp(tokens[COMMAND_TOKEN].value, "stats") == 0)) {
+
+        process_stat(c, tokens, ntokens);
+
+    } else if (ntokens >= 2 && ntokens <= 4 && (strcmp(tokens[COMMAND_TOKEN].value, "flush_all") == 0)) {
+        time_t exptime = 0;
+
+        set_noreply_maybe(c, tokens, ntokens);
+
+        pthread_mutex_lock(&c->thread->stats.mutex);
+        c->thread->stats.flush_cmds++;
+        pthread_mutex_unlock(&c->thread->stats.mutex);
+
+        if(ntokens == (c->noreply ? 3 : 2)) {
+            settings.oldest_live = current_time - 1;
+            item_flush_expired();
+            out_string(c, "OK");
+            return;
+        }
+
+        exptime = strtol(tokens[1].value, NULL, 10);
+        if(errno == ERANGE) {
+            out_string(c, "CLIENT_ERROR bad command line format");
+            return;
+        }
+
+        /*
+          If exptime is zero realtime() would return zero too, and
+          realtime(exptime) - 1 would overflow to the max unsigned
+          value.  So we process exptime == 0 the same way we do when
+          no delay is given at all.
+        */
+        if (exptime > 0)
+            settings.oldest_live = realtime(exptime) - 1;
+        else /* exptime == 0 */
+            settings.oldest_live = current_time - 1;
+        item_flush_expired();
+        out_string(c, "OK");
+        return;
+
+    } else if (ntokens == 2 && (strcmp(tokens[COMMAND_TOKEN].value, "version") == 0)) {
+
+        out_string(c, "VERSION " VERSION);
+
+    } else if (ntokens == 2 && (strcmp(tokens[COMMAND_TOKEN].value, "quit") == 0)) {
+
+        conn_set_state(c, conn_closing);
+
+    } else if (ntokens > 1 && strcmp(tokens[COMMAND_TOKEN].value, "slabs") == 0) {
+        if (ntokens == 5 && strcmp(tokens[COMMAND_TOKEN + 1].value, "reassign") == 0) {
+            int src, dst, rv;
+
+            if (settings.slab_reassign == false) {
+                out_string(c, "CLIENT_ERROR slab reassignment disabled");
+                return;
+            }
+
+            src = strtol(tokens[2].value, NULL, 10);
+            dst = strtol(tokens[3].value, NULL, 10);
+
+            if (errno == ERANGE) {
+                out_string(c, "CLIENT_ERROR bad command line format");
+                return;
+            }
+
+            rv = slabs_reassign(src, dst);
+            switch (rv) {
+            case REASSIGN_OK:
+                out_string(c, "OK");
+                break;
+            case REASSIGN_RUNNING:
+                out_string(c, "BUSY currently processing reassign request");
+                break;
+            case REASSIGN_BADCLASS:
+                out_string(c, "BADCLASS invalid src or dst class id");
+                break;
+            case REASSIGN_NOSPARE:
+                out_string(c, "NOSPARE source class has no spare pages");
+                break;
+            case REASSIGN_DEST_NOT_FULL:
+                out_string(c, "NOTFULL dest class has spare memory");
+                break;
+            case REASSIGN_SRC_NOT_SAFE:
+                out_string(c, "UNSAFE src class is in an unsafe state");
+                break;
+            case REASSIGN_SRC_DST_SAME:
+                out_string(c, "SAME src and dst class are identical");
+                break;
+            default:
+                assert(false);
+                abort();
+            }
+            return;
+        } else if (ntokens == 4 &&
+            (strcmp(tokens[COMMAND_TOKEN + 1].value, "automove") == 0)) {
+            process_slabs_automove_command(c, tokens, ntokens);
+        } else {
+            out_string(c, "ERROR");
+        }
+    } else if ((ntokens == 3 || ntokens == 4) && (strcmp(tokens[COMMAND_TOKEN].value, "verbosity") == 0)) {
+        process_verbosity_command(c, tokens, ntokens);
+    } else {
+        out_string(c, "ERROR");
+    }
+    return;
+}
+
+/*
+ * if we have a complete line in the buffer, process it.
+ */
+static int try_read_command(conn *c) {
+    assert(c != NULL);
+    assert(c->rcurr <= (c->rbuf + c->rsize));
+    assert(c->rbytes > 0);
+
+    if (c->protocol == negotiating_prot || c->transport == udp_transport)  {
+        if ((unsigned char)c->rbuf[0] == (unsigned char)PROTOCOL_BINARY_REQ) {
+            c->protocol = binary_prot;
+        } else {
+            c->protocol = ascii_prot;
+        }
+
+        if (settings.verbose > 1) {
+            fprintf(stderr, "%d: Client using the %s protocol\n", c->sfd,
+                    prot_text(c->protocol));
+        }
+    }
+
+    if (c->protocol == binary_prot) {
+        /* Do we have the complete packet header? */
+        if (c->rbytes < sizeof(c->binary_header)) {
+            /* need more data! */
+            return 0;
+        } else {
+#ifdef NEED_ALIGN
+            if (((long)(c->rcurr)) % 8 != 0) {
+                /* must realign input buffer */
+                memmove(c->rbuf, c->rcurr, c->rbytes);
+                c->rcurr = c->rbuf;
+                if (settings.verbose > 1) {
+                    fprintf(stderr, "%d: Realign input buffer\n", c->sfd);
+                }
+            }
+#endif
+            protocol_binary_request_header* req;
+            req = (protocol_binary_request_header*)c->rcurr;
+
+            if (settings.verbose > 1) {
+                /* Dump the packet before we convert it to host order */
+                int ii;
+                fprintf(stderr, "<%d Read binary protocol data:", c->sfd);
+                for (ii = 0; ii < sizeof(req->bytes); ++ii) {
+                    if (ii % 4 == 0) {
+                        fprintf(stderr, "\n<%d   ", c->sfd);
+                    }
+                    fprintf(stderr, " 0x%02x", req->bytes[ii]);
+                }
+                fprintf(stderr, "\n");
+            }
+
+            c->binary_header = *req;
+            c->binary_header.request.keylen = ntohs(req->request.keylen);
+            c->binary_header.request.bodylen = ntohl(req->request.bodylen);
+            c->binary_header.request.cas = ntohll(req->request.cas);
+
+            if (c->binary_header.request.magic != PROTOCOL_BINARY_REQ) {
+                if (settings.verbose) {
+                    fprintf(stderr, "Invalid magic:  %x\n",
+                            c->binary_header.request.magic);
+                }
+                conn_set_state(c, conn_closing);
+                return -1;
+            }
+
+            c->msgcurr = 0;
+            c->msgused = 0;
+            c->iovused = 0;
+            if (add_msghdr(c) != 0) {
+                out_string(c, "SERVER_ERROR out of memory");
+                return 0;
+            }
+
+            c->cmd = c->binary_header.request.opcode;
+            c->keylen = c->binary_header.request.keylen;
+            c->opaque = c->binary_header.request.opaque;
+            /* clear the returned cas value */
+            c->cas = 0;
+
+            dispatch_bin_command(c);
+
+            c->rbytes -= sizeof(c->binary_header);
+            c->rcurr += sizeof(c->binary_header);
+        }
+    } else {
+        char *el, *cont;
+
+        if (c->rbytes == 0)
+            return 0;
+
+        el = memchr(c->rcurr, '\n', c->rbytes);
+        if (!el) {
+            if (c->rbytes > 1024) {
+                /*
+                 * We didn't have a '\n' in the first k. This _has_ to be a
+                 * large multiget, if not we should just nuke the connection.
+                 */
+                char *ptr = c->rcurr;
+                while (*ptr == ' ') { /* ignore leading whitespaces */
+                    ++ptr;
+                }
+
+                if (ptr - c->rcurr > 100 ||
+                    (strncmp(ptr, "get ", 4) && strncmp(ptr, "gets ", 5))) {
+
+                    conn_set_state(c, conn_closing);
+                    return 1;
+                }
+            }
+
+            return 0;
+        }
+        cont = el + 1;
+        if ((el - c->rcurr) > 1 && *(el - 1) == '\r') {
+            el--;
+        }
+        *el = '\0';
+
+        assert(cont <= (c->rcurr + c->rbytes));
+
+        process_command(c, c->rcurr);
+
+        c->rbytes -= (cont - c->rcurr);
+        c->rcurr = cont;
+
+        assert(c->rcurr <= (c->rbuf + c->rsize));
+    }
+
+    return 1;
+}
+
+/*
+ * read a UDP request.
+ */
+static enum try_read_result try_read_udp(conn *c) {
+    int res;
+
+    assert(c != NULL);
+
+    c->request_addr_size = sizeof(c->request_addr);
+    res = recvfrom(c->sfd, c->rbuf, c->rsize,
+                   0, &c->request_addr, &c->request_addr_size);
+    if (res > 8) {
+        unsigned char *buf = (unsigned char *)c->rbuf;
+        pthread_mutex_lock(&c->thread->stats.mutex);
+        c->thread->stats.bytes_read += res;
+        pthread_mutex_unlock(&c->thread->stats.mutex);
+
+        /* Beginning of UDP packet is the request ID; save it. */
+        c->request_id = buf[0] * 256 + buf[1];
+
+        /* If this is a multi-packet request, drop it. */
+        if (buf[4] != 0 || buf[5] != 1) {
+            out_string(c, "SERVER_ERROR multi-packet request not supported");
+            return READ_NO_DATA_RECEIVED;
+        }
+
+        /* Don't care about any of the rest of the header. */
+        res -= 8;
+        memmove(c->rbuf, c->rbuf + 8, res);
+
+        c->rbytes = res;
+        c->rcurr = c->rbuf;
+        return READ_DATA_RECEIVED;
+    }
+    return READ_NO_DATA_RECEIVED;
+}
+
+/*
+ * read from network as much as we can, handle buffer overflow and connection
+ * close.
+ * before reading, move the remaining incomplete fragment of a command
+ * (if any) to the beginning of the buffer.
+ *
+ * To protect us from someone flooding a connection with bogus data causing
+ * the connection to eat up all available memory, break out and start looking
+ * at the data I've got after a number of reallocs...
+ *
+ * @return enum try_read_result
+ */
+static enum try_read_result try_read_network(conn *c) {
+    enum try_read_result gotdata = READ_NO_DATA_RECEIVED;
+    int res;
+    int num_allocs = 0;
+    assert(c != NULL);
+
+    if (c->rcurr != c->rbuf) {
+        if (c->rbytes != 0) /* otherwise there's nothing to copy */
+            memmove(c->rbuf, c->rcurr, c->rbytes);
+        c->rcurr = c->rbuf;
+    }
+
+    while (1) {
+        if (c->rbytes >= c->rsize) {
+            if (num_allocs == 4) {
+                return gotdata;
+            }
+            ++num_allocs;
+            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 reading request");
+                c->write_and_go = conn_closing;
+                return READ_MEMORY_ERROR;
+            }
+            c->rcurr = c->rbuf = new_rbuf;
+            c->rsize *= 2;
+        }
+
+        int avail = c->rsize - c->rbytes;
+        res = read(c->sfd, c->rbuf + c->rbytes, avail);
+        if (res > 0) {
+            pthread_mutex_lock(&c->thread->stats.mutex);
+            c->thread->stats.bytes_read += res;
+            pthread_mutex_unlock(&c->thread->stats.mutex);
+            gotdata = READ_DATA_RECEIVED;
+            c->rbytes += res;
+            if (res == avail) {
+                continue;
+            } else {
+                break;
+            }
+        }
+        if (res == 0) {
+            return READ_ERROR;
+        }
+        if (res == -1) {
+            if (errno == EAGAIN || errno == EWOULDBLOCK) {
+                break;
+            }
+            return READ_ERROR;
+        }
+    }
+    return gotdata;
+}
+
+static bool update_event(conn *c, const int new_flags) {
+    assert(c != NULL);
+
+    struct event_base *base = c->event.ev_base;
+    if (c->ev_flags == new_flags)
+        return true;
+    if (event_del(&c->event) == -1) return false;
+    event_set(&c->event, c->sfd, new_flags, event_handler, (void *)c);
+    event_base_set(base, &c->event);
+    c->ev_flags = new_flags;
+    if (event_add(&c->event, 0) == -1) return false;
+    return true;
+}
+
+/*
+ * Sets whether we are listening for new connections or not.
+ */
+void do_accept_new_conns(const bool do_accept) {
+    conn *next;
+
+    for (next = listen_conn; next; next = next->next) {
+        if (do_accept) {
+            update_event(next, EV_READ | EV_PERSIST);
+            if (listen(next->sfd, settings.backlog) != 0) {
+                perror("listen");
+            }
+        }
+        else {
+            update_event(next, 0);
+            if (listen(next->sfd, 0) != 0) {
+                perror("listen");
+            }
+        }
+    }
+
+    if (do_accept) {
+        STATS_LOCK();
+        stats.accepting_conns = true;
+        STATS_UNLOCK();
+    } else {
+        STATS_LOCK();
+        stats.accepting_conns = false;
+        stats.listen_disabled_num++;
+        STATS_UNLOCK();
+        allow_new_conns = false;
+        maxconns_handler(-42, 0, 0);
+    }
+}
+
+/*
+ * Transmit the next chunk of data from our list of msgbuf structures.
+ *
+ * Returns:
+ *   TRANSMIT_COMPLETE   All done writing.
+ *   TRANSMIT_INCOMPLETE More data remaining to write.
+ *   TRANSMIT_SOFT_ERROR Can't write any more right now.
+ *   TRANSMIT_HARD_ERROR Can't write (c->state is set to conn_closing)
+ */
+static enum transmit_result transmit(conn *c) {
+    assert(c != NULL);
+
+    if (c->msgcurr < c->msgused &&
+            c->msglist[c->msgcurr].msg_iovlen == 0) {
+        /* Finished writing the current msg; advance to the next. */
+        c->msgcurr++;
+    }
+    if (c->msgcurr < c->msgused) {
+        ssize_t res;
+        struct msghdr *m = &c->msglist[c->msgcurr];
+
+        res = sendmsg(c->sfd, m, 0);
+        if (res > 0) {
+            pthread_mutex_lock(&c->thread->stats.mutex);
+            c->thread->stats.bytes_written += res;
+            pthread_mutex_unlock(&c->thread->stats.mutex);
+
+            /* We've written some of the data. Remove the completed
+               iovec entries from the list of pending writes. */
+            while (m->msg_iovlen > 0 && res >= m->msg_iov->iov_len) {
+                res -= m->msg_iov->iov_len;
+                m->msg_iovlen--;
+                m->msg_iov++;
+            }
+
+            /* Might have written just part of the last iovec entry;
+               adjust it so the next write will do the rest. */
+            if (res > 0) {
+                m->msg_iov->iov_base = (caddr_t)m->msg_iov->iov_base + res;
+                m->msg_iov->iov_len -= res;
+            }
+            return TRANSMIT_INCOMPLETE;
+        }
+        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");
+                conn_set_state(c, conn_closing);
+                return TRANSMIT_HARD_ERROR;
+            }
+            return TRANSMIT_SOFT_ERROR;
+        }
+        /* 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)
+            perror("Failed to write, and not due to blocking");
+
+        if (IS_UDP(c->transport))
+            conn_set_state(c, conn_read);
+        else
+            conn_set_state(c, conn_closing);
+        return TRANSMIT_HARD_ERROR;
+    } else {
+        return TRANSMIT_COMPLETE;
+    }
+}
+
+static void drive_machine(conn *c) {
+    bool stop = false;
+    int sfd, flags = 1;
+    socklen_t addrlen;
+    struct sockaddr_storage addr;
+    int nreqs = settings.reqs_per_event;
+    int res;
+    const char *str;
+
+    assert(c != NULL);
+
+    while (!stop) {
+
+        switch(c->state) {
+        case conn_listening:
+            addrlen = sizeof(addr);
+            if ((sfd = accept(c->sfd, (struct sockaddr *)&addr, &addrlen)) == -1) {
+                if (errno == EAGAIN || errno == EWOULDBLOCK) {
+                    /* these are transient, so don't log anything */
+                    stop = true;
+                } else if (errno == EMFILE) {
+                    if (settings.verbose > 0)
+                        fprintf(stderr, "Too many open connections\n");
+                    accept_new_conns(false);
+                    stop = true;
+                } else {
+                    perror("accept()");
+                    stop = true;
+                }
+                break;
+            }
+            if ((flags = fcntl(sfd, F_GETFL, 0)) < 0 ||
+                fcntl(sfd, F_SETFL, flags | O_NONBLOCK) < 0) {
+                perror("setting O_NONBLOCK");
+                close(sfd);
+                break;
+            }
+
+            if (settings.maxconns_fast &&
+                stats.curr_conns + stats.reserved_fds >= settings.maxconns - 1) {
+                str = "ERROR Too many open connections\r\n";
+                res = write(sfd, str, strlen(str));
+                close(sfd);
+                STATS_LOCK();
+                stats.rejected_conns++;
+                STATS_UNLOCK();
+            } else {
+                dispatch_conn_new(sfd, conn_new_cmd, EV_READ | EV_PERSIST,
+                                     DATA_BUFFER_SIZE, tcp_transport);
+            }
+
+            stop = true;
+            break;
+
+        case conn_waiting:
+            if (!update_event(c, EV_READ | EV_PERSIST)) {
+                if (settings.verbose > 0)
+                    fprintf(stderr, "Couldn't update event\n");
+                conn_set_state(c, conn_closing);
+                break;
+            }
+
+            conn_set_state(c, conn_read);
+            stop = true;
+            break;
+
+        case conn_read:
+            res = IS_UDP(c->transport) ? try_read_udp(c) : try_read_network(c);
+
+            switch (res) {
+            case READ_NO_DATA_RECEIVED:
+                conn_set_state(c, conn_waiting);
+                break;
+            case READ_DATA_RECEIVED:
+                conn_set_state(c, conn_parse_cmd);
+                break;
+            case READ_ERROR:
+                conn_set_state(c, conn_closing);
+                break;
+            case READ_MEMORY_ERROR: /* Failed to allocate more memory */
+                /* State already set by try_read_network */
+                break;
+            default:
+                assert(false);
+                abort();
+            }
+            break;
+
+        case conn_parse_cmd :
+            if (try_read_command(c) == 0) {
+                /* wee need more data! */
+                conn_set_state(c, conn_waiting);
+            }
+
+            break;
+
+        case conn_new_cmd:
+            /* Only process nreqs at a time to avoid starving other
+               connections */
+
+            --nreqs;
+            if (nreqs >= 0) {
+                reset_cmd_handler(c);
+            } else {
+                pthread_mutex_lock(&c->thread->stats.mutex);
+                c->thread->stats.conn_yields++;
+                pthread_mutex_unlock(&c->thread->stats.mutex);
+                if (c->rbytes > 0) {
+                    /* We have already read in data into the input buffer,
+                       so libevent will most likely not signal read events
+                       on the socket (unless more data is available. As a
+                       hack we should just put in a request to write data,
+                       because that should be possible ;-)
+                    */
+                    if (!update_event(c, EV_WRITE | EV_PERSIST)) {
+                        if (settings.verbose > 0)
+                            fprintf(stderr, "Couldn't update event\n");
+                        conn_set_state(c, conn_closing);
+                    }
+                }
+                stop = true;
+            }
+            break;
+
+        case conn_nread:
+            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;
+                if (c->ritem != c->rcurr) {
+                    memmove(c->ritem, c->rcurr, tocopy);
+                }
+                c->ritem += tocopy;
+                c->rlbytes -= tocopy;
+                c->rcurr += tocopy;
+                c->rbytes -= tocopy;
+                if (c->rlbytes == 0) {
+                    break;
+                }
+            }
+
+            /*  now try reading from the socket */
+            res = read(c->sfd, c->ritem, c->rlbytes);
+            if (res > 0) {
+                pthread_mutex_lock(&c->thread->stats.mutex);
+                c->thread->stats.bytes_read += res;
+                pthread_mutex_unlock(&c->thread->stats.mutex);
+                if (c->rcurr == c->ritem) {
+                    c->rcurr += res;
+                }
+                c->ritem += res;
+                c->rlbytes -= res;
+                break;
+            }
+            if (res == 0) { /* end of stream */
+                conn_set_state(c, 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");
+                    conn_set_state(c, conn_closing);
+                    break;
+                }
+                stop = true;
+                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"
+                        "errno: %d %s \n"
+                        "rcurr=%lx ritem=%lx rbuf=%lx rlbytes=%d rsize=%d\n",
+                        errno, strerror(errno),
+                        (long)c->rcurr, (long)c->ritem, (long)c->rbuf,
+                        (int)c->rlbytes, (int)c->rsize);
+            }
+            conn_set_state(c, conn_closing);
+            break;
+
+        case conn_swallow:
+            /* we are reading sbytes and throwing them away */
+            if (c->sbytes == 0) {
+                conn_set_state(c, conn_new_cmd);
+                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;
+                c->rcurr += 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) {
+                pthread_mutex_lock(&c->thread->stats.mutex);
+                c->thread->stats.bytes_read += res;
+                pthread_mutex_unlock(&c->thread->stats.mutex);
+                c->sbytes -= res;
+                break;
+            }
+            if (res == 0) { /* end of stream */
+                conn_set_state(c, 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");
+                    conn_set_state(c, conn_closing);
+                    break;
+                }
+                stop = true;
+                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");
+            conn_set_state(c, conn_closing);
+            break;
+
+        case conn_write:
+            /*
+             * We want to write out a simple response. If we haven't already,
+             * assemble it into a msgbuf list (this will be a single-entry
+             * list for TCP or a two-entry list for UDP).
+             */
+            if (c->iovused == 0 || (IS_UDP(c->transport) && c->iovused == 1)) {
+                if (add_iov(c, c->wcurr, c->wbytes) != 0) {
+                    if (settings.verbose > 0)
+                        fprintf(stderr, "Couldn't build response\n");
+                    conn_set_state(c, conn_closing);
+                    break;
+                }
+            }
+
+            /* fall through... */
+
+        case conn_mwrite:
+          if (IS_UDP(c->transport) && c->msgcurr == 0 && build_udp_headers(c) != 0) {
+            if (settings.verbose > 0)
+              fprintf(stderr, "Failed to build UDP headers\n");
+            conn_set_state(c, conn_closing);
+            break;
+          }
+            switch (transmit(c)) {
+            case TRANSMIT_COMPLETE:
+                if (c->state == conn_mwrite) {
+                    while (c->ileft > 0) {
+                        item *it = *(c->icurr);
+                        assert((it->it_flags & ITEM_SLABBED) == 0);
+                        item_remove(it);
+                        c->icurr++;
+                        c->ileft--;
+                    }
+                    while (c->suffixleft > 0) {
+                        char *suffix = *(c->suffixcurr);
+                        cache_free(c->thread->suffix_cache, suffix);
+                        c->suffixcurr++;
+                        c->suffixleft--;
+                    }
+                    /* XXX:  I don't know why this wasn't the general case */
+                    if(c->protocol == binary_prot) {
+                        conn_set_state(c, c->write_and_go);
+                    } else {
+                        conn_set_state(c, conn_new_cmd);
+                    }
+                } else if (c->state == conn_write) {
+                    if (c->write_and_free) {
+                        free(c->write_and_free);
+                        c->write_and_free = 0;
+                    }
+                    conn_set_state(c, c->write_and_go);
+                } else {
+                    if (settings.verbose > 0)
+                        fprintf(stderr, "Unexpected state %d\n", c->state);
+                    conn_set_state(c, conn_closing);
+                }
+                break;
+
+            case TRANSMIT_INCOMPLETE:
+            case TRANSMIT_HARD_ERROR:
+                break;                   /* Continue in state machine. */
+
+            case TRANSMIT_SOFT_ERROR:
+                stop = true;
+                break;
+            default:
+                assert(false);
+                abort();
+            }
+            break;
+
+        case conn_closing:
+            if (IS_UDP(c->transport))
+                conn_cleanup(c);
+            else
+                conn_close(c);
+            stop = true;
+            break;
+
+        case conn_max_state:
+            assert(false);
+            break;
+        default:
+            assert(false);
+            abort();
+        }
+    }
+
+    return;
+}
+
+void event_handler(const int fd, const short which, void *arg) {
+    conn *c;
+
+    c = (conn *)arg;
+    assert(c != NULL);
+
+    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;
+    }
+
+    drive_machine(c);
+
+    /* wait for next event */
+    return;
+}
+
+static int new_socket(struct addrinfo *ai) {
+    int sfd;
+    int flags;
+
+    if ((sfd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol)) == -1) {
+        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;
+}
+
+
+/*
+ * Sets a socket's send buffer size to the maximum allowed by the system.
+ */
+static void maximize_sndbuf(const int sfd) {
+    socklen_t intsize = sizeof(int);
+    int last_good = 0;
+    int min, max, avg;
+    int old_size;
+
+    /* Start with the default size. */
+    if (getsockopt(sfd, SOL_SOCKET, SO_SNDBUF, &old_size, &intsize) != 0) {
+        if (settings.verbose > 0)
+            perror("getsockopt(SO_SNDBUF)");
+        return;
+    }
+
+    /* Binary-search for the real maximum. */
+    min = old_size;
+    max = MAX_SENDBUF_SIZE;
+
+    while (min <= max) {
+        avg = ((unsigned int)(min + max)) / 2;
+        if (setsockopt(sfd, SOL_SOCKET, SO_SNDBUF, (void *)&avg, intsize) == 0) {
+            last_good = avg;
+            min = avg + 1;
+        } else {
+            max = avg - 1;
+        }
+    }
+
+    if (settings.verbose > 1)
+        fprintf(stderr, "<%d send buffer was %d, now %d\n", sfd, old_size, last_good);
+}
+
+/**
+ * Create a socket and bind it to a specific port number
+ * @param interface the interface to bind to
+ * @param port the port number to bind to
+ * @param transport the transport protocol (TCP / UDP)
+ * @param portnumber_file A filepointer to write the port numbers to
+ *        when they are successfully added to the list of ports we
+ *        listen on.
+ */
+static int server_socket(const char *interface,
+                         int port,
+                         enum network_transport transport,
+                         FILE *portnumber_file) {
+    int sfd;
+    struct linger ling = {0, 0};
+    struct addrinfo *ai;
+    struct addrinfo *next;
+    struct addrinfo hints = { .ai_flags = AI_PASSIVE,
+                              .ai_family = AF_UNSPEC };
+    char port_buf[NI_MAXSERV];
+    int error;
+    int success = 0;
+    int flags =1;
+
+    hints.ai_socktype = IS_UDP(transport) ? SOCK_DGRAM : SOCK_STREAM;
+
+    if (port == -1) {
+        port = 0;
+    }
+    snprintf(port_buf, sizeof(port_buf), "%d", port);
+    error= getaddrinfo(interface, port_buf, &hints, &ai);
+    if (error != 0) {
+        if (error != EAI_SYSTEM)
+          fprintf(stderr, "getaddrinfo(): %s\n", gai_strerror(error));
+        else
+          perror("getaddrinfo()");
+        return 1;
+    }
+
+    for (next= ai; next; next= next->ai_next) {
+        conn *listen_conn_add;
+        if ((sfd = new_socket(next)) == -1) {
+            /* getaddrinfo can return "junk" addresses,
+             * we make sure at least one works before erroring.
+             */
+            if (errno == EMFILE) {
+                /* ...unless we're out of fds */
+                perror("server_socket");
+                exit(EX_OSERR);
+            }
+            continue;
+        }
+
+#ifdef IPV6_V6ONLY
+        if (next->ai_family == AF_INET6) {
+            error = setsockopt(sfd, IPPROTO_IPV6, IPV6_V6ONLY, (char *) &flags, sizeof(flags));
+            if (error != 0) {
+                perror("setsockopt");
+                close(sfd);
+                continue;
+            }
+        }
+#endif
+
+        setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, (void *)&flags, sizeof(flags));
+        if (IS_UDP(transport)) {
+            maximize_sndbuf(sfd);
+        } else {
+            error = setsockopt(sfd, SOL_SOCKET, SO_KEEPALIVE, (void *)&flags, sizeof(flags));
+            if (error != 0)
+                perror("setsockopt");
+
+            error = setsockopt(sfd, SOL_SOCKET, SO_LINGER, (void *)&ling, sizeof(ling));
+            if (error != 0)
+                perror("setsockopt");
+
+            error = setsockopt(sfd, IPPROTO_TCP, TCP_NODELAY, (void *)&flags, sizeof(flags));
+            if (error != 0)
+                perror("setsockopt");
+        }
+
+        if (bind(sfd, next->ai_addr, next->ai_addrlen) == -1) {
+            if (errno != EADDRINUSE) {
+                perror("bind()");
+                close(sfd);
+                freeaddrinfo(ai);
+                return 1;
+            }
+            close(sfd);
+            continue;
+        } else {
+            success++;
+            if (!IS_UDP(transport) && listen(sfd, settings.backlog) == -1) {
+                perror("listen()");
+                close(sfd);
+                freeaddrinfo(ai);
+                return 1;
+            }
+            if (portnumber_file != NULL &&
+                (next->ai_addr->sa_family == AF_INET ||
+                 next->ai_addr->sa_family == AF_INET6)) {
+                union {
+                    struct sockaddr_in in;
+                    struct sockaddr_in6 in6;
+                } my_sockaddr;
+                socklen_t len = sizeof(my_sockaddr);
+                if (getsockname(sfd, (struct sockaddr*)&my_sockaddr, &len)==0) {
+                    if (next->ai_addr->sa_family == AF_INET) {
+                        fprintf(portnumber_file, "%s INET: %u\n",
+                                IS_UDP(transport) ? "UDP" : "TCP",
+                                ntohs(my_sockaddr.in.sin_port));
+                    } else {
+                        fprintf(portnumber_file, "%s INET6: %u\n",
+                                IS_UDP(transport) ? "UDP" : "TCP",
+                                ntohs(my_sockaddr.in6.sin6_port));
+                    }
+                }
+            }
+        }
+
+        if (IS_UDP(transport)) {
+            int c;
+
+            for (c = 0; c < settings.num_threads_per_udp; c++) {
+                /* this is guaranteed to hit all threads because we round-robin */
+                dispatch_conn_new(sfd, conn_read, EV_READ | EV_PERSIST,
+                                  UDP_READ_BUFFER_SIZE, transport);
+            }
+        } else {
+            if (!(listen_conn_add = conn_new(sfd, conn_listening,
+                                             EV_READ | EV_PERSIST, 1,
+                                             transport, main_base))) {
+                fprintf(stderr, "failed to create listening connection\n");
+                exit(EXIT_FAILURE);
+            }
+            listen_conn_add->next = listen_conn;
+            listen_conn = listen_conn_add;
+        }
+    }
+
+    freeaddrinfo(ai);
+
+    /* Return zero iff we detected no errors in starting up connections */
+    return success == 0;
+}
+
+static int server_sockets(int port, enum network_transport transport,
+                          FILE *portnumber_file) {
+    if (settings.inter == NULL) {
+        return server_socket(settings.inter, port, transport, portnumber_file);
+    } else {
+        // tokenize them and bind to each one of them..
+        char *b;
+        int ret = 0;
+        char *list = strdup(settings.inter);
+
+        if (list == NULL) {
+            fprintf(stderr, "Failed to allocate memory for parsing server interface string\n");
+            return 1;
+        }
+        for (char *p = strtok_r(list, ";,", &b);
+             p != NULL;
+             p = strtok_r(NULL, ";,", &b)) {
+            int the_port = port;
+            char *s = strchr(p, ':');
+            if (s != NULL) {
+                *s = '\0';
+                ++s;
+                if (!safe_strtol(s, &the_port)) {
+                    fprintf(stderr, "Invalid port number: \"%s\"", s);
+                    return 1;
+                }
+            }
+            if (strcmp(p, "*") == 0) {
+                p = NULL;
+            }
+            ret |= server_socket(p, the_port, transport, portnumber_file);
+        }
+        free(list);
+        return ret;
+    }
+}
+
+static int new_socket_unix(void) {
+    int sfd;
+    int flags;
+
+    if ((sfd = socket(AF_UNIX, 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;
+}
+
+static int server_socket_unix(const char *path, int access_mask) {
+    int sfd;
+    struct linger ling = {0, 0};
+    struct sockaddr_un addr;
+    struct stat tstat;
+    int flags =1;
+    int old_umask;
+
+    if (!path) {
+        return 1;
+    }
+
+    if ((sfd = new_socket_unix()) == -1) {
+        return 1;
+    }
+
+    /*
+     * Clean up a previous socket file if we left it around
+     */
+    if (lstat(path, &tstat) == 0) {
+        if (S_ISSOCK(tstat.st_mode))
+            unlink(path);
+    }
+
+    setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, (void *)&flags, sizeof(flags));
+    setsockopt(sfd, SOL_SOCKET, SO_KEEPALIVE, (void *)&flags, sizeof(flags));
+    setsockopt(sfd, SOL_SOCKET, SO_LINGER, (void *)&ling, sizeof(ling));
+
+    /*
+     * the memset call clears nonstandard fields in some impementations
+     * that otherwise mess things up.
+     */
+    memset(&addr, 0, sizeof(addr));
+
+    addr.sun_family = AF_UNIX;
+    strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
+    assert(strcmp(addr.sun_path, path) == 0);
+    old_umask = umask( ~(access_mask&0777));
+    if (bind(sfd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
+        perror("bind()");
+        close(sfd);
+        umask(old_umask);
+        return 1;
+    }
+    umask(old_umask);
+    if (listen(sfd, settings.backlog) == -1) {
+        perror("listen()");
+        close(sfd);
+        return 1;
+    }
+    if (!(listen_conn = conn_new(sfd, conn_listening,
+                                 EV_READ | EV_PERSIST, 1,
+                                 local_transport, main_base))) {
+        fprintf(stderr, "failed to create listening connection\n");
+        exit(EXIT_FAILURE);
+    }
+
+    return 0;
+}
+
+/*
+ * We keep the current time of day in a global variable that's updated by a
+ * timer event. This saves us a bunch of time() system calls (we really only
+ * need to get the time once a second, whereas there can be tens of thousands
+ * of requests a second) and allows us to use server-start-relative timestamps
+ * rather than absolute UNIX timestamps, a space savings on systems where
+ * sizeof(time_t) > sizeof(unsigned int).
+ */
+volatile rel_time_t current_time;
+static struct event clockevent;
+
+/* libevent uses a monotonic clock when available for event scheduling. Aside
+ * from jitter, simply ticking our internal timer here is accurate enough.
+ * Note that users who are setting explicit dates for expiration times *must*
+ * ensure their clocks are correct before starting memcached. */
+static void clock_handler(const int fd, const short which, void *arg) {
+    struct timeval t = {.tv_sec = 1, .tv_usec = 0};
+    static bool initialized = false;
+#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
+    static bool monotonic = false;
+    static time_t monotonic_start;
+#endif
+
+    if (initialized) {
+        /* only delete the event if it's actually there. */
+        evtimer_del(&clockevent);
+    } else {
+        initialized = true;
+        /* process_started is initialized to time() - 2. We initialize to 1 so
+         * flush_all won't underflow during tests. */
+#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
+        struct timespec ts;
+        if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) {
+            monotonic = true;
+            monotonic_start = ts.tv_sec - 2;
+        }
+#endif
+    }
+
+    evtimer_set(&clockevent, clock_handler, 0);
+    event_base_set(main_base, &clockevent);
+    evtimer_add(&clockevent, &t);
+
+#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
+    if (monotonic) {
+        struct timespec ts;
+        if (clock_gettime(CLOCK_MONOTONIC, &ts) == -1)
+            return;
+        current_time = (rel_time_t) (ts.tv_sec - monotonic_start);
+        return;
+    }
+#endif
+    {
+        struct timeval tv;
+        gettimeofday(&tv, NULL);
+        current_time = (rel_time_t) (tv.tv_sec - process_started);
+    }
+}
+
+static void usage(void) {
+    printf(PACKAGE " " VERSION "\n");
+    printf("-p <num>      TCP port number to listen on (default: 11211)\n"
+           "-U <num>      UDP port number to listen on (default: 11211, 0 is off)\n"
+           "-s <file>     UNIX socket path to listen on (disables network support)\n"
+           "-a <mask>     access mask for UNIX socket, in octal (default: 0700)\n"
+           "-l <addr>     interface to listen on (default: INADDR_ANY, all addresses)\n"
+           "              <addr> may be specified as host:port. If you don't specify\n"
+           "              a port number, the value you specified with -p or -U is\n"
+           "              used. You may specify multiple addresses separated by comma\n"
+           "              or by using -l multiple times\n"
+
+           "-d            run as a daemon\n"
+           "-r            maximize core file limit\n"
+           "-u <username> assume identity of <username> (only when run as root)\n"
+           "-m <num>      max memory to use for items in megabytes (default: 64 MB)\n"
+           "-M            return error on memory exhausted (rather than removing items)\n"
+           "-c <num>      max simultaneous connections (default: 1024)\n"
+           "-k            lock down all paged memory.  Note that there is a\n"
+           "              limit on how much memory you may lock.  Trying to\n"
+           "              allocate more than that would fail, so be sure you\n"
+           "              set the limit correctly for the user you started\n"
+           "              the daemon with (not for -u <username> user;\n"
+           "              under sh this is done with 'ulimit -S -l NUM_KB').\n"
+           "-v            verbose (print errors/warnings while in event loop)\n"
+           "-vv           very verbose (also print client commands/reponses)\n"
+           "-vvv          extremely verbose (also print internal state transitions)\n"
+           "-h            print this help and exit\n"
+           "-i            print memcached and libevent license\n"
+           "-P <file>     save PID in <file>, only used with -d option\n"
+           "-f <factor>   chunk size growth factor (default: 1.25)\n"
+           "-n <bytes>    minimum space allocated for key+value+flags (default: 48)\n");
+    printf("-L            Try to use large memory pages (if available). Increasing\n"
+           "              the memory page size could reduce the number of TLB misses\n"
+           "              and improve the performance. In order to get large pages\n"
+           "              from the OS, memcached will allocate the total item-cache\n"
+           "              in one large chunk.\n");
+    printf("-D <char>     Use <char> as the delimiter between key prefixes and IDs.\n"
+           "              This is used for per-prefix stats reporting. The default is\n"
+           "              \":\" (colon). If this option is specified, stats collection\n"
+           "              is turned on automatically; if not, then it may be turned on\n"
+           "              by sending the \"stats detail on\" command to the server.\n");
+    printf("-t <num>      number of threads to use (default: 4)\n");
+    printf("-R            Maximum number of requests per event, limits the number of\n"
+           "              requests process for a given connection to prevent \n"
+           "              starvation (default: 20)\n");
+    printf("-C            Disable use of CAS\n");
+    printf("-b            Set the backlog queue limit (default: 1024)\n");
+    printf("-B            Binding protocol - one of ascii, binary, or auto (default)\n");
+    printf("-I            Override the size of each slab page. Adjusts max item size\n"
+           "              (default: 1mb, min: 1k, max: 128m)\n");
+#ifdef ENABLE_SASL
+    printf("-S            Turn on Sasl authentication\n");
+#endif
+    printf("-o            Comma separated list of extended or experimental options\n"
+           "              - (EXPERIMENTAL) maxconns_fast: immediately close new\n"
+           "                connections if over maxconns limit\n"
+           "              - hashpower: An integer multiplier for how large the hash\n"
+           "                table should be. Can be grown at runtime if not big enough.\n"
+           "                Set this based on \"STAT hash_power_level\" before a \n"
+           "                restart.\n"
+           );
+    return;
+}
+
+static 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;
+}
+
+static void save_pid(const char *pid_file) {
+    FILE *fp;
+    if (access(pid_file, F_OK) == 0) {
+        if ((fp = fopen(pid_file, "r")) != NULL) {
+            char buffer[1024];
+            if (fgets(buffer, sizeof(buffer), fp) != NULL) {
+                unsigned int pid;
+                if (safe_strtoul(buffer, &pid) && kill((pid_t)pid, 0) == 0) {
+                    fprintf(stderr, "WARNING: The pid file contained the following (running) pid: %u\n", pid);
+                }
+            }
+            fclose(fp);
+        }
+    }
+
+    if ((fp = fopen(pid_file, "w")) == NULL) {
+        vperror("Could not open the pid file %s for writing", pid_file);
+        return;
+    }
+
+    fprintf(fp,"%ld\n", (long)getpid());
+    if (fclose(fp) == -1) {
+        vperror("Could not close the pid file %s", pid_file);
+    }
+}
+
+static void remove_pidfile(const char *pid_file) {
+  if (pid_file == NULL)
+      return;
+
+  if (unlink(pid_file) != 0) {
+      vperror("Could not remove the pid file %s", pid_file);
+  }
+
+}
+
+static void sig_handler(const int sig) {
+    printf("SIGINT handled.\n");
+    exit(EXIT_SUCCESS);
+}
+
+#ifndef HAVE_SIGIGNORE
+static int sigignore(int sig) {
+    struct sigaction sa = { .sa_handler = SIG_IGN, .sa_flags = 0 };
+
+    if (sigemptyset(&sa.sa_mask) == -1 || sigaction(sig, &sa, 0) == -1) {
+        return -1;
+    }
+    return 0;
+}
+#endif
+
+
+/*
+ * On systems that supports multiple page sizes we may reduce the
+ * number of TLB-misses by using the biggest available page size
+ */
+static int enable_large_pages(void) {
+#if defined(HAVE_GETPAGESIZES) && defined(HAVE_MEMCNTL)
+    int ret = -1;
+    size_t sizes[32];
+    int avail = getpagesizes(sizes, 32);
+    if (avail != -1) {
+        size_t max = sizes[0];
+        struct memcntl_mha arg = {0};
+        int ii;
+
+        for (ii = 1; ii < avail; ++ii) {
+            if (max < sizes[ii]) {
+                max = sizes[ii];
+            }
+        }
+
+        arg.mha_flags   = 0;
+        arg.mha_pagesize = max;
+        arg.mha_cmd = MHA_MAPSIZE_BSSBRK;
+
+        if (memcntl(0, 0, MC_HAT_ADVISE, (caddr_t)&arg, 0, 0) == -1) {
+            fprintf(stderr, "Failed to set large pages: %s\n",
+                    strerror(errno));
+            fprintf(stderr, "Will use default page size\n");
+        } else {
+            ret = 0;
+        }
+    } else {
+        fprintf(stderr, "Failed to get supported pagesizes: %s\n",
+                strerror(errno));
+        fprintf(stderr, "Will use default page size\n");
+    }
+
+    return ret;
+#else
+    return 0;
+#endif
+}
+
+/**
+ * Do basic sanity check of the runtime environment
+ * @return true if no errors found, false if we can't use this env
+ */
+static bool sanitycheck(void) {
+    /* One of our biggest problems is old and bogus libevents */
+    const char *ever = event_get_version();
+    if (ever != NULL) {
+        if (strncmp(ever, "1.", 2) == 0) {
+            /* Require at least 1.3 (that's still a couple of years old) */
+            if ((ever[2] == '1' || ever[2] == '2') && !isdigit(ever[3])) {
+                fprintf(stderr, "You are using libevent %s.\nPlease upgrade to"
+                        " a more recent version (1.3 or newer)\n",
+                        event_get_version());
+                return false;
+            }
+        }
+    }
+
+    return true;
+}
+
+int main (int argc, char **argv) {
+    int c;
+    bool lock_memory = false;
+    bool do_daemonize = false;
+    bool preallocate = false;
+    int maxcore = 0;
+    char *username = NULL;
+    char *pid_file = NULL;
+    struct passwd *pw;
+    struct rlimit rlim;
+    char unit = '\0';
+    int size_max = 0;
+    int retval = EXIT_SUCCESS;
+    /* listening sockets */
+    static int *l_socket = NULL;
+
+    /* udp socket */
+    static int *u_socket = NULL;
+    bool protocol_specified = false;
+    bool tcp_specified = false;
+    bool udp_specified = false;
+
+    char *subopts;
+    char *subopts_value;
+    enum {
+        MAXCONNS_FAST = 0,
+        HASHPOWER_INIT,
+        SLAB_REASSIGN,
+        SLAB_AUTOMOVE
+    };
+    char *const subopts_tokens[] = {
+        [MAXCONNS_FAST] = (char*)"maxconns_fast",
+        [HASHPOWER_INIT] = (char*)"hashpower",
+        [SLAB_REASSIGN] = (char*)"slab_reassign",
+        [SLAB_AUTOMOVE] = (char*)"slab_automove",
+        NULL
+    };
+
+    if (!sanitycheck()) {
+        return EX_OSERR;
+    }
+
+    /* handle SIGINT */
+    signal(SIGINT, sig_handler);
+
+    /* init settings */
+    settings_init();
+
+    /* set stderr non-buffering (for running under, say, daemontools) */
+    setbuf(stderr, NULL);
+
+    /* process arguments */
+    while (-1 != (c = getopt(argc, argv,
+          "a:"  /* access mask for unix socket */
+          "p:"  /* TCP port number to listen on */
+          "s:"  /* unix socket path to listen on */
+          "U:"  /* UDP port number to listen on */
+          "m:"  /* max memory to use for items in megabytes */
+          "M"   /* return error on memory exhausted */
+          "c:"  /* max simultaneous connections */
+          "k"   /* lock down all paged memory */
+          "hi"  /* help, licence info */
+          "r"   /* maximize core file limit */
+          "v"   /* verbose */
+          "d"   /* daemon mode */
+          "l:"  /* interface to listen on */
+          "u:"  /* user identity to run as */
+          "P:"  /* save PID in file */
+          "f:"  /* factor? */
+          "n:"  /* minimum space allocated for key+value+flags */
+          "t:"  /* threads */
+          "D:"  /* prefix delimiter? */
+          "L"   /* Large memory pages */
+          "R:"  /* max requests per event */
+          "C"   /* Disable use of CAS */
+          "b:"  /* backlog queue limit */
+          "B:"  /* Binding protocol */
+          "I:"  /* Max item size */
+          "S"   /* Sasl ON */
+          "o:"  /* Extended generic options */
+        ))) {
+        switch (c) {
+        case 'a':
+            /* access for unix domain socket, as octal mask (like chmod)*/
+            settings.access= strtol(optarg,NULL,8);
+            break;
+
+        case 'U':
+            settings.udpport = atoi(optarg);
+            udp_specified = true;
+            break;
+        case 'p':
+            settings.port = atoi(optarg);
+            tcp_specified = true;
+            break;
+        case 's':
+            settings.socketpath = optarg;
+            break;
+        case 'm':
+            settings.maxbytes = ((size_t)atoi(optarg)) * 1024 * 1024;
+            break;
+        case 'M':
+            settings.evict_to_free = 0;
+            break;
+        case 'c':
+            settings.maxconns = atoi(optarg);
+            break;
+        case 'h':
+            usage();
+            exit(EXIT_SUCCESS);
+        case 'i':
+            usage_license();
+            exit(EXIT_SUCCESS);
+        case 'k':
+            lock_memory = true;
+            break;
+        case 'v':
+            settings.verbose++;
+            break;
+        case 'l':
+            if (settings.inter != NULL) {
+                size_t len = strlen(settings.inter) + strlen(optarg) + 2;
+                char *p = malloc(len);
+                if (p == NULL) {
+                    fprintf(stderr, "Failed to allocate memory\n");
+                    return 1;
+                }
+                snprintf(p, len, "%s,%s", settings.inter, optarg);
+                free(settings.inter);
+                settings.inter = p;
+            } else {
+                settings.inter= strdup(optarg);
+            }
+            break;
+        case 'd':
+            do_daemonize = true;
+            break;
+        case 'r':
+            maxcore = 1;
+            break;
+        case 'R':
+            settings.reqs_per_event = atoi(optarg);
+            if (settings.reqs_per_event == 0) {
+                fprintf(stderr, "Number of requests per event must be greater than 0\n");
+                return 1;
+            }
+            break;
+        case 'u':
+            username = optarg;
+            break;
+        case 'P':
+            pid_file = optarg;
+            break;
+        case 'f':
+            settings.factor = atof(optarg);
+            if (settings.factor <= 1.0) {
+                fprintf(stderr, "Factor must be greater than 1\n");
+                return 1;
+            }
+            break;
+        case 'n':
+            settings.chunk_size = atoi(optarg);
+            if (settings.chunk_size == 0) {
+                fprintf(stderr, "Chunk size must be greater than 0\n");
+                return 1;
+            }
+            break;
+        case 't':
+            settings.num_threads = atoi(optarg);
+            if (settings.num_threads <= 0) {
+                fprintf(stderr, "Number of threads must be greater than 0\n");
+                return 1;
+            }
+            /* There're other problems when you get above 64 threads.
+             * In the future we should portably detect # of cores for the
+             * default.
+             */
+            if (settings.num_threads > 64) {
+                fprintf(stderr, "WARNING: Setting a high number of worker"
+                                "threads is not recommended.\n"
+                                " Set this value to the number of cores in"
+                                " your machine or less.\n");
+            }
+            break;
+        case 'D':
+            if (! optarg || ! optarg[0]) {
+                fprintf(stderr, "No delimiter specified\n");
+                return 1;
+            }
+            settings.prefix_delimiter = optarg[0];
+            settings.detail_enabled = 1;
+            break;
+        case 'L' :
+            if (enable_large_pages() == 0) {
+                preallocate = true;
+            }
+            break;
+        case 'C' :
+            settings.use_cas = false;
+            break;
+        case 'b' :
+            settings.backlog = atoi(optarg);
+            break;
+        case 'B':
+            protocol_specified = true;
+            if (strcmp(optarg, "auto") == 0) {
+                settings.binding_protocol = negotiating_prot;
+            } else if (strcmp(optarg, "binary") == 0) {
+                settings.binding_protocol = binary_prot;
+            } else if (strcmp(optarg, "ascii") == 0) {
+                settings.binding_protocol = ascii_prot;
+            } else {
+                fprintf(stderr, "Invalid value for binding protocol: %s\n"
+                        " -- should be one of auto, binary, or ascii\n", optarg);
+                exit(EX_USAGE);
+            }
+            break;
+        case 'I':
+            unit = optarg[strlen(optarg)-1];
+            if (unit == 'k' || unit == 'm' ||
+                unit == 'K' || unit == 'M') {
+                optarg[strlen(optarg)-1] = '\0';
+                size_max = atoi(optarg);
+                if (unit == 'k' || unit == 'K')
+                    size_max *= 1024;
+                if (unit == 'm' || unit == 'M')
+                    size_max *= 1024 * 1024;
+                settings.item_size_max = size_max;
+            } else {
+                settings.item_size_max = atoi(optarg);
+            }
+            if (settings.item_size_max < 1024) {
+                fprintf(stderr, "Item max size cannot be less than 1024 bytes.\n");
+                return 1;
+            }
+            if (settings.item_size_max > 1024 * 1024 * 128) {
+                fprintf(stderr, "Cannot set item size limit higher than 128 mb.\n");
+                return 1;
+            }
+            if (settings.item_size_max > 1024 * 1024) {
+                fprintf(stderr, "WARNING: Setting item max size above 1MB is not"
+                    " recommended!\n"
+                    " Raising this limit increases the minimum memory requirements\n"
+                    " and will decrease your memory efficiency.\n"
+                );
+            }
+            break;
+        case 'S': /* set Sasl authentication to true. Default is false */
+#ifndef ENABLE_SASL
+            fprintf(stderr, "This server is not built with SASL support.\n");
+            exit(EX_USAGE);
+#endif
+            settings.sasl = true;
+            break;
+        case 'o': /* It's sub-opts time! */
+            subopts = optarg;
+
+            while (*subopts != '\0') {
+
+            switch (getsubopt(&subopts, subopts_tokens, &subopts_value)) {
+            case MAXCONNS_FAST:
+                settings.maxconns_fast = true;
+                break;
+            case HASHPOWER_INIT:
+                if (subopts_value == NULL) {
+                    fprintf(stderr, "Missing numeric argument for hashpower\n");
+                    return 1;
+                }
+                settings.hashpower_init = atoi(subopts_value);
+                if (settings.hashpower_init < 12) {
+                    fprintf(stderr, "Initial hashtable multiplier of %d is too low\n",
+                        settings.hashpower_init);
+                    return 1;
+                } else if (settings.hashpower_init > 64) {
+                    fprintf(stderr, "Initial hashtable multiplier of %d is too high\n"
+                        "Choose a value based on \"STAT hash_power_level\" from a running instance\n",
+                        settings.hashpower_init);
+                    return 1;
+                }
+                break;
+            case SLAB_REASSIGN:
+                settings.slab_reassign = true;
+                break;
+            case SLAB_AUTOMOVE:
+                settings.slab_automove = true;
+                break;
+            default:
+                printf("Illegal suboption \"%s\"\n", subopts_value);
+                return 1;
+            }
+
+            }
+            break;
+        default:
+            fprintf(stderr, "Illegal argument \"%c\"\n", c);
+            return 1;
+        }
+    }
+
+    /*
+     * Use one workerthread to serve each UDP port if the user specified
+     * multiple ports
+     */
+    if (settings.inter != NULL && strchr(settings.inter, ',')) {
+        settings.num_threads_per_udp = 1;
+    } else {
+        settings.num_threads_per_udp = settings.num_threads;
+    }
+
+    if (settings.sasl) {
+        if (!protocol_specified) {
+            settings.binding_protocol = binary_prot;
+        } else {
+            if (settings.binding_protocol != binary_prot) {
+                fprintf(stderr, "ERROR: You cannot allow the ASCII protocol while using SASL.\n");
+                exit(EX_USAGE);
+            }
+        }
+    }
+
+    if (tcp_specified && !udp_specified) {
+        settings.udpport = settings.port;
+    } else if (udp_specified && !tcp_specified) {
+        settings.port = settings.udpport;
+    }
+
+    if (maxcore != 0) {
+        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(EX_OSERR);
+        }
+    }
+
+    /*
+     * 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(EX_OSERR);
+    } else {
+        rlim.rlim_cur = settings.maxconns;
+        rlim.rlim_max = settings.maxconns;
+        if (setrlimit(RLIMIT_NOFILE, &rlim) != 0) {
+            fprintf(stderr, "failed to set rlimit for open files. Try starting as root or requesting smaller maxconns value.\n");
+            exit(EX_OSERR);
+        }
+    }
+
+    /* 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");
+            exit(EX_USAGE);
+        }
+        if ((pw = getpwnam(username)) == 0) {
+            fprintf(stderr, "can't find the user %s to switch to\n", username);
+            exit(EX_NOUSER);
+        }
+        if (setgid(pw->pw_gid) < 0 || setuid(pw->pw_uid) < 0) {
+            fprintf(stderr, "failed to assume identity of user %s\n", username);
+            exit(EX_OSERR);
+        }
+    }
+
+    /* Initialize Sasl if -S was specified */
+    if (settings.sasl) {
+        init_sasl();
+    }
+
+    /* daemonize if requested */
+    /* if we want to ensure our ability to dump core, don't chdir to / */
+    if (do_daemonize) {
+        if (sigignore(SIGHUP) == -1) {
+            perror("Failed to ignore SIGHUP");
+        }
+        if (daemonize(maxcore, settings.verbose) == -1) {
+            fprintf(stderr, "failed to daemon() in order to daemonize\n");
+            exit(EXIT_FAILURE);
+        }
+    }
+
+    /* lock paged memory if needed */
+    if (lock_memory) {
+#ifdef HAVE_MLOCKALL
+        int res = mlockall(MCL_CURRENT | MCL_FUTURE);
+        if (res != 0) {
+            fprintf(stderr, "warning: -k invalid, mlockall() failed: %s\n",
+                    strerror(errno));
+        }
+#else
+        fprintf(stderr, "warning: -k invalid, mlockall() not supported on this platform.  proceeding without.\n");
+#endif
+    }
+
+    /* initialize main thread libevent instance */
+    main_base = event_init();
+
+    /* initialize other stuff */
+    stats_init();
+    assoc_init(settings.hashpower_init);
+    conn_init();
+    slabs_init(settings.maxbytes, settings.factor, preallocate);
+
+    /*
+     * ignore SIGPIPE signals; we can use errno == EPIPE if we
+     * need that information
+     */
+    if (sigignore(SIGPIPE) == -1) {
+        perror("failed to ignore SIGPIPE; sigaction");
+        exit(EX_OSERR);
+    }
+    /* start up worker threads if MT mode */
+    thread_init(settings.num_threads, main_base);
+
+    if (start_assoc_maintenance_thread() == -1) {
+        exit(EXIT_FAILURE);
+    }
+
+    if (settings.slab_reassign &&
+        start_slab_maintenance_thread() == -1) {
+        exit(EXIT_FAILURE);
+    }
+
+    /* initialise clock event */
+    clock_handler(0, 0, 0);
+
+    /* create unix mode sockets after dropping privileges */
+    if (settings.socketpath != NULL) {
+        errno = 0;
+        if (server_socket_unix(settings.socketpath,settings.access)) {
+            vperror("failed to listen on UNIX socket: %s", settings.socketpath);
+            exit(EX_OSERR);
+        }
+    }
+
+    /* create the listening socket, bind it, and init */
+    if (settings.socketpath == NULL) {
+        const char *portnumber_filename = getenv("MEMCACHED_PORT_FILENAME");
+        char temp_portnumber_filename[PATH_MAX];
+        FILE *portnumber_file = NULL;
+
+        if (portnumber_filename != NULL) {
+            snprintf(temp_portnumber_filename,
+                     sizeof(temp_portnumber_filename),
+                     "%s.lck", portnumber_filename);
+
+            portnumber_file = fopen(temp_portnumber_filename, "a");
+            if (portnumber_file == NULL) {
+                fprintf(stderr, "Failed to open \"%s\": %s\n",
+                        temp_portnumber_filename, strerror(errno));
+            }
+        }
+
+        errno = 0;
+        if (settings.port && server_sockets(settings.port, tcp_transport,
+                                           portnumber_file)) {
+            vperror("failed to listen on TCP port %d", settings.port);
+            exit(EX_OSERR);
+        }
+
+        /*
+         * initialization order: first create the listening sockets
+         * (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 UDP listening socket and bind it */
+        errno = 0;
+        if (settings.udpport && server_sockets(settings.udpport, udp_transport,
+                                              portnumber_file)) {
+            vperror("failed to listen on UDP port %d", settings.udpport);
+            exit(EX_OSERR);
+        }
+
+        if (portnumber_file) {
+            fclose(portnumber_file);
+            rename(temp_portnumber_filename, portnumber_filename);
+        }
+    }
+
+    /* Give the sockets a moment to open. I know this is dumb, but the error
+     * is only an advisory.
+     */
+    usleep(1000);
+    if (stats.curr_conns + stats.reserved_fds >= settings.maxconns - 1) {
+        fprintf(stderr, "Maxconns setting is too low, use -c to increase.\n");
+        exit(EXIT_FAILURE);
+    }
+
+    if (pid_file != NULL) {
+        save_pid(pid_file);
+    }
+
+    /* Drop privileges no longer needed */
+    drop_privileges();
+
+    /* enter the event loop */
+    if (event_base_loop(main_base, 0) != 0) {
+        retval = EXIT_FAILURE;
+    }
+
+    stop_assoc_maintenance_thread();
+
+    /* remove the PID file if we're a daemon */
+    if (do_daemonize)
+        remove_pidfile(pid_file);
+    /* Clean up strdup() call for bind() address */
+    if (settings.inter)
+      free(settings.inter);
+    if (l_socket)
+      free(l_socket);
+    if (u_socket)
+      free(u_socket);
+
+    return retval;
+}
diff --git a/memcached/memcached.h b/memcached/memcached.h
new file mode 100644 (file)
index 0000000..d070502
--- /dev/null
@@ -0,0 +1,571 @@
+/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+
+/** \file
+ * The main memcached header holding commonly used data
+ * structures and function prototypes.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <netinet/in.h>
+#include <event.h>
+#include <netdb.h>
+#include <pthread.h>
+#include <unistd.h>
+
+#include "protocol_binary.h"
+#include "cache.h"
+
+#include "sasl_defs.h"
+
+/** Maximum length of a key. */
+#define KEY_MAX_LENGTH 250
+
+/** Size of an incr buf. */
+#define INCR_MAX_STORAGE_LEN 24
+
+#define DATA_BUFFER_SIZE 2048
+#define UDP_READ_BUFFER_SIZE 65536
+#define UDP_MAX_PAYLOAD_SIZE 1400
+#define UDP_HEADER_SIZE 8
+#define MAX_SENDBUF_SIZE (256 * 1024 * 1024)
+/* I'm told the max length of a 64-bit num converted to string is 20 bytes.
+ * Plus a few for spaces, \r\n, \0 */
+#define SUFFIX_SIZE 24
+
+/** Initial size of list of items being returned by "get". */
+#define ITEM_LIST_INITIAL 200
+
+/** Initial size of list of CAS suffixes appended to "gets" lines. */
+#define SUFFIX_LIST_INITIAL 20
+
+/** Initial size of the sendmsg() scatter/gather array. */
+#define IOV_LIST_INITIAL 400
+
+/** Initial number of sendmsg() argument structures to allocate. */
+#define MSG_LIST_INITIAL 10
+
+/** High water marks for buffer shrinking */
+#define READ_BUFFER_HIGHWAT 8192
+#define ITEM_LIST_HIGHWAT 400
+#define IOV_LIST_HIGHWAT 600
+#define MSG_LIST_HIGHWAT 100
+
+/* Binary protocol stuff */
+#define MIN_BIN_PKT_LENGTH 16
+#define BIN_PKT_HDR_WORDS (MIN_BIN_PKT_LENGTH/sizeof(uint32_t))
+
+/* Initial power multiplier for the hash table */
+#define HASHPOWER_DEFAULT 16
+
+/* unistd.h is here */
+#if HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+/* Slab sizing definitions. */
+#define POWER_SMALLEST 1
+#define POWER_LARGEST  200
+#define CHUNK_ALIGN_BYTES 8
+#define DONT_PREALLOC_SLABS
+#define MAX_NUMBER_OF_SLAB_CLASSES (POWER_LARGEST + 1)
+
+/** How long an object can reasonably be assumed to be locked before
+    harvesting it on a low memory condition. */
+#define TAIL_REPAIR_TIME (3 * 3600)
+
+/* warning: don't use these macros with a function, as it evals its arg twice */
+#define ITEM_get_cas(i) (((i)->it_flags & ITEM_CAS) ? \
+        (i)->data->cas : (uint64_t)0)
+
+#define ITEM_set_cas(i,v) { \
+    if ((i)->it_flags & ITEM_CAS) { \
+        (i)->data->cas = v; \
+    } \
+}
+
+#define ITEM_key(item) (((char*)&((item)->data)) \
+         + (((item)->it_flags & ITEM_CAS) ? sizeof(uint64_t) : 0))
+
+#define ITEM_suffix(item) ((char*) &((item)->data) + (item)->nkey + 1 \
+         + (((item)->it_flags & ITEM_CAS) ? sizeof(uint64_t) : 0))
+
+#define ITEM_data(item) ((char*) &((item)->data) + (item)->nkey + 1 \
+         + (item)->nsuffix \
+         + (((item)->it_flags & ITEM_CAS) ? sizeof(uint64_t) : 0))
+
+#define ITEM_ntotal(item) (sizeof(struct _stritem) + (item)->nkey + 1 \
+         + (item)->nsuffix + (item)->nbytes \
+         + (((item)->it_flags & ITEM_CAS) ? sizeof(uint64_t) : 0))
+
+#define STAT_KEY_LEN 128
+#define STAT_VAL_LEN 128
+
+/** Append a simple stat with a stat name, value format and value */
+#define APPEND_STAT(name, fmt, val) \
+    append_stat(name, add_stats, c, fmt, val);
+
+/** Append an indexed stat with a stat name (with format), value format
+    and value */
+#define APPEND_NUM_FMT_STAT(name_fmt, num, name, fmt, val)          \
+    klen = snprintf(key_str, STAT_KEY_LEN, name_fmt, num, name);    \
+    vlen = snprintf(val_str, STAT_VAL_LEN, fmt, val);               \
+    add_stats(key_str, klen, val_str, vlen, c);
+
+/** Common APPEND_NUM_FMT_STAT format. */
+#define APPEND_NUM_STAT(num, name, fmt, val) \
+    APPEND_NUM_FMT_STAT("%d:%s", num, name, fmt, val)
+
+/**
+ * Callback for any function producing stats.
+ *
+ * @param key the stat's key
+ * @param klen length of the key
+ * @param val the stat's value in an ascii form (e.g. text form of a number)
+ * @param vlen length of the value
+ * @parm cookie magic callback cookie
+ */
+typedef void (*ADD_STAT)(const char *key, const uint16_t klen,
+                         const char *val, const uint32_t vlen,
+                         const void *cookie);
+
+/*
+ * NOTE: If you modify this table you _MUST_ update the function state_text
+ */
+/**
+ * Possible states of a connection.
+ */
+enum conn_states {
+    conn_listening,  /**< the socket which listens for connections */
+    conn_new_cmd,    /**< Prepare connection for next command */
+    conn_waiting,    /**< waiting for a readable socket */
+    conn_read,       /**< reading in a command line */
+    conn_parse_cmd,  /**< try to parse a command from the input buffer */
+    conn_write,      /**< writing out a simple response */
+    conn_nread,      /**< reading in a fixed number of bytes */
+    conn_swallow,    /**< swallowing unnecessary bytes w/o storing */
+    conn_closing,    /**< closing this connection */
+    conn_mwrite,     /**< writing out many items sequentially */
+    conn_max_state   /**< Max state value (used for assertion) */
+};
+
+enum bin_substates {
+    bin_no_state,
+    bin_reading_set_header,
+    bin_reading_cas_header,
+    bin_read_set_value,
+    bin_reading_get_key,
+    bin_reading_stat,
+    bin_reading_del_header,
+    bin_reading_incr_header,
+    bin_read_flush_exptime,
+    bin_reading_sasl_auth,
+    bin_reading_sasl_auth_data,
+    bin_reading_touch_key,
+};
+
+enum protocol {
+    ascii_prot = 3, /* arbitrary value. */
+    binary_prot,
+    negotiating_prot /* Discovering the protocol */
+};
+
+enum network_transport {
+    local_transport, /* Unix sockets*/
+    tcp_transport,
+    udp_transport
+};
+
+#define IS_UDP(x) (x == udp_transport)
+
+#define NREAD_ADD 1
+#define NREAD_SET 2
+#define NREAD_REPLACE 3
+#define NREAD_APPEND 4
+#define NREAD_PREPEND 5
+#define NREAD_CAS 6
+
+enum store_item_type {
+    NOT_STORED=0, STORED, EXISTS, NOT_FOUND
+};
+
+enum delta_result_type {
+    OK, NON_NUMERIC, EOM, DELTA_ITEM_NOT_FOUND, DELTA_ITEM_CAS_MISMATCH
+};
+
+/** Time relative to server start. Smaller than time_t on 64-bit systems. */
+typedef unsigned int rel_time_t;
+
+/** Stats stored per slab (and per thread). */
+struct slab_stats {
+    uint64_t  set_cmds;
+    uint64_t  get_hits;
+    uint64_t  touch_hits;
+    uint64_t  delete_hits;
+    uint64_t  cas_hits;
+    uint64_t  cas_badval;
+    uint64_t  incr_hits;
+    uint64_t  decr_hits;
+};
+
+/**
+ * Stats stored per-thread.
+ */
+struct thread_stats {
+    pthread_mutex_t   mutex;
+    uint64_t          get_cmds;
+    uint64_t          get_misses;
+    uint64_t          touch_cmds;
+    uint64_t          touch_misses;
+    uint64_t          delete_misses;
+    uint64_t          incr_misses;
+    uint64_t          decr_misses;
+    uint64_t          cas_misses;
+    uint64_t          bytes_read;
+    uint64_t          bytes_written;
+    uint64_t          flush_cmds;
+    uint64_t          conn_yields; /* # of yields for connections (-R option)*/
+    uint64_t          auth_cmds;
+    uint64_t          auth_errors;
+    struct slab_stats slab_stats[MAX_NUMBER_OF_SLAB_CLASSES];
+};
+
+/**
+ * Global stats.
+ */
+struct stats {
+    pthread_mutex_t mutex;
+    unsigned int  curr_items;
+    unsigned int  total_items;
+    uint64_t      curr_bytes;
+    unsigned int  curr_conns;
+    unsigned int  total_conns;
+    uint64_t      rejected_conns;
+    unsigned int  reserved_fds;
+    unsigned int  conn_structs;
+    uint64_t      get_cmds;
+    uint64_t      set_cmds;
+    uint64_t      touch_cmds;
+    uint64_t      get_hits;
+    uint64_t      get_misses;
+    uint64_t      touch_hits;
+    uint64_t      touch_misses;
+    uint64_t      evictions;
+    uint64_t      reclaimed;
+    time_t        started;          /* when the process was started */
+    bool          accepting_conns;  /* whether we are currently accepting */
+    uint64_t      listen_disabled_num;
+    unsigned int  hash_power_level; /* Better hope it's not over 9000 */
+    uint64_t      hash_bytes;       /* size used for hash tables */
+    bool          hash_is_expanding; /* If the hash table is being expanded */
+    uint64_t      expired_unfetched; /* items reclaimed but never touched */
+    uint64_t      evicted_unfetched; /* items evicted but never touched */
+    bool          slab_reassign_running; /* slab reassign in progress */
+    uint64_t      slabs_moved;       /* times slabs were moved around */
+};
+
+#define MAX_VERBOSITY_LEVEL 2
+
+/* When adding a setting, be sure to update process_stat_settings */
+/**
+ * Globally accessible settings as derived from the commandline.
+ */
+struct settings {
+    size_t maxbytes;
+    int maxconns;
+    int port;
+    int udpport;
+    char *inter;
+    int verbose;
+    rel_time_t oldest_live; /* ignore existing items older than this */
+    int evict_to_free;
+    char *socketpath;   /* path to unix socket if using local socket */
+    int access;  /* access mask (a la chmod) for unix domain socket */
+    double factor;          /* chunk size growth factor */
+    int chunk_size;
+    int num_threads;        /* number of worker (without dispatcher) libevent threads to run */
+    int num_threads_per_udp; /* number of worker threads serving each udp socket */
+    char prefix_delimiter;  /* character that marks a key prefix (for stats) */
+    int detail_enabled;     /* nonzero if we're collecting detailed stats */
+    int reqs_per_event;     /* Maximum number of io to process on each
+                               io-event. */
+    bool use_cas;
+    enum protocol binding_protocol;
+    int backlog;
+    int item_size_max;        /* Maximum item size, and upper end for slabs */
+    bool sasl;              /* SASL on/off */
+    bool maxconns_fast;     /* Whether or not to early close connections */
+    bool slab_reassign;     /* Whether or not slab reassignment is allowed */
+    bool slab_automove;     /* Whether or not to automatically move slabs */
+    int hashpower_init;     /* Starting hash power level */
+};
+
+#ifndef __INTEL_COMPILER
+#pragma GCC diagnostic ignored "-Wshadow"
+#endif
+extern struct stats stats;
+extern time_t process_started;
+extern struct settings settings;
+
+#define ITEM_LINKED 1
+#define ITEM_CAS 2
+
+/* temp */
+#define ITEM_SLABBED 4
+
+#define ITEM_FETCHED 8
+
+#ifndef __INTEL_COMPILER
+#pragma GCC diagnostic ignored "-Wshadow"
+#endif
+/**
+ * Structure for storing items within memcached.
+ */
+typedef struct _stritem {
+    struct _stritem *next;
+    struct _stritem *prev;
+    struct _stritem *h_next;    /* hash chain next */
+    rel_time_t      time;       /* least recent access */
+    rel_time_t      exptime;    /* expire time */
+    int             nbytes;     /* size of data */
+    unsigned short  refcount;
+    uint8_t         nsuffix;    /* length of flags-and-length string */
+    uint8_t         it_flags;   /* ITEM_* above */
+    uint8_t         slabs_clsid;/* which slab class we're in */
+    uint8_t         nkey;       /* key length, w/terminating null and padding */
+    /* this odd type prevents type-punning issues when we do
+     * the little shuffle to save space when not using CAS. */
+    union {
+        uint64_t cas;
+        char end;
+    } data[];
+    /* if it_flags & ITEM_CAS we have 8 bytes CAS */
+    /* then null-terminated key */
+    /* then " flags length\r\n" (no terminating null) */
+    /* then data with terminating \r\n (no terminating null; it's binary!) */
+} item;
+
+typedef struct {
+    pthread_t thread_id;        /* unique ID of this thread */
+    struct event_base *base;    /* libevent handle this thread uses */
+    struct event notify_event;  /* listen event for notify pipe */
+    int notify_receive_fd;      /* receiving end of notify pipe */
+    int notify_send_fd;         /* sending end of notify pipe */
+    struct thread_stats stats;  /* Stats generated by this thread */
+    struct conn_queue *new_conn_queue; /* queue of new connections to handle */
+    cache_t *suffix_cache;      /* suffix cache */
+} LIBEVENT_THREAD;
+
+typedef struct {
+    pthread_t thread_id;        /* unique ID of this thread */
+    struct event_base *base;    /* libevent handle this thread uses */
+} LIBEVENT_DISPATCHER_THREAD;
+
+/**
+ * The structure representing a connection into memcached.
+ */
+typedef struct conn conn;
+struct conn {
+    int    sfd;
+    sasl_conn_t *sasl_conn;
+    enum conn_states  state;
+    enum bin_substates substate;
+    struct event event;
+    short  ev_flags;
+    short  which;   /** which events were just triggered */
+
+    char   *rbuf;   /** buffer to read commands into */
+    char   *rcurr;  /** but if we parsed some already, this is where we stopped */
+    int    rsize;   /** total allocated size of rbuf */
+    int    rbytes;  /** how much data, starting from rcur, do we have unparsed */
+
+    char   *wbuf;
+    char   *wcurr;
+    int    wsize;
+    int    wbytes;
+    /** which state to go into after finishing current write */
+    enum conn_states  write_and_go;
+    void   *write_and_free; /** free this memory after finishing writing */
+
+    char   *ritem;  /** when we read in an item's value, it goes here */
+    int    rlbytes;
+
+    /* data for the nread state */
+
+    /**
+     * item is used to hold an item structure created after reading the command
+     * line of set/add/replace commands, but before we finished reading the actual
+     * data. The data is read into ITEM_data(item) to avoid extra copying.
+     */
+
+    void   *item;     /* for commands set/add/replace  */
+
+    /* data for the swallow state */
+    int    sbytes;    /* how many bytes to swallow */
+
+    /* data for the mwrite state */
+    struct iovec *iov;
+    int    iovsize;   /* number of elements allocated in iov[] */
+    int    iovused;   /* number of elements used in iov[] */
+
+    struct msghdr *msglist;
+    int    msgsize;   /* number of elements allocated in msglist[] */
+    int    msgused;   /* number of elements used in msglist[] */
+    int    msgcurr;   /* element in msglist[] being transmitted now */
+    int    msgbytes;  /* number of bytes in current msg */
+
+    item   **ilist;   /* list of items to write out */
+    int    isize;
+    item   **icurr;
+    int    ileft;
+
+    char   **suffixlist;
+    int    suffixsize;
+    char   **suffixcurr;
+    int    suffixleft;
+
+    enum protocol protocol;   /* which protocol this connection speaks */
+    enum network_transport transport; /* what transport is used by this connection */
+
+    /* data for UDP clients */
+    int    request_id; /* Incoming UDP request ID, if this is a UDP "connection" */
+    struct sockaddr request_addr; /* Who sent the most recent request */
+    socklen_t request_addr_size;
+    unsigned char *hdrbuf; /* udp packet headers */
+    int    hdrsize;   /* number of headers' worth of space is allocated */
+
+    bool   noreply;   /* True if the reply should not be sent. */
+    /* current stats command */
+    struct {
+        char *buffer;
+        size_t size;
+        size_t offset;
+    } stats;
+
+    /* Binary protocol stuff */
+    /* This is where the binary header goes */
+    protocol_binary_request_header binary_header;
+    uint64_t cas; /* the cas to return */
+    short cmd; /* current command being processed */
+    int opaque;
+    int keylen;
+    conn   *next;     /* Used for generating a list of conn structures */
+    LIBEVENT_THREAD *thread; /* Pointer to the thread object serving this connection */
+};
+
+
+/* current time of day (updated periodically) */
+extern volatile rel_time_t current_time;
+
+/* TODO: Move to slabs.h? */
+extern volatile int slab_rebalance_signal;
+
+struct slab_rebalance {
+    void *slab_start;
+    void *slab_end;
+    void *slab_pos;
+    int s_clsid;
+    int d_clsid;
+    int busy_items;
+    uint8_t done;
+};
+
+extern struct slab_rebalance slab_rebal;
+
+/*
+ * Functions
+ */
+void do_accept_new_conns(const bool do_accept);
+enum delta_result_type do_add_delta(conn *c, const char *key,
+                                    const size_t nkey, const bool incr,
+                                    const int64_t delta, char *buf,
+                                    uint64_t *cas, const uint32_t hv);
+enum store_item_type do_store_item(item *item, int comm, conn* c, const uint32_t hv);
+conn *conn_new(const int sfd, const enum conn_states init_state, const int event_flags, const int read_buffer_size, enum network_transport transport, struct event_base *base);
+extern int daemonize(int nochdir, int noclose);
+
+static inline int mutex_lock(pthread_mutex_t *mutex)
+{
+    while (pthread_mutex_trylock(mutex));
+    return 0;
+}
+
+#define mutex_unlock(x) pthread_mutex_unlock(x)
+
+#include "stats.h"
+#include "slabs.h"
+#include "assoc.h"
+#include "items.h"
+#include "trace.h"
+#include "hash.h"
+#include "util.h"
+
+/*
+ * Functions such as the libevent-related calls that need to do cross-thread
+ * communication in multithreaded mode (rather than actually doing the work
+ * in the current thread) are called via "dispatch_" frontends, which are
+ * also #define-d to directly call the underlying code in singlethreaded mode.
+ */
+
+void thread_init(int nthreads, struct event_base *main_base);
+int  dispatch_event_add(int thread, conn *c);
+void dispatch_conn_new(int sfd, enum conn_states init_state, int event_flags, int read_buffer_size, enum network_transport transport);
+
+/* Lock wrappers for cache functions that are called from main loop. */
+enum delta_result_type add_delta(conn *c, const char *key,
+                                 const size_t nkey, const int incr,
+                                 const int64_t delta, char *buf,
+                                 uint64_t *cas);
+void accept_new_conns(const bool do_accept);
+conn *conn_from_freelist(void);
+bool  conn_add_to_freelist(conn *c);
+int   is_listen_thread(void);
+item *item_alloc(char *key, size_t nkey, int flags, rel_time_t exptime, int nbytes);
+char *item_cachedump(const unsigned int slabs_clsid, const unsigned int limit, unsigned int *bytes);
+void  item_flush_expired(void);
+item *item_get(const char *key, const size_t nkey);
+item *item_touch(const char *key, const size_t nkey, uint32_t exptime);
+int   item_link(item *it);
+void  item_remove(item *it);
+int   item_replace(item *it, item *new_it, const uint32_t hv);
+void  item_stats(ADD_STAT add_stats, void *c);
+void  item_stats_sizes(ADD_STAT add_stats, void *c);
+void  item_unlink(item *it);
+void  item_update(item *it);
+
+void item_lock(uint32_t hv);
+void item_unlock(uint32_t hv);
+unsigned short refcount_incr(unsigned short *refcount);
+unsigned short refcount_decr(unsigned short *refcount);
+void STATS_LOCK(void);
+void STATS_UNLOCK(void);
+void threadlocal_stats_reset(void);
+void threadlocal_stats_aggregate(struct thread_stats *stats);
+void slab_stats_aggregate(struct thread_stats *stats, struct slab_stats *out);
+
+/* Stat processing functions */
+void append_stat(const char *name, ADD_STAT add_stats, conn *c,
+                 const char *fmt, ...);
+
+enum store_item_type store_item(item *item, int comm, conn *c);
+
+#if defined(HAVE_DROP_PRIVILEGES) && HAVE_DROP_PRIVILEGES
+extern void drop_privileges(void);
+#else
+#define drop_privileges()
+#endif
+
+/* If supported, give compiler hints for branch prediction. */
+#if !defined(__GNUC__) || (__GNUC__ == 2 && __GNUC_MINOR__ < 96)
+#define __builtin_expect(x, expected_value) (x)
+#endif
+
+#define likely(x)       __builtin_expect((x),1)
+#define unlikely(x)     __builtin_expect((x),0)
diff --git a/memcached/memcached.spec.in b/memcached/memcached.spec.in
new file mode 100644 (file)
index 0000000..f473d8f
--- /dev/null
@@ -0,0 +1,119 @@
+Name:           memcached
+Version:        @VERSION@
+Release:        @RELEASE@%{?dist}
+Summary:        High Performance, Distributed Memory Object Cache
+
+Group:          System Environment/Daemons
+License:        BSD
+URL:            http://www.danga.com/memcached/
+Source0:        http://memcached.googlecode.com/files/%{name}-@FULLVERSION@.tar.gz
+BuildRoot:      %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
+
+BuildRequires:  libevent-devel
+BuildRequires:  perl(Test::More)
+BuildRequires:  /usr/bin/prove
+Requires: initscripts
+Requires(post): /sbin/chkconfig
+Requires(preun): /sbin/chkconfig, /sbin/service
+Requires(postun): /sbin/service
+
+%description
+memcached is a high-performance, distributed memory object caching
+system, generic in nature, but intended for use in speeding up dynamic
+web applications by alleviating database load.
+
+%prep
+%setup -q -n %{name}-@FULLVERSION@
+
+
+%build
+%configure
+
+make %{?_smp_mflags}
+
+%check
+make test
+
+%install
+rm -rf %{buildroot}
+make install DESTDIR=%{buildroot}
+
+# remove memcached-debug
+rm -f %{buildroot}/%{_bindir}/memcached-debug
+
+# Perl script for monitoring memcached
+install -Dp -m0755 scripts/memcached-tool %{buildroot}%{_bindir}/memcached-tool
+
+# Init script
+install -Dp -m0755 scripts/memcached.sysv %{buildroot}%{_initrddir}/memcached
+
+# Default configs
+mkdir -p %{buildroot}/%{_sysconfdir}/sysconfig
+cat <<EOF >%{buildroot}/%{_sysconfdir}/sysconfig/%{name}
+PORT="11211"
+USER="nobody"
+MAXCONN="1024"
+CACHESIZE="64"
+OPTIONS=""
+EOF
+
+# pid directory
+mkdir -p %{buildroot}/%{_localstatedir}/run/memcached
+
+%clean
+rm -rf %{buildroot}
+
+
+%post
+/sbin/chkconfig --add %{name}
+
+%preun
+if [ "$1" = 0 ] ; then
+    /sbin/service %{name} stop > /dev/null 2>&1
+    /sbin/chkconfig --del %{name}
+fi
+exit 0
+
+%postun
+if [ "$1" -ge 1 ]; then
+    /sbin/service %{name} condrestart > /dev/null 2>&1
+fi
+exit 0
+
+
+%files
+%defattr(-,root,root,-)
+%doc AUTHORS ChangeLog COPYING NEWS README doc/CONTRIBUTORS doc/*.txt
+%config(noreplace) %{_sysconfdir}/sysconfig/%{name}
+
+%dir %attr(750,nobody,nobody) %{_localstatedir}/run/memcached
+%{_bindir}/memcached-tool
+%{_bindir}/memcached
+%{_mandir}/man1/memcached.1*
+%{_initrddir}/memcached
+%{_includedir}/memcached
+
+%changelog
+* Mon Nov  2 2009 Dormando <dormando@rydia.net> - 1.4.3-1
+- Fix autogen more.
+
+* Sat Aug 29 2009 Dustin Sallings <dustin@spy.net> - 1.4.1-1
+- Autogenerate the version number from tags.
+
+* Wed Jul  4 2007 Paul Lindner <lindner@inuus.com> - 1.2.2-5
+- Use /var/run/memcached/ directory to hold PID file
+
+* Sat May 12 2007 Paul Lindner <lindner@inuus.com> - 1.2.2-4
+- Remove tabs from spec file, rpmlint reports no more errors
+
+* Thu May 10 2007 Paul Lindner <lindner@inuus.com> - 1.2.2-3
+- Enable build-time regression tests
+- add dependency on initscripts
+- remove memcached-debug (not needed in dist)
+- above suggestions from Bernard Johnson
+
+* Mon May  7 2007 Paul Lindner <lindner@inuus.com> - 1.2.2-2
+- Tidyness improvements suggested by Ruben Kerkhof in bugzilla #238994
+
+* Fri May  4 2007 Paul Lindner <lindner@inuus.com> - 1.2.2-1
+- Initial spec file created via rpmdev-newspec
diff --git a/memcached/memcached_dtrace.d b/memcached/memcached_dtrace.d
new file mode 100644 (file)
index 0000000..eeffb6f
--- /dev/null
@@ -0,0 +1,295 @@
+/*
+ * Copyright (c) <2008>, Sun Microsystems, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of the  nor the
+ *       names of its contributors may be used to endorse or promote products
+ *       derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY SUN MICROSYSTEMS, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL SUN MICROSYSTEMS, INC. BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+provider memcached {
+   /**
+    * Fired when a connection object is allocated from the connection pool.
+    * @param connid the connection id
+    */
+   probe conn__allocate(int connid);
+
+   /**
+    * Fired when a connection object is released back to the connection pool.
+    * @param connid the connection id
+    */
+   probe conn__release(int connid);
+
+   /**
+    * Fired when a new connection object is created (there are no more
+    * connection objects in the connection pool).
+    * @param ptr pointer to the connection object
+    */
+   probe conn__create(void *ptr);
+
+   /**
+    * Fired when a connection object is destroyed ("released back to
+    * the memory subsystem").
+    * @param ptr pointer to the connection object
+    */
+   probe conn__destroy(void *ptr);
+
+   /**
+    * Fired when a connection is dispatched from the "main thread" to a
+    * worker thread.
+    * @param connid the connection id
+    * @param threadid the thread id
+    */
+   probe conn__dispatch(int connid, int threadid);
+
+   /**
+    * Allocate memory from the slab allocator.
+    * @param size the requested size
+    * @param slabclass the allocation will be fulfilled in this class
+    * @param slabsize the size of each item in this class
+    * @param ptr pointer to allocated memory
+    */
+   probe slabs__allocate(int size, int slabclass, int slabsize, void* ptr);
+
+   /**
+    * Failed to allocate memory (out of memory).
+    * @param size the requested size
+    * @param slabclass the class that failed to fulfill the request
+    */
+   probe slabs__allocate__failed(int size, int slabclass);
+
+   /**
+    * Fired when a slab class attempts to allocate more space.
+    * @param slabclass class that needs more memory
+    */
+   probe slabs__slabclass__allocate(int slabclass);
+
+   /**
+    * Failed to allocate memory (out of memory).
+    * @param slabclass the class that failed grab more memory
+    */
+   probe slabs__slabclass__allocate__failed(int slabclass);
+
+   /**
+    * Release memory.
+    * @param size the size of the memory
+    * @param slabclass the class the memory belongs to
+    * @param ptr pointer to the memory to release
+    */
+   probe slabs__free(int size, int slabclass, void* ptr);
+
+   /**
+    * Fired when the when we have searched the hash table for a named key.
+    * These two elements provide an insight in how well the hash function
+    * functions. Long traversals are a sign of a less optimal function,
+    * wasting cpu capacity.
+    *
+    * @param key the key searched for
+    * @param keylen length of the key
+    * @param depth the depth in the list of hash table
+    */
+   probe assoc__find(const char *key, int keylen, int depth);
+
+   /**
+    * Fired when a new item has been inserted.
+    * @param key the key just inserted
+    * @param keylen length of the key
+    * @param nokeys the total number of keys currently stored,
+    *               including the key for which insert was called.
+    */
+   probe assoc__insert(const char *key, int keylen, int nokeys);
+
+   /**
+    * Fired when a new item has been removed.
+    * @param key the key just deleted
+    * @param keylen length of the key
+    * @param nokeys the total number of keys currently stored,
+    *               excluding the key for which delete was called.
+    */
+   probe assoc__delete(const char *key, int keylen, int nokeys);
+
+   /**
+    * Fired when an item is linked into the cache.
+    * @param key the items key
+    * @param keylen length of the key
+    * @param size the size of the data
+    */
+   probe item__link(const char *key, int keylen, int size);
+
+   /**
+    * Fired when an item is deleted.
+    * @param key the items key
+    * @param keylen length of the key
+    * @param size the size of the data
+    */
+   probe item__unlink(const char *key, int keylen, int size);
+
+   /**
+    * Fired when the refcount for an item is reduced.
+    * @param key the items key
+    * @param keylen length of the key
+    * @param size the size of the data
+    */
+   probe item__remove(const char *key, int keylen, int size);
+
+   /**
+    * Fired when the "last refenced" time is updated.
+    * @param key the items key
+    * @param keylen length of the key
+    * @param size the size of the data
+    */
+   probe item__update(const char *key, int keylen, int size);
+
+   /**
+    * Fired when an item is replaced with another item.
+    * @param oldkey the key of the item to replace
+    * @param oldkeylen the length of the old key
+    * @param oldsize the size of the old item
+    * @param newkey the key of the new item
+    * @param newkeylen the length of the new key
+    * @param newsize the size of the new item
+    */
+   probe item__replace(const char *oldkey, int oldkeylen, int oldsize,
+                       const char *newkey, int newkeylen, int newsize);
+
+   /**
+    * Fired when the processing of a command starts.
+    * @param connid the connection id
+    * @param request the incomming request
+    * @param size the size of the request
+    */
+   probe process__command__start(int connid, const void *request, int size);
+
+   /**
+    * Fired when the processing of a command is done.
+    * @param connid the connection id
+    * @param respnse the response to send back to the client
+    * @param size the size of the response
+    */
+   probe process__command__end(int connid, const void *response, int size);
+
+   /**
+    * Fired for a get-command
+    * @param connid connection id
+    * @param key requested key
+    * @param keylen length of the key
+    * @param size size of the key's data (or signed int -1 if not found)
+    * @param casid the casid for the item
+    */
+   probe command__get(int connid, const char *key, int keylen, int size, int64_t casid);
+
+   /**
+    * Fired for an add-command.
+    * @param connid connection id
+    * @param key requested key
+    * @param keylen length of the key
+    * @param size the new size of the key's data (or signed int -1 if
+    *             not found)
+    * @param casid the casid for the item
+    */
+   probe command__add(int connid, const char *key, int keylen, int size, int64_t casid);
+
+   /**
+    * Fired for a set-command.
+    * @param connid connection id
+    * @param key requested key
+    * @param keylen length of the key
+    * @param size the new size of the key's data (or signed int -1 if
+    *             not found)
+    * @param casid the casid for the item
+    */
+   probe command__set(int connid, const char *key, int keylen, int size, int64_t casid);
+
+   /**
+    * Fired for a replace-command.
+    * @param connid connection id
+    * @param key requested key
+    * @param keylen length of the key
+    * @param size the new size of the key's data (or signed int -1 if
+    *             not found)
+    * @param casid the casid for the item
+    */
+   probe command__replace(int connid, const char *key, int keylen, int size, int64_t casid);
+
+   /**
+    * Fired for a prepend-command.
+    * @param connid connection id
+    * @param key requested key
+    * @param keylen length of the key
+    * @param size the new size of the key's data (or signed int -1 if
+    *             not found)
+    * @param casid the casid for the item
+    */
+   probe command__prepend(int connid, const char *key, int keylen, int size, int64_t casid);
+
+   /**
+    * Fired for an append-command.
+    * @param connid connection id
+    * @param key requested key
+    * @param keylen length of the key
+    * @param size the new size of the key's data (or signed int -1 if
+    *             not found)
+    * @param casid the casid for the item
+    */
+   probe command__append(int connid, const char *key, int keylen, int size, int64_t casid);
+
+   /**
+    * Fired for a cas-command.
+    * @param connid connection id
+    * @param key requested key
+    * @param keylen length of the key
+    * @param size size of the key's data (or signed int -1 if not found)
+    * @param casid the cas id requested
+    */
+   probe command__cas(int connid, const char *key, int keylen, int size, int64_t casid);
+
+   /**
+    * Fired for an incr command.
+    * @param connid connection id
+    * @param key the requested key
+    * @param keylen length of the key
+    * @param val the new value
+    */
+   probe command__incr(int connid, const char *key, int keylen, int64_t val);
+
+   /**
+    * Fired for a decr command.
+    * @param connid connection id
+    * @param key the requested key
+    * @param keylen length of the key
+    * @param val the new value
+    */
+   probe command__decr(int connid, const char *key, int keylen, int64_t val);
+
+   /**
+    * Fired for a delete command.
+    * @param connid connection id
+    * @param key the requested key
+    * @param keylen length of the key
+    */
+   probe command__delete(int connid, const char *key, int keylen);
+
+};
+
+#pragma D attributes Unstable/Unstable/Common provider memcached provider
+#pragma D attributes Private/Private/Common provider memcached module
+#pragma D attributes Private/Private/Common provider memcached function
+#pragma D attributes Unstable/Unstable/Common provider memcached name
+#pragma D attributes Unstable/Unstable/Common provider memcached args
diff --git a/memcached/protocol_binary.h b/memcached/protocol_binary.h
new file mode 100644 (file)
index 0000000..dfc59c8
--- /dev/null
@@ -0,0 +1,470 @@
+/*
+ * Copyright (c) <2008>, Sun Microsystems, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of the  nor the
+ *       names of its contributors may be used to endorse or promote products
+ *       derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY SUN MICROSYSTEMS, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL SUN MICROSYSTEMS, INC. BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+/*
+ * Summary: Constants used by to implement the binary protocol.
+ *
+ * Copy: See Copyright for the status of this software.
+ *
+ * Author: Trond Norbye <trond.norbye@sun.com>
+ */
+
+#ifndef PROTOCOL_BINARY_H
+#define PROTOCOL_BINARY_H
+
+/**
+ * This file contains definitions of the constants and packet formats
+ * defined in the binary specification. Please note that you _MUST_ remember
+ * to convert each multibyte field to / from network byte order to / from
+ * host order.
+ */
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+    /**
+     * Definition of the legal "magic" values used in a packet.
+     * See section 3.1 Magic byte
+     */
+    typedef enum {
+        PROTOCOL_BINARY_REQ = 0x80,
+        PROTOCOL_BINARY_RES = 0x81
+    } protocol_binary_magic;
+
+    /**
+     * Definition of the valid response status numbers.
+     * See section 3.2 Response Status
+     */
+    typedef enum {
+        PROTOCOL_BINARY_RESPONSE_SUCCESS = 0x00,
+        PROTOCOL_BINARY_RESPONSE_KEY_ENOENT = 0x01,
+        PROTOCOL_BINARY_RESPONSE_KEY_EEXISTS = 0x02,
+        PROTOCOL_BINARY_RESPONSE_E2BIG = 0x03,
+        PROTOCOL_BINARY_RESPONSE_EINVAL = 0x04,
+        PROTOCOL_BINARY_RESPONSE_NOT_STORED = 0x05,
+        PROTOCOL_BINARY_RESPONSE_DELTA_BADVAL = 0x06,
+        PROTOCOL_BINARY_RESPONSE_AUTH_ERROR = 0x20,
+        PROTOCOL_BINARY_RESPONSE_AUTH_CONTINUE = 0x21,
+        PROTOCOL_BINARY_RESPONSE_UNKNOWN_COMMAND = 0x81,
+        PROTOCOL_BINARY_RESPONSE_ENOMEM = 0x82
+    } protocol_binary_response_status;
+
+    /**
+     * Defintion of the different command opcodes.
+     * See section 3.3 Command Opcodes
+     */
+    typedef enum {
+        PROTOCOL_BINARY_CMD_GET = 0x00,
+        PROTOCOL_BINARY_CMD_SET = 0x01,
+        PROTOCOL_BINARY_CMD_ADD = 0x02,
+        PROTOCOL_BINARY_CMD_REPLACE = 0x03,
+        PROTOCOL_BINARY_CMD_DELETE = 0x04,
+        PROTOCOL_BINARY_CMD_INCREMENT = 0x05,
+        PROTOCOL_BINARY_CMD_DECREMENT = 0x06,
+        PROTOCOL_BINARY_CMD_QUIT = 0x07,
+        PROTOCOL_BINARY_CMD_FLUSH = 0x08,
+        PROTOCOL_BINARY_CMD_GETQ = 0x09,
+        PROTOCOL_BINARY_CMD_NOOP = 0x0a,
+        PROTOCOL_BINARY_CMD_VERSION = 0x0b,
+        PROTOCOL_BINARY_CMD_GETK = 0x0c,
+        PROTOCOL_BINARY_CMD_GETKQ = 0x0d,
+        PROTOCOL_BINARY_CMD_APPEND = 0x0e,
+        PROTOCOL_BINARY_CMD_PREPEND = 0x0f,
+        PROTOCOL_BINARY_CMD_STAT = 0x10,
+        PROTOCOL_BINARY_CMD_SETQ = 0x11,
+        PROTOCOL_BINARY_CMD_ADDQ = 0x12,
+        PROTOCOL_BINARY_CMD_REPLACEQ = 0x13,
+        PROTOCOL_BINARY_CMD_DELETEQ = 0x14,
+        PROTOCOL_BINARY_CMD_INCREMENTQ = 0x15,
+        PROTOCOL_BINARY_CMD_DECREMENTQ = 0x16,
+        PROTOCOL_BINARY_CMD_QUITQ = 0x17,
+        PROTOCOL_BINARY_CMD_FLUSHQ = 0x18,
+        PROTOCOL_BINARY_CMD_APPENDQ = 0x19,
+        PROTOCOL_BINARY_CMD_PREPENDQ = 0x1a,
+        PROTOCOL_BINARY_CMD_TOUCH = 0x1c,
+        PROTOCOL_BINARY_CMD_GAT = 0x1d,
+        PROTOCOL_BINARY_CMD_GATQ = 0x1e,
+        PROTOCOL_BINARY_CMD_GATK = 0x23,
+        PROTOCOL_BINARY_CMD_GATKQ = 0x24,
+
+        PROTOCOL_BINARY_CMD_SASL_LIST_MECHS = 0x20,
+        PROTOCOL_BINARY_CMD_SASL_AUTH = 0x21,
+        PROTOCOL_BINARY_CMD_SASL_STEP = 0x22,
+
+        /* These commands are used for range operations and exist within
+         * this header for use in other projects.  Range operations are
+         * not expected to be implemented in the memcached server itself.
+         */
+        PROTOCOL_BINARY_CMD_RGET      = 0x30,
+        PROTOCOL_BINARY_CMD_RSET      = 0x31,
+        PROTOCOL_BINARY_CMD_RSETQ     = 0x32,
+        PROTOCOL_BINARY_CMD_RAPPEND   = 0x33,
+        PROTOCOL_BINARY_CMD_RAPPENDQ  = 0x34,
+        PROTOCOL_BINARY_CMD_RPREPEND  = 0x35,
+        PROTOCOL_BINARY_CMD_RPREPENDQ = 0x36,
+        PROTOCOL_BINARY_CMD_RDELETE   = 0x37,
+        PROTOCOL_BINARY_CMD_RDELETEQ  = 0x38,
+        PROTOCOL_BINARY_CMD_RINCR     = 0x39,
+        PROTOCOL_BINARY_CMD_RINCRQ    = 0x3a,
+        PROTOCOL_BINARY_CMD_RDECR     = 0x3b,
+        PROTOCOL_BINARY_CMD_RDECRQ    = 0x3c
+        /* End Range operations */
+
+    } protocol_binary_command;
+
+    /**
+     * Definition of the data types in the packet
+     * See section 3.4 Data Types
+     */
+    typedef enum {
+        PROTOCOL_BINARY_RAW_BYTES = 0x00
+    } protocol_binary_datatypes;
+
+    /**
+     * Definition of the header structure for a request packet.
+     * See section 2
+     */
+    typedef union {
+        struct {
+            uint8_t magic;
+            uint8_t opcode;
+            uint16_t keylen;
+            uint8_t extlen;
+            uint8_t datatype;
+            uint16_t reserved;
+            uint32_t bodylen;
+            uint32_t opaque;
+            uint64_t cas;
+        } request;
+        uint8_t bytes[24];
+    } protocol_binary_request_header;
+
+    /**
+     * Definition of the header structure for a response packet.
+     * See section 2
+     */
+    typedef union {
+        struct {
+            uint8_t magic;
+            uint8_t opcode;
+            uint16_t keylen;
+            uint8_t extlen;
+            uint8_t datatype;
+            uint16_t status;
+            uint32_t bodylen;
+            uint32_t opaque;
+            uint64_t cas;
+        } response;
+        uint8_t bytes[24];
+    } protocol_binary_response_header;
+
+    /**
+     * Definition of a request-packet containing no extras
+     */
+    typedef union {
+        struct {
+            protocol_binary_request_header header;
+        } message;
+        uint8_t bytes[sizeof(protocol_binary_request_header)];
+    } protocol_binary_request_no_extras;
+
+    /**
+     * Definition of a response-packet containing no extras
+     */
+    typedef union {
+        struct {
+            protocol_binary_response_header header;
+        } message;
+        uint8_t bytes[sizeof(protocol_binary_response_header)];
+    } protocol_binary_response_no_extras;
+
+    /**
+     * Definition of the packet used by the get, getq, getk and getkq command.
+     * See section 4
+     */
+    typedef protocol_binary_request_no_extras protocol_binary_request_get;
+    typedef protocol_binary_request_no_extras protocol_binary_request_getq;
+    typedef protocol_binary_request_no_extras protocol_binary_request_getk;
+    typedef protocol_binary_request_no_extras protocol_binary_request_getkq;
+
+    /**
+     * Definition of the packet returned from a successful get, getq, getk and
+     * getkq.
+     * See section 4
+     */
+    typedef union {
+        struct {
+            protocol_binary_response_header header;
+            struct {
+                uint32_t flags;
+            } body;
+        } message;
+        uint8_t bytes[sizeof(protocol_binary_response_header) + 4];
+    } protocol_binary_response_get;
+
+    typedef protocol_binary_response_get protocol_binary_response_getq;
+    typedef protocol_binary_response_get protocol_binary_response_getk;
+    typedef protocol_binary_response_get protocol_binary_response_getkq;
+
+    /**
+     * Definition of the packet used by the delete command
+     * See section 4
+     */
+    typedef protocol_binary_request_no_extras protocol_binary_request_delete;
+
+    /**
+     * Definition of the packet returned by the delete command
+     * See section 4
+     */
+    typedef protocol_binary_response_no_extras protocol_binary_response_delete;
+
+    /**
+     * Definition of the packet used by the flush command
+     * See section 4
+     * Please note that the expiration field is optional, so remember to see
+     * check the header.bodysize to see if it is present.
+     */
+    typedef union {
+        struct {
+            protocol_binary_request_header header;
+            struct {
+                uint32_t expiration;
+            } body;
+        } message;
+        uint8_t bytes[sizeof(protocol_binary_request_header) + 4];
+    } protocol_binary_request_flush;
+
+    /**
+     * Definition of the packet returned by the flush command
+     * See section 4
+     */
+    typedef protocol_binary_response_no_extras protocol_binary_response_flush;
+
+    /**
+     * Definition of the packet used by set, add and replace
+     * See section 4
+     */
+    typedef union {
+        struct {
+            protocol_binary_request_header header;
+            struct {
+                uint32_t flags;
+                uint32_t expiration;
+            } body;
+        } message;
+        uint8_t bytes[sizeof(protocol_binary_request_header) + 8];
+    } protocol_binary_request_set;
+    typedef protocol_binary_request_set protocol_binary_request_add;
+    typedef protocol_binary_request_set protocol_binary_request_replace;
+
+    /**
+     * Definition of the packet returned by set, add and replace
+     * See section 4
+     */
+    typedef protocol_binary_response_no_extras protocol_binary_response_set;
+    typedef protocol_binary_response_no_extras protocol_binary_response_add;
+    typedef protocol_binary_response_no_extras protocol_binary_response_replace;
+
+    /**
+     * Definition of the noop packet
+     * See section 4
+     */
+    typedef protocol_binary_request_no_extras protocol_binary_request_noop;
+
+    /**
+     * Definition of the packet returned by the noop command
+     * See section 4
+     */
+    typedef protocol_binary_response_no_extras protocol_binary_response_noop;
+
+    /**
+     * Definition of the structure used by the increment and decrement
+     * command.
+     * See section 4
+     */
+    typedef union {
+        struct {
+            protocol_binary_request_header header;
+            struct {
+                uint64_t delta;
+                uint64_t initial;
+                uint32_t expiration;
+            } body;
+        } message;
+        uint8_t bytes[sizeof(protocol_binary_request_header) + 20];
+    } protocol_binary_request_incr;
+    typedef protocol_binary_request_incr protocol_binary_request_decr;
+
+    /**
+     * Definition of the response from an incr or decr command
+     * command.
+     * See section 4
+     */
+    typedef union {
+        struct {
+            protocol_binary_response_header header;
+            struct {
+                uint64_t value;
+            } body;
+        } message;
+        uint8_t bytes[sizeof(protocol_binary_response_header) + 8];
+    } protocol_binary_response_incr;
+    typedef protocol_binary_response_incr protocol_binary_response_decr;
+
+    /**
+     * Definition of the quit
+     * See section 4
+     */
+    typedef protocol_binary_request_no_extras protocol_binary_request_quit;
+
+    /**
+     * Definition of the packet returned by the quit command
+     * See section 4
+     */
+    typedef protocol_binary_response_no_extras protocol_binary_response_quit;
+
+    /**
+     * Definition of the packet used by append and prepend command
+     * See section 4
+     */
+    typedef protocol_binary_request_no_extras protocol_binary_request_append;
+    typedef protocol_binary_request_no_extras protocol_binary_request_prepend;
+
+    /**
+     * Definition of the packet returned from a successful append or prepend
+     * See section 4
+     */
+    typedef protocol_binary_response_no_extras protocol_binary_response_append;
+    typedef protocol_binary_response_no_extras protocol_binary_response_prepend;
+
+    /**
+     * Definition of the packet used by the version command
+     * See section 4
+     */
+    typedef protocol_binary_request_no_extras protocol_binary_request_version;
+
+    /**
+     * Definition of the packet returned from a successful version command
+     * See section 4
+     */
+    typedef protocol_binary_response_no_extras protocol_binary_response_version;
+
+
+    /**
+     * Definition of the packet used by the stats command.
+     * See section 4
+     */
+    typedef protocol_binary_request_no_extras protocol_binary_request_stats;
+
+    /**
+     * Definition of the packet returned from a successful stats command
+     * See section 4
+     */
+    typedef protocol_binary_response_no_extras protocol_binary_response_stats;
+
+    /**
+     * Definition of the packet used by the touch command.
+     */
+    typedef union {
+        struct {
+            protocol_binary_request_header header;
+            struct {
+                uint32_t expiration;
+            } body;
+        } message;
+        uint8_t bytes[sizeof(protocol_binary_request_header) + 4];
+    } protocol_binary_request_touch;
+
+    /**
+     * Definition of the packet returned from the touch command
+     */
+    typedef protocol_binary_response_no_extras protocol_binary_response_touch;
+
+    /**
+     * Definition of the packet used by the GAT(Q) command.
+     */
+    typedef union {
+        struct {
+            protocol_binary_request_header header;
+            struct {
+                uint32_t expiration;
+            } body;
+        } message;
+        uint8_t bytes[sizeof(protocol_binary_request_header) + 4];
+    } protocol_binary_request_gat;
+
+    typedef protocol_binary_request_gat protocol_binary_request_gatq;
+    typedef protocol_binary_request_gat protocol_binary_request_gatk;
+    typedef protocol_binary_request_gat protocol_binary_request_gatkq;
+
+    /**
+     * Definition of the packet returned from the GAT(Q)
+     */
+    typedef protocol_binary_response_get protocol_binary_response_gat;
+    typedef protocol_binary_response_get protocol_binary_response_gatq;
+    typedef protocol_binary_response_get protocol_binary_response_gatk;
+    typedef protocol_binary_response_get protocol_binary_response_gatkq;
+
+    /**
+     * Definition of a request for a range operation.
+     * See http://code.google.com/p/memcached/wiki/RangeOps
+     *
+     * These types are used for range operations and exist within
+     * this header for use in other projects.  Range operations are
+     * not expected to be implemented in the memcached server itself.
+     */
+    typedef union {
+        struct {
+            protocol_binary_response_header header;
+            struct {
+                uint16_t size;
+                uint8_t  reserved;
+                uint8_t  flags;
+                uint32_t max_results;
+            } body;
+        } message;
+        uint8_t bytes[sizeof(protocol_binary_request_header) + 4];
+    } protocol_binary_request_rangeop;
+
+    typedef protocol_binary_request_rangeop protocol_binary_request_rget;
+    typedef protocol_binary_request_rangeop protocol_binary_request_rset;
+    typedef protocol_binary_request_rangeop protocol_binary_request_rsetq;
+    typedef protocol_binary_request_rangeop protocol_binary_request_rappend;
+    typedef protocol_binary_request_rangeop protocol_binary_request_rappendq;
+    typedef protocol_binary_request_rangeop protocol_binary_request_rprepend;
+    typedef protocol_binary_request_rangeop protocol_binary_request_rprependq;
+    typedef protocol_binary_request_rangeop protocol_binary_request_rdelete;
+    typedef protocol_binary_request_rangeop protocol_binary_request_rdeleteq;
+    typedef protocol_binary_request_rangeop protocol_binary_request_rincr;
+    typedef protocol_binary_request_rangeop protocol_binary_request_rincrq;
+    typedef protocol_binary_request_rangeop protocol_binary_request_rdecr;
+    typedef protocol_binary_request_rangeop protocol_binary_request_rdecrq;
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* PROTOCOL_BINARY_H */
diff --git a/memcached/sasl_defs.c b/memcached/sasl_defs.c
new file mode 100644 (file)
index 0000000..1faefc8
--- /dev/null
@@ -0,0 +1,190 @@
+/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+#include "memcached.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sasl/saslplug.h>
+
+char my_sasl_hostname[1025];
+
+#ifdef HAVE_SASL_CB_GETCONF
+/* The locations we may search for a SASL config file if the user didn't
+ * specify one in the environment variable SASL_CONF_PATH
+ */
+const char * const locations[] = {
+    "/etc/sasl/memcached.conf",
+    "/etc/sasl2/memcached.conf",
+    NULL
+};
+#endif
+
+#ifndef HAVE_SASL_CALLBACK_FT
+typedef int (*sasl_callback_ft)(void);
+#endif
+
+#ifdef ENABLE_SASL_PWDB
+#define MAX_ENTRY_LEN 256
+
+static const char *memcached_sasl_pwdb;
+
+static int sasl_server_userdb_checkpass(sasl_conn_t *conn,
+                                        void *context,
+                                        const char *user,
+                                        const char *pass,
+                                        unsigned passlen,
+                                        struct propctx *propctx)
+{
+    size_t unmlen = strlen(user);
+    if ((passlen + unmlen) > (MAX_ENTRY_LEN - 4)) {
+        fprintf(stderr,
+                "WARNING: Failed to authenticate <%s> due to too long password (%d)\n",
+                user, passlen);
+        return SASL_NOAUTHZ;
+    }
+
+    FILE *pwfile = fopen(memcached_sasl_pwdb, "r");
+    if (pwfile == NULL) {
+        if (settings.verbose) {
+            vperror("WARNING: Failed to open sasl database <%s>",
+                    memcached_sasl_pwdb);
+        }
+        return SASL_NOAUTHZ;
+    }
+
+    char buffer[MAX_ENTRY_LEN];
+    bool ok = false;
+
+    while ((fgets(buffer, sizeof(buffer), pwfile)) != NULL) {
+        if (memcmp(user, buffer, unmlen) == 0 && buffer[unmlen] == ':') {
+            /* This is the correct user */
+            ++unmlen;
+            if (memcmp(pass, buffer + unmlen, passlen) == 0 &&
+                (buffer[unmlen + passlen] == ':' || /* Additional tokens */
+                 buffer[unmlen + passlen] == '\n' || /* end of line */
+                 buffer[unmlen + passlen] == '\r'|| /* dos format? */
+                 buffer[unmlen + passlen] == '\0')) { /* line truncated */
+                ok = true;
+            }
+
+            break;
+        }
+    }
+    (void)fclose(pwfile);
+    if (ok) {
+        return SASL_OK;
+    }
+
+    if (settings.verbose) {
+        fprintf(stderr, "INFO: User <%s> failed to authenticate\n", user);
+    }
+
+    return SASL_NOAUTHZ;
+}
+#endif
+
+#ifdef HAVE_SASL_CB_GETCONF
+static int sasl_getconf(void *context, const char **path)
+{
+    *path = getenv("SASL_CONF_PATH");
+
+    if (*path == NULL) {
+        for (int i = 0; locations[i] != NULL; ++i) {
+            if (access(locations[i], F_OK) == 0) {
+                *path = locations[i];
+                break;
+            }
+        }
+    }
+
+    if (settings.verbose) {
+        if (*path != NULL) {
+            fprintf(stderr, "Reading configuration from: <%s>\n", *path);
+        } else {
+            fprintf(stderr, "Failed to locate a config path\n");
+        }
+
+    }
+
+    return (*path != NULL) ? SASL_OK : SASL_FAIL;
+}
+#endif
+
+static int sasl_log(void *context, int level, const char *message)
+{
+    bool log = true;
+
+    switch (level) {
+    case SASL_LOG_NONE:
+        log = false;
+        break;
+    case SASL_LOG_PASS:
+    case SASL_LOG_TRACE:
+    case SASL_LOG_DEBUG:
+    case SASL_LOG_NOTE:
+        if (settings.verbose < 2) {
+            log = false;
+        }
+        break;
+    case SASL_LOG_WARN:
+    case SASL_LOG_FAIL:
+        if (settings.verbose < 1) {
+            log = false;
+        }
+        break;
+    default:
+        /* This is an error */
+        ;
+    }
+
+    if (log) {
+        fprintf(stderr, "SASL (severity %d): %s\n", level, message);
+    }
+
+    return SASL_OK;
+}
+
+static sasl_callback_t sasl_callbacks[] = {
+#ifdef ENABLE_SASL_PWDB
+   { SASL_CB_SERVER_USERDB_CHECKPASS, sasl_server_userdb_checkpass, NULL },
+#endif
+
+   { SASL_CB_LOG, (sasl_callback_ft)sasl_log, NULL },
+
+#ifdef HAVE_SASL_CB_GETCONF
+   { SASL_CB_GETCONF, sasl_getconf, NULL },
+#endif
+
+   { SASL_CB_LIST_END, NULL, NULL }
+};
+
+void init_sasl(void) {
+#ifdef ENABLE_SASL_PWDB
+    memcached_sasl_pwdb = getenv("MEMCACHED_SASL_PWDB");
+    if (memcached_sasl_pwdb == NULL) {
+       if (settings.verbose) {
+          fprintf(stderr,
+                  "INFO: MEMCACHED_SASL_PWDB not specified. "
+                  "Internal passwd database disabled\n");
+       }
+       sasl_callbacks[0].id = SASL_CB_LIST_END;
+       sasl_callbacks[0].proc = NULL;
+    }
+#endif
+
+    memset(my_sasl_hostname, 0, sizeof(my_sasl_hostname));
+    if (gethostname(my_sasl_hostname, sizeof(my_sasl_hostname)-1) == -1) {
+        if (settings.verbose) {
+            fprintf(stderr, "Error discovering hostname for SASL\n");
+        }
+        my_sasl_hostname[0] = '\0';
+    }
+
+    if (sasl_server_init(sasl_callbacks, "memcached") != SASL_OK) {
+        fprintf(stderr, "Error initializing sasl.\n");
+        exit(EXIT_FAILURE);
+    } else {
+        if (settings.verbose) {
+            fprintf(stderr, "Initialized SASL.\n");
+        }
+    }
+}
diff --git a/memcached/sasl_defs.h b/memcached/sasl_defs.h
new file mode 100644 (file)
index 0000000..f36c694
--- /dev/null
@@ -0,0 +1,31 @@
+#ifndef SASL_DEFS_H
+#define SASL_DEFS_H 1
+
+// Longest one I could find was ``9798-U-RSA-SHA1-ENC''
+#define MAX_SASL_MECH_LEN 32
+
+#if defined(HAVE_SASL_SASL_H) && defined(ENABLE_SASL)
+
+#include <sasl/sasl.h>
+void init_sasl(void);
+
+extern char my_sasl_hostname[1025];
+
+#else /* End of SASL support */
+
+typedef void* sasl_conn_t;
+
+#define init_sasl() {}
+#define sasl_dispose(x) {}
+#define sasl_server_new(a, b, c, d, e, f, g, h) 1
+#define sasl_listmech(a, b, c, d, e, f, g, h) 1
+#define sasl_server_start(a, b, c, d, e, f) 1
+#define sasl_server_step(a, b, c, d, e) 1
+#define sasl_getprop(a, b, c) {}
+
+#define SASL_OK 0
+#define SASL_CONTINUE -1
+
+#endif /* sasl compat */
+
+#endif /* SASL_DEFS_H */
diff --git a/memcached/scripts/README.damemtop b/memcached/scripts/README.damemtop
new file mode 100644 (file)
index 0000000..b6b3a9a
--- /dev/null
@@ -0,0 +1,20 @@
+dormando's awesome memcached top
+
+A flexible 'top' like utility for viewing memcached clusters.
+
+Under development. Latest code is available at:
+http://github.com/dormando/damemtop
+
+See --help for full information.
+
+Requires 'AnyEvent', and 'YAML' libraries from CPAN:
+http://search.cpan.org/
+
+'AnyEvent' depends on 'common::sense' (also at CPAN).
+
+If you have a large cluster and want higher performance, find
+and install 'EV' from CPAN. AnyEvent will automagically use it
+and use epoll, kqeueue, etc, for socket handling.
+
+Pester me for questions/bugs/ideas. As of writing the util is
+in early release and missing many future features.
diff --git a/memcached/scripts/damemtop b/memcached/scripts/damemtop
new file mode 100755 (executable)
index 0000000..a5e2bcf
--- /dev/null
@@ -0,0 +1,571 @@
+#!/usr/bin/perl
+#  dormando's awesome memcached top utility!
+#
+#  Copyright 2009 Dormando (dormando@rydia.net).  All rights reserved.
+#
+#  Use and distribution licensed under the BSD license.  See
+#  the COPYING file for full text.
+
+use strict;
+use warnings FATAL => 'all';
+
+use AnyEvent;
+use AnyEvent::Socket;
+use AnyEvent::Handle;
+use Getopt::Long;
+use YAML qw/Dump Load LoadFile/;
+use Term::ReadKey qw/ReadMode ReadKey GetTerminalSize/;
+
+our $VERSION = '0.1';
+
+my $CLEAR     = `clear`;
+my @TERM_SIZE = ();
+$|++;
+
+my %opts = ();
+GetOptions(\%opts, 'help|h', 'config=s');
+
+if ($opts{help}) {
+    show_help(); exit;
+}
+
+$SIG{INT} = sub {
+    ReadMode('normal');
+    print "\n";
+    exit;
+};
+
+# TODO: make this load from central location, and merge in homedir changes.
+# then merge Getopt::Long stuff on top of that
+# TODO: Set a bunch of defaults and merge in.
+my $CONF = load_config();
+my %CONS    = ();
+my $LAST_RUN = time; # time after the last loop cycle.
+my $TIME_SINCE_LAST_RUN = time; # time since last loop cycle.
+my $loop_timer;
+my $main_cond;
+my $prev_stats_results;
+
+my %display_modes = (
+    't' => \&display_top_mode,
+    '?' => \&display_help_mode,
+    'h' => \&display_help_mode,
+);
+
+my %column_compute = (
+    'hostname' => { stats => [], code => \&compute_hostname},
+    'hit_rate' => { stats => ['get_hits', 'get_misses'],
+                    code  => \&compute_hit_rate },
+    'fill_rate' => { stats => ['bytes', 'limit_maxbytes'],
+                    code => \&compute_fill_rate },
+);
+
+my %column_format = (
+    'hit_rate' => \&format_percent,
+    'fill_rate' => \&format_percent,
+);
+
+# This can collapse into %column_compute
+my %column_format_totals = (
+    'hit_rate' => 0,
+    'fill_rate' => 0,
+);
+
+ReadMode('cbreak');
+my $LAST_KEY = '';
+my $read_keys = AnyEvent->io (
+    fh => \*STDIN, poll => 'r',
+    cb => sub {
+        $LAST_KEY = ReadKey(-1);
+        # If there is a running timer, cancel it.
+        # Don't want to interrupt a main loop run.
+        # fire_main_loop()'s iteration will pick up the keypress.
+        if ($loop_timer) {
+            $loop_timer = undef;
+            $main_cond->send;
+        }
+    }
+);
+
+# start main loop
+fire_main_loop();
+
+### AnyEvent related code.
+
+sub fire_main_loop {
+    for (;;) {
+        $loop_timer = undef;
+        $main_cond = AnyEvent->condvar;
+        my $time_taken = main_loop();
+        my $delay = $CONF->{delay} - $time_taken;
+        $delay = 0 if $delay < 0;
+        $loop_timer = AnyEvent->timer(
+            after => $delay,
+            cb    => $main_cond,
+        );
+        $main_cond->recv;
+    }
+}
+
+sub main_loop {
+    my $start = AnyEvent->now; # use ->time to find the end.
+    maintain_connections();
+
+    my $cv = AnyEvent->condvar;
+
+    # FIXME: Need to dump early if there're no connected conns
+    # FIXME: Make this only fetch stats from cons we care to visualize?
+    # maybe keep everything anyway to maintain averages?
+    my %stats_results = ();
+    while (my ($hostname, $con) = each %CONS) {
+        $cv->begin;
+        call_stats($con, ['', 'items', 'slabs'], sub {
+            $stats_results{$hostname} = shift;
+            $cv->end;
+        });
+    }
+    $cv->recv;
+
+    # Short circuit since we don't have anything to compare to.
+    unless ($prev_stats_results) {
+        $prev_stats_results = \%stats_results;
+        return $CONF->{delay};
+    }
+
+    # Semi-exact global time diff for stats that want to average
+    # themselves per-second.
+    my $this_run = AnyEvent->time;
+    $TIME_SINCE_LAST_RUN = $this_run - $LAST_RUN;
+    $LAST_RUN = $this_run;
+
+    # Done all our fetches. Drive the display.
+    display_run($prev_stats_results, \%stats_results);
+    $prev_stats_results = \%stats_results;
+
+    my $end  = AnyEvent->time;
+    my $diff = $LAST_RUN - $start;
+    print "loop took: $diff";
+    return $diff;
+}
+
+sub maintain_connections {
+    my $cv    = AnyEvent->condvar;
+
+    $cv->begin (sub { shift->send });
+    for my $host (@{$CONF->{servers}}) {
+        next if $CONS{$host};
+        $cv->begin;
+        $CONS{$host} = connect_memcached($host, sub {
+            if ($_[0] eq 'err') {
+                print "Failed connecting to $host: ", $_[1], "\n";
+                delete $CONS{$host};
+            }
+            $cv->end;
+        });
+    }
+    $cv->end;
+
+    $cv->recv;
+}
+
+sub connect_memcached {
+    my ($fullhost, $cb)   = @_;
+    my ($host, $port) = split /:/, $fullhost;
+
+    my $con; $con = AnyEvent::Handle->new (
+        connect => [$host => $port],
+        on_connect => sub {
+            $cb->('con');
+        },
+        on_connect_error => sub {
+            $cb->('err', $!);
+            $con->destroy;
+        },
+        on_eof   => sub {
+            $cb->('err', $!);
+            $con->destroy;
+        },
+    );
+    return $con;
+}
+
+# Function's getting a little weird since I started optimizing it.
+# As of my first set of production tests, this routine is where we spend
+# almost all of our processing time.
+sub call_stats {
+    my ($con, $cmds, $cb) = @_;
+
+    my $stats = {};
+    my $num_types = @$cmds;
+
+    my $reader; $reader = sub {
+        my ($con, $results) = @_;
+        {
+            my %temp = ();
+            for my $line (split(/\n/, $results)) {
+                my ($k, $v) = (split(/\s+/, $line))[1,2];
+                $temp{$k} = $v;
+            }
+            $stats->{$cmds->[0]} = \%temp;
+        }
+        shift @$cmds;
+        unless (@$cmds) {
+            # Out of commands to process, return goodies.
+            $cb->($stats);
+            return;
+        }
+    };
+
+    for my $cmd (@$cmds) {
+        $con->push_write('stats ' . $cmd . "\n");
+        $stats->{$cmd} = {};
+        $con->push_read(line => "END\r\n", $reader);
+    }
+}
+
+### Compute routines
+
+sub compute_hostname {
+    return $_[0];
+}
+
+sub compute_hit_rate {
+    my $s = $_[1];
+    my $total = $s->{get_hits} + $s->{get_misses};
+    return 'NA' unless $total;
+    return $s->{get_hits} / $total;
+}
+
+sub compute_fill_rate {
+    my $s = $_[1];
+    return $s->{bytes} / $s->{limit_maxbytes};
+}
+
+sub format_column {
+    my ($col, $val) = @_;
+    my $res;
+    $col =~ s/^all_//;
+    if ($column_format{$col}) {
+        if (ref($column_format{$col}) eq 'CODE') {
+            return $column_format{$col}->($val);
+        } else {
+            return $val .= $column_format{$col};
+        }
+    } else {
+        return format_commas($val);
+    }
+}
+
+sub column_can_total {
+    my $col = shift;
+    $col =~ s/^all_//;
+    return 1 unless exists $column_format_totals{$col};
+    return $column_format_totals{$col};
+}
+
+### Display routines
+
+# If there isn't a specific column type computer, see if we just want to
+# look at the specific stat and return it.
+# If column is a generic type and of 'all_cmd_get' format, return the more
+# complete stat instead of the diffed stat.
+sub compute_column {
+    my ($col, $host, $prev_stats, $curr_stats) = @_;
+    my $diff_stats = 1;
+    $diff_stats    = 0 if ($col =~ s/^all_//);
+
+    # Really should decide on whether or not to flatten the hash :/
+    my $find_stat = sub {
+        for my $type (keys %{$_[0]}) {
+            return $_[0]->{$type}->{$_[1]} if exists $_[0]->{$type}->{$_[1]};
+        }
+    };
+
+    my $diff_stat = sub {
+        my $stat = shift;
+        return 'NA' unless defined $find_stat->($curr_stats, $stat);
+        if ($diff_stats) {
+            my $diff = eval {
+                return ($find_stat->($curr_stats, $stat)
+                       - $find_stat->($prev_stats, $stat))
+                       / $TIME_SINCE_LAST_RUN;
+            };
+            return 'NA' if ($@);
+            return $diff;
+        } else {
+            return $find_stat->($curr_stats, $stat);
+        }
+    };
+
+    if (my $comp = $column_compute{$col}) {
+        my %s = ();
+        for my $stat (@{$comp->{stats}}) {
+            $s{$stat} = $diff_stat->($stat);
+        }
+        return $comp->{code}->($host, \%s);
+    } else {
+        return $diff_stat->($col);
+    }
+    return 'NA';
+}
+
+# We have a bunch of stats from a bunch of connections.
+# At this point we run a particular display mode, capture the lines, then
+# truncate and display them.
+sub display_run {
+    my $prev_stats = shift;
+    my $curr_stats = shift;
+    @TERM_SIZE = GetTerminalSize;
+    die "cannot detect terminal size" unless $TERM_SIZE[0] && $TERM_SIZE[1];
+
+    if ($LAST_KEY eq 'q') {
+        print "\n";
+        ReadMode('normal'); exit;
+    }
+
+    if ($LAST_KEY ne $CONF->{mode} && exists $display_modes{$LAST_KEY}) {
+        $CONF->{prev_mode} = $CONF->{mode};
+        $CONF->{mode} = $LAST_KEY;
+    } elsif ($CONF->{mode} eq 'h' || $CONF->{mode} eq '?') {
+        # Bust out of help mode on any key.
+        $CONF->{mode} = $CONF->{prev_mode};
+    }
+    my $lines = $display_modes{$CONF->{mode}}->($prev_stats, $curr_stats);
+    display_lines($lines) if $lines;
+}
+
+# Default "top" mode.
+# create a set of computed columns as requested by the config.
+# this has gotten a little out of hand... needs more cleanup/abstraction.
+sub display_top_mode {
+    my $prev_stats = shift;
+    my $curr_stats = shift;
+
+    my @columns = @{$CONF->{top_mode}->{columns}};
+    my @rows    = ();
+    my @tot_row = ();
+
+    # Round one.
+    for my $host (sort keys %{$curr_stats}) {
+        my @row = ();
+        for my $colnum (0 .. @columns-1) {
+            my $col = $columns[$colnum];
+            my $res = compute_column($col, $host, $prev_stats->{$host},
+                      $curr_stats->{$host});
+            $tot_row[$colnum] += $res if is_numeric($res);
+            push @row, $res;
+        }
+        push(@rows, \@row);
+    }
+
+    # Sort rows by sort column (ascending or descending)
+    if (my $sort = $CONF->{top_mode}->{sort_column}) {
+        my $order  = $CONF->{top_mode}->{sort_order} || 'asc';
+        my $colnum = 0;
+        for (0 .. @columns-1) { $colnum = $_ if $columns[$_] eq $sort; }
+        my @newrows;
+        if ($order eq 'asc') {
+            if (is_numeric($rows[0]->[$colnum])) {
+                @newrows = sort { $a->[$colnum] <=> $b->[$colnum] } @rows;
+            } else {
+                @newrows = sort { $a->[$colnum] cmp $b->[$colnum] } @rows;
+            }
+        } else {
+            if (is_numeric($rows[0]->[$colnum])) {
+                @newrows = sort { $b->[$colnum] <=> $a->[$colnum] } @rows;
+            } else {
+                @newrows = sort { $b->[$colnum] cmp $a->[$colnum] } @rows;
+            }
+        }
+        @rows = @newrows;
+    }
+
+    # Format each column after the sort...
+    {
+        my @newrows = ();
+        for my $row (@rows) {
+            my @newrow = ();
+            for my $colnum (0 .. @columns-1) {
+                push @newrow, is_numeric($row->[$colnum]) ?
+                            format_column($columns[$colnum], $row->[$colnum]) :
+                            $row->[$colnum];
+            }
+            push @newrows, \@newrow;
+        }
+        @rows = @newrows;
+    }
+
+    # Create average and total rows.
+    my @avg_row = ();
+    for my $col (0 .. @columns-1) {
+        if (is_numeric($tot_row[$col])) {
+            my $countable_rows = 0;
+            for my $row (@rows) {
+                next unless $row->[$col];
+                $countable_rows++ unless $row->[$col] eq 'NA';
+            }
+            $countable_rows = 1 unless $countable_rows;
+            push @avg_row, format_column($columns[$col],
+                 sprintf('%.2f', $tot_row[$col] / $countable_rows));
+        } else {
+            push @avg_row, 'NA';
+        }
+        $tot_row[$col] = 'NA' unless defined $tot_row[$col];
+        $tot_row[$col] = 'NA' unless (column_can_total($columns[$col]));
+        $tot_row[$col] = format_column($columns[$col], $tot_row[$col])
+                         unless $tot_row[$col] eq 'NA';
+    }
+    unshift @rows, \@avg_row;
+    unshift @rows, ['AVERAGE:'];
+    unshift @rows, \@tot_row;
+    unshift @rows, ['TOTAL:'];
+
+    # Round two. Pass @rows into a function which returns an array with the
+    # desired format spacing for each column.
+    unshift @rows, \@columns;
+    my $spacing = find_optimal_spacing(\@rows);
+
+    my @display_lines = ();
+    for my $row (@rows) {
+        my $line = '';
+        for my $col (0 .. @$row-1) {
+            my $space = $spacing->[$col];
+            $line .= sprintf("%-${space}s ", $row->[$col]);
+        }
+        push @display_lines, $line;
+    }
+
+    return \@display_lines;
+}
+
+sub display_help_mode {
+    my $help = <<"ENDHELP";
+
+dormando's awesome memcached top utility version v$VERSION
+
+This early version requires you to edit the ~/.damemtop/damemtop.yaml
+(or /etc/damemtop.yaml) file in order to change options.
+See --help for more info.
+
+Hit any key to exit help.
+ENDHELP
+    my @lines = split /\n/, $help;
+    display_lines(\@lines);
+    $LAST_KEY = ReadKey(0);
+    return;
+}
+
+# Takes a set of lines, clears screen, dumps header, trims lines, etc
+# MAYBE: mode to wrap lines instead of trim them?
+sub display_lines {
+    my $lines = shift;
+
+    my $width         = $TERM_SIZE[0];
+    my $height_remain = $TERM_SIZE[1];
+
+    unshift @$lines, display_header($width);
+    clear_screen() unless $CONF->{no_clear};
+
+    while (--$height_remain && @$lines) {
+        # truncate too long lines.
+        my $line = shift @$lines;
+        $line = substr $line, 0, $width-1;
+        print $line, "\n";
+    }
+}
+
+sub display_header {
+    my $topbar = 'damemtop: ' . scalar localtime;
+    if ($CONF->{mode} eq 't' && $CONF->{top_mode}->{sort_column}) {
+        $topbar .= ' [sort: ' . $CONF->{top_mode}->{sort_column} . ']';
+    }
+    $topbar .= ' [delay: ' . $CONF->{delay} . 's]';
+    return $topbar;
+}
+
+### Utilities
+
+# find the optimal format spacing for each column, which is:
+# longest length of item in col + 2 (whitespace).
+sub find_optimal_spacing {
+    my $rows  = shift;
+    my @maxes = ();
+
+    my $num_cols = @{$rows->[0]};
+    for my $row (@$rows) {
+        for my $col (0 .. $num_cols-1) {
+            $maxes[$col] = 0 unless $maxes[$col];
+            next unless $row->[$col];
+            $maxes[$col] = length($row->[$col])
+                if length($row->[$col]) > $maxes[$col];
+        }
+    }
+    for my $col (0 .. $num_cols) {
+        $maxes[$col] += 1;
+    }
+
+    return \@maxes;
+}
+
+# doesn't try too hard to identify numbers...
+sub is_numeric {
+    return 0 unless $_[0];
+    return 1 if $_[0] =~ m/^\d+(\.\d*)?(\w+)?$/;
+    return 0;
+}
+
+sub format_percent {
+    return sprintf("%.2f%%", $_[0] * 100);
+}
+
+sub format_commas {
+    my $num = shift;
+    $num = int($num);
+    $num =~ s/(^[-+]?\d+?(?=(?>(?:\d{3})+)(?!\d))|\G\d{3}(?=\d))/$1,/g;
+    return $num;
+}
+
+# Can tick counters/etc here as well.
+sub clear_screen {
+    print $CLEAR;
+}
+
+# tries minimally to find a localized config file.
+# TODO: Handle the YAML error and make it prettier.
+sub load_config {
+    my $config = $opts{config} if $opts{config};
+    my $homedir = "$ENV{HOME}/.damemtop/damemtop.yaml";
+    if (-e $homedir) {
+        $config = $homedir;
+    } else {
+        $config = '/etc/damemtop.yaml';
+    }
+    return LoadFile($config);
+}
+
+sub show_help {
+    print <<"ENDHELP";
+dormando's awesome memcached top utility version v$VERSION
+
+This program is copyright (c) 2009 Dormando.
+Use and distribution licensed under the BSD license.  See
+the COPYING file for full text.
+
+contact: dormando\@rydia.net or memcached\@googlegroups.com.
+
+This early version requires you to edit the ~/.damemtop/damemtop.yaml
+(or /etc/damemtop.yaml) file in order to change options.
+
+You may display any column that is in the output of
+'stats', 'stats items', or 'stats slabs' from memcached's ASCII protocol.
+Start a column with 'all_' (ie; 'all_get_hits') to display the current stat,
+otherwise the stat is displayed as an average per second.
+
+Specify a "sort_column" under "top_mode" to sort the output by any column.
+
+Some special "computed" columns exist:
+hit_rate (get/miss hit ratio)
+fill_rate (% bytes used out of the maximum memory limit)
+ENDHELP
+    exit;
+}
diff --git a/memcached/scripts/damemtop.yaml b/memcached/scripts/damemtop.yaml
new file mode 100644 (file)
index 0000000..f34acfd
--- /dev/null
@@ -0,0 +1,16 @@
+delay: 3
+mode: t
+top_mode:
+    sort_column: "hostname"
+    sort_order: "asc"
+    columns:
+        - hostname
+        - all_version
+        - all_fill_rate
+        - hit_rate
+        - evictions
+        - bytes_written
+        - "2:get_hits"
+servers:
+    - 127.0.0.1:11211
+    - 127.0.0.2:11211
diff --git a/memcached/scripts/mc_slab_mover b/memcached/scripts/mc_slab_mover
new file mode 100755 (executable)
index 0000000..ac984d7
--- /dev/null
@@ -0,0 +1,260 @@
+#! /usr/bin/perl
+# See memcached for LICENSE
+# Copyright 2011 Dormando (dormando@rydia.net)
+
+=head1 NAME
+
+mc_slab_mover -- example utility for slab page reassignment for memcached
+
+=head1 SYNOPSIS
+
+    $ mc_slab_mover --host="127.0.0.1:11211" --verbose
+    $ mc_slab_mover --host="127.0.0.1:11211" --automove
+    $ mc_slab_mover --host="127.0.0.1:11211" --sleep=60 --loops=4 --automove
+
+=head1 DESCRIPTION
+
+This utility is an example implementation of an algorithm for reassigning
+slab memory in a running memcached instance. If memcached's built-in
+automover isn't working for you, you may use this script as an example
+base and expand on it. We welcome modifications or alternatives on the
+mailing list.
+
+=head1 ALGORITHM
+
+The default algorithm is simple, and may serve for a common case: over
+time one slab may grow in use compare to others, and as evictions stop
+in one slab and start in another it will reassign memory.
+
+If a slab has the most evictions three times in a row, it will pull a page
+from a slab which has had zero evictions three times in a row.
+
+There are many traffic patterns where this does not work well. IE: If you
+never use expirations and rely on the LRU (so all slabs always evict),
+it will not be as likely to find source pages to move.
+
+=head1 OPTIONS
+
+=over
+
+=item --host="IP:PORT"
+
+The hostname to connect to. NOTE: If connection to the host breaks, script
+will stop.
+
+=item --sleep=10
+
+How long to wait between loops for gathering stats.
+
+=item --loops=3
+
+How many loops to run before making a decision for a move.
+
+=item --verbose
+
+Prints a formatted dump of some common statistics per loop.
+
+=item --automove
+
+Enables the automover, and will attempt to move memory around if it finds
+viable candidates.
+
+=back
+
+=head1 AUTHOR
+
+Dormando E<lt>L<dormando@rydia.net>E<gt>
+
+=head1 LICENSE
+
+Licensed for use and redistribution under the same terms as Memcached itself.
+
+=cut
+
+use warnings;
+use strict;
+
+use IO::Socket::INET;
+
+use FindBin;
+use Data::Dumper qw/Dumper/;
+use Getopt::Long;
+
+my %opts = ('sleep' => 10, automove => 0, verbose => 0, loops => 3);
+GetOptions(
+        "host=s" => \$opts{host},
+        "sleep=i" => \$opts{'sleep'},
+        "loops=i" => \$opts{loops},
+        "automove" => \$opts{automove},
+        "verbose"  => \$opts{verbose},
+    ) or usage();
+
+die "Must specify at least --host='127.0.0.1:11211'" unless $opts{host};
+my $sock = IO::Socket::INET->new(PeerAddr => $opts{host},
+                                 Timeout  => 3);
+die "$!\n" unless $sock;
+
+my %stats = ();
+my %move  = (winner => 0, wins => 0);
+
+$SIG{INT} = sub {
+    print "STATS: ", Dumper(\%stats), "\n";
+    exit;
+};
+$SIG{USR1} = sub {
+    print "STATS: ", Dumper(\%stats), "\n";
+};
+run();
+
+sub usage {
+    print qq{Usage:
+    mc_slab_ratios --host="127.0.0.1:11211" --verbose --automove
+    run `perldoc mc_slab_ratios` for full information
+
+};
+    exit 1;
+}
+
+sub run {
+    my $slabs_before = grab_stats();
+
+    while (1) {
+        sleep $opts{'sleep'};
+        my $slabs_after  = grab_stats();
+
+        my ($totals, $sorted) = calc_results_evicted($slabs_before, $slabs_after);
+#        my ($totals, $sorted) = calc_results_numratio($slabs_before, $slabs_after);
+
+        my $pct = sub {
+            my ($num, $divisor) = @_;
+            return 0 unless $divisor;
+            return ($num / $divisor);
+        };
+        if ($opts{verbose}) {
+            printf "  %02s: %-8s (pct  ) %-10s (pct    ) %-6s (pct  ) get_hits (pct   ) cmd_set (pct  )\n",
+                'sb', 'evicted', 'items', 'pages';
+            for my $slab (@$sorted) {
+                printf "  %02d: %-8d (%.2f%%) %-10s (%.4f%%) %-6d (%.2f%%) %-8d (%.3f%%) %-7d (%.2f%%)\n",
+                    $slab->{slab}, $slab->{evicted_d},
+                    $pct->($slab->{evicted_d}, $totals->{evicted_d}),
+                    $slab->{number},
+                    $pct->($slab->{number}, $totals->{number}),
+                    $slab->{total_pages},
+                    $pct->($slab->{total_pages}, $totals->{total_pages}),
+                    $slab->{get_hits_d},
+                    $pct->($slab->{get_hits_d}, $totals->{get_hits_d}),
+                    $slab->{cmd_set_d},
+                    $pct->($slab->{cmd_set_d}, $totals->{cmd_set_d});
+            }
+        }
+
+        next unless @$sorted;
+        my $highest = $sorted->[-1];
+        $stats{$highest->{slab}}++;
+        print "  (winner: ", $highest->{slab}, " wins: ", $stats{$highest->{slab}}, ")\n";
+        automove_basic($totals, $sorted) if ($opts{automove});
+
+        $slabs_before = $slabs_after;
+    }
+}
+
+sub grab_stats {
+    my %slabs = ();
+    for my $stat (qw/items slabs/) {
+        print $sock "stats $stat\r\n";
+        while (my $line = <$sock>) {
+            chomp $line;
+            last if ($line =~ m/^END/);
+            if ($line =~ m/^STAT (?:items:)?(\d+):(\S+) (\S+)/) {
+                my ($slab, $var, $val) = ($1, $2, $3);
+                $slabs{$slab}->{$var} = $val;
+            }
+        }
+    }
+
+    return \%slabs;
+}
+
+# Really stupid algo, same as the initial algo built into memcached.
+# If a slab "wins" most evictions 3 times in a row, pick from a slab which
+# has had 0 evictions 3 times in a row and move it over.
+sub automove_basic {
+    my ($totals, $sorted) = @_;
+
+    my $source = 0;
+    my $dest   = 0;
+    my $high = $sorted->[-1];
+    return unless $high->{evicted_d} > 0;
+    if ($move{winner} == $high->{slab}) {
+        $move{wins}++;
+        $dest = $move{winner} if $move{wins} >= $opts{loops};
+    } else {
+        $move{wins} = 1;
+        $move{winner} = $high->{slab};
+    }
+    for my $slab (@$sorted) {
+        my $id = $slab->{slab};
+        if ($slab->{evicted_d} == 0 && $slab->{total_pages} > 2) {
+            $move{zeroes}->{$id}++;
+            $source = $id if (!$source && $move{zeroes}->{$id} >= $opts{loops});
+        } else {
+            delete $move{zeroes}->{$slab->{slab}}
+                if exists $move{zeroes}->{$slab->{slab}};
+        }
+    }
+
+    if ($source && $dest) {
+        print "  slabs reassign $source $dest\n";
+        print $sock "slabs reassign $source $dest\r\n";
+        my $res = <$sock>;
+        print "  RES: ", $res;
+    } elsif ($dest && !$source) {
+        print "FAIL: want to move memory to $dest but no valid source slab available\n";
+    }
+}
+
+# Using just the evicted stats.
+sub calc_results_evicted {
+    my ($slabs, $totals) = calc_slabs(@_);
+    my @sorted = sort { $a->{evicted_d} <=> $b->{evicted_d} } values %$slabs;
+    return ($totals, \@sorted);
+}
+
+# Weighted ratios of evictions vs total stored items
+# Seems to fail as an experiment, but it tries to weight stats.
+# In this case evictions in underused classes tend to get vastly inflated
+sub calc_results_numratio {
+    my ($slabs, $totals) = calc_slabs(@_, sub {
+        my ($sb, $sa, $s) = @_;
+        if ($s->{evicted_d}) {
+            $s->{numratio} = $s->{evicted_d} / $s->{number};
+        } else { $s->{numratio} = 0; }
+    });
+    my @sorted = sort { $a->{numratio} <=> $b->{numratio} } values %$slabs;
+    return ($totals, \@sorted);
+}
+
+sub calc_slabs {
+    my ($slabs_before, $slabs_after, $code) = @_;
+    my %slabs  = ();
+    my %totals = ();
+    for my $id (keys %$slabs_after) {
+        my $sb = $slabs_before->{$id};
+        my $sa = $slabs_after->{$id};
+        next unless ($sb && $sa);
+        my %slab = %$sa;
+        for my $key (keys %slab) {
+            # Add totals, diffs
+            if ($slab{$key} =~ m/^\d+$/) {
+                $totals{$key} += $slab{$key};
+                $slab{$key . '_d'} = $sa->{$key} - $sb->{$key};
+                $totals{$key . '_d'} += $sa->{$key} - $sb->{$key};
+            }
+        }
+        # External code
+        $code->($sb, $sa, \%slab) if $code;
+        $slab{slab} = $id;
+        $slabs{$id} = \%slab;
+    }
+    return (\%slabs, \%totals);
+}
diff --git a/memcached/scripts/memcached-init b/memcached/scripts/memcached-init
new file mode 100755 (executable)
index 0000000..ebe2ab0
--- /dev/null
@@ -0,0 +1,111 @@
+#! /bin/bash
+### BEGIN INIT INFO
+# Provides:          memcached
+# Required-Start:    $syslog
+# Required-Stop:     $syslog
+# Should-Start:        $local_fs
+# Should-Stop:        $local_fs
+# Default-Start:     2 3 4 5
+# Default-Stop:      0 1 6
+# Short-Description:    memcached - Memory caching daemon
+# Description:        memcached - Memory caching daemon
+### END INIT INFO
+
+# Usage:
+# cp /etc/memcached.conf /etc/memcached_server1.conf
+# cp /etc/memcached.conf /etc/memcached_server2.conf
+# start all instances:
+# /etc/init.d/memcached start
+# start one instance:
+# /etc/init.d/memcached start server1
+# stop all instances:
+# /etc/init.d/memcached stop
+# stop one instance:
+# /etc/init.d/memcached stop server1
+# There is no "status" command.
+
+PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
+DAEMON=/usr/bin/memcached
+DAEMONNAME=memcached
+DAEMONBOOTSTRAP=/usr/share/memcached/scripts/start-memcached
+DESC=memcached
+
+test -x $DAEMON || exit 0
+test -x $DAEMONBOOTSTRAP || exit 0
+
+set -e
+
+FILES=(/etc/memcached_*.conf)
+# check for alternative config schema
+if [ -r "${FILES[0]}" ]; then
+  CONFIGS=()
+  for FILE in "${FILES[@]}";
+  do
+    # remove prefix
+    NAME=${FILE#/etc/}
+    # remove suffix
+    NAME=${NAME%.conf}
+
+    # check optional second param
+    if [ $# -ne 2 ];
+    then
+      # add to config array
+      CONFIGS+=($NAME)
+    elif [ "memcached_$2" == "$NAME" ];
+    then
+      # use only one memcached
+      CONFIGS=($NAME)
+      break;
+    fi;
+  done;
+
+  if [ ${#CONFIGS[@]} == 0 ];
+  then
+    echo "Config not exist for: $2" >&2
+    exit 1
+  fi;
+else
+  CONFIGS=(memcached)
+fi;
+
+CONFIG_NUM=${#CONFIGS[@]}
+for ((i=0; i < $CONFIG_NUM; i++)); do
+  NAME=${CONFIGS[${i}]}
+  PIDFILE="/var/run/${NAME}.pid"
+
+case "$1" in
+    start)
+        echo -n "Starting $DESC: "
+        start-stop-daemon --start --quiet --exec "$DAEMONBOOTSTRAP" -- /etc/${NAME}.conf $PIDFILE
+        echo "$NAME."
+        ;;
+    stop)
+        echo -n "Stopping $DESC: "
+        start-stop-daemon --stop --quiet --oknodo --pidfile $PIDFILE --exec $DAEMON
+        echo "$NAME."
+        rm -f $PIDFILE
+        ;;
+
+    restart|force-reload)
+    #
+    #   If the "reload" option is implemented, move the "force-reload"
+    #   option to the "reload" entry above. If not, "force-reload" is
+    #   just the same as "restart".
+    #
+        echo -n "Restarting $DESC: "
+        start-stop-daemon --stop --quiet --oknodo --pidfile $PIDFILE
+        rm -f $PIDFILE
+        sleep 1
+        start-stop-daemon --start --quiet --exec "$DAEMONBOOTSTRAP" -- /etc/${NAME}.conf $PIDFILE
+        echo "$NAME."
+        ;;
+    *)
+        N=/etc/init.d/$NAME
+    # echo "Usage: $N {start|stop|restart|reload|force-reload}" >&2
+        echo "Usage: $N {start|stop|restart|force-reload}" >&2
+        exit 1
+        ;;
+esac
+done;
+
+exit 0
diff --git a/memcached/scripts/memcached-tool b/memcached/scripts/memcached-tool
new file mode 100755 (executable)
index 0000000..bae4efd
--- /dev/null
@@ -0,0 +1,169 @@
+#!/usr/bin/perl
+#
+# memcached-tool:
+#   stats/management tool for memcached.
+#
+# Author:
+#   Brad Fitzpatrick <brad@danga.com>
+#
+# License:
+#   public domain.  I give up all rights to this
+#   tool.  modify and copy at will.
+#
+
+use strict;
+use IO::Socket::INET;
+
+my $addr = shift;
+my $mode = shift || "display";
+my ($from, $to);
+
+if ($mode eq "display") {
+    undef $mode if @ARGV;
+} elsif ($mode eq "move") {
+    $from = shift;
+    $to = shift;
+    undef $mode if $from < 6 || $from > 17;
+    undef $mode if $to   < 6 || $to   > 17;
+    print STDERR "ERROR: parameters out of range\n\n" unless $mode;
+} elsif ($mode eq 'dump') {
+    ;
+} elsif ($mode eq 'stats') {
+    ;
+} else {
+    undef $mode;
+}
+
+undef $mode if @ARGV;
+
+die
+    "Usage: memcached-tool <host[:port] | /path/to/socket> [mode]\n
+       memcached-tool 10.0.0.5:11211 display    # shows slabs
+       memcached-tool 10.0.0.5:11211            # same.  (default is display)
+       memcached-tool 10.0.0.5:11211 stats      # shows general stats
+       memcached-tool 10.0.0.5:11211 dump       # dumps keys and values
+" unless $addr && $mode;
+
+
+my $sock;
+if ($addr =~ m:/:) {
+    $sock = IO::Socket::UNIX->new(
+        Peer => $addr,
+    );
+}
+else {
+    $addr .= ':11211' unless $addr =~ /:\d+$/;
+
+    $sock = IO::Socket::INET->new(
+        PeerAddr => $addr,
+        Proto    => 'tcp',
+    );
+}
+die "Couldn't connect to $addr\n" unless $sock;
+
+if ($mode eq 'dump') {
+    my %items;
+    my $totalitems;
+
+    print $sock "stats items\r\n";
+
+    while (<$sock>) {
+        last if /^END/;
+        if (/^STAT items:(\d*):number (\d*)/) {
+            $items{$1} = $2;
+            $totalitems += $2;
+        }
+    }
+    print STDERR "Dumping memcache contents\n";
+    print STDERR "  Number of buckets: " . scalar(keys(%items)) . "\n";
+    print STDERR "  Number of items  : $totalitems\n";
+
+    foreach my $bucket (sort(keys(%items))) {
+        print STDERR "Dumping bucket $bucket - " . $items{$bucket} . " total items\n";
+        print $sock "stats cachedump $bucket $items{$bucket}\r\n";
+        my %keyexp;
+        while (<$sock>) {
+            last if /^END/;
+            # return format looks like this
+            # ITEM foo [6 b; 1176415152 s]
+            if (/^ITEM (\S+) \[.* (\d+) s\]/) {
+                $keyexp{$1} = $2;
+            }
+        }
+
+        foreach my $k (keys(%keyexp)) {
+            print $sock "get $k\r\n";
+            my $response = <$sock>;
+            if ($response =~ /VALUE (\S+) (\d+) (\d+)/) {
+                my $flags = $2;
+                my $len = $3;
+                my $val;
+                read $sock, $val, $len;
+                print "add $k $flags $keyexp{$k} $len\r\n$val\r\n";
+                # get the END
+                $_ = <$sock>;
+                $_ = <$sock>;
+            }
+        }
+    }
+    exit;
+}
+
+if ($mode eq 'stats') {
+    my %items;
+
+    print $sock "stats\r\n";
+
+    while (<$sock>) {
+        last if /^END/;
+        chomp;
+        if (/^STAT\s+(\S*)\s+(.*)/) {
+            $items{$1} = $2;
+        }
+    }
+    printf ("#%-17s %5s %11s\n", $addr, "Field", "Value");
+    foreach my $name (sort(keys(%items))) {
+        printf ("%24s %12s\n", $name, $items{$name});
+
+    }
+    exit;
+}
+
+# display mode:
+
+my %items;  # class -> { number, age, chunk_size, chunks_per_page,
+#            total_pages, total_chunks, used_chunks,
+#            free_chunks, free_chunks_end }
+
+print $sock "stats items\r\n";
+my $max = 0;
+while (<$sock>) {
+    last if /^END/;
+    if (/^STAT items:(\d+):(\w+) (\d+)/) {
+        $items{$1}{$2} = $3;
+        $max = $1;
+    }
+}
+
+print $sock "stats slabs\r\n";
+while (<$sock>) {
+    last if /^END/;
+    if (/^STAT (\d+):(\w+) (\d+)/) {
+        $items{$1}{$2} = $3;
+    }
+}
+
+print "  #  Item_Size  Max_age   Pages   Count   Full?  Evicted Evict_Time OOM\n";
+foreach my $n (1..$max) {
+    my $it = $items{$n};
+    next if (0 == $it->{total_pages});
+    my $size = $it->{chunk_size} < 1024 ?
+        "$it->{chunk_size}B" :
+        sprintf("%.1fK", $it->{chunk_size} / 1024.0);
+    my $full = $it->{free_chunks_end} == 0 ? "yes" : " no";
+    printf("%3d %8s %9ds %7d %7d %7s %8d %8d %4d\n",
+           $n, $size, $it->{age}, $it->{total_pages},
+           $it->{number}, $full, $it->{evicted},
+           $it->{evicted_time}, $it->{outofmemory});
+}
+
diff --git a/memcached/scripts/memcached.service b/memcached/scripts/memcached.service
new file mode 100644 (file)
index 0000000..e896dd7
--- /dev/null
@@ -0,0 +1,10 @@
+[Unit]
+Description=memcached daemon
+After=network.target
+
+[Service]
+EnvironmentFile=/etc/sysconfig/memcached
+ExecStart=/usr/bin/memcached -p ${PORT} -u ${USER} -m ${CACHESIZE} -c ${MAXCONN} $OPTIONS
+
+[Install]
+WantedBy=multi-user.target
diff --git a/memcached/scripts/memcached.sysv b/memcached/scripts/memcached.sysv
new file mode 100755 (executable)
index 0000000..024cf33
--- /dev/null
@@ -0,0 +1,78 @@
+#! /bin/sh
+#
+# chkconfig: - 55 45
+# description:  The memcached daemon is a network memory cache service.
+# processname: memcached
+# config: /etc/sysconfig/memcached
+
+# Source function library.
+. /etc/rc.d/init.d/functions
+
+PORT=11211
+USER=nobody
+MAXCONN=1024
+CACHESIZE=64
+OPTIONS=""
+
+if [ -f /etc/sysconfig/memcached ];then
+    . /etc/sysconfig/memcached
+fi
+
+# Check that networking is up.
+if [ "$NETWORKING" = "no" ]
+then
+    exit 0
+fi
+
+RETVAL=0
+prog="memcached"
+
+start () {
+    echo -n $"Starting $prog: "
+    # insure that /var/run/memcached has proper permissions
+    chown $USER /var/run/memcached
+    daemon memcached -d -p $PORT -u $USER  -m $CACHESIZE -c $MAXCONN -P /var/run/memcached/memcached.pid $OPTIONS
+    RETVAL=$?
+    echo
+    [ $RETVAL -eq 0 ] && touch /var/lock/subsys/memcached
+}
+stop () {
+    echo -n $"Stopping $prog: "
+    killproc memcached
+    RETVAL=$?
+    echo
+    if [ $RETVAL -eq 0 ] ; then
+        rm -f /var/lock/subsys/memcached
+        rm -f /var/run/memcached.pid
+    fi
+}
+
+restart () {
+    stop
+    start
+}
+
+
+# See how we were called.
+case "$1" in
+    start)
+        start
+        ;;
+    stop)
+    stop
+    ;;
+    status)
+    status memcached
+    ;;
+    restart|reload)
+    restart
+    ;;
+    condrestart)
+    [ -f /var/lock/subsys/memcached ] && restart || :
+    ;;
+    *)
+    echo $"Usage: $0 {start|stop|status|restart|reload|condrestart}"
+    exit 1
+esac
+
+exit $?
diff --git a/memcached/scripts/start-memcached b/memcached/scripts/start-memcached
new file mode 100755 (executable)
index 0000000..ce39fa6
--- /dev/null
@@ -0,0 +1,128 @@
+#!/usr/bin/perl -w
+
+# start-memcached
+# 2003/2004 - Jay Bonci <jaybonci@debian.org>
+# This script handles the parsing of the /etc/memcached.conf file
+# and was originally created for the Debian distribution.
+# Anyone may use this little script under the same terms as
+# memcached itself.
+
+use POSIX qw(setsid);
+use strict;
+
+if($> != 0 and $< != 0)
+{
+    print STDERR "Only root wants to run start-memcached.\n";
+    exit;
+}
+
+my $params; my $etchandle; my $etcfile = "/etc/memcached.conf";
+
+# This script assumes that memcached is located at /usr/bin/memcached, and
+# that the pidfile is writable at /var/run/memcached.pid
+
+my $memcached = "/usr/bin/memcached";
+my $pidfile = "/var/run/memcached.pid";
+
+if (scalar(@ARGV) == 2) {
+    $etcfile = shift(@ARGV);
+    $pidfile = shift(@ARGV);
+}
+
+# If we don't get a valid logfile parameter in the /etc/memcached.conf file,
+# we'll just throw away all of our in-daemon output. We need to re-tie it so
+# that non-bash shells will not hang on logout. Thanks to Michael Renner for
+# the tip
+my $fd_reopened = "/dev/null";
+
+sub handle_logfile
+{
+    my ($logfile) = @_;
+    $fd_reopened = $logfile;
+}
+
+sub reopen_logfile
+{
+    my ($logfile) = @_;
+
+    open *STDERR, ">>$logfile";
+    open *STDOUT, ">>$logfile";
+    open *STDIN, ">>/dev/null";
+    $fd_reopened = $logfile;
+}
+
+# This is set up in place here to support other non -[a-z] directives
+
+my $conf_directives = {
+    "logfile" => \&handle_logfile,
+};
+
+if(open $etchandle, $etcfile)
+{
+    foreach my $line (<$etchandle>)
+    {
+        $line ||= "";
+        $line =~ s/\#.*//g;
+        $line =~ s/\s+$//g;
+        $line =~ s/^\s+//g;
+        next unless $line;
+        next if $line =~ /^\-[dh]/;
+
+        if($line =~ /^[^\-]/)
+        {
+            my ($directive, $arg) = $line =~ /^(.*?)\s+(.*)/;
+            $conf_directives->{$directive}->($arg);
+            next;
+        }
+
+        push @$params, $line;
+    }
+
+}else{
+    $params = [];
+}
+
+push @$params, "-u root" unless(grep "-u", @$params);
+$params = join " ", @$params;
+
+if(-e $pidfile)
+{
+    open PIDHANDLE, "$pidfile";
+    my $localpid = <PIDHANDLE>;
+    close PIDHANDLE;
+
+    chomp $localpid;
+    if(-d "/proc/$localpid")
+    {
+        print STDERR "memcached is already running.\n";
+        exit;
+    }else{
+        `rm -f $localpid`;
+    }
+
+}
+
+my $pid = fork();
+
+if($pid == 0)
+{
+    # setsid makes us the session leader
+    setsid();
+    reopen_logfile($fd_reopened);
+    # must fork again now that tty is closed
+    $pid = fork();
+    if ($pid) {
+      if(open PIDHANDLE,">$pidfile")
+      {
+          print PIDHANDLE $pid;
+          close PIDHANDLE;
+      }else{
+
+          print STDERR "Can't write pidfile to $pidfile.\n";
+      }
+      exit(0);
+    }
+    exec "$memcached $params";
+    exit(0);
+
+}
diff --git a/memcached/sizes.c b/memcached/sizes.c
new file mode 100644 (file)
index 0000000..95a644c
--- /dev/null
@@ -0,0 +1,29 @@
+#include <stdio.h>
+
+#include "memcached.h"
+
+static void display(const char *name, size_t size) {
+    printf("%s\t%d\n", name, (int)size);
+}
+
+int main(int argc, char **argv) {
+
+    display("Slab Stats", sizeof(struct slab_stats));
+    display("Thread stats",
+            sizeof(struct thread_stats)
+            - (200 * sizeof(struct slab_stats)));
+    display("Global stats", sizeof(struct stats));
+    display("Settings", sizeof(struct settings));
+    display("Item (no cas)", sizeof(item));
+    display("Item (cas)", sizeof(item) + sizeof(uint64_t));
+    display("Libevent thread",
+            sizeof(LIBEVENT_THREAD) - sizeof(struct thread_stats));
+    display("Connection", sizeof(conn));
+
+    printf("----------------------------------------\n");
+
+    display("libevent thread cumulative", sizeof(LIBEVENT_THREAD));
+    display("Thread stats cumulative\t", sizeof(struct thread_stats));
+
+    return 0;
+}
diff --git a/memcached/slabs.c b/memcached/slabs.c
new file mode 100644 (file)
index 0000000..7cffe43
--- /dev/null
@@ -0,0 +1,840 @@
+/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Slabs memory allocation, based on powers-of-N. Slabs are up to 1MB in size
+ * and are divided into chunks. The chunk sizes start off at the size of the
+ * "item" structure plus space for a small key and value. They increase by
+ * a multiplier factor from there, up to half the maximum slab size. The last
+ * slab size is always 1MB, since that's the maximum item size allowed by the
+ * memcached protocol.
+ */
+#include "memcached.h"
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/signal.h>
+#include <sys/resource.h>
+#include <fcntl.h>
+#include <netinet/in.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include <pthread.h>
+
+/* powers-of-N allocation structures */
+
+typedef struct {
+    unsigned int size;      /* sizes of items */
+    unsigned int perslab;   /* how many items per slab */
+
+    void *slots;           /* list of item ptrs */
+    unsigned int sl_curr;   /* total free items in list */
+
+    void *end_page_ptr;         /* pointer to next free item at end of page, or 0 */
+    unsigned int end_page_free; /* number of items remaining at end of last alloced page */
+
+    unsigned int slabs;     /* how many slabs were allocated for this class */
+
+    void **slab_list;       /* array of slab pointers */
+    unsigned int list_size; /* size of prev array */
+
+    unsigned int killing;  /* index+1 of dying slab, or zero if none */
+    size_t requested; /* The number of requested bytes */
+} slabclass_t;
+
+static slabclass_t slabclass[MAX_NUMBER_OF_SLAB_CLASSES];
+static size_t mem_limit = 0;
+static size_t mem_malloced = 0;
+static int power_largest;
+
+static void *mem_base = NULL;
+static void *mem_current = NULL;
+static size_t mem_avail = 0;
+
+/**
+ * Access to the slab allocator is protected by this lock
+ */
+static pthread_mutex_t slabs_lock = PTHREAD_MUTEX_INITIALIZER;
+
+/*
+ * Forward Declarations
+ */
+static int do_slabs_newslab(const unsigned int id);
+static void *memory_allocate(size_t size);
+
+#ifndef DONT_PREALLOC_SLABS
+/* Preallocate as many slab pages as possible (called from slabs_init)
+   on start-up, so users don't get confused out-of-memory errors when
+   they do have free (in-slab) space, but no space to make new slabs.
+   if maxslabs is 18 (POWER_LARGEST - POWER_SMALLEST + 1), then all
+   slab types can be made.  if max memory is less than 18 MB, only the
+   smaller ones will be made.  */
+static void slabs_preallocate (const unsigned int maxslabs);
+#endif
+
+/*
+ * Figures out which slab class (chunk size) is required to store an item of
+ * a given size.
+ *
+ * Given object size, return id to use when allocating/freeing memory for object
+ * 0 means error: can't store such a large object
+ */
+
+unsigned int slabs_clsid(const size_t size) {
+    int res = POWER_SMALLEST;
+
+    if (size == 0)
+        return 0;
+    while (size > slabclass[res].size)
+        if (res++ == power_largest)     /* won't fit in the biggest slab */
+            return 0;
+    return res;
+}
+
+/**
+ * Determines the chunk sizes and initializes the slab class descriptors
+ * accordingly.
+ */
+void slabs_init(const size_t limit, const double factor, const bool prealloc) {
+    int i = POWER_SMALLEST - 1;
+    unsigned int size = sizeof(item) + settings.chunk_size;
+
+    mem_limit = limit;
+
+    if (prealloc) {
+        /* Allocate everything in a big chunk with malloc */
+        mem_base = malloc(mem_limit);
+        if (mem_base != NULL) {
+            mem_current = mem_base;
+            mem_avail = mem_limit;
+        } else {
+            fprintf(stderr, "Warning: Failed to allocate requested memory in"
+                    " one large chunk.\nWill allocate in smaller chunks\n");
+        }
+    }
+
+    memset(slabclass, 0, sizeof(slabclass));
+
+    while (++i < POWER_LARGEST && size <= settings.item_size_max / factor) {
+        /* Make sure items are always n-byte aligned */
+        if (size % CHUNK_ALIGN_BYTES)
+            size += CHUNK_ALIGN_BYTES - (size % CHUNK_ALIGN_BYTES);
+
+        slabclass[i].size = size;
+        slabclass[i].perslab = settings.item_size_max / slabclass[i].size;
+        size *= factor;
+        if (settings.verbose > 1) {
+            fprintf(stderr, "slab class %3d: chunk size %9u perslab %7u\n",
+                    i, slabclass[i].size, slabclass[i].perslab);
+        }
+    }
+
+    power_largest = i;
+    slabclass[power_largest].size = settings.item_size_max;
+    slabclass[power_largest].perslab = 1;
+    if (settings.verbose > 1) {
+        fprintf(stderr, "slab class %3d: chunk size %9u perslab %7u\n",
+                i, slabclass[i].size, slabclass[i].perslab);
+    }
+
+    /* for the test suite:  faking of how much we've already malloc'd */
+    {
+        char *t_initial_malloc = getenv("T_MEMD_INITIAL_MALLOC");
+        if (t_initial_malloc) {
+            mem_malloced = (size_t)atol(t_initial_malloc);
+        }
+
+    }
+
+#ifndef DONT_PREALLOC_SLABS
+    {
+        char *pre_alloc = getenv("T_MEMD_SLABS_ALLOC");
+
+        if (pre_alloc == NULL || atoi(pre_alloc) != 0) {
+            slabs_preallocate(power_largest);
+        }
+    }
+#endif
+}
+
+#ifndef DONT_PREALLOC_SLABS
+static void slabs_preallocate (const unsigned int maxslabs) {
+    int i;
+    unsigned int prealloc = 0;
+
+    /* pre-allocate a 1MB slab in every size class so people don't get
+       confused by non-intuitive "SERVER_ERROR out of memory"
+       messages.  this is the most common question on the mailing
+       list.  if you really don't want this, you can rebuild without
+       these three lines.  */
+
+    for (i = POWER_SMALLEST; i <= POWER_LARGEST; i++) {
+        if (++prealloc > maxslabs)
+            return;
+        do_slabs_newslab(i);
+    }
+
+}
+#endif
+
+static int grow_slab_list (const unsigned int id) {
+    slabclass_t *p = &slabclass[id];
+    if (p->slabs == p->list_size) {
+        size_t new_size =  (p->list_size != 0) ? p->list_size * 2 : 16;
+        void *new_list = realloc(p->slab_list, new_size * sizeof(void *));
+        if (new_list == 0) return 0;
+        p->list_size = new_size;
+        p->slab_list = new_list;
+    }
+    return 1;
+}
+
+#ifndef __INTEL_COMPILER
+#pragma GCC diagnostic ignored "-Wsign-compare"
+#endif
+
+static int do_slabs_newslab(const unsigned int id) {
+    slabclass_t *p = &slabclass[id];
+    int len = settings.slab_reassign ? settings.item_size_max
+        : p->size * p->perslab;
+    char *ptr;
+
+    if ((mem_limit && mem_malloced + len > mem_limit && p->slabs > 0) ||
+        (grow_slab_list(id) == 0) ||
+        ((ptr = memory_allocate((size_t)len)) == 0)) {
+
+        MEMCACHED_SLABS_SLABCLASS_ALLOCATE_FAILED(id);
+        return 0;
+    }
+
+    memset(ptr, 0, (size_t)len);
+    p->end_page_ptr = ptr;
+    p->end_page_free = p->perslab;
+
+    p->slab_list[p->slabs++] = ptr;
+    mem_malloced += len;
+    MEMCACHED_SLABS_SLABCLASS_ALLOCATE(id);
+
+    return 1;
+}
+
+/*@null@*/
+static void *do_slabs_alloc(const size_t size, unsigned int id) {
+    slabclass_t *p;
+    void *ret = NULL;
+    item *it = NULL;
+
+    if (id < POWER_SMALLEST || id > power_largest) {
+        MEMCACHED_SLABS_ALLOCATE_FAILED(size, 0);
+        return NULL;
+    }
+
+    p = &slabclass[id];
+    assert(p->sl_curr == 0 || ((item *)p->slots)->slabs_clsid == 0);
+
+#ifdef USE_SYSTEM_MALLOC
+    if (mem_limit && mem_malloced + size > mem_limit) {
+        MEMCACHED_SLABS_ALLOCATE_FAILED(size, id);
+        return 0;
+    }
+    mem_malloced += size;
+    ret = malloc(size);
+    MEMCACHED_SLABS_ALLOCATE(size, id, 0, ret);
+    return ret;
+#endif
+
+    /* fail unless we have space at the end of a recently allocated page,
+       we have something on our freelist, or we could allocate a new page */
+    if (! (p->end_page_ptr != 0 || p->sl_curr != 0 ||
+           do_slabs_newslab(id) != 0)) {
+        /* We don't have more memory available */
+        ret = NULL;
+    } else if (p->sl_curr != 0) {
+        /* return off our freelist */
+        it = (item *)p->slots;
+        p->slots = it->next;
+        if (it->next) it->next->prev = 0;
+        p->sl_curr--;
+        ret = (void *)it;
+    } else {
+        /* if we recently allocated a whole page, return from that */
+        assert(p->end_page_ptr != NULL);
+        ret = p->end_page_ptr;
+        if (--p->end_page_free != 0) {
+            p->end_page_ptr = ((caddr_t)p->end_page_ptr) + p->size;
+        } else {
+            p->end_page_ptr = 0;
+        }
+    }
+
+    if (ret) {
+        p->requested += size;
+        MEMCACHED_SLABS_ALLOCATE(size, id, p->size, ret);
+    } else {
+        MEMCACHED_SLABS_ALLOCATE_FAILED(size, id);
+    }
+
+    return ret;
+}
+
+static void do_slabs_free(void *ptr, const size_t size, unsigned int id) {
+    slabclass_t *p;
+    item *it;
+
+    assert(((item *)ptr)->slabs_clsid == 0);
+    assert(id >= POWER_SMALLEST && id <= power_largest);
+    if (id < POWER_SMALLEST || id > power_largest)
+        return;
+
+    MEMCACHED_SLABS_FREE(size, id, ptr);
+    p = &slabclass[id];
+
+#ifdef USE_SYSTEM_MALLOC
+    mem_malloced -= size;
+    free(ptr);
+    return;
+#endif
+
+    it = (item *)ptr;
+    it->it_flags |= ITEM_SLABBED;
+    it->prev = 0;
+    it->next = p->slots;
+    if (it->next) it->next->prev = it;
+    p->slots = it;
+
+    p->sl_curr++;
+    p->requested -= size;
+    return;
+}
+
+static int nz_strcmp(int nzlength, const char *nz, const char *z) {
+    int zlength=strlen(z);
+    return (zlength == nzlength) && (strncmp(nz, z, zlength) == 0) ? 0 : -1;
+}
+
+bool get_stats(const char *stat_type, int nkey, ADD_STAT add_stats, void *c) {
+    bool ret = true;
+
+    if (add_stats != NULL) {
+        if (!stat_type) {
+            /* prepare general statistics for the engine */
+            STATS_LOCK();
+            APPEND_STAT("bytes", "%llu", (unsigned long long)stats.curr_bytes);
+            APPEND_STAT("curr_items", "%u", stats.curr_items);
+            APPEND_STAT("total_items", "%u", stats.total_items);
+            APPEND_STAT("evictions", "%llu",
+                        (unsigned long long)stats.evictions);
+            APPEND_STAT("reclaimed", "%llu",
+                        (unsigned long long)stats.reclaimed);
+            STATS_UNLOCK();
+        } else if (nz_strcmp(nkey, stat_type, "items") == 0) {
+            item_stats(add_stats, c);
+        } else if (nz_strcmp(nkey, stat_type, "slabs") == 0) {
+            slabs_stats(add_stats, c);
+        } else if (nz_strcmp(nkey, stat_type, "sizes") == 0) {
+            item_stats_sizes(add_stats, c);
+        } else {
+            ret = false;
+        }
+    } else {
+        ret = false;
+    }
+
+    return ret;
+}
+
+/*@null@*/
+static void do_slabs_stats(ADD_STAT add_stats, void *c) {
+    int i, total;
+    /* Get the per-thread stats which contain some interesting aggregates */
+    struct thread_stats thread_stats;
+    threadlocal_stats_aggregate(&thread_stats);
+
+    total = 0;
+    for(i = POWER_SMALLEST; i <= power_largest; i++) {
+        slabclass_t *p = &slabclass[i];
+        if (p->slabs != 0) {
+            uint32_t perslab, slabs;
+            slabs = p->slabs;
+            perslab = p->perslab;
+
+            char key_str[STAT_KEY_LEN];
+            char val_str[STAT_VAL_LEN];
+            int klen = 0, vlen = 0;
+
+            APPEND_NUM_STAT(i, "chunk_size", "%u", p->size);
+            APPEND_NUM_STAT(i, "chunks_per_page", "%u", perslab);
+            APPEND_NUM_STAT(i, "total_pages", "%u", slabs);
+            APPEND_NUM_STAT(i, "total_chunks", "%u", slabs * perslab);
+            APPEND_NUM_STAT(i, "used_chunks", "%u",
+                            slabs*perslab - p->sl_curr - p->end_page_free);
+            APPEND_NUM_STAT(i, "free_chunks", "%u", p->sl_curr);
+            APPEND_NUM_STAT(i, "free_chunks_end", "%u", p->end_page_free);
+            APPEND_NUM_STAT(i, "mem_requested", "%llu",
+                            (unsigned long long)p->requested);
+            APPEND_NUM_STAT(i, "get_hits", "%llu",
+                    (unsigned long long)thread_stats.slab_stats[i].get_hits);
+            APPEND_NUM_STAT(i, "cmd_set", "%llu",
+                    (unsigned long long)thread_stats.slab_stats[i].set_cmds);
+            APPEND_NUM_STAT(i, "delete_hits", "%llu",
+                    (unsigned long long)thread_stats.slab_stats[i].delete_hits);
+            APPEND_NUM_STAT(i, "incr_hits", "%llu",
+                    (unsigned long long)thread_stats.slab_stats[i].incr_hits);
+            APPEND_NUM_STAT(i, "decr_hits", "%llu",
+                    (unsigned long long)thread_stats.slab_stats[i].decr_hits);
+            APPEND_NUM_STAT(i, "cas_hits", "%llu",
+                    (unsigned long long)thread_stats.slab_stats[i].cas_hits);
+            APPEND_NUM_STAT(i, "cas_badval", "%llu",
+                    (unsigned long long)thread_stats.slab_stats[i].cas_badval);
+            APPEND_NUM_STAT(i, "touch_hits", "%llu",
+                    (unsigned long long)thread_stats.slab_stats[i].touch_hits);
+            total++;
+        }
+    }
+
+    /* add overall slab stats and append terminator */
+
+    APPEND_STAT("active_slabs", "%d", total);
+    APPEND_STAT("total_malloced", "%llu", (unsigned long long)mem_malloced);
+    add_stats(NULL, 0, NULL, 0, c);
+}
+
+static void *memory_allocate(size_t size) {
+    void *ret;
+
+    if (mem_base == NULL) {
+        /* We are not using a preallocated large memory chunk */
+        ret = malloc(size);
+    } else {
+        ret = mem_current;
+
+        if (size > mem_avail) {
+            return NULL;
+        }
+
+        /* mem_current pointer _must_ be aligned!!! */
+        if (size % CHUNK_ALIGN_BYTES) {
+            size += CHUNK_ALIGN_BYTES - (size % CHUNK_ALIGN_BYTES);
+        }
+
+        mem_current = ((char*)mem_current) + size;
+        if (size < mem_avail) {
+            mem_avail -= size;
+        } else {
+            mem_avail = 0;
+        }
+    }
+
+    return ret;
+}
+
+void *slabs_alloc(size_t size, unsigned int id) {
+    void *ret;
+
+    pthread_mutex_lock(&slabs_lock);
+    ret = do_slabs_alloc(size, id);
+    pthread_mutex_unlock(&slabs_lock);
+    return ret;
+}
+
+void slabs_free(void *ptr, size_t size, unsigned int id) {
+    pthread_mutex_lock(&slabs_lock);
+    do_slabs_free(ptr, size, id);
+    pthread_mutex_unlock(&slabs_lock);
+}
+
+void slabs_stats(ADD_STAT add_stats, void *c) {
+    pthread_mutex_lock(&slabs_lock);
+    do_slabs_stats(add_stats, c);
+    pthread_mutex_unlock(&slabs_lock);
+}
+
+void slabs_adjust_mem_requested(unsigned int id, size_t old, size_t ntotal)
+{
+    pthread_mutex_lock(&slabs_lock);
+    slabclass_t *p;
+    if (id < POWER_SMALLEST || id > power_largest) {
+        fprintf(stderr, "Internal error! Invalid slab class\n");
+        abort();
+    }
+
+    p = &slabclass[id];
+    p->requested = p->requested - old + ntotal;
+    pthread_mutex_unlock(&slabs_lock);
+}
+
+static pthread_cond_t maintenance_cond = PTHREAD_COND_INITIALIZER;
+static volatile int do_run_slab_thread = 1;
+
+#define DEFAULT_SLAB_BULK_CHECK 1
+int slab_bulk_check = DEFAULT_SLAB_BULK_CHECK;
+
+static int slab_rebalance_start(void) {
+    slabclass_t *s_cls;
+    slabclass_t *d_cls;
+    int no_go = 0;
+
+    pthread_mutex_lock(&cache_lock);
+    pthread_mutex_lock(&slabs_lock);
+
+    if (slab_rebal.s_clsid < POWER_SMALLEST ||
+        slab_rebal.s_clsid > power_largest  ||
+        slab_rebal.d_clsid < POWER_SMALLEST ||
+        slab_rebal.d_clsid > power_largest  ||
+        slab_rebal.s_clsid == slab_rebal.d_clsid)
+        no_go = -2;
+
+    s_cls = &slabclass[slab_rebal.s_clsid];
+    d_cls = &slabclass[slab_rebal.d_clsid];
+
+    if (d_cls->end_page_ptr || s_cls->end_page_ptr ||
+        !grow_slab_list(slab_rebal.d_clsid)) {
+        no_go = -1;
+    }
+
+    if (s_cls->slabs < 2)
+        no_go = -3;
+
+    if (no_go != 0) {
+        pthread_mutex_unlock(&slabs_lock);
+        pthread_mutex_unlock(&cache_lock);
+        return no_go; /* Should use a wrapper function... */
+    }
+
+    s_cls->killing = 1;
+
+    slab_rebal.slab_start = s_cls->slab_list[s_cls->killing - 1];
+    slab_rebal.slab_end   = (char *)slab_rebal.slab_start +
+        (s_cls->size * s_cls->perslab);
+    slab_rebal.slab_pos   = slab_rebal.slab_start;
+    slab_rebal.done       = 0;
+
+    /* Also tells do_item_get to search for items in this slab */
+    slab_rebalance_signal = 2;
+
+    if (settings.verbose > 1) {
+        fprintf(stderr, "Started a slab rebalance\n");
+    }
+
+    pthread_mutex_unlock(&slabs_lock);
+    pthread_mutex_unlock(&cache_lock);
+
+    STATS_LOCK();
+    stats.slab_reassign_running = true;
+    STATS_UNLOCK();
+
+    return 0;
+}
+
+enum move_status {
+    MOVE_PASS=0, MOVE_DONE, MOVE_BUSY
+};
+
+/* refcount == 0 is safe since nobody can incr while cache_lock is held.
+ * refcount != 0 is impossible since flags/etc can be modified in other
+ * threads. instead, note we found a busy one and bail. logic in do_item_get
+ * will prevent busy items from continuing to be busy
+ */
+static int slab_rebalance_move(void) {
+    slabclass_t *s_cls;
+    int x;
+    int was_busy = 0;
+    int refcount = 0;
+    enum move_status status = MOVE_PASS;
+
+    pthread_mutex_lock(&cache_lock);
+    pthread_mutex_lock(&slabs_lock);
+
+    s_cls = &slabclass[slab_rebal.s_clsid];
+
+    for (x = 0; x < slab_bulk_check; x++) {
+        item *it = slab_rebal.slab_pos;
+        status = MOVE_PASS;
+        if (it->slabs_clsid != 255) {
+            refcount = refcount_incr(&it->refcount);
+            if (refcount == 1) { /* item is unlinked, unused */
+                if (it->it_flags & ITEM_SLABBED) {
+                    /* remove from slab freelist */
+                    if (s_cls->slots == it) {
+                        s_cls->slots = it->next;
+                    }
+                    if (it->next) it->next->prev = it->prev;
+                    if (it->prev) it->prev->next = it->next;
+                    s_cls->sl_curr--;
+                    status = MOVE_DONE;
+                } else {
+                    status = MOVE_BUSY;
+                }
+            } else if (refcount == 2) { /* item is linked but not busy */
+                if ((it->it_flags & ITEM_LINKED) != 0) {
+                    do_item_unlink_nolock(it, hash(ITEM_key(it), it->nkey, 0));
+                    status = MOVE_DONE;
+                } else {
+                    /* refcount == 1 + !ITEM_LINKED means the item is being
+                     * uploaded to, or was just unlinked but hasn't been freed
+                     * yet. Let it bleed off on its own and try again later */
+                    status = MOVE_BUSY;
+                }
+            } else {
+                if (settings.verbose > 2) {
+                    fprintf(stderr, "Slab reassign hit a busy item: refcount: %d (%d -> %d)\n",
+                        it->refcount, slab_rebal.s_clsid, slab_rebal.d_clsid);
+                }
+                status = MOVE_BUSY;
+            }
+        }
+
+        switch (status) {
+            case MOVE_DONE:
+                it->refcount = 0;
+                it->it_flags = 0;
+                it->slabs_clsid = 255;
+                break;
+            case MOVE_BUSY:
+                slab_rebal.busy_items++;
+                was_busy++;
+                refcount_decr(&it->refcount);
+                break;
+            case MOVE_PASS:
+                break;
+            default:
+                assert(false);
+                abort();
+        }
+
+        slab_rebal.slab_pos = (char *)slab_rebal.slab_pos + s_cls->size;
+        if (slab_rebal.slab_pos >= slab_rebal.slab_end)
+            break;
+    }
+
+    if (slab_rebal.slab_pos >= slab_rebal.slab_end) {
+        /* Some items were busy, start again from the top */
+        if (slab_rebal.busy_items) {
+            slab_rebal.slab_pos = slab_rebal.slab_start;
+            slab_rebal.busy_items = 0;
+        } else {
+            slab_rebal.done++;
+        }
+    }
+
+    pthread_mutex_unlock(&slabs_lock);
+    pthread_mutex_unlock(&cache_lock);
+
+    return was_busy;
+}
+
+static void slab_rebalance_finish(void) {
+    slabclass_t *s_cls;
+    slabclass_t *d_cls;
+
+    pthread_mutex_lock(&cache_lock);
+    pthread_mutex_lock(&slabs_lock);
+
+    s_cls = &slabclass[slab_rebal.s_clsid];
+    d_cls   = &slabclass[slab_rebal.d_clsid];
+
+    /* At this point the stolen slab is completely clear */
+    s_cls->slab_list[s_cls->killing - 1] =
+        s_cls->slab_list[s_cls->slabs - 1];
+    s_cls->slabs--;
+    s_cls->killing = 0;
+
+    memset(slab_rebal.slab_start, 0, (size_t)settings.item_size_max);
+
+    d_cls->slab_list[d_cls->slabs++] = slab_rebal.slab_start;
+    d_cls->end_page_ptr = slab_rebal.slab_start;
+    d_cls->end_page_free = d_cls->perslab;
+
+    slab_rebal.done       = 0;
+    slab_rebal.s_clsid    = 0;
+    slab_rebal.d_clsid    = 0;
+    slab_rebal.slab_start = NULL;
+    slab_rebal.slab_end   = NULL;
+    slab_rebal.slab_pos   = NULL;
+
+    slab_rebalance_signal = 0;
+
+    pthread_mutex_unlock(&slabs_lock);
+    pthread_mutex_unlock(&cache_lock);
+
+    STATS_LOCK();
+    stats.slab_reassign_running = false;
+    stats.slabs_moved++;
+    STATS_UNLOCK();
+
+    if (settings.verbose > 1) {
+        fprintf(stderr, "finished a slab move\n");
+    }
+}
+
+/* Return 1 means a decision was reached.
+ * Move to its own thread (created/destroyed as needed) once automover is more
+ * complex.
+ */
+static int slab_automove_decision(int *src, int *dst) {
+    static uint64_t evicted_old[POWER_LARGEST];
+    static unsigned int slab_zeroes[POWER_LARGEST];
+    static unsigned int slab_winner = 0;
+    static unsigned int slab_wins   = 0;
+    uint64_t evicted_new[POWER_LARGEST];
+    uint64_t evicted_diff = 0;
+    uint64_t evicted_max  = 0;
+    unsigned int highest_slab = 0;
+    unsigned int total_pages[POWER_LARGEST];
+    int i;
+    int source = 0;
+    int dest = 0;
+    static rel_time_t next_run;
+
+    /* Run less frequently than the slabmove tester. */
+    if (current_time >= next_run) {
+        next_run = current_time + 10;
+    } else {
+        return 0;
+    }
+
+    item_stats_evictions(evicted_new);
+    pthread_mutex_lock(&cache_lock);
+    for (i = POWER_SMALLEST; i < power_largest; i++) {
+        total_pages[i] = slabclass[i].slabs;
+    }
+    pthread_mutex_unlock(&cache_lock);
+
+    /* Find a candidate source; something with zero evicts 3+ times */
+    for (i = POWER_SMALLEST; i < power_largest; i++) {
+        evicted_diff = evicted_new[i] - evicted_old[i];
+        if (evicted_diff == 0 && total_pages[i] > 2) {
+            slab_zeroes[i]++;
+            if (source == 0 && slab_zeroes[i] >= 3)
+                source = i;
+        } else {
+            slab_zeroes[i] = 0;
+            if (evicted_diff > evicted_max) {
+                evicted_max = evicted_diff;
+                highest_slab = i;
+            }
+        }
+        evicted_old[i] = evicted_new[i];
+    }
+
+    /* Pick a valid destination */
+    if (slab_winner != 0 && slab_winner == highest_slab) {
+        slab_wins++;
+        if (slab_wins >= 3)
+            dest = slab_winner;
+    } else {
+        slab_wins = 1;
+        slab_winner = highest_slab;
+    }
+
+    if (source && dest) {
+        *src = source;
+        *dst = dest;
+        return 1;
+    }
+    return 0;
+}
+
+#ifndef __INTEL_COMPILER
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#endif
+/* Slab rebalancer thread.
+ * Does not use spinlocks since it is not timing sensitive. Burn less CPU and
+ * go to sleep if locks are contended
+ */
+static void *slab_maintenance_thread(void *arg) {
+    int was_busy = 0;
+    int src, dest;
+
+    while (do_run_slab_thread) {
+        if (slab_rebalance_signal == 1) {
+            if (slab_rebalance_start() < 0) {
+                /* Handle errors with more specifity as required. */
+                slab_rebalance_signal = 0;
+            }
+
+        } else if (slab_rebalance_signal && slab_rebal.slab_start != NULL) {
+            /* If we have a decision to continue, continue it */
+            was_busy = slab_rebalance_move();
+        } else if (settings.slab_automove && slab_automove_decision(&src, &dest) == 1) {
+            /* Blind to the return codes. It will retry on its own */
+            slabs_reassign(src, dest);
+        }
+
+        if (slab_rebal.done) {
+            slab_rebalance_finish();
+        }
+
+        /* Sleep a bit if no work to do, or waiting on busy objects */
+        if (was_busy || !slab_rebalance_signal)
+            sleep(1);
+    }
+    return NULL;
+}
+
+static enum reassign_result_type do_slabs_reassign(int src, int dst) {
+    if (slab_rebalance_signal != 0)
+        return REASSIGN_RUNNING;
+
+    if (src == dst)
+        return REASSIGN_SRC_DST_SAME;
+
+    if (src < POWER_SMALLEST || src > power_largest ||
+        dst < POWER_SMALLEST || dst > power_largest)
+        return REASSIGN_BADCLASS;
+
+    if (slabclass[src].slabs < 2)
+        return REASSIGN_NOSPARE;
+
+    if (slabclass[dst].end_page_ptr)
+        return REASSIGN_DEST_NOT_FULL;
+
+    if (slabclass[src].end_page_ptr)
+        return REASSIGN_SRC_NOT_SAFE;
+
+    slab_rebal.s_clsid = src;
+    slab_rebal.d_clsid = dst;
+
+    slab_rebalance_signal = 1;
+
+    return REASSIGN_OK;
+}
+
+enum reassign_result_type slabs_reassign(int src, int dst) {
+    enum reassign_result_type ret;
+    mutex_lock(&slabs_lock);
+    ret = do_slabs_reassign(src, dst);
+    pthread_mutex_unlock(&slabs_lock);
+    return ret;
+}
+
+static pthread_t maintenance_tid;
+
+int start_slab_maintenance_thread(void) {
+    int ret;
+    slab_rebalance_signal = 0;
+    slab_rebal.slab_start = NULL;
+    char *env = getenv("MEMCACHED_SLAB_BULK_CHECK");
+    if (env != NULL) {
+        slab_bulk_check = atoi(env);
+        if (slab_bulk_check == 0) {
+            slab_bulk_check = DEFAULT_SLAB_BULK_CHECK;
+        }
+    }
+    if ((ret = pthread_create(&maintenance_tid, NULL,
+                              slab_maintenance_thread, NULL)) != 0) {
+        fprintf(stderr, "Can't create thread: %s\n", strerror(ret));
+        return -1;
+    }
+    return 0;
+}
+
+void stop_slab_maintenance_thread(void) {
+    mutex_lock(&cache_lock);
+    do_run_slab_thread = 0;
+    pthread_cond_signal(&maintenance_cond);
+    pthread_mutex_unlock(&cache_lock);
+
+    /* Wait for the maintenance thread to stop */
+    pthread_join(maintenance_tid, NULL);
+}
diff --git a/memcached/slabs.h b/memcached/slabs.h
new file mode 100644 (file)
index 0000000..90e2bd8
--- /dev/null
@@ -0,0 +1,49 @@
+/* slabs memory allocation */
+#ifndef SLABS_H
+#define SLABS_H
+
+/** Init the subsystem. 1st argument is the limit on no. of bytes to allocate,
+    0 if no limit. 2nd argument is the growth factor; each slab will use a chunk
+    size equal to the previous slab's chunk size times this factor.
+    3rd argument specifies if the slab allocator should allocate all memory
+    up front (if true), or allocate memory in chunks as it is needed (if false)
+*/
+void slabs_init(const size_t limit, const double factor, const bool prealloc);
+
+
+/**
+ * Given object size, return id to use when allocating/freeing memory for object
+ * 0 means error: can't store such a large object
+ */
+
+#ifndef __INTEL_COMPILER
+#pragma GCC diagnostic ignored "-Wshadow"
+#endif
+unsigned int slabs_clsid(const size_t size);
+
+/** Allocate object of given length. 0 on error */ /*@null@*/
+void *slabs_alloc(const size_t size, unsigned int id);
+
+/** Free previously allocated object */
+void slabs_free(void *ptr, size_t size, unsigned int id);
+
+/** Adjust the stats for memory requested */
+void slabs_adjust_mem_requested(unsigned int id, size_t old, size_t ntotal);
+
+/** Return a datum for stats in binary protocol */
+bool get_stats(const char *stat_type, int nkey, ADD_STAT add_stats, void *c);
+
+/** Fill buffer with stats */ /*@null@*/
+void slabs_stats(ADD_STAT add_stats, void *c);
+
+int start_slab_maintenance_thread(void);
+void stop_slab_maintenance_thread(void);
+
+enum reassign_result_type {
+    REASSIGN_OK=0, REASSIGN_RUNNING, REASSIGN_BADCLASS, REASSIGN_NOSPARE,
+    REASSIGN_DEST_NOT_FULL, REASSIGN_SRC_NOT_SAFE, REASSIGN_SRC_DST_SAME
+};
+
+enum reassign_result_type slabs_reassign(int src, int dst);
+
+#endif
diff --git a/memcached/solaris_priv.c b/memcached/solaris_priv.c
new file mode 100644 (file)
index 0000000..349334e
--- /dev/null
@@ -0,0 +1,44 @@
+#include <stdlib.h>
+#include <priv.h>
+#include <stdio.h>
+#include "memcached.h"
+
+/*
+ * this section of code will drop all (Solaris) privileges including
+ * those normally granted to all userland process (basic privileges). The
+ * effect of this is that after running this code, the process will not able
+ * to fork(), exec(), etc.  See privileges(5) for more information.
+ */
+void drop_privileges(void) {
+   priv_set_t *privs = priv_str_to_set("basic", ",", NULL);
+
+   if (privs == NULL) {
+      perror("priv_str_to_set");
+      exit(EXIT_FAILURE);
+   }
+
+   (void)priv_delset(privs, PRIV_FILE_LINK_ANY);
+   (void)priv_delset(privs, PRIV_PROC_EXEC);
+   (void)priv_delset(privs, PRIV_PROC_FORK);
+   (void)priv_delset(privs, PRIV_PROC_INFO);
+   (void)priv_delset(privs, PRIV_PROC_SESSION);
+
+   if (setppriv(PRIV_SET, PRIV_PERMITTED, privs) != 0) {
+      perror("setppriv(PRIV_SET, PRIV_PERMITTED)");
+      exit(EXIT_FAILURE);
+   }
+
+   priv_emptyset(privs);
+
+   if (setppriv(PRIV_SET, PRIV_INHERITABLE, privs) != 0) {
+      perror("setppriv(PRIV_SET, PRIV_INHERITABLE)");
+      exit(EXIT_FAILURE);
+   }
+
+   if (setppriv(PRIV_SET, PRIV_LIMIT, privs) != 0) {
+      perror("setppriv(PRIV_SET, PRIV_LIMIT)");
+      exit(EXIT_FAILURE);
+   }
+
+   priv_freeset(privs);
+}
diff --git a/memcached/stats.c b/memcached/stats.c
new file mode 100644 (file)
index 0000000..5e6b040
--- /dev/null
@@ -0,0 +1,375 @@
+/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Detailed statistics management. For simple stats like total number of
+ * "get" requests, we use inline code in memcached.c and friends, but when
+ * stats detail mode is activated, the code here records more information.
+ *
+ * Author:
+ *   Steven Grimm <sgrimm@facebook.com>
+ */
+#include "memcached.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+/*
+ * Stats are tracked on the basis of key prefixes. This is a simple
+ * fixed-size hash of prefixes; we run the prefixes through the same
+ * CRC function used by the cache hashtable.
+ */
+typedef struct _prefix_stats PREFIX_STATS;
+struct _prefix_stats {
+    char         *prefix;
+    size_t        prefix_len;
+    uint64_t      num_gets;
+    uint64_t      num_sets;
+    uint64_t      num_deletes;
+    uint64_t      num_hits;
+    PREFIX_STATS *next;
+};
+
+#define PREFIX_HASH_SIZE 256
+
+static PREFIX_STATS *prefix_stats[PREFIX_HASH_SIZE];
+static int num_prefixes = 0;
+static int total_prefix_size = 0;
+
+void stats_prefix_init() {
+    memset(prefix_stats, 0, sizeof(prefix_stats));
+}
+
+/*
+ * Cleans up all our previously collected stats. NOTE: the stats lock is
+ * assumed to be held when this is called.
+ */
+void stats_prefix_clear() {
+    int i;
+
+    for (i = 0; i < PREFIX_HASH_SIZE; i++) {
+        PREFIX_STATS *cur, *next;
+        for (cur = prefix_stats[i]; cur != NULL; cur = next) {
+            next = cur->next;
+            free(cur->prefix);
+            free(cur);
+        }
+        prefix_stats[i] = NULL;
+    }
+    num_prefixes = 0;
+    total_prefix_size = 0;
+}
+
+/*
+ * Returns the stats structure for a prefix, creating it if it's not already
+ * in the list.
+ */
+/*@null@*/
+static PREFIX_STATS *stats_prefix_find(const char *key, const size_t nkey) {
+    PREFIX_STATS *pfs;
+    uint32_t hashval;
+    size_t length;
+    bool bailout = true;
+
+    assert(key != NULL);
+
+    for (length = 0; length < nkey && key[length] != '\0'; length++) {
+        if (key[length] == settings.prefix_delimiter) {
+            bailout = false;
+            break;
+        }
+    }
+
+    if (bailout) {
+        return NULL;
+    }
+
+    hashval = hash(key, length, 0) % PREFIX_HASH_SIZE;
+
+    for (pfs = prefix_stats[hashval]; NULL != pfs; pfs = pfs->next) {
+        if (strncmp(pfs->prefix, key, length) == 0)
+            return pfs;
+    }
+
+    pfs = calloc(sizeof(PREFIX_STATS), 1);
+    if (NULL == pfs) {
+        perror("Can't allocate space for stats structure: calloc");
+        return NULL;
+    }
+
+    pfs->prefix = malloc(length + 1);
+    if (NULL == pfs->prefix) {
+        perror("Can't allocate space for copy of prefix: malloc");
+        free(pfs);
+        return NULL;
+    }
+
+    strncpy(pfs->prefix, key, length);
+    pfs->prefix[length] = '\0';      /* because strncpy() sucks */
+    pfs->prefix_len = length;
+
+    pfs->next = prefix_stats[hashval];
+    prefix_stats[hashval] = pfs;
+
+    num_prefixes++;
+    total_prefix_size += length;
+
+    return pfs;
+}
+
+/*
+ * Records a "get" of a key.
+ */
+void stats_prefix_record_get(const char *key, const size_t nkey, const bool is_hit) {
+    PREFIX_STATS *pfs;
+
+    STATS_LOCK();
+    pfs = stats_prefix_find(key, nkey);
+    if (NULL != pfs) {
+        pfs->num_gets++;
+        if (is_hit) {
+            pfs->num_hits++;
+        }
+    }
+    STATS_UNLOCK();
+}
+
+/*
+ * Records a "delete" of a key.
+ */
+void stats_prefix_record_delete(const char *key, const size_t nkey) {
+    PREFIX_STATS *pfs;
+
+    STATS_LOCK();
+    pfs = stats_prefix_find(key, nkey);
+    if (NULL != pfs) {
+        pfs->num_deletes++;
+    }
+    STATS_UNLOCK();
+}
+
+/*
+ * Records a "set" of a key.
+ */
+void stats_prefix_record_set(const char *key, const size_t nkey) {
+    PREFIX_STATS *pfs;
+
+    STATS_LOCK();
+    pfs = stats_prefix_find(key, nkey);
+    if (NULL != pfs) {
+        pfs->num_sets++;
+    }
+    STATS_UNLOCK();
+}
+
+/*
+ * Returns stats in textual form suitable for writing to a client.
+ */
+/*@null@*/
+char *stats_prefix_dump(int *length) {
+    const char *format = "PREFIX %s get %llu hit %llu set %llu del %llu\r\n";
+    PREFIX_STATS *pfs;
+    char *buf;
+    int i, pos;
+    size_t size = 0, written = 0, total_written = 0;
+
+    /*
+     * Figure out how big the buffer needs to be. This is the sum of the
+     * lengths of the prefixes themselves, plus the size of one copy of
+     * the per-prefix output with 20-digit values for all the counts,
+     * plus space for the "END" at the end.
+     */
+    STATS_LOCK();
+    size = strlen(format) + total_prefix_size +
+           num_prefixes * (strlen(format) - 2 /* %s */
+                           + 4 * (20 - 4)) /* %llu replaced by 20-digit num */
+                           + sizeof("END\r\n");
+    buf = malloc(size);
+    if (NULL == buf) {
+        perror("Can't allocate stats response: malloc");
+        STATS_UNLOCK();
+        return NULL;
+    }
+
+    pos = 0;
+    for (i = 0; i < PREFIX_HASH_SIZE; i++) {
+        for (pfs = prefix_stats[i]; NULL != pfs; pfs = pfs->next) {
+            written = snprintf(buf + pos, size-pos, format,
+                           pfs->prefix, pfs->num_gets, pfs->num_hits,
+                           pfs->num_sets, pfs->num_deletes);
+            pos += written;
+            total_written += written;
+            assert(total_written < size);
+        }
+    }
+
+    STATS_UNLOCK();
+    memcpy(buf + pos, "END\r\n", 6);
+
+    *length = pos + 5;
+    return buf;
+}
+
+
+#ifdef UNIT_TEST
+
+/****************************************************************************
+      To run unit tests, compile with $(CC) -DUNIT_TEST stats.c assoc.o
+      (need assoc.o to get the hash() function).
+****************************************************************************/
+
+struct settings settings;
+
+static char *current_test = "";
+static int test_count = 0;
+static int fail_count = 0;
+
+static void fail(char *what) { printf("\tFAIL: %s\n", what); fflush(stdout); fail_count++; }
+static void test_equals_int(char *what, int a, int b) { test_count++; if (a != b) fail(what); }
+static void test_equals_ptr(char *what, void *a, void *b) { test_count++; if (a != b) fail(what); }
+static void test_equals_str(char *what, const char *a, const char *b) { test_count++; if (strcmp(a, b)) fail(what); }
+static void test_equals_ull(char *what, uint64_t a, uint64_t b) { test_count++; if (a != b) fail(what); }
+static void test_notequals_ptr(char *what, void *a, void *b) { test_count++; if (a == b) fail(what); }
+static void test_notnull_ptr(char *what, void *a) { test_count++; if (NULL == a) fail(what); }
+
+static void test_prefix_find() {
+    PREFIX_STATS *pfs1, *pfs2;
+
+    pfs1 = stats_prefix_find("abc");
+    test_notnull_ptr("initial prefix find", pfs1);
+    test_equals_ull("request counts", 0ULL,
+        pfs1->num_gets + pfs1->num_sets + pfs1->num_deletes + pfs1->num_hits);
+    pfs2 = stats_prefix_find("abc");
+    test_equals_ptr("find of same prefix", pfs1, pfs2);
+    pfs2 = stats_prefix_find("abc:");
+    test_equals_ptr("find of same prefix, ignoring delimiter", pfs1, pfs2);
+    pfs2 = stats_prefix_find("abc:d");
+    test_equals_ptr("find of same prefix, ignoring extra chars", pfs1, pfs2);
+    pfs2 = stats_prefix_find("xyz123");
+    test_notequals_ptr("find of different prefix", pfs1, pfs2);
+    pfs2 = stats_prefix_find("ab:");
+    test_notequals_ptr("find of shorter prefix", pfs1, pfs2);
+}
+
+static void test_prefix_record_get() {
+    PREFIX_STATS *pfs;
+
+    stats_prefix_record_get("abc:123", 0);
+    pfs = stats_prefix_find("abc:123");
+    test_equals_ull("get count after get #1", 1, pfs->num_gets);
+    test_equals_ull("hit count after get #1", 0, pfs->num_hits);
+    stats_prefix_record_get("abc:456", 0);
+    test_equals_ull("get count after get #2", 2, pfs->num_gets);
+    test_equals_ull("hit count after get #2", 0, pfs->num_hits);
+    stats_prefix_record_get("abc:456", 1);
+    test_equals_ull("get count after get #3", 3, pfs->num_gets);
+    test_equals_ull("hit count after get #3", 1, pfs->num_hits);
+    stats_prefix_record_get("def:", 1);
+    test_equals_ull("get count after get #4", 3, pfs->num_gets);
+    test_equals_ull("hit count after get #4", 1, pfs->num_hits);
+}
+
+static void test_prefix_record_delete() {
+    PREFIX_STATS *pfs;
+
+    stats_prefix_record_delete("abc:123");
+    pfs = stats_prefix_find("abc:123");
+    test_equals_ull("get count after delete #1", 0, pfs->num_gets);
+    test_equals_ull("hit count after delete #1", 0, pfs->num_hits);
+    test_equals_ull("delete count after delete #1", 1, pfs->num_deletes);
+    test_equals_ull("set count after delete #1", 0, pfs->num_sets);
+    stats_prefix_record_delete("def:");
+    test_equals_ull("delete count after delete #2", 1, pfs->num_deletes);
+}
+
+static void test_prefix_record_set() {
+    PREFIX_STATS *pfs;
+
+    stats_prefix_record_set("abc:123");
+    pfs = stats_prefix_find("abc:123");
+    test_equals_ull("get count after set #1", 0, pfs->num_gets);
+    test_equals_ull("hit count after set #1", 0, pfs->num_hits);
+    test_equals_ull("delete count after set #1", 0, pfs->num_deletes);
+    test_equals_ull("set count after set #1", 1, pfs->num_sets);
+    stats_prefix_record_delete("def:");
+    test_equals_ull("set count after set #2", 1, pfs->num_sets);
+}
+
+static void test_prefix_dump() {
+    int hashval = hash("abc", 3, 0) % PREFIX_HASH_SIZE;
+    char tmp[500];
+    char *expected;
+    int keynum;
+    int length;
+
+    test_equals_str("empty stats", "END\r\n", stats_prefix_dump(&length));
+    test_equals_int("empty stats length", 5, length);
+    stats_prefix_record_set("abc:123");
+    expected = "PREFIX abc get 0 hit 0 set 1 del 0\r\nEND\r\n";
+    test_equals_str("stats after set", expected, stats_prefix_dump(&length));
+    test_equals_int("stats length after set", strlen(expected), length);
+    stats_prefix_record_get("abc:123", 0);
+    expected = "PREFIX abc get 1 hit 0 set 1 del 0\r\nEND\r\n";
+    test_equals_str("stats after get #1", expected, stats_prefix_dump(&length));
+    test_equals_int("stats length after get #1", strlen(expected), length);
+    stats_prefix_record_get("abc:123", 1);
+    expected = "PREFIX abc get 2 hit 1 set 1 del 0\r\nEND\r\n";
+    test_equals_str("stats after get #2", expected, stats_prefix_dump(&length));
+    test_equals_int("stats length after get #2", strlen(expected), length);
+    stats_prefix_record_delete("abc:123");
+    expected = "PREFIX abc get 2 hit 1 set 1 del 1\r\nEND\r\n";
+    test_equals_str("stats after del #1", expected, stats_prefix_dump(&length));
+    test_equals_int("stats length after del #1", strlen(expected), length);
+
+    /* The order of results might change if we switch hash functions. */
+    stats_prefix_record_delete("def:123");
+    expected = "PREFIX abc get 2 hit 1 set 1 del 1\r\n"
+               "PREFIX def get 0 hit 0 set 0 del 1\r\n"
+               "END\r\n";
+    test_equals_str("stats after del #2", expected, stats_prefix_dump(&length));
+    test_equals_int("stats length after del #2", strlen(expected), length);
+
+    /* Find a key that hashes to the same bucket as "abc" */
+    for (keynum = 0; keynum < PREFIX_HASH_SIZE * 100; keynum++) {
+        snprintf(tmp, sizeof(tmp), "%d", keynum);
+        if (hashval == hash(tmp, strlen(tmp), 0) % PREFIX_HASH_SIZE) {
+            break;
+        }
+    }
+    stats_prefix_record_set(tmp);
+    snprintf(tmp, sizeof(tmp),
+             "PREFIX %d get 0 hit 0 set 1 del 0\r\n"
+             "PREFIX abc get 2 hit 1 set 1 del 1\r\n"
+             "PREFIX def get 0 hit 0 set 0 del 1\r\n"
+             "END\r\n", keynum);
+    test_equals_str("stats with two stats in one bucket",
+                    tmp, stats_prefix_dump(&length));
+    test_equals_int("stats length with two stats in one bucket",
+                    strlen(tmp), length);
+}
+
+static void run_test(char *what, void (*func)(void)) {
+    current_test = what;
+    test_count = fail_count = 0;
+    puts(what);
+    fflush(stdout);
+
+    stats_prefix_clear();
+    (func)();
+    printf("\t%d / %d pass\n", (test_count - fail_count), test_count);
+}
+
+/* In case we're compiled in thread mode */
+void mt_stats_lock() { }
+void mt_stats_unlock() { }
+
+main(int argc, char **argv) {
+    stats_prefix_init();
+    settings.prefix_delimiter = ':';
+    run_test("stats_prefix_find", test_prefix_find);
+    run_test("stats_prefix_record_get", test_prefix_record_get);
+    run_test("stats_prefix_record_delete", test_prefix_record_delete);
+    run_test("stats_prefix_record_set", test_prefix_record_set);
+    run_test("stats_prefix_dump", test_prefix_dump);
+}
+
+#endif
diff --git a/memcached/stats.h b/memcached/stats.h
new file mode 100644 (file)
index 0000000..4a27ae9
--- /dev/null
@@ -0,0 +1,8 @@
+/* stats */
+void stats_prefix_init(void);
+void stats_prefix_clear(void);
+void stats_prefix_record_get(const char *key, const size_t nkey, const bool is_hit);
+void stats_prefix_record_delete(const char *key, const size_t nkey);
+void stats_prefix_record_set(const char *key, const size_t nkey);
+/*@null@*/
+char *stats_prefix_dump(int *length);
diff --git a/memcached/t/00-startup.t b/memcached/t/00-startup.t
new file mode 100755 (executable)
index 0000000..26967d0
--- /dev/null
@@ -0,0 +1,82 @@
+#!/usr/bin/perl
+
+use strict;
+use Test::More tests => 18;
+use FindBin qw($Bin);
+use lib "$Bin/lib";
+use MemcachedTest;
+
+eval {
+    my $server = new_memcached();
+    ok($server, "started the server");
+};
+is($@, '', 'Basic startup works');
+
+eval {
+    my $server = new_memcached("-l fooble");
+};
+ok($@, "Died with illegal -l args");
+
+eval {
+    my $server = new_memcached("-l 127.0.0.1");
+};
+is($@,'', "-l 127.0.0.1 works");
+
+eval {
+    my $server = new_memcached('-C');
+    my $stats = mem_stats($server->sock, 'settings');
+    is('no', $stats->{'cas_enabled'});
+};
+is($@, '', "-C works");
+
+eval {
+    my $server = new_memcached('-b 8675');
+    my $stats = mem_stats($server->sock, 'settings');
+    is('8675', $stats->{'tcp_backlog'});
+};
+is($@, '', "-b works");
+
+foreach my $val ('auto', 'ascii') {
+    eval {
+        my $server = new_memcached("-B $val");
+        my $stats = mem_stats($server->sock, 'settings');
+        ok($stats->{'binding_protocol'} =~ /$val/, "$val works");
+    };
+    is($@, '', "$val works");
+}
+
+# For the binary test, we just verify it starts since we don't have an easy bin client.
+eval {
+    my $server = new_memcached("-B binary");
+};
+is($@, '', "binary works");
+
+eval {
+    my $server = new_memcached("-vv -B auto");
+};
+is($@, '', "auto works");
+
+eval {
+    my $server = new_memcached("-vv -B ascii");
+};
+is($@, '', "ascii works");
+
+
+# For the binary test, we just verify it starts since we don't have an easy bin client.
+eval {
+    my $server = new_memcached("-vv -B binary");
+};
+is($@, '', "binary works");
+
+
+# Should blow up with something invalid.
+eval {
+    my $server = new_memcached("-B http");
+};
+ok($@, "Died with illegal -B arg.");
+
+# Should not allow -t 0
+eval {
+    my $server = new_memcached("-t 0");
+};
+ok($@, "Died with illegal 0 thread count");
diff --git a/memcached/t/64bit.t b/memcached/t/64bit.t
new file mode 100755 (executable)
index 0000000..425ff8a
--- /dev/null
@@ -0,0 +1,44 @@
+#!/usr/bin/perl
+
+use strict;
+use Test::More;
+use FindBin qw($Bin);
+use lib "$Bin/lib";
+use MemcachedTest;
+
+$ENV{T_MEMD_INITIAL_MALLOC} = "4294967328"; # 2**32 + 32 , just over 4GB
+$ENV{T_MEMD_SLABS_ALLOC}    = 0;  # don't preallocate slabs
+
+my $server = new_memcached("-m 4098 -M");
+my $sock = $server->sock;
+
+my ($stats, $slabs) = @_;
+
+$stats = mem_stats($sock);
+
+if ($stats->{'pointer_size'} eq "32") {
+    plan skip_all => 'Skipping 64-bit tests on 32-bit build';
+    exit 0;
+} else {
+    plan tests => 6;
+}
+
+is($stats->{'pointer_size'}, 64, "is 64 bit");
+is($stats->{'limit_maxbytes'}, "4297064448", "max bytes is 4098 MB");
+
+$slabs = mem_stats($sock, 'slabs');
+is($slabs->{'total_malloced'}, "4294967328", "expected (faked) value of total_malloced");
+is($slabs->{'active_slabs'}, 0, "no active slabs");
+
+my $hit_limit = 0;
+for (1..5) {
+    my $size = 400 * 1024;
+    my $data = "a" x $size;
+    print $sock "set big$_ 0 0 $size\r\n$data\r\n";
+    my $res = <$sock>;
+    $hit_limit = 1 if $res ne "STORED\r\n";
+}
+ok($hit_limit, "hit size limit");
+
+$slabs = mem_stats($sock, 'slabs');
+is($slabs->{'active_slabs'}, 1, "1 active slab");
diff --git a/memcached/t/binary-get.t b/memcached/t/binary-get.t
new file mode 100755 (executable)
index 0000000..f57a8a3
--- /dev/null
@@ -0,0 +1,23 @@
+#!/usr/bin/perl
+
+use strict;
+use Test::More tests => 8;
+use FindBin qw($Bin);
+use lib "$Bin/lib";
+use MemcachedTest;
+
+my $server = new_memcached();
+my $sock = $server->sock;
+
+my $count = 1;
+
+foreach my $blob ("mooo\0", "mumble\0\0\0\0\r\rblarg", "\0", "\r") {
+    my $key = "foo$count";
+    my $len = length($blob);
+    print "len is $len\n";
+    print $sock "set $key 0 0 $len\r\n$blob\r\n";
+    is(scalar <$sock>, "STORED\r\n", "stored $key");
+    mem_get_is($sock, $key, $blob);
+    $count++;
+}
+
diff --git a/memcached/t/binary-sasl.t b/memcached/t/binary-sasl.t
new file mode 100755 (executable)
index 0000000..69a05c2
--- /dev/null
@@ -0,0 +1,632 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use Cwd;
+use FindBin qw($Bin);
+use lib "$Bin/lib";
+use MemcachedTest;
+
+my $supports_sasl = supports_sasl();
+
+use Test::More;
+
+if (supports_sasl()) {
+    if ($ENV{'RUN_SASL_TESTS'}) {
+        plan tests => 25;
+    } else {
+        plan skip_all => 'Skipping SASL tests';
+        exit 0;
+    }
+} else {
+    plan tests => 1;
+    eval {
+        my $server = new_memcached("-S");
+    };
+    ok($@, "Died with illegal -S args when SASL is not supported.");
+    exit 0;
+}
+
+eval {
+    my $server = new_memcached("-S -B auto");
+};
+ok($@, "SASL shouldn't be used with protocol auto negotiate");
+
+eval {
+    my $server = new_memcached("-S -B ascii");
+};
+ok($@, "SASL isn't implemented in the ascii protocol");
+
+eval {
+    my $server = new_memcached("-S -B binary -B ascii");
+};
+ok($@, "SASL isn't implemented in the ascii protocol");
+
+# Based almost 100% off testClient.py which is:
+# Copyright (c) 2007  Dustin Sallings <dustin@spy.net>
+
+# Command constants
+use constant CMD_GET        => 0x00;
+use constant CMD_SET        => 0x01;
+use constant CMD_ADD        => 0x02;
+use constant CMD_REPLACE    => 0x03;
+use constant CMD_DELETE     => 0x04;
+use constant CMD_INCR       => 0x05;
+use constant CMD_DECR       => 0x06;
+use constant CMD_QUIT       => 0x07;
+use constant CMD_FLUSH      => 0x08;
+use constant CMD_GETQ       => 0x09;
+use constant CMD_NOOP       => 0x0A;
+use constant CMD_VERSION    => 0x0B;
+use constant CMD_GETK       => 0x0C;
+use constant CMD_GETKQ      => 0x0D;
+use constant CMD_APPEND     => 0x0E;
+use constant CMD_PREPEND    => 0x0F;
+use constant CMD_STAT       => 0x10;
+use constant CMD_SETQ       => 0x11;
+use constant CMD_ADDQ       => 0x12;
+use constant CMD_REPLACEQ   => 0x13;
+use constant CMD_DELETEQ    => 0x14;
+use constant CMD_INCREMENTQ => 0x15;
+use constant CMD_DECREMENTQ => 0x16;
+use constant CMD_QUITQ      => 0x17;
+use constant CMD_FLUSHQ     => 0x18;
+use constant CMD_APPENDQ    => 0x19;
+use constant CMD_PREPENDQ   => 0x1A;
+
+use constant CMD_SASL_LIST_MECHS    => 0x20;
+use constant CMD_SASL_AUTH          => 0x21;
+use constant CMD_SASL_STEP          => 0x22;
+use constant ERR_AUTH_ERROR   => 0x20;
+
+
+# REQ and RES formats are divided even though they currently share
+# the same format, since they _could_ differ in the future.
+use constant REQ_PKT_FMT      => "CCnCCnNNNN";
+use constant RES_PKT_FMT      => "CCnCCnNNNN";
+use constant INCRDECR_PKT_FMT => "NNNNN";
+use constant MIN_RECV_BYTES   => length(pack(RES_PKT_FMT));
+use constant REQ_MAGIC        => 0x80;
+use constant RES_MAGIC        => 0x81;
+
+my $pwd=getcwd;
+$ENV{'SASL_CONF_PATH'} = "$pwd/t/sasl";
+
+my $server = new_memcached('-B binary -S ');
+
+my $mc = MC::Client->new;
+
+my $check = sub {
+    my ($key, $orig_val) = @_;
+    my ($status, $val, $cas) = $mc->get($key);
+
+    if ($val =~ /^\d+$/) {
+        cmp_ok($val,'==', $orig_val, "$val = $orig_val");
+    }
+    else {
+        cmp_ok($val, 'eq', $orig_val, "$val = $orig_val");
+    }
+};
+
+my $set = sub {
+    my ($key, $orig_value, $exp) = @_;
+    $exp = defined $exp ? $exp : 0;
+    my ($status, $rv)= $mc->set($key, $orig_value, $exp);
+    $check->($key, $orig_value);
+};
+
+my $empty = sub {
+    my $key = shift;
+    my ($status,$rv) =()= eval { $mc->get($key) };
+    #if ($status == ERR_AUTH_ERROR) {
+    #    ok($@->auth_error, "Not authorized to connect");
+    #}
+    #else {
+    #    ok($@->not_found, "We got a not found error when we expected one");
+    #}
+    if ($status) {
+        ok($@->not_found, "We got a not found error when we expected one");
+    }
+};
+
+my $delete = sub {
+    my ($key, $when) = @_;
+    $mc->delete($key, $when);
+    $empty->($key);
+};
+
+# BEGIN THE TEST
+ok($server, "started the server");
+
+my $v = $mc->version;
+ok(defined $v && length($v), "Proper version: $v");
+
+# list mechs
+my $mechs= $mc->list_mechs();
+Test::More::cmp_ok($mechs, 'eq', 'CRAM-MD5 PLAIN', "list_mechs $mechs");
+
+# this should fail, not authenticated
+{
+    my ($status, $val)= $mc->set('x', "somevalue");
+    ok($status, "this fails to authenticate");
+    cmp_ok($status,'==',ERR_AUTH_ERROR, "error code matches");
+}
+$empty->('x');
+{
+    my $mc = MC::Client->new;
+    my ($status, $val) = $mc->delete('x');
+    ok($status, "this fails to authenticate");
+    cmp_ok($status,'==',ERR_AUTH_ERROR, "error code matches");
+}
+$empty->('x');
+{
+    my $mc = MC::Client->new;
+    my ($status, $val)= $mc->set('x', "somevalue");
+    ok($status, "this fails to authenticate");
+    cmp_ok($status,'==',ERR_AUTH_ERROR, "error code matches");
+}
+$empty->('x');
+{
+    my $mc = MC::Client->new;
+    my ($status, $val)=  $mc->flush('x');
+    ok($status, "this fails to authenticate");
+    cmp_ok($status,'==',ERR_AUTH_ERROR, "error code matches");
+}
+$empty->('x');
+
+# Build the auth DB for testing.
+my $sasldb = '/tmp/test-memcached.sasldb';
+unlink $sasldb;
+
+my $saslpasswd_path;
+for my $dir (split(/:/, $ENV{PATH}),
+             "/usr/bin",
+             "/usr/sbin",
+             "/usr/local/bin",
+             "/usr/local/sbin",
+    ) {
+    my $exe = $dir . '/saslpasswd2';
+    if (-x $exe) {
+        $saslpasswd_path = $exe;
+        last;
+    }
+}
+
+system("echo testpass | $saslpasswd_path -a memcached -c -p testuser");
+
+$mc = MC::Client->new;
+
+# Attempt a bad auth mech.
+is ($mc->authenticate('testuser', 'testpass', "X" x 40), 0x4, "bad mech");
+
+# Attempt bad authentication.
+is ($mc->authenticate('testuser', 'wrongpassword'), 0x20, "bad auth");
+
+# Now try good authentication and make the tests work.
+is ($mc->authenticate('testuser', 'testpass'), 0, "authenticated");
+# these should work
+{
+    my ($status, $val)= $mc->set('x', "somevalue");
+    ok(! $status);
+}
+$check->('x','somevalue');
+
+{
+    my ($status, $val)= $mc->delete('x');
+    ok(! $status);
+}
+$empty->('x');
+
+{
+    my ($status, $val)= $mc->set('x', "somevalue");
+    ok(! $status);
+}
+$check->('x','somevalue');
+
+{
+    my ($status, $val)=  $mc->flush('x');
+    ok(! $status);
+}
+$empty->('x');
+
+# check the SASL stats, make sure they track things correctly
+# note: the enabled or not is presence checked in stats.t
+
+# while authenticated, get current counter
+#
+# My initial approach was going to be to get current counts, reauthenticate
+# and fail, followed by a reauth successfully so I'd know what happened.
+# Reauthentication is currently unsupported, so it doesn't work that way at the
+# moment.  Adding tests may break this.
+
+{
+    my %stats = $mc->stats('');
+    is ($stats{'auth_cmds'}, 2, "auth commands counted");
+    is ($stats{'auth_errors'}, 1, "auth errors correct");
+}
+
+
+# Along with the assertion added to the code to verify we're staying
+# within bounds when we do a stats detail dump (detail turned on at
+# the top).
+# my %stats = $mc->stats('detail dump');
+
+# ######################################################################
+# Test ends around here.
+# ######################################################################
+
+package MC::Client;
+
+use strict;
+use warnings;
+use fields qw(socket);
+use IO::Socket::INET;
+
+use constant ERR_AUTH_ERROR   => 0x20;
+
+sub new {
+    my $self = shift;
+    my ($s) = @_;
+    $s = $server unless defined $s;
+    my $sock = $s->sock;
+    $self = fields::new($self);
+    $self->{socket} = $sock;
+    return $self;
+}
+
+sub authenticate {
+    my ($self, $user, $pass, $mech)= @_;
+    $mech ||= 'PLAIN';
+    my $buf = sprintf("%c%s%c%s", 0, $user, 0, $pass);
+    my ($status, $rv, undef) = $self->_do_command(::CMD_SASL_AUTH, $mech, $buf, '');
+    return $status;
+}
+sub list_mechs {
+    my ($self)= @_;
+    my ($status, $rv, undef) = $self->_do_command(::CMD_SASL_LIST_MECHS, '', '', '');
+    return join(" ", sort(split(/\s+/, $rv)));
+}
+
+sub build_command {
+    my $self = shift;
+    die "Not enough args to send_command" unless @_ >= 4;
+    my ($cmd, $key, $val, $opaque, $extra_header, $cas) = @_;
+
+    $extra_header = '' unless defined $extra_header;
+    my $keylen    = length($key);
+    my $vallen    = length($val);
+    my $extralen  = length($extra_header);
+    my $datatype  = 0;  # field for future use
+    my $reserved  = 0;  # field for future use
+    my $totallen  = $keylen + $vallen + $extralen;
+    my $ident_hi  = 0;
+    my $ident_lo  = 0;
+
+    if ($cas) {
+        $ident_hi = int($cas / 2 ** 32);
+        $ident_lo = int($cas % 2 ** 32);
+    }
+
+    my $msg = pack(::REQ_PKT_FMT, ::REQ_MAGIC, $cmd, $keylen, $extralen,
+                   $datatype, $reserved, $totallen, $opaque, $ident_hi,
+                   $ident_lo);
+    my $full_msg = $msg . $extra_header . $key . $val;
+    return $full_msg;
+}
+
+sub send_command {
+    my $self = shift;
+    die "Not enough args to send_command" unless @_ >= 4;
+    my ($cmd, $key, $val, $opaque, $extra_header, $cas) = @_;
+
+    my $full_msg = $self->build_command($cmd, $key, $val, $opaque, $extra_header, $cas);
+
+    my $sent = $self->{socket}->send($full_msg);
+    die("Send failed:  $!") unless $sent;
+    if($sent != length($full_msg)) {
+        die("only sent $sent of " . length($full_msg) . " bytes");
+    }
+}
+
+sub flush_socket {
+    my $self = shift;
+    $self->{socket}->flush;
+}
+
+# Send a silent command and ensure it doesn't respond.
+sub send_silent {
+    my $self = shift;
+    die "Not enough args to send_silent" unless @_ >= 4;
+    my ($cmd, $key, $val, $opaque, $extra_header, $cas) = @_;
+
+    $self->send_command($cmd, $key, $val, $opaque, $extra_header, $cas);
+    $self->send_command(::CMD_NOOP, '', '', $opaque + 1);
+
+    my ($ropaque, $status, $data) = $self->_handle_single_response;
+    Test::More::is($ropaque, $opaque + 1);
+}
+
+sub silent_mutation {
+    my $self = shift;
+    my ($cmd, $key, $value) = @_;
+
+    $empty->($key);
+    my $extra = pack "NN", 82, 0;
+    $mc->send_silent($cmd, $key, $value, 7278552, $extra, 0);
+    $check->($key, $value);
+}
+
+sub _handle_single_response {
+    my $self = shift;
+    my $myopaque = shift;
+
+    $self->{socket}->recv(my $response, ::MIN_RECV_BYTES);
+
+    my ($magic, $cmd, $keylen, $extralen, $datatype, $status, $remaining,
+        $opaque, $ident_hi, $ident_lo) = unpack(::RES_PKT_FMT, $response);
+
+    return ($opaque, '', '', '', 0) if not defined $remaining;
+    return ($opaque, '', '', '', 0) if ($remaining == 0);
+
+    # fetch the value
+    my $rv="";
+    while($remaining - length($rv) > 0) {
+        $self->{socket}->recv(my $buf, $remaining - length($rv));
+        $rv .= $buf;
+    }
+    if(length($rv) != $remaining) {
+        my $found = length($rv);
+        die("Expected $remaining bytes, got $found");
+    }
+
+    my $cas = ($ident_hi * 2 ** 32) + $ident_lo;
+
+    #if ($status) {
+        #die MC::Error->new($status, $rv);
+    #}
+
+    return ($opaque, $status, $rv, $cas, $keylen);
+}
+
+sub _do_command {
+    my $self = shift;
+    die unless @_ >= 3;
+    my ($cmd, $key, $val, $extra_header, $cas) = @_;
+
+    $extra_header = '' unless defined $extra_header;
+    my $opaque = int(rand(2**32));
+    $self->send_command($cmd, $key, $val, $opaque, $extra_header, $cas);
+    my (undef, $status, $rv, $rcas) = $self->_handle_single_response($opaque);
+    return ($status, $rv, $rcas);
+}
+
+sub _incrdecr_header {
+    my $self = shift;
+    my ($amt, $init, $exp) = @_;
+
+    my $amt_hi = int($amt / 2 ** 32);
+    my $amt_lo = int($amt % 2 ** 32);
+
+    my $init_hi = int($init / 2 ** 32);
+    my $init_lo = int($init % 2 ** 32);
+
+    my $extra_header = pack(::INCRDECR_PKT_FMT, $amt_hi, $amt_lo, $init_hi,
+                            $init_lo, $exp);
+
+    return $extra_header;
+}
+
+sub _incrdecr {
+    my $self = shift;
+    my ($cmd, $key, $amt, $init, $exp) = @_;
+
+    my ($status, $data, undef) = $self->_do_command($cmd, $key, '',
+                                           $self->_incrdecr_header($amt, $init, $exp));
+
+    my $header = substr $data, 0, 8, '';
+    my ($resp_hi, $resp_lo) = unpack "NN", $header;
+    my $resp = ($resp_hi * 2 ** 32) + $resp_lo;
+
+    return $resp;
+}
+
+sub silent_incrdecr {
+    my $self = shift;
+    my ($cmd, $key, $amt, $init, $exp) = @_;
+    my $opaque = 8275753;
+
+    $mc->send_silent($cmd, $key, '', $opaque,
+                     $mc->_incrdecr_header($amt, $init, $exp));
+}
+
+sub stats {
+    my $self = shift;
+    my $key  = shift;
+    my $cas = 0;
+    my $opaque = int(rand(2**32));
+    $self->send_command(::CMD_STAT, $key, '', $opaque, '', $cas);
+
+    my %rv = ();
+    my $found_key = '';
+    my $found_val = '';
+    my $status= 0;
+    do {
+        my ($op, $status, $data, $cas, $keylen) = $self->_handle_single_response($opaque);
+        if ($keylen > 0) {
+            $found_key = substr($data, 0, $keylen);
+            $found_val = substr($data, $keylen);
+            $rv{$found_key} = $found_val;
+        } else {
+            $found_key = '';
+        }
+    } while($found_key ne '');
+    return %rv;
+}
+
+sub get {
+    my $self = shift;
+    my $key  = shift;
+    my ($status, $rv, $cas) = $self->_do_command(::CMD_GET, $key, '', '');
+
+    my $header = substr $rv, 0, 4, '';
+    my $flags  = unpack("N", $header);
+
+    return ($status, $rv);
+}
+
+sub get_multi {
+    my $self = shift;
+    my @keys = @_;
+
+    for (my $i = 0; $i < @keys; $i++) {
+        $self->send_command(::CMD_GETQ, $keys[$i], '', $i, '', 0);
+    }
+
+    my $terminal = @keys + 10;
+    $self->send_command(::CMD_NOOP, '', '', $terminal);
+
+    my %return;
+    my $status = 0;
+    while (1) {
+        my ($opaque, $status, $data) = $self->_handle_single_response;
+        last if $opaque == $terminal;
+
+        my $header = substr $data, 0, 4, '';
+        my $flags  = unpack("N", $header);
+
+        $return{$keys[$opaque]} = [$flags, $data];
+    }
+
+    return %return if wantarray;
+    return \%return;
+}
+
+sub version {
+    my $self = shift;
+    return $self->_do_command(::CMD_VERSION, '', '');
+}
+
+sub flush {
+    my $self = shift;
+    return $self->_do_command(::CMD_FLUSH, '', '');
+}
+
+sub add {
+    my $self = shift;
+    my ($key, $val, $flags, $expire) = @_;
+    my $extra_header = pack "NN", $flags, $expire;
+    my $cas = 0;
+    return $self->_do_command(::CMD_ADD, $key, $val, $extra_header, $cas);
+}
+
+sub set {
+    my $self = shift;
+    my $flags = 0;
+    my $cas = 0;
+    my ($key, $val, $expire) = @_;
+    $expire = defined $expire ? $expire : 0;
+    my $extra_header = pack "NN", $flags, $expire;
+    return $self->_do_command(::CMD_SET, $key, $val, $extra_header, $cas);
+}
+
+sub _append_prepend {
+    my $self = shift;
+    my ($cmd, $key, $val, $cas) = @_;
+    return $self->_do_command($cmd, $key, $val, '', $cas);
+}
+
+sub replace {
+    my $self = shift;
+    my ($key, $val, $flags, $expire) = @_;
+    my $extra_header = pack "NN", $flags, $expire;
+    my $cas = 0;
+    return $self->_do_command(::CMD_REPLACE, $key, $val, $extra_header, $cas);
+}
+
+sub delete {
+    my $self = shift;
+    my ($key) = @_;
+    return $self->_do_command(::CMD_DELETE, $key, '');
+}
+
+sub incr {
+    my $self = shift;
+    my ($key, $amt, $init, $exp) = @_;
+    $amt = 1 unless defined $amt;
+    $init = 0 unless defined $init;
+    $exp = 0 unless defined $exp;
+
+    return $self->_incrdecr(::CMD_INCR, $key, $amt, $init, $exp);
+}
+
+sub decr {
+    my $self = shift;
+    my ($key, $amt, $init, $exp) = @_;
+    $amt = 1 unless defined $amt;
+    $init = 0 unless defined $init;
+    $exp = 0 unless defined $exp;
+
+    return $self->_incrdecr(::CMD_DECR, $key, $amt, $init, $exp);
+}
+
+sub noop {
+    my $self = shift;
+    return $self->_do_command(::CMD_NOOP, '', '');
+}
+
+package MC::Error;
+
+use strict;
+use warnings;
+
+use constant ERR_UNKNOWN_CMD  => 0x81;
+use constant ERR_NOT_FOUND    => 0x1;
+use constant ERR_EXISTS       => 0x2;
+use constant ERR_TOO_BIG      => 0x3;
+use constant ERR_EINVAL       => 0x4;
+use constant ERR_NOT_STORED   => 0x5;
+use constant ERR_DELTA_BADVAL => 0x6;
+use constant ERR_AUTH_ERROR   => 0x20;
+
+use overload '""' => sub {
+    my $self = shift;
+    return "Memcache Error ($self->[0]): $self->[1]";
+};
+
+sub new {
+    my $class = shift;
+    my $error = [@_];
+    my $self = bless $error, (ref $class || $class);
+
+    return $self;
+}
+
+sub not_found {
+    my $self = shift;
+    return $self->[0] == ERR_NOT_FOUND;
+}
+
+sub exists {
+    my $self = shift;
+    return $self->[0] == ERR_EXISTS;
+}
+
+sub too_big {
+    my $self = shift;
+    return $self->[0] == ERR_TOO_BIG;
+}
+
+sub delta_badval {
+    my $self = shift;
+    return $self->[0] == ERR_DELTA_BADVAL;
+}
+
+sub auth_error {
+    my $self = shift;
+    return $self->[0] == ERR_AUTH_ERROR;
+}
+
+unlink $sasldb;
+
+# vim: filetype=perl
+
diff --git a/memcached/t/binary.t b/memcached/t/binary.t
new file mode 100755 (executable)
index 0000000..504ddef
--- /dev/null
@@ -0,0 +1,885 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use Test::More tests => 3539;
+use FindBin qw($Bin);
+use lib "$Bin/lib";
+use MemcachedTest;
+
+my $server = new_memcached();
+ok($server, "started the server");
+
+# Based almost 100% off testClient.py which is:
+# Copyright (c) 2007  Dustin Sallings <dustin@spy.net>
+
+# Command constants
+use constant CMD_GET        => 0x00;
+use constant CMD_SET        => 0x01;
+use constant CMD_ADD        => 0x02;
+use constant CMD_REPLACE    => 0x03;
+use constant CMD_DELETE     => 0x04;
+use constant CMD_INCR       => 0x05;
+use constant CMD_DECR       => 0x06;
+use constant CMD_QUIT       => 0x07;
+use constant CMD_FLUSH      => 0x08;
+use constant CMD_GETQ       => 0x09;
+use constant CMD_NOOP       => 0x0A;
+use constant CMD_VERSION    => 0x0B;
+use constant CMD_GETK       => 0x0C;
+use constant CMD_GETKQ      => 0x0D;
+use constant CMD_APPEND     => 0x0E;
+use constant CMD_PREPEND    => 0x0F;
+use constant CMD_STAT       => 0x10;
+use constant CMD_SETQ       => 0x11;
+use constant CMD_ADDQ       => 0x12;
+use constant CMD_REPLACEQ   => 0x13;
+use constant CMD_DELETEQ    => 0x14;
+use constant CMD_INCREMENTQ => 0x15;
+use constant CMD_DECREMENTQ => 0x16;
+use constant CMD_QUITQ      => 0x17;
+use constant CMD_FLUSHQ     => 0x18;
+use constant CMD_APPENDQ    => 0x19;
+use constant CMD_PREPENDQ   => 0x1A;
+use constant CMD_TOUCH      => 0x1C;
+use constant CMD_GAT        => 0x1D;
+use constant CMD_GATQ       => 0x1E;
+use constant CMD_GATK       => 0x23;
+use constant CMD_GATKQ      => 0x24;
+
+# REQ and RES formats are divided even though they currently share
+# the same format, since they _could_ differ in the future.
+use constant REQ_PKT_FMT      => "CCnCCnNNNN";
+use constant RES_PKT_FMT      => "CCnCCnNNNN";
+use constant INCRDECR_PKT_FMT => "NNNNN";
+use constant MIN_RECV_BYTES   => length(pack(RES_PKT_FMT));
+use constant REQ_MAGIC        => 0x80;
+use constant RES_MAGIC        => 0x81;
+
+my $mc = MC::Client->new;
+
+# Let's turn on detail stats for all this stuff
+
+$mc->stats('detail on');
+
+my $check = sub {
+    my ($key, $orig_flags, $orig_val) = @_;
+    my ($flags, $val, $cas) = $mc->get($key);
+    is($flags, $orig_flags, "Flags is set properly");
+    ok($val eq $orig_val || $val == $orig_val, $val . " = " . $orig_val);
+};
+
+my $set = sub {
+    my ($key, $exp, $orig_flags, $orig_value) = @_;
+    $mc->set($key, $orig_value, $orig_flags, $exp);
+    $check->($key, $orig_flags, $orig_value);
+};
+
+my $empty = sub {
+    my $key = shift;
+    my $rv =()= eval { $mc->get($key) };
+    is($rv, 0, "Didn't get a result from get");
+    ok($@->not_found, "We got a not found error when we expected one");
+};
+
+my $delete = sub {
+    my ($key, $when) = @_;
+    $mc->delete($key, $when);
+    $empty->($key);
+};
+
+# diag "Test Version";
+my $v = $mc->version;
+ok(defined $v && length($v), "Proper version: $v");
+
+# Bug 71
+{
+    my %stats1 = $mc->stats('');
+    $mc->flush;
+    my %stats2 = $mc->stats('');
+
+    is($stats2{'cmd_flush'}, $stats1{'cmd_flush'} + 1,
+       "Stats not updated on a binary flush");
+}
+
+# diag "Flushing...";
+$mc->flush;
+
+# diag "Noop";
+$mc->noop;
+
+# diag "Simple set/get";
+$set->('x', 5, 19, "somevalue");
+
+# diag "Delete";
+$delete->('x');
+
+# diag "Flush";
+$set->('x', 5, 19, "somevaluex");
+$set->('y', 5, 17, "somevaluey");
+$mc->flush;
+$empty->('x');
+$empty->('y');
+
+{
+    # diag "Add";
+    $empty->('i');
+    $mc->add('i', 'ex', 5, 10);
+    $check->('i', 5, "ex");
+
+    my $rv =()= eval { $mc->add('i', "ex2", 10, 5) };
+    is($rv, 0, "Add didn't return anything");
+    ok($@->exists, "Expected exists error received");
+    $check->('i', 5, "ex");
+}
+
+{
+    # diag "Too big.";
+    $empty->('toobig');
+    $mc->set('toobig', 'not too big', 10, 10);
+    eval {
+        my $bigval = ("x" x (1024*1024)) . "x";
+        $mc->set('toobig', $bigval, 10, 10);
+    };
+    ok($@->too_big, "Was too big");
+    $empty->('toobig');
+}
+
+{
+    # diag "Replace";
+    $empty->('j');
+
+    my $rv =()= eval { $mc->replace('j', "ex", 19, 5) };
+    is($rv, 0, "Replace didn't return anything");
+    ok($@->not_found, "Expected not_found error received");
+    $empty->('j');
+    $mc->add('j', "ex2", 14, 5);
+    $check->('j', 14, "ex2");
+    $mc->replace('j', "ex3", 24, 5);
+    $check->('j', 24, "ex3");
+}
+
+{
+    # diag "MultiGet";
+    $mc->add('xx', "ex", 1, 5);
+    $mc->add('wye', "why", 2, 5);
+    my $rv = $mc->get_multi(qw(xx wye zed));
+
+    # CAS is returned with all gets.
+    $rv->{xx}->[2]  = 0;
+    $rv->{wye}->[2] = 0;
+    is_deeply($rv->{xx}, [1, 'ex', 0], "X is correct");
+    is_deeply($rv->{wye}, [2, 'why', 0], "Y is correct");
+    is(keys(%$rv), 2, "Got only two answers like we expect");
+}
+
+# diag "Test increment";
+$mc->flush;
+is($mc->incr("x"), 0, "First incr call is zero");
+is($mc->incr("x"), 1, "Second incr call is one");
+is($mc->incr("x", 211), 212, "Adding 211 gives you 212");
+is($mc->incr("x", 2**33), 8589934804, "Blast the 32bit border");
+
+# diag "Issue 48 - incrementing plain text.";
+{
+    $mc->set("issue48", "text", 0, 0);
+    my $rv =()= eval { $mc->incr('issue48'); };
+    ok($@ && $@->delta_badval, "Expected invalid value when incrementing text.");
+    $check->('issue48', 0, "text");
+
+    $rv =()= eval { $mc->decr('issue48'); };
+    ok($@ && $@->delta_badval, "Expected invalid value when decrementing text.");
+    $check->('issue48', 0, "text");
+}
+
+
+# diag "Test decrement";
+$mc->flush;
+is($mc->incr("x", undef, 5), 5, "Initial value");
+is($mc->decr("x"), 4, "Decrease by one");
+is($mc->decr("x", 211), 0, "Floor is zero");
+
+{
+    # diag "bug220
+    my ($rv, $cas) = $mc->set("bug220", "100", 0, 0);
+    my ($irv, $icas) = $mc->incr_cas("bug220", 999);
+    ok($icas != $cas);
+    is($irv, 1099, "Incr amount failed");
+    my ($flags, $val, $gcas) = $mc->get("bug220");
+    is($gcas, $icas, "CAS didn't match after incr/gets");
+
+    ($irv, $icas) = $mc->incr_cas("bug220", 999);
+    ok($icas != $cas);
+    is($irv, 2098, "Incr amount failed");
+    ($flags, $val, $gcas) = $mc->get("bug220");
+    is($gcas, $icas, "CAS didn't match after incr/gets");
+}
+
+{
+    # diag "bug21";
+    $mc->add("bug21", "9223372036854775807", 0, 0);
+    is($mc->incr("bug21"), 9223372036854775808, "First incr for bug21.");
+    is($mc->incr("bug21"), 9223372036854775809, "Second incr for bug21.");
+    is($mc->decr("bug21"), 9223372036854775808, "Decr for bug21.");
+}
+
+{
+    # diag "CAS";
+    $mc->flush;
+
+    {
+        my $rv =()= eval { $mc->set("x", "bad value", 19, 5, 0x7FFFFFF) };
+        is($rv, 0, "Empty return on expected failure");
+        ok($@->not_found, "Error was 'not found' as expected");
+    }
+
+    my ($r, $rcas) = $mc->add("x", "original value", 5, 19);
+
+    my ($flags, $val, $i) = $mc->get("x");
+    is($val, "original value", "->gets returned proper value");
+    is($rcas, $i, "Add CAS matched.");
+
+    {
+        my $rv =()= eval { $mc->set("x", "broken value", 19, 5, $i+1) };
+        is($rv, 0, "Empty return on expected failure (1)");
+        ok($@->exists, "Expected error state of 'exists' (1)");
+    }
+
+    ($r, $rcas) = $mc->set("x", "new value", 19, 5, $i);
+
+    my ($newflags, $newval, $newi) = $mc->get("x");
+    is($newval, "new value", "CAS properly overwrote value");
+    is($rcas, $newi, "Get CAS matched.");
+
+    {
+        my $rv =()= eval { $mc->set("x", "replay value", 19, 5,  $i) };
+        is($rv, 0, "Empty return on expected failure (2)");
+        ok($@->exists, "Expected error state of 'exists' (2)");
+    }
+}
+
+# diag "Touch commands";
+{
+    $mc->flush;
+    $mc->set("totouch", "toast", 0, 1);
+    my $res = $mc->touch("totouch", 10);
+    sleep 2;
+    $check->("totouch", 0, "toast");
+
+    $mc->set("totouch", "toast2", 0, 1);
+    my ($flags, $val, $i) = $mc->gat("totouch", 10);
+    is($val, "toast2", "GAT returned correct value");
+    sleep 2;
+    $check->("totouch", 0, "toast2");
+
+    # Test miss as well
+}
+
+# diag "Silent set.";
+$mc->silent_mutation(::CMD_SETQ, 'silentset', 'silentsetval');
+
+# diag "Silent add.";
+$mc->silent_mutation(::CMD_ADDQ, 'silentadd', 'silentaddval');
+
+# diag "Silent replace.";
+{
+    my $key = "silentreplace";
+    my $extra = pack "NN", 829, 0;
+    $empty->($key);
+    # $mc->send_silent(::CMD_REPLACEQ, $key, 'somevalue', 7278552, $extra, 0);
+    # $empty->($key);
+
+    $mc->add($key, "xval", 831, 0);
+    $check->($key, 831, 'xval');
+
+    $mc->send_silent(::CMD_REPLACEQ, $key, 'somevalue', 7278552, $extra, 0);
+    $check->($key, 829, 'somevalue');
+}
+
+# diag "Silent delete";
+{
+    my $key = "silentdelete";
+    $empty->($key);
+    $mc->set($key, "some val", 19, 0);
+    $mc->send_silent(::CMD_DELETEQ, $key, '', 772);
+    $empty->($key);
+}
+
+# diag "Silent increment";
+{
+    my $key = "silentincr";
+    my $opaque = 98428747;
+    $empty->($key);
+    $mc->silent_incrdecr(::CMD_INCREMENTQ, $key, 0, 0, 0);
+    is($mc->incr($key, 0), 0, "First call is 0");
+
+    $mc->silent_incrdecr(::CMD_INCREMENTQ, $key, 8, 0, 0);
+    is($mc->incr($key, 0), 8);
+}
+
+# diag "Silent decrement";
+{
+    my $key = "silentdecr";
+    my $opaque = 98428147;
+    $empty->($key);
+    $mc->silent_incrdecr(::CMD_DECREMENTQ, $key, 0, 185, 0);
+    is($mc->incr($key, 0), 185);
+
+    $mc->silent_incrdecr(::CMD_DECREMENTQ, $key, 8, 0, 0);
+    is($mc->incr($key, 0), 177);
+}
+
+# diag "Silent flush";
+{
+    my %stats1 = $mc->stats('');
+
+    $set->('x', 5, 19, "somevaluex");
+    $set->('y', 5, 17, "somevaluey");
+    $mc->send_silent(::CMD_FLUSHQ, '', '', 2775256);
+    $empty->('x');
+    $empty->('y');
+
+    my %stats2 = $mc->stats('');
+    is($stats2{'cmd_flush'}, $stats1{'cmd_flush'} + 1,
+       "Stats not updated on a binary quiet flush");
+}
+
+# diag "Append";
+{
+    my $key = "appendkey";
+    my $value = "some value";
+    $set->($key, 8, 19, $value);
+    $mc->_append_prepend(::CMD_APPEND, $key, " more");
+    $check->($key, 19, $value . " more");
+}
+
+# diag "Prepend";
+{
+    my $key = "prependkey";
+    my $value = "some value";
+    $set->($key, 8, 19, $value);
+    $mc->_append_prepend(::CMD_PREPEND, $key, "prefixed ");
+    $check->($key, 19, "prefixed " . $value);
+}
+
+# diag "Silent append";
+{
+    my $key = "appendqkey";
+    my $value = "some value";
+    $set->($key, 8, 19, $value);
+    $mc->send_silent(::CMD_APPENDQ, $key, " more", 7284492);
+    $check->($key, 19, $value . " more");
+}
+
+# diag "Silent prepend";
+{
+    my $key = "prependqkey";
+    my $value = "some value";
+    $set->($key, 8, 19, $value);
+    $mc->send_silent(::CMD_PREPENDQ, $key, "prefixed ", 7284492);
+    $check->($key, 19, "prefixed " . $value);
+}
+
+# diag "Leaky binary get test.";
+# # http://code.google.com/p/memcached/issues/detail?id=16
+{
+    # Get a new socket so we can speak text to it.
+    my $sock = $server->new_sock;
+    my $max = 1024 * 1024;
+    my $big = "a big value that's > .5M and < 1M. ";
+    while (length($big) * 2 < $max) {
+        $big = $big . $big;
+    }
+    my $biglen = length($big);
+
+    for(1..100) {
+        my $key = "some_key_$_";
+        # print STDERR "Key is $key\n";
+        # print $sock "set $key 0 0 $vallen\r\n$value\r\n";
+        print $sock "set $key 0 0 $biglen\r\n$big\r\n";
+        is(scalar <$sock>, "STORED\r\n", "stored big");
+        my ($f, $v, $c) = $mc->get($key);
+    }
+}
+
+# diag "Test stats settings."
+{
+    my %stats = $mc->stats('settings');
+
+    is(1024, $stats{'maxconns'});
+    is('NULL', $stats{'domain_socket'});
+    is('on', $stats{'evictions'});
+    is('yes', $stats{'cas_enabled'});
+}
+
+# diag "Test quit commands.";
+{
+    my $s2 = new_memcached();
+    my $mc2 = MC::Client->new($s2);
+    $mc2->send_command(CMD_QUITQ, '', '', 0, '', 0);
+
+    # Five seconds ought to be enough to get hung up on.
+    my $oldalarmt = alarm(5);
+
+    # Verify we can't read anything.
+    my $bytesread = -1;
+    eval {
+        local $SIG{'ALRM'} = sub { die "timeout" };
+        my $data = "";
+        $bytesread = sysread($mc2->{socket}, $data, 24),
+    };
+    is($bytesread, 0, "Read after quit.");
+
+    # Restore signal stuff.
+    alarm($oldalarmt);
+}
+
+# diag "Test protocol boundary overruns";
+{
+    use List::Util qw[min];
+    # Attempting some protocol overruns by toying around with the edge
+    # of the data buffer at a few different sizes.  This assumes the
+    # boundary is at or around 2048 bytes.
+    for (my $i = 1900; $i < 2100; $i++) {
+        my $k = "test_key_$i";
+        my $v = 'x' x $i;
+        # diag "Trying $i $k";
+        my $extra = pack "NN", 82, 0;
+        my $data = $mc->build_command(::CMD_SETQ, $k, $v, 0, $extra, 0);
+        $data .= $mc->build_command(::CMD_SETQ, "alt_$k", "blah", 0, $extra, 0);
+        if (length($data) > 2024) {
+            for (my $j = 2024; $j < min(2096, length($data)); $j++) {
+                $mc->{socket}->send(substr($data, 0, $j));
+                $mc->flush_socket;
+                sleep(0.001);
+                $mc->{socket}->send(substr($data, $j));
+                $mc->flush_socket;
+            }
+        } else {
+            $mc->{socket}->send($data);
+        }
+        $mc->flush_socket;
+        $check->($k, 82, $v);
+        $check->("alt_$k", 82, "blah");
+    }
+}
+
+# Along with the assertion added to the code to verify we're staying
+# within bounds when we do a stats detail dump (detail turned on at
+# the top).
+my %stats = $mc->stats('detail dump');
+
+# This test causes a disconnection.
+{
+    # diag "Key too large.";
+    my $key = "x" x 365;
+    eval {
+        $mc->get($key, 'should die', 10, 10);
+    };
+    ok($@->einval, "Invalid key length");
+}
+
+# ######################################################################
+# Test ends around here.
+# ######################################################################
+
+package MC::Client;
+
+use strict;
+use warnings;
+use fields qw(socket);
+use IO::Socket::INET;
+
+sub new {
+    my $self = shift;
+    my ($s) = @_;
+    $s = $server unless defined $s;
+    my $sock = $s->sock;
+    $self = fields::new($self);
+    $self->{socket} = $sock;
+    return $self;
+}
+
+sub build_command {
+    my $self = shift;
+    die "Not enough args to send_command" unless @_ >= 4;
+    my ($cmd, $key, $val, $opaque, $extra_header, $cas) = @_;
+
+    $extra_header = '' unless defined $extra_header;
+    my $keylen    = length($key);
+    my $vallen    = length($val);
+    my $extralen  = length($extra_header);
+    my $datatype  = 0;  # field for future use
+    my $reserved  = 0;  # field for future use
+    my $totallen  = $keylen + $vallen + $extralen;
+    my $ident_hi  = 0;
+    my $ident_lo  = 0;
+
+    if ($cas) {
+        $ident_hi = int($cas / 2 ** 32);
+        $ident_lo = int($cas % 2 ** 32);
+    }
+
+    my $msg = pack(::REQ_PKT_FMT, ::REQ_MAGIC, $cmd, $keylen, $extralen,
+                   $datatype, $reserved, $totallen, $opaque, $ident_hi,
+                   $ident_lo);
+    my $full_msg = $msg . $extra_header . $key . $val;
+    return $full_msg;
+}
+
+sub send_command {
+    my $self = shift;
+    die "Not enough args to send_command" unless @_ >= 4;
+    my ($cmd, $key, $val, $opaque, $extra_header, $cas) = @_;
+
+    my $full_msg = $self->build_command($cmd, $key, $val, $opaque, $extra_header, $cas);
+
+    my $sent = $self->{socket}->send($full_msg);
+    die("Send failed:  $!") unless $sent;
+    if($sent != length($full_msg)) {
+        die("only sent $sent of " . length($full_msg) . " bytes");
+    }
+}
+
+sub flush_socket {
+    my $self = shift;
+    $self->{socket}->flush;
+}
+
+# Send a silent command and ensure it doesn't respond.
+sub send_silent {
+    my $self = shift;
+    die "Not enough args to send_silent" unless @_ >= 4;
+    my ($cmd, $key, $val, $opaque, $extra_header, $cas) = @_;
+
+    $self->send_command($cmd, $key, $val, $opaque, $extra_header, $cas);
+    $self->send_command(::CMD_NOOP, '', '', $opaque + 1);
+
+    my ($ropaque, $data) = $self->_handle_single_response;
+    Test::More::is($ropaque, $opaque + 1);
+}
+
+sub silent_mutation {
+    my $self = shift;
+    my ($cmd, $key, $value) = @_;
+
+    $empty->($key);
+    my $extra = pack "NN", 82, 0;
+    $mc->send_silent($cmd, $key, $value, 7278552, $extra, 0);
+    $check->($key, 82, $value);
+}
+
+sub _handle_single_response {
+    my $self = shift;
+    my $myopaque = shift;
+
+    my $hdr = "";
+    while(::MIN_RECV_BYTES - length($hdr) > 0) {
+        $self->{socket}->recv(my $response, ::MIN_RECV_BYTES - length($hdr));
+        $hdr .= $response;
+    }
+    Test::More::is(length($hdr), ::MIN_RECV_BYTES, "Expected read length");
+
+    my ($magic, $cmd, $keylen, $extralen, $datatype, $status, $remaining,
+        $opaque, $ident_hi, $ident_lo) = unpack(::RES_PKT_FMT, $hdr);
+    Test::More::is($magic, ::RES_MAGIC, "Got proper response magic");
+
+    my $cas = ($ident_hi * 2 ** 32) + $ident_lo;
+
+    return ($opaque, '', $cas, 0) if($remaining == 0);
+
+    # fetch the value
+    my $rv="";
+    while($remaining - length($rv) > 0) {
+        $self->{socket}->recv(my $buf, $remaining - length($rv));
+        $rv .= $buf;
+    }
+    if(length($rv) != $remaining) {
+        my $found = length($rv);
+        die("Expected $remaining bytes, got $found");
+    }
+
+    if (defined $myopaque) {
+        Test::More::is($opaque, $myopaque, "Expected opaque");
+    } else {
+        Test::More::pass("Implicit pass since myopaque is undefined");
+    }
+
+    if ($status) {
+        die MC::Error->new($status, $rv);
+    }
+
+    return ($opaque, $rv, $cas, $keylen);
+}
+
+sub _do_command {
+    my $self = shift;
+    die unless @_ >= 3;
+    my ($cmd, $key, $val, $extra_header, $cas) = @_;
+
+    $extra_header = '' unless defined $extra_header;
+    my $opaque = int(rand(2**32));
+    $self->send_command($cmd, $key, $val, $opaque, $extra_header, $cas);
+    my (undef, $rv, $rcas) = $self->_handle_single_response($opaque);
+    return ($rv, $rcas);
+}
+
+sub _incrdecr_header {
+    my $self = shift;
+    my ($amt, $init, $exp) = @_;
+
+    my $amt_hi = int($amt / 2 ** 32);
+    my $amt_lo = int($amt % 2 ** 32);
+
+    my $init_hi = int($init / 2 ** 32);
+    my $init_lo = int($init % 2 ** 32);
+
+    my $extra_header = pack(::INCRDECR_PKT_FMT, $amt_hi, $amt_lo, $init_hi,
+                            $init_lo, $exp);
+
+    return $extra_header;
+}
+
+sub _incrdecr_cas {
+    my $self = shift;
+    my ($cmd, $key, $amt, $init, $exp) = @_;
+
+    my ($data, $rcas) = $self->_do_command($cmd, $key, '',
+                                           $self->_incrdecr_header($amt, $init, $exp));
+
+    my $header = substr $data, 0, 8, '';
+    my ($resp_hi, $resp_lo) = unpack "NN", $header;
+    my $resp = ($resp_hi * 2 ** 32) + $resp_lo;
+
+    return $resp, $rcas;
+}
+
+sub _incrdecr {
+    my $self = shift;
+    my ($v, $c) = $self->_incrdecr_cas(@_);
+    return $v
+}
+
+sub silent_incrdecr {
+    my $self = shift;
+    my ($cmd, $key, $amt, $init, $exp) = @_;
+    my $opaque = 8275753;
+
+    $mc->send_silent($cmd, $key, '', $opaque,
+                     $mc->_incrdecr_header($amt, $init, $exp));
+}
+
+sub stats {
+    my $self = shift;
+    my $key  = shift;
+    my $cas = 0;
+    my $opaque = int(rand(2**32));
+    $self->send_command(::CMD_STAT, $key, '', $opaque, '', $cas);
+
+    my %rv = ();
+    my $found_key = '';
+    my $found_val = '';
+    do {
+        my ($op, $data, $cas, $keylen) = $self->_handle_single_response($opaque);
+        if($keylen > 0) {
+            $found_key = substr($data, 0, $keylen);
+            $found_val = substr($data, $keylen);
+            $rv{$found_key} = $found_val;
+        } else {
+            $found_key = '';
+        }
+    } while($found_key ne '');
+    return %rv;
+}
+
+sub get {
+    my $self = shift;
+    my $key  = shift;
+    my ($rv, $cas) = $self->_do_command(::CMD_GET, $key, '', '');
+
+    my $header = substr $rv, 0, 4, '';
+    my $flags  = unpack("N", $header);
+
+    return ($flags, $rv, $cas);
+}
+
+sub get_multi {
+    my $self = shift;
+    my @keys = @_;
+
+    for (my $i = 0; $i < @keys; $i++) {
+        $self->send_command(::CMD_GETQ, $keys[$i], '', $i, '', 0);
+    }
+
+    my $terminal = @keys + 10;
+    $self->send_command(::CMD_NOOP, '', '', $terminal);
+
+    my %return;
+    while (1) {
+        my ($opaque, $data) = $self->_handle_single_response;
+        last if $opaque == $terminal;
+
+        my $header = substr $data, 0, 4, '';
+        my $flags  = unpack("N", $header);
+
+        $return{$keys[$opaque]} = [$flags, $data];
+    }
+
+    return %return if wantarray;
+    return \%return;
+}
+
+sub touch {
+    my $self = shift;
+    my ($key, $expire) = @_;
+    my $extra_header = pack "N", $expire;
+    my $cas = 0;
+    return $self->_do_command(::CMD_TOUCH, $key, '', $extra_header, $cas);
+}
+
+sub gat {
+    my $self   = shift;
+    my $key    = shift;
+    my $expire = shift;
+    my $extra_header = pack "N", $expire;
+    my ($rv, $cas) = $self->_do_command(::CMD_GAT, $key, '', $extra_header);
+
+    my $header = substr $rv, 0, 4, '';
+    my $flags  = unpack("N", $header);
+
+    return ($flags, $rv, $cas);
+}
+
+sub version {
+    my $self = shift;
+    return $self->_do_command(::CMD_VERSION, '', '');
+}
+
+sub flush {
+    my $self = shift;
+    return $self->_do_command(::CMD_FLUSH, '', '');
+}
+
+sub add {
+    my $self = shift;
+    my ($key, $val, $flags, $expire) = @_;
+    my $extra_header = pack "NN", $flags, $expire;
+    my $cas = 0;
+    return $self->_do_command(::CMD_ADD, $key, $val, $extra_header, $cas);
+}
+
+sub set {
+    my $self = shift;
+    my ($key, $val, $flags, $expire, $cas) = @_;
+    my $extra_header = pack "NN", $flags, $expire;
+    return $self->_do_command(::CMD_SET, $key, $val, $extra_header, $cas);
+}
+
+sub _append_prepend {
+    my $self = shift;
+    my ($cmd, $key, $val, $cas) = @_;
+    return $self->_do_command($cmd, $key, $val, '', $cas);
+}
+
+sub replace {
+    my $self = shift;
+    my ($key, $val, $flags, $expire) = @_;
+    my $extra_header = pack "NN", $flags, $expire;
+    my $cas = 0;
+    return $self->_do_command(::CMD_REPLACE, $key, $val, $extra_header, $cas);
+}
+
+sub delete {
+    my $self = shift;
+    my ($key) = @_;
+    return $self->_do_command(::CMD_DELETE, $key, '');
+}
+
+sub incr {
+    my $self = shift;
+    my ($key, $amt, $init, $exp) = @_;
+    $amt = 1 unless defined $amt;
+    $init = 0 unless defined $init;
+    $exp = 0 unless defined $exp;
+
+    return $self->_incrdecr(::CMD_INCR, $key, $amt, $init, $exp);
+}
+
+sub incr_cas {
+    my $self = shift;
+    my ($key, $amt, $init, $exp) = @_;
+    $amt = 1 unless defined $amt;
+    $init = 0 unless defined $init;
+    $exp = 0 unless defined $exp;
+
+    return $self->_incrdecr_cas(::CMD_INCR, $key, $amt, $init, $exp);
+}
+
+sub decr {
+    my $self = shift;
+    my ($key, $amt, $init, $exp) = @_;
+    $amt = 1 unless defined $amt;
+    $init = 0 unless defined $init;
+    $exp = 0 unless defined $exp;
+
+    return $self->_incrdecr(::CMD_DECR, $key, $amt, $init, $exp);
+}
+
+sub noop {
+    my $self = shift;
+    return $self->_do_command(::CMD_NOOP, '', '');
+}
+
+package MC::Error;
+
+use strict;
+use warnings;
+
+use constant ERR_UNKNOWN_CMD  => 0x81;
+use constant ERR_NOT_FOUND    => 0x1;
+use constant ERR_EXISTS       => 0x2;
+use constant ERR_TOO_BIG      => 0x3;
+use constant ERR_EINVAL       => 0x4;
+use constant ERR_NOT_STORED   => 0x5;
+use constant ERR_DELTA_BADVAL => 0x6;
+
+use overload '""' => sub {
+    my $self = shift;
+    return "Memcache Error ($self->[0]): $self->[1]";
+};
+
+sub new {
+    my $class = shift;
+    my $error = [@_];
+    my $self = bless $error, (ref $class || $class);
+
+    return $self;
+}
+
+sub not_found {
+    my $self = shift;
+    return $self->[0] == ERR_NOT_FOUND;
+}
+
+sub exists {
+    my $self = shift;
+    return $self->[0] == ERR_EXISTS;
+}
+
+sub too_big {
+    my $self = shift;
+    return $self->[0] == ERR_TOO_BIG;
+}
+
+sub delta_badval {
+    my $self = shift;
+    return $self->[0] == ERR_DELTA_BADVAL;
+}
+
+sub einval {
+    my $self = shift;
+    return $self->[0] == ERR_EINVAL;
+}
+
+# vim: filetype=perl
+
diff --git a/memcached/t/bogus-commands.t b/memcached/t/bogus-commands.t
new file mode 100755 (executable)
index 0000000..aaae6cd
--- /dev/null
@@ -0,0 +1,13 @@
+#!/usr/bin/perl
+
+use strict;
+use Test::More tests => 1;
+use FindBin qw($Bin);
+use lib "$Bin/lib";
+use MemcachedTest;
+
+my $server = new_memcached();
+my $sock = $server->sock;
+
+print $sock "boguscommand slkdsldkfjsd\r\n";
+is(scalar <$sock>, "ERROR\r\n", "got error back");
diff --git a/memcached/t/cas.t b/memcached/t/cas.t
new file mode 100644 (file)
index 0000000..8797ebf
--- /dev/null
@@ -0,0 +1,158 @@
+#!/usr/bin/perl
+
+use strict;
+use Test::More tests => 43;
+use FindBin qw($Bin);
+use lib "$Bin/lib";
+use MemcachedTest;
+
+
+my $server = new_memcached();
+my $sock = $server->sock;
+my $sock2 = $server->new_sock;
+
+my @result;
+my @result2;
+
+ok($sock != $sock2, "have two different connections open");
+
+sub check_args {
+    my ($line, $name) = @_;
+
+    my $svr = new_memcached();
+    my $s = $svr->sock;
+
+    print $s $line;
+    is(scalar <$s>, "CLIENT_ERROR bad command line format\r\n", $name);
+    undef $svr;
+}
+
+check_args "cas bad blah 0 0 0\r\n\r\n", "bad flags";
+check_args "cas bad 0 blah 0 0\r\n\r\n", "bad exp";
+check_args "cas bad 0 0 blah 0\r\n\r\n", "bad cas";
+check_args "cas bad 0 0 0 blah\r\n\r\n", "bad size";
+
+# gets foo (should not exist)
+print $sock "gets foo\r\n";
+is(scalar <$sock>, "END\r\n", "gets failed");
+
+# set foo
+print $sock "set foo 0 0 6\r\nbarval\r\n";
+is(scalar <$sock>, "STORED\r\n", "stored barval");
+
+# gets foo and verify identifier exists
+@result = mem_gets($sock, "foo");
+mem_gets_is($sock,$result[0],"foo","barval");
+
+# cas fail
+print $sock "cas foo 0 0 6 123\r\nbarva2\r\n";
+is(scalar <$sock>, "EXISTS\r\n", "cas failed for foo");
+
+# gets foo - success
+@result = mem_gets($sock, "foo");
+mem_gets_is($sock,$result[0],"foo","barval");
+
+# cas success
+print $sock "cas foo 0 0 6 $result[0]\r\nbarva2\r\n";
+is(scalar <$sock>, "STORED\r\n", "cas success, set foo");
+
+# cas failure (reusing the same key)
+print $sock "cas foo 0 0 6 $result[0]\r\nbarva2\r\n";
+is(scalar <$sock>, "EXISTS\r\n", "reusing a CAS ID");
+
+# delete foo
+print $sock "delete foo\r\n";
+is(scalar <$sock>, "DELETED\r\n", "deleted foo");
+
+# cas missing
+print $sock "cas foo 0 0 6 $result[0]\r\nbarva2\r\n";
+is(scalar <$sock>, "NOT_FOUND\r\n", "cas failed, foo does not exist");
+
+# cas empty
+print $sock "cas foo 0 0 6 \r\nbarva2\r\n";
+is(scalar <$sock>, "ERROR\r\n", "cas empty, throw error");
+# cant parse barval2\r\n
+is(scalar <$sock>, "ERROR\r\n", "error out on barval2 parsing");
+
+# set foo1
+print $sock "set foo1 0 0 1\r\n1\r\n";
+is(scalar <$sock>, "STORED\r\n", "set foo1");
+# set foo2
+print $sock "set foo2 0 0 1\r\n2\r\n";
+is(scalar <$sock>, "STORED\r\n", "set foo2");
+
+# gets foo1 check
+print $sock "gets foo1\r\n";
+ok(scalar <$sock> =~ /VALUE foo1 0 1 (\d+)\r\n/, "gets foo1 regexp success");
+my $foo1_cas = $1;
+is(scalar <$sock>, "1\r\n","gets foo1 data is 1");
+is(scalar <$sock>, "END\r\n","gets foo1 END");
+
+# gets foo2 check
+print $sock "gets foo2\r\n";
+ok(scalar <$sock> =~ /VALUE foo2 0 1 (\d+)\r\n/,"gets foo2 regexp success");
+my $foo2_cas = $1;
+is(scalar <$sock>, "2\r\n","gets foo2 data is 2");
+is(scalar <$sock>, "END\r\n","gets foo2 END");
+
+# validate foo1 != foo2
+ok($foo1_cas != $foo2_cas,"foo1 != foo2 single-gets success");
+
+# multi-gets
+print $sock "gets foo1 foo2\r\n";
+ok(scalar <$sock> =~ /VALUE foo1 0 1 (\d+)\r\n/, "validating first set of data is foo1");
+$foo1_cas = $1;
+is(scalar <$sock>, "1\r\n", "validating foo1 set of data is 1");
+ok(scalar <$sock> =~ /VALUE foo2 0 1 (\d+)\r\n/, "validating second set of data is foo2");
+$foo2_cas = $1;
+is(scalar <$sock>, "2\r\n", "validating foo2 set of data is 2");
+is(scalar <$sock>, "END\r\n","validating foo1,foo2 gets is over - END");
+
+# validate foo1 != foo2
+ok($foo1_cas != $foo2_cas, "foo1 != foo2 multi-gets success");
+
+### simulate race condition with cas
+
+# gets foo1 - success
+@result = mem_gets($sock, "foo1");
+ok($result[0] != "", "sock - gets foo1 is not empty");
+
+# gets foo2 - success
+@result2 = mem_gets($sock2, "foo1");
+ok($result2[0] != "","sock2 - gets foo1 is not empty");
+
+print $sock "cas foo1 0 0 6 $result[0]\r\nbarva2\r\n";
+print $sock2 "cas foo1 0 0 5 $result2[0]\r\napple\r\n";
+
+my $res1 = <$sock>;
+my $res2 = <$sock2>;
+
+ok( ( $res1 eq "STORED\r\n" && $res2 eq "EXISTS\r\n") ||
+    ( $res1 eq "EXISTS\r\n" && $res2 eq "STORED\r\n"),
+    "cas on same item from two sockets");
+
+### bug 15: http://code.google.com/p/memcached/issues/detail?id=15
+
+# set foo
+print $sock "set bug15 0 0 1\r\n0\r\n";
+is(scalar <$sock>, "STORED\r\n", "stored 0");
+
+# Check out the first gets.
+print $sock "gets bug15\r\n";
+ok(scalar <$sock> =~ /VALUE bug15 0 1 (\d+)\r\n/, "gets bug15 regexp success");
+my $bug15_cas = $1;
+is(scalar <$sock>, "0\r\n", "gets bug15 data is 0");
+is(scalar <$sock>, "END\r\n","gets bug15 END");
+
+# Increment
+print $sock "incr bug15 1\r\n";
+is(scalar <$sock>, "1\r\n", "incr worked");
+
+# Validate a changed CAS
+print $sock "gets bug15\r\n";
+ok(scalar <$sock> =~ /VALUE bug15 0 1 (\d+)\r\n/, "gets bug15 regexp success");
+my $next_bug15_cas = $1;
+is(scalar <$sock>, "1\r\n", "gets bug15 data is 0");
+is(scalar <$sock>, "END\r\n","gets bug15 END");
+
+ok($bug15_cas != $next_bug15_cas, "CAS changed");
diff --git a/memcached/t/daemonize.t b/memcached/t/daemonize.t
new file mode 100755 (executable)
index 0000000..e6fe7f5
--- /dev/null
@@ -0,0 +1,33 @@
+#!/usr/bin/perl
+
+use strict;
+use Test::More tests => 7;
+use FindBin qw($Bin);
+use lib "$Bin/lib";
+use MemcachedTest;
+
+use File::Temp qw(tempfile);
+
+my (undef, $tmpfn) = tempfile();
+
+my $server = new_memcached("-d -P $tmpfn");
+my $sock = $server->sock;
+sleep 0.5;
+
+ok(-e $tmpfn, "pid file exists");
+ok(-s $tmpfn, "pid file has length");
+
+open (my $fh, $tmpfn) or die;
+my $readpid = do { local $/; <$fh>; };
+chomp $readpid;
+close ($fh);
+
+ok(kill(0, $readpid), "process is still running");
+
+my $stats = mem_stats($sock);
+is($stats->{pid}, $readpid, "memcached reports same pid as file");
+
+ok($server->new_sock, "opened new socket");
+ok(kill(9, $readpid), "sent KILL signal");
+sleep 0.5;
+ok(! $server->new_sock, "failed to open new socket");
diff --git a/memcached/t/dash-M.t b/memcached/t/dash-M.t
new file mode 100644 (file)
index 0000000..af9e09b
--- /dev/null
@@ -0,0 +1,34 @@
+#!/usr/bin/perl
+
+use strict;
+use Test::More;
+use FindBin qw($Bin);
+use lib "$Bin/lib";
+use MemcachedTest;
+
+my $server = new_memcached('-M -m 1');
+my $sock = $server->sock;
+
+my $value = "B" x 8192;
+my $vallen = length($value);
+
+my $resp = "STORED\r\n";
+my $key = 0;
+
+while($resp eq "STORED\r\n") {
+    print $sock "set dash$key 0 0 $vallen\r\n$value\r\n";
+    $key++;
+    $resp = scalar <$sock>;
+}
+
+my $max_stored = $key - 1;
+
+plan tests => $max_stored + 1;
+
+print $sock "set dash$key 0 0 $vallen\r\n$value\r\n";
+is(scalar <$sock>, "SERVER_ERROR out of memory storing object\r\n",
+   "failed to add another one.");
+
+for($key = 0; $key < $max_stored; $key++) {
+    mem_get_is $sock, "dash$key", $value, "Failed at dash$key";
+}
diff --git a/memcached/t/evictions.t b/memcached/t/evictions.t
new file mode 100644 (file)
index 0000000..d0fa394
--- /dev/null
@@ -0,0 +1,31 @@
+#!/usr/bin/perl
+# Test the 'stats items' evictions counters.
+
+use strict;
+use Test::More tests => 92;
+use FindBin qw($Bin);
+use lib "$Bin/lib";
+use MemcachedTest;
+
+my $server = new_memcached("-m 3");
+my $sock = $server->sock;
+my $value = "B"x66560;
+my $key = 0;
+
+# These aren't set to expire.
+for ($key = 0; $key < 40; $key++) {
+    print $sock "set key$key 0 0 66560\r\n$value\r\n";
+    is(scalar <$sock>, "STORED\r\n", "stored key$key");
+}
+
+# These ones would expire in 600 seconds.
+for ($key = 0; $key < 50; $key++) {
+    print $sock "set key$key 0 600 66560\r\n$value\r\n";
+    is(scalar <$sock>, "STORED\r\n", "stored key$key");
+}
+
+my $stats  = mem_stats($sock, "items");
+my $evicted = $stats->{"items:31:evicted"};
+isnt($evicted, "0", "check evicted");
+my $evicted_nonzero = $stats->{"items:31:evicted_nonzero"};
+isnt($evicted_nonzero, "0", "check evicted_nonzero");
diff --git a/memcached/t/expirations.t b/memcached/t/expirations.t
new file mode 100755 (executable)
index 0000000..c92125a
--- /dev/null
@@ -0,0 +1,64 @@
+#!/usr/bin/perl
+
+use strict;
+use Test::More tests => 15;
+use FindBin qw($Bin);
+use lib "$Bin/lib";
+use MemcachedTest;
+
+my $server = new_memcached();
+my $sock = $server->sock;
+my $expire;
+
+sub wait_for_early_second {
+    my $have_hires = eval "use Time::HiRes (); 1";
+    if ($have_hires) {
+        my $tsh = Time::HiRes::time();
+        my $ts = int($tsh);
+        return if ($tsh - $ts) < 0.5;
+    }
+
+    my $ts = int(time());
+    while (1) {
+        my $t = int(time());
+        return if $t != $ts;
+        select undef, undef, undef, 0.10;  # 1/10th of a second sleeps until time changes.
+    }
+}
+
+wait_for_early_second();
+
+print $sock "set foo 0 1 6\r\nfooval\r\n";
+is(scalar <$sock>, "STORED\r\n", "stored foo");
+
+mem_get_is($sock, "foo", "fooval");
+sleep(1.5);
+mem_get_is($sock, "foo", undef);
+
+$expire = time() - 1;
+print $sock "set foo 0 $expire 6\r\nfooval\r\n";
+is(scalar <$sock>, "STORED\r\n", "stored foo");
+mem_get_is($sock, "foo", undef, "already expired");
+
+$expire = time() + 1;
+print $sock "set foo 0 $expire 6\r\nfoov+1\r\n";
+is(scalar <$sock>, "STORED\r\n", "stored foo");
+mem_get_is($sock, "foo", "foov+1");
+sleep(2.2);
+mem_get_is($sock, "foo", undef, "now expired");
+
+$expire = time() - 20;
+print $sock "set boo 0 $expire 6\r\nbooval\r\n";
+is(scalar <$sock>, "STORED\r\n", "stored boo");
+mem_get_is($sock, "boo", undef, "now expired");
+
+print $sock "add add 0 2 6\r\naddval\r\n";
+is(scalar <$sock>, "STORED\r\n", "stored add");
+mem_get_is($sock, "add", "addval");
+# second add fails
+print $sock "add add 0 2 7\r\naddval2\r\n";
+is(scalar <$sock>, "NOT_STORED\r\n", "add failure");
+sleep(2.3);
+print $sock "add add 0 2 7\r\naddval3\r\n";
+is(scalar <$sock>, "STORED\r\n", "stored add again");
+mem_get_is($sock, "add", "addval3");
diff --git a/memcached/t/flags.t b/memcached/t/flags.t
new file mode 100755 (executable)
index 0000000..e523196
--- /dev/null
@@ -0,0 +1,18 @@
+#!/usr/bin/perl
+
+use strict;
+use Test::More tests => 6;
+use FindBin qw($Bin);
+use lib "$Bin/lib";
+use MemcachedTest;
+
+my $server = new_memcached();
+my $sock = $server->sock;
+
+# set foo (and should get it)
+for my $flags (0, 123, 2**16-1) {
+    print $sock "set foo $flags 0 6\r\nfooval\r\n";
+    is(scalar <$sock>, "STORED\r\n", "stored foo");
+    mem_get_is({ sock => $sock,
+                 flags => $flags }, "foo", "fooval", "got flags $flags back");
+}
diff --git a/memcached/t/flush-all.t b/memcached/t/flush-all.t
new file mode 100755 (executable)
index 0000000..b803bb7
--- /dev/null
@@ -0,0 +1,56 @@
+#!/usr/bin/perl
+
+use strict;
+use Test::More tests => 21;
+use FindBin qw($Bin);
+use lib "$Bin/lib";
+use MemcachedTest;
+
+my $server = new_memcached();
+my $sock = $server->sock;
+my $expire;
+
+print $sock "set foo 0 0 6\r\nfooval\r\n";
+is(scalar <$sock>, "STORED\r\n", "stored foo");
+
+mem_get_is($sock, "foo", "fooval");
+print $sock "flush_all\r\n";
+is(scalar <$sock>, "OK\r\n", "did flush_all");
+mem_get_is($sock, "foo", undef);
+
+# Test flush_all with zero delay.
+print $sock "set foo 0 0 6\r\nfooval\r\n";
+is(scalar <$sock>, "STORED\r\n", "stored foo");
+
+mem_get_is($sock, "foo", "fooval");
+print $sock "flush_all 0\r\n";
+is(scalar <$sock>, "OK\r\n", "did flush_all");
+mem_get_is($sock, "foo", undef);
+
+# check that flush_all doesn't blow away items that immediately get set
+print $sock "set foo 0 0 3\r\nnew\r\n";
+is(scalar <$sock>, "STORED\r\n", "stored foo = 'new'");
+mem_get_is($sock, "foo", 'new');
+
+# and the other form, specifying a flush_all time...
+my $expire = time() + 2;
+print $sock "flush_all $expire\r\n";
+is(scalar <$sock>, "OK\r\n", "did flush_all in future");
+
+print $sock "set foo 0 0 4\r\n1234\r\n";
+is(scalar <$sock>, "STORED\r\n", "stored foo = '1234'");
+mem_get_is($sock, "foo", '1234');
+sleep(3);
+mem_get_is($sock, "foo", undef);
+
+print $sock "set foo 0 0 5\r\n12345\r\n";
+is(scalar <$sock>, "STORED\r\n", "stored foo = '12345'");
+mem_get_is($sock, "foo", '12345');
+print $sock "flush_all 86400\r\n";
+is(scalar <$sock>, "OK\r\n", "did flush_all for far future");
+# Check foo still exists.
+mem_get_is($sock, "foo", '12345');
+print $sock "set foo2 0 0 5\r\n54321\r\n";
+is(scalar <$sock>, "STORED\r\n", "stored foo2 = '54321'");
+mem_get_is($sock, "foo", '12345');
+mem_get_is($sock, "foo2", '54321');
diff --git a/memcached/t/getset.t b/memcached/t/getset.t
new file mode 100755 (executable)
index 0000000..3992273
--- /dev/null
@@ -0,0 +1,99 @@
+#!/usr/bin/perl
+
+use strict;
+use Test::More tests => 539;
+use FindBin qw($Bin);
+use lib "$Bin/lib";
+use MemcachedTest;
+
+
+my $server = new_memcached();
+my $sock = $server->sock;
+
+
+# set foo (and should get it)
+print $sock "set foo 0 0 6\r\nfooval\r\n";
+is(scalar <$sock>, "STORED\r\n", "stored foo");
+mem_get_is($sock, "foo", "fooval");
+
+# add bar (and should get it)
+print $sock "add bar 0 0 6\r\nbarval\r\n";
+is(scalar <$sock>, "STORED\r\n", "stored barval");
+mem_get_is($sock, "bar", "barval");
+
+# add foo (but shouldn't get new value)
+print $sock "add foo 0 0 5\r\nfoov2\r\n";
+is(scalar <$sock>, "NOT_STORED\r\n", "not stored");
+mem_get_is($sock, "foo", "fooval");
+
+# replace bar (should work)
+print $sock "replace bar 0 0 6\r\nbarva2\r\n";
+is(scalar <$sock>, "STORED\r\n", "replaced barval 2");
+
+# replace notexist (shouldn't work)
+print $sock "replace notexist 0 0 6\r\nbarva2\r\n";
+is(scalar <$sock>, "NOT_STORED\r\n", "didn't replace notexist");
+
+# delete foo.
+print $sock "delete foo\r\n";
+is(scalar <$sock>, "DELETED\r\n", "deleted foo");
+
+# delete foo again.  not found this time.
+print $sock "delete foo\r\n";
+is(scalar <$sock>, "NOT_FOUND\r\n", "deleted foo, but not found");
+
+# add moo
+#
+print $sock "add moo 0 0 6\r\nmooval\r\n";
+is(scalar <$sock>, "STORED\r\n", "stored barval");
+mem_get_is($sock, "moo", "mooval");
+
+# check-and-set (cas) failure case, try to set value with incorrect cas unique val
+print $sock "cas moo 0 0 6 0\r\nMOOVAL\r\n";
+is(scalar <$sock>, "EXISTS\r\n", "check and set with invalid id");
+
+# test "gets", grab unique ID
+print $sock "gets moo\r\n";
+# VALUE moo 0 6 3084947704
+#
+my @retvals = split(/ /, scalar <$sock>);
+my $data = scalar <$sock>; # grab data
+my $dot  = scalar <$sock>; # grab dot on line by itself
+is($retvals[0], "VALUE", "get value using 'gets'");
+my $unique_id = $retvals[4];
+# clean off \r\n
+$unique_id =~ s/\r\n$//;
+ok($unique_id =~ /^\d+$/, "unique ID '$unique_id' is an integer");
+# now test that we can store moo with the correct unique id
+print $sock "cas moo 0 0 6 $unique_id\r\nMOOVAL\r\n";
+is(scalar <$sock>, "STORED\r\n");
+mem_get_is($sock, "moo", "MOOVAL");
+
+# pipeling is okay
+print $sock "set foo 0 0 6\r\nfooval\r\ndelete foo\r\nset foo 0 0 6\r\nfooval\r\ndelete foo\r\n";
+is(scalar <$sock>, "STORED\r\n",  "pipeline set");
+is(scalar <$sock>, "DELETED\r\n", "pipeline delete");
+is(scalar <$sock>, "STORED\r\n",  "pipeline set");
+is(scalar <$sock>, "DELETED\r\n", "pipeline delete");
+
+
+# Test sets up to a large size around 1MB.
+# Everything up to 1MB - 1k should succeed, everything 1MB +1k should fail.
+
+my $len = 1024;
+while ($len < 1024*1028) {
+    my $val = "B"x$len;
+    if ($len > (1024*1024)) {
+        # Ensure causing a memory overflow doesn't leave stale data.
+        print $sock "set foo_$len 0 0 3\r\nMOO\r\n";
+        is(scalar <$sock>, "STORED\r\n");
+        print $sock "set foo_$len 0 0 $len\r\n$val\r\n";
+        is(scalar <$sock>, "SERVER_ERROR object too large for cache\r\n", "failed to store size $len");
+        mem_get_is($sock, "foo_$len");
+    } else {
+        print $sock "set foo_$len 0 0 $len\r\n$val\r\n";
+        is(scalar <$sock>, "STORED\r\n", "stored size $len");
+    }
+    $len += 2048;
+}
+
diff --git a/memcached/t/incrdecr.t b/memcached/t/incrdecr.t
new file mode 100755 (executable)
index 0000000..e0ba65f
--- /dev/null
@@ -0,0 +1,71 @@
+#!/usr/bin/perl
+
+use strict;
+use Test::More tests => 23;
+use FindBin qw($Bin);
+use lib "$Bin/lib";
+use MemcachedTest;
+
+my $server = new_memcached();
+my $sock = $server->sock;
+
+# Bug 21
+print $sock "set bug21 0 0 19\r\n9223372036854775807\r\n";
+is(scalar <$sock>, "STORED\r\n", "stored text");
+print $sock "incr bug21 1\r\n";
+is(scalar <$sock>, "9223372036854775808\r\n", "bug21 incr 1");
+print $sock "incr bug21 1\r\n";
+is(scalar <$sock>, "9223372036854775809\r\n", "bug21 incr 2");
+print $sock "decr bug21 1\r\n";
+is(scalar <$sock>, "9223372036854775808\r\n", "bug21 decr");
+
+print $sock "set num 0 0 1\r\n1\r\n";
+is(scalar <$sock>, "STORED\r\n", "stored num");
+mem_get_is($sock, "num", 1, "stored 1");
+
+print $sock "incr num 1\r\n";
+is(scalar <$sock>, "2\r\n", "+ 1 = 2");
+mem_get_is($sock, "num", 2);
+
+print $sock "incr num 8\r\n";
+is(scalar <$sock>, "10\r\n", "+ 8 = 10");
+mem_get_is($sock, "num", 10);
+
+print $sock "decr num 1\r\n";
+is(scalar <$sock>, "9\r\n", "- 1 = 9");
+
+print $sock "decr num 9\r\n";
+is(scalar <$sock>, "0\r\n", "- 9 = 0");
+
+print $sock "decr num 5\r\n";
+is(scalar <$sock>, "0\r\n", "- 5 = 0");
+
+printf $sock "set num 0 0 10\r\n4294967296\r\n";
+is(scalar <$sock>, "STORED\r\n", "stored 2**32");
+
+print $sock "incr num 1\r\n";
+is(scalar <$sock>, "4294967297\r\n", "4294967296 + 1 = 4294967297");
+
+printf $sock "set num 0 0 %d\r\n18446744073709551615\r\n", length("18446744073709551615");
+is(scalar <$sock>, "STORED\r\n", "stored 2**64-1");
+
+print $sock "incr num 1\r\n";
+is(scalar <$sock>, "0\r\n", "(2**64 - 1) + 1 = 0");
+
+print $sock "decr bogus 5\r\n";
+is(scalar <$sock>, "NOT_FOUND\r\n", "can't decr bogus key");
+
+print $sock "decr incr 5\r\n";
+is(scalar <$sock>, "NOT_FOUND\r\n", "can't incr bogus key");
+
+print $sock "set bigincr 0 0 1\r\n0\r\n";
+is(scalar <$sock>, "STORED\r\n", "stored bigincr");
+print $sock "incr bigincr 18446744073709551610\r\n";
+is(scalar <$sock>, "18446744073709551610\r\n");
+
+print $sock "set text 0 0 2\r\nhi\r\n";
+is(scalar <$sock>, "STORED\r\n", "stored hi");
+print $sock "incr text 1\r\n";
+is(scalar <$sock>,
+   "CLIENT_ERROR cannot increment or decrement non-numeric value\r\n",
+   "hi - 1 = 0");
diff --git a/memcached/t/issue_104.t b/memcached/t/issue_104.t
new file mode 100755 (executable)
index 0000000..2213f69
--- /dev/null
@@ -0,0 +1,24 @@
+#!/usr/bin/perl
+
+use strict;
+use Test::More tests => 6;
+use FindBin qw($Bin);
+use lib "$Bin/lib";
+use MemcachedTest;
+
+my $server = new_memcached();
+my $sock = $server->sock;
+
+# first get should miss
+print $sock "get foo\r\n";
+is(scalar <$sock>, "END\r\n", "get foo");
+
+# Now set and get (should hit)
+print $sock "set foo 0 0 6\r\nfooval\r\n";
+is(scalar <$sock>, "STORED\r\n", "stored foo");
+mem_get_is($sock, "foo", "fooval");
+
+my $stats = mem_stats($sock);
+is($stats->{cmd_get}, 2, "Should have 2 get requests");
+is($stats->{get_hits}, 1, "Should have 1 hit");
+is($stats->{get_misses}, 1, "Should have 1 miss");
diff --git a/memcached/t/issue_108.t b/memcached/t/issue_108.t
new file mode 100644 (file)
index 0000000..07a78b7
--- /dev/null
@@ -0,0 +1,27 @@
+#!/usr/bin/perl
+
+use strict;
+use Test::More tests => 4;
+use FindBin qw($Bin);
+use lib "$Bin/lib";
+use MemcachedTest;
+
+my $server = new_memcached();
+my $sock = $server->sock;
+my $key = "del_key";
+
+print $sock "add $key 0 0 1\r\nx\r\n";
+is (scalar <$sock>, "STORED\r\n", "Added a key");
+
+print $sock "delete $key 0\r\n";
+is (scalar <$sock>, "DELETED\r\n", "Properly deleted with 0");
+
+print $sock "add $key 0 0 1\r\nx\r\n";
+is (scalar <$sock>, "STORED\r\n", "Added again a key");
+
+print $sock "delete $key 0 noreply\r\n";
+# will not reply, but a subsequent add will succeed
+
+print $sock "add $key 0 0 1\r\nx\r\n";
+is (scalar <$sock>, "STORED\r\n", "Add succeeded after quiet deletion.");
+
diff --git a/memcached/t/issue_14.t b/memcached/t/issue_14.t
new file mode 100644 (file)
index 0000000..fb14b03
--- /dev/null
@@ -0,0 +1,34 @@
+#!/usr/bin/perl
+
+use strict;
+use Test::More tests => 21;
+use FindBin qw($Bin);
+use lib "$Bin/lib";
+use MemcachedTest;
+
+my $server = new_memcached();
+my $sock = $server->sock;
+my $value = "B"x66560;
+my $key = 0;
+
+for ($key = 0; $key < 10; $key++) {
+    print $sock "set key$key 0 2 66560\r\n$value\r\n";
+    is (scalar <$sock>, "STORED\r\n", "stored key$key");
+}
+
+#print $sock "stats slabs"
+my $first_stats  = mem_stats($sock, "slabs");
+my $first_malloc = $first_stats->{total_malloced};
+
+sleep(4);
+
+for ($key = 10; $key < 20; $key++) {
+    print $sock "set key$key 0 2 66560\r\n$value\r\n";
+    is (scalar <$sock>, "STORED\r\n", "stored key$key");
+}
+
+my $second_stats  = mem_stats($sock, "slabs");
+my $second_malloc = $second_stats->{total_malloced};
+
+
+is ($second_malloc, $first_malloc, "Memory grows..")
diff --git a/memcached/t/issue_140.t b/memcached/t/issue_140.t
new file mode 100644 (file)
index 0000000..16eac3d
--- /dev/null
@@ -0,0 +1,41 @@
+#!/usr/bin/perl
+
+use strict;
+use Test::More;
+use FindBin qw($Bin);
+use lib "$Bin/lib";
+use MemcachedTest;
+
+plan skip_all => 'Fix for Issue 140 was only an illusion';
+
+plan tests => 7;
+
+my $server = new_memcached();
+my $sock = $server->sock;
+
+print $sock "set a 0 0 1\r\na\r\n";
+is (scalar <$sock>, "STORED\r\n", "stored key");
+
+my $stats  = mem_stats($sock, "items");
+my $age = $stats->{"items:1:age"};
+isnt ($age, "0", "Age should not be zero");
+
+print $sock "flush_all\r\n";
+is (scalar <$sock>, "OK\r\n", "items flushed");
+
+my $stats  = mem_stats($sock, "items");
+my $age = $stats->{"items:1:age"};
+is ($age, undef, "all should be gone");
+
+print $sock "set a 0 1 1\r\na\r\n";
+is (scalar <$sock>, "STORED\r\n", "stored key");
+
+my $stats  = mem_stats($sock, "items");
+my $age = $stats->{"items:1:age"};
+isnt ($age, "0", "Age should not be zero");
+
+sleep(3);
+
+my $stats  = mem_stats($sock, "items");
+my $age = $stats->{"items:1:age"};
+is ($age, undef, "all should be gone");
diff --git a/memcached/t/issue_152.t b/memcached/t/issue_152.t
new file mode 100644 (file)
index 0000000..81aa710
--- /dev/null
@@ -0,0 +1,17 @@
+#!/usr/bin/perl
+
+use strict;
+use Test::More tests => 2;
+use FindBin qw($Bin);
+use lib "$Bin/lib";
+use MemcachedTest;
+
+my $server = new_memcached();
+my $sock = $server->sock;
+my $key = "a"x251;
+
+print $sock "set a 1 0 1\r\na\r\n";
+is (scalar <$sock>, "STORED\r\n", "Stored key");
+
+print $sock "get a $key\r\n";
+is (scalar <$sock>, "CLIENT_ERROR bad command line format\r\n", "illegal key");
diff --git a/memcached/t/issue_163.t b/memcached/t/issue_163.t
new file mode 100644 (file)
index 0000000..a5eb1ce
--- /dev/null
@@ -0,0 +1,37 @@
+#!/usr/bin/perl
+
+use strict;
+use Test::More tests => 7;
+use FindBin qw($Bin);
+use lib "$Bin/lib";
+use MemcachedTest;
+
+my $server = new_memcached();
+my $sock = $server->sock;
+my $value1 = "A"x66560;
+my $value2 = "B"x66570;
+
+print $sock "set key 0 1 66560\r\n$value1\r\n";
+is (scalar <$sock>, "STORED\r\n", "stored key");
+
+my $stats  = mem_stats($sock, "slabs");
+my $requested = $stats->{"31:mem_requested"};
+isnt ($requested, "0", "We should have requested some memory");
+
+sleep(3);
+print $sock "set key 0 0 66570\r\n$value2\r\n";
+is (scalar <$sock>, "STORED\r\n", "stored key");
+
+my $stats  = mem_stats($sock, "items");
+my $reclaimed = $stats->{"items:31:reclaimed"};
+is ($reclaimed, "1", "Objects should be reclaimed");
+
+print $sock "delete key\r\n";
+is (scalar <$sock>, "DELETED\r\n", "deleted key");
+
+print $sock "set key 0 0 66560\r\n$value1\r\n";
+is (scalar <$sock>, "STORED\r\n", "stored key");
+
+my $stats  = mem_stats($sock, "slabs");
+my $requested2 = $stats->{"31:mem_requested"};
+is ($requested2, $requested, "we've not allocated and freed the same amont");
diff --git a/memcached/t/issue_183.t b/memcached/t/issue_183.t
new file mode 100644 (file)
index 0000000..94b956c
--- /dev/null
@@ -0,0 +1,23 @@
+#!/usr/bin/perl
+
+use strict;
+use Test::More tests => 5;
+use FindBin qw($Bin);
+use lib "$Bin/lib";
+use MemcachedTest;
+
+my $server = new_memcached();
+my $sock = $server->sock;
+print $sock "set key 0 0 1\r\n1\r\n";
+is (scalar <$sock>, "STORED\r\n", "stored key");
+my $s1  = mem_stats($sock);
+my $r1 = $s1->{"reclaimed"};
+is ($r1, "0", "Objects should not be reclaimed");
+sleep(2);
+print $sock "flush_all\r\n";
+is (scalar <$sock>, "OK\r\n", "Cache flushed");
+print $sock "set key 0 0 1\r\n1\r\n";
+is (scalar <$sock>, "STORED\r\n", "stored key");
+my $s2  = mem_stats($sock);
+my $r2 = $s2->{"reclaimed"};
+is ($r2, "1", "Objects should be reclaimed");
diff --git a/memcached/t/issue_22.t b/memcached/t/issue_22.t
new file mode 100644 (file)
index 0000000..403d3d3
--- /dev/null
@@ -0,0 +1,39 @@
+#!/usr/bin/perl
+
+use strict;
+use Test::More tests => 84;
+use FindBin qw($Bin);
+use lib "$Bin/lib";
+use MemcachedTest;
+
+my $server = new_memcached("-m 3");
+my $sock = $server->sock;
+my $value = "B"x66560;
+my $key = 0;
+
+for ($key = 0; $key < 40; $key++) {
+    print $sock "set key$key 0 0 66560\r\n$value\r\n";
+    is (scalar <$sock>, "STORED\r\n", "stored key$key");
+}
+
+my $first_stats  = mem_stats($sock, "items");
+my $first_evicted = $first_stats->{"items:31:evicted"};
+# I get 1 eviction on a 32 bit binary, but 4 on a 64 binary..
+# Just check that I have evictions...
+isnt ($first_evicted, "0", "check evicted");
+
+print $sock "stats reset\r\n";
+is (scalar <$sock>, "RESET\r\n", "Stats reset");
+
+my $second_stats  = mem_stats($sock, "items");
+my $second_evicted = $second_stats->{"items:31:evicted"};
+is ($second_evicted, "0", "check evicted");
+
+for ($key = 40; $key < 80; $key++) {
+    print $sock "set key$key 0 0 66560\r\n$value\r\n";
+    is (scalar <$sock>, "STORED\r\n", "stored key$key");
+}
+
+my $last_stats  = mem_stats($sock, "items");
+my $last_evicted = $last_stats->{"items:31:evicted"};
+is ($last_evicted, "40", "check evicted");
diff --git a/memcached/t/issue_29.t b/memcached/t/issue_29.t
new file mode 100644 (file)
index 0000000..a585f97
--- /dev/null
@@ -0,0 +1,26 @@
+#!/usr/bin/perl
+
+use strict;
+use Test::More tests => 4;
+use FindBin qw($Bin);
+use lib "$Bin/lib";
+use MemcachedTest;
+
+my $server = new_memcached();
+my $sock = $server->sock;
+
+print $sock "set issue29 0 0 0\r\n\r\n";
+is (scalar <$sock>, "STORED\r\n", "stored issue29");
+
+my $first_stats  = mem_stats($sock, "slabs");
+my $first_used = $first_stats->{"1:used_chunks"};
+
+is(1, $first_used, "Used one");
+
+print $sock "set issue29_b 0 0 0\r\n\r\n";
+is (scalar <$sock>, "STORED\r\n", "stored issue29_b");
+
+my $second_stats  = mem_stats($sock, "slabs");
+my $second_used = $second_stats->{"1:used_chunks"};
+
+is(2, $second_used, "Used two")
diff --git a/memcached/t/issue_3.t b/memcached/t/issue_3.t
new file mode 100644 (file)
index 0000000..66aaf16
--- /dev/null
@@ -0,0 +1,47 @@
+#!/usr/bin/perl
+
+use strict;
+use Test::More tests => 8;
+use FindBin qw($Bin);
+use lib "$Bin/lib";
+use MemcachedTest;
+
+my $server = new_memcached();
+my $sock = $server->sock;
+my $key = "del_key";
+
+print $sock "delete $key\r\n";
+is (scalar <$sock>, "NOT_FOUND\r\n", "not found on delete");
+
+print $sock "delete $key 10\r\n";
+is (scalar <$sock>, "CLIENT_ERROR bad command line format."
+    . "  Usage: delete <key> [noreply]\r\n", "invalid delete");
+
+print $sock "add $key 0 0 1\r\nx\r\n";
+is (scalar <$sock>, "STORED\r\n", "Add before a broken delete.");
+
+print $sock "delete $key 10 noreply\r\n";
+# Does not reply
+# is (scalar <$sock>, "ERROR\r\n", "Even more invalid delete");
+
+print $sock "add $key 0 0 1\r\nx\r\n";
+is (scalar <$sock>, "NOT_STORED\r\n", "Failed to add after failed silent delete.");
+
+print $sock "delete $key noreply\r\n";
+# Will not reply, so let's do a set and check that.
+
+print $sock "set $key 0 0 1\r\nx\r\n";
+is (scalar <$sock>, "STORED\r\n", "Stored a key");
+
+print $sock "delete $key\r\n";
+is (scalar <$sock>, "DELETED\r\n", "Properly deleted");
+
+print $sock "set $key 0 0 1\r\nx\r\n";
+is (scalar <$sock>, "STORED\r\n", "Stored a key");
+
+print $sock "delete $key noreply\r\n";
+# will not reply, but a subsequent add will succeed
+
+print $sock "add $key 0 0 1\r\nx\r\n";
+is (scalar <$sock>, "STORED\r\n", "Add succeeded after deletion.");
+
diff --git a/memcached/t/issue_41.t b/memcached/t/issue_41.t
new file mode 100644 (file)
index 0000000..fb1a292
--- /dev/null
@@ -0,0 +1,39 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use POSIX qw(ceil);
+use Test::More tests => 691;
+use FindBin qw($Bin);
+use lib "$Bin/lib";
+use MemcachedTest;
+
+my $server = new_memcached();
+my $sock = $server->sock;
+
+my $factor = 2;
+my $val = "x" x $factor;
+my $key = '';
+
+# SET items of diverse size to the daemon so it can attempt
+# to return a large stats output for slabs
+for (my $i=0; $i<69; $i++) {
+    for (my $j=0; $j<10; $j++) {
+        $key = "$i:$j";
+        print $sock "set key$key 0 0 $factor\r\n$val\r\n";
+        is (scalar <$sock>, "STORED\r\n", "stored key$key");
+    }
+    $factor *= 1.2;
+    $factor = ceil($factor);
+    $val = "x" x $factor;
+}
+
+# This request will kill the daemon if it has not allocated
+# enough memory internally.
+my $stats = mem_stats($sock, "slabs");
+
+# Verify whether the daemon is still running or not by asking
+# it for statistics.
+print $sock "version\r\n";
+my $v = scalar <$sock>;
+ok(defined $v && length($v), "memcached didn't respond");
diff --git a/memcached/t/issue_42.t b/memcached/t/issue_42.t
new file mode 100644 (file)
index 0000000..299c1ec
--- /dev/null
@@ -0,0 +1,21 @@
+#!/usr/bin/perl
+
+use strict;
+use Test::More tests => 11;
+use FindBin qw($Bin);
+use lib "$Bin/lib";
+use MemcachedTest;
+
+my $server = new_memcached();
+my $sock = $server->sock;
+my $value = "B"x10;
+my $key = 0;
+
+for ($key = 0; $key < 10; $key++) {
+    print $sock "set key$key 0 0 10\r\n$value\r\n";
+    is (scalar <$sock>, "STORED\r\n", "stored key$key");
+}
+
+my $first_stats = mem_stats($sock, "slabs");
+my $req = $first_stats->{"1:mem_requested"};
+ok ($req == "640" || $req == "800", "Check allocated size");
diff --git a/memcached/t/issue_50.t b/memcached/t/issue_50.t
new file mode 100644 (file)
index 0000000..80876b2
--- /dev/null
@@ -0,0 +1,16 @@
+#!/usr/bin/perl
+
+use strict;
+use Test::More tests => 1;
+use FindBin qw($Bin);
+use lib "$Bin/lib";
+use MemcachedTest;
+
+my $server = new_memcached('-B binary');
+my $sock = $server->sock;
+
+$SIG{ALRM} = sub { die "alarm\n" };
+alarm(2);
+print $sock "Here's a bunch of garbage that doesn't look like the bin prot.";
+my $rv = <$sock>;
+ok(1, "Either the above worked and quit, or hung forever.");
diff --git a/memcached/t/issue_61.t b/memcached/t/issue_61.t
new file mode 100644 (file)
index 0000000..ec38a7d
--- /dev/null
@@ -0,0 +1,20 @@
+#!/usr/bin/perl
+
+use strict;
+use Test::More tests => 7;
+use FindBin qw($Bin);
+use lib "$Bin/lib";
+use MemcachedTest;
+
+my $server = new_memcached("-R 1");
+my $sock = $server->sock;
+
+print $sock "set foobar 0 0 5\r\nBubba\r\nset foobar 0 0 5\r\nBubba\r\nset foobar 0 0 5\r\nBubba\r\nset foobar 0 0 5\r\nBubba\r\nset foobar 0 0 5\r\nBubba\r\nset foobar 0 0 5\r\nBubba\r\n";
+is (scalar <$sock>, "STORED\r\n", "stored foobar");
+is (scalar <$sock>, "STORED\r\n", "stored foobar");
+is (scalar <$sock>, "STORED\r\n", "stored foobar");
+is (scalar <$sock>, "STORED\r\n", "stored foobar");
+is (scalar <$sock>, "STORED\r\n", "stored foobar");
+is (scalar <$sock>, "STORED\r\n", "stored foobar");
+my $stats = mem_stats($sock);
+is ($stats->{"conn_yields"}, "5", "Got a decent number of yields");
diff --git a/memcached/t/issue_67.t b/memcached/t/issue_67.t
new file mode 100644 (file)
index 0000000..c6b8b4c
--- /dev/null
@@ -0,0 +1,86 @@
+#!/usr/bin/perl
+
+use strict;
+use Test::More tests => 22;
+use FindBin qw($Bin);
+use lib "$Bin/lib";
+use MemcachedTest;
+use Carp qw(croak);
+
+use Cwd;
+my $builddir = getcwd;
+
+$ENV{'MEMCACHED_PORT_FILENAME'} = "/tmp/ports.$$";
+
+sub read_ports {
+    my %rv = ();
+    open(my $f, "/tmp/ports.$$") || die("Can't open ports file.");
+    while(<$f>) {
+        my ($type, $port) = split(/:\s+/);
+        $rv{$type} = $port + 0;
+    }
+    unlink "/tmp/ports.$$";
+    return %rv;
+}
+
+sub validate_port {
+    my ($name, $got, $expected) = @_;
+    # diag "Wanted $expected, got $got";
+    if ($expected == -1) {
+        ok(!defined($got), "$name expected no port, got $got");
+    } elsif ($expected == 0) {
+        ok($got != 11211, "$name expected random port (got $got)");
+    } else {
+        is($got, $expected, "$name");
+    }
+}
+
+sub run_server {
+    my ($args) = @_;
+
+    my $exe = "$builddir/memcached-debug";
+    croak("memcached binary doesn't exist.  Haven't run 'make' ?\n") unless -e $exe;
+
+    my $childpid = fork();
+
+    my $root = '';
+    $root = "-u root" if ($< == 0);
+    my $cmd = "$builddir/timedrun 10 $exe $root $args";
+
+    unless($childpid) {
+        exec $cmd;
+        exit; # NOTREACHED
+    }
+
+    for (1..20) {
+        if (-f "/tmp/ports.$$") {
+            return Memcached::Handle->new(pid  => $childpid);
+        }
+        select undef, undef, undef, 0.10;
+    }
+    croak "Failed to start server.";
+}
+
+sub when {
+    my ($name, $params, $expected_tcp, $expected_udp) = @_;
+
+    my $server = run_server($params);
+    my %ports = read_ports();
+
+    validate_port($name, $ports{'TCP INET'}, $expected_tcp);
+    validate_port($name, $ports{'UDP INET'}, $expected_udp);
+}
+
+# Disabling the defaults since it conflicts with a running instance.
+# when('no arguments', '', 11211, 11211);
+when('specifying tcp port', '-p 11212', 11212, 11212);
+when('specifying udp port', '-U 11222', 11222, 11222);
+when('specifying tcp ephemeral port', '-p -1', 0, 0);
+when('specifying udp ephemeral port', '-U -1', 0, 0);
+when('tcp port disabled', '-p 0', -1, -1);
+when('udp port disabled', '-U 0', -1, -1);
+when('specifying tcp and udp ports', '-p 11232 -U 11233', 11232, 11233);
+when('specifying tcp and disabling udp', '-p 11242 -U 0', 11242, -1);
+when('specifying udp and disabling tcp', '-p -1 -U 11252', 0, 11252);
+when('specifying tcp and ephemeral udp', '-p 11262 -U -1', 11262, 0);
+when('specifying udp and ephemeral tcp', '-p -1 -U 11272', 0, 11272);
diff --git a/memcached/t/issue_68.t b/memcached/t/issue_68.t
new file mode 100644 (file)
index 0000000..94e3e95
--- /dev/null
@@ -0,0 +1,21 @@
+#!/usr/bin/perl
+
+use strict;
+use Test::More tests => 996;
+use FindBin qw($Bin);
+use lib "$Bin/lib";
+use MemcachedTest;
+
+my $server = new_memcached();
+my $sock = $server->sock;
+
+for (my $keyi = 1; $keyi < 250; $keyi++) {
+    my $key = "x" x $keyi;
+    print $sock "set $key 0 0 1\r\n9\r\n";
+    is (scalar <$sock>, "STORED\r\n", "stored $key");
+    mem_get_is($sock, $key, "9");
+    print $sock "incr $key 1\r\n";
+    is (scalar <$sock>, "10\r\n", "incr $key to 10");
+    mem_get_is($sock, $key, "10");
+}
+
diff --git a/memcached/t/issue_70.t b/memcached/t/issue_70.t
new file mode 100644 (file)
index 0000000..95e39db
--- /dev/null
@@ -0,0 +1,22 @@
+#!/usr/bin/perl
+
+use strict;
+use Test::More tests => 4;
+use FindBin qw($Bin);
+use lib "$Bin/lib";
+use MemcachedTest;
+
+my $server = new_memcached();
+my $sock = $server->sock;
+
+print $sock "set issue70 0 0 0\r\n\r\n";
+is (scalar <$sock>, "STORED\r\n", "stored issue70");
+
+print $sock "set issue70 0 0 -1\r\n";
+is (scalar <$sock>, "CLIENT_ERROR bad command line format\r\n");
+
+print $sock "set issue70 0 0 4294967295\r\n";
+is (scalar <$sock>, "CLIENT_ERROR bad command line format\r\n");
+
+print $sock "set issue70 0 0 2147483647\r\nscoobyscoobydoo";
+is (scalar <$sock>, "CLIENT_ERROR bad command line format\r\n");
diff --git a/memcached/t/item_size_max.t b/memcached/t/item_size_max.t
new file mode 100644 (file)
index 0000000..85460fa
--- /dev/null
@@ -0,0 +1,52 @@
+#!/usr/bin/perl
+
+use strict;
+use Test::More tests => 7;
+use FindBin qw($Bin);
+use lib "$Bin/lib";
+use MemcachedTest;
+
+my $server = new_memcached();
+my $sock = $server->sock;
+
+my $stats = mem_stats($sock, ' settings');
+
+# Ensure default still works.
+is($stats->{item_size_max}, 1024 * 1024);
+$server->stop();
+
+# Should die.
+eval {
+    $server = new_memcached('-I 1000');
+};
+ok($@ && $@ =~ m/^Failed/, "Shouldn't start with < 1k item max");
+
+eval {
+    $server = new_memcached('-I 256m');
+};
+ok($@ && $@ =~ m/^Failed/, "Shouldn't start with > 128m item max");
+
+# Minimum.
+$server = new_memcached('-I 1024');
+my $stats = mem_stats($server->sock, ' settings');
+is($stats->{item_size_max}, 1024);
+$server->stop();
+
+# Reasonable but unreasonable.
+$server = new_memcached('-I 1049600');
+my $stats = mem_stats($server->sock, ' settings');
+is($stats->{item_size_max}, 1049600);
+$server->stop();
+
+# Suffix kilobytes.
+$server = new_memcached('-I 512k');
+my $stats = mem_stats($server->sock, ' settings');
+is($stats->{item_size_max}, 524288);
+$server->stop();
+
+# Suffix megabytes.
+$server = new_memcached('-I 32m');
+my $stats = mem_stats($server->sock, ' settings');
+is($stats->{item_size_max}, 33554432);
+$server->stop();
+
diff --git a/memcached/t/lib/MemcachedTest.pm b/memcached/t/lib/MemcachedTest.pm
new file mode 100644 (file)
index 0000000..88f37a2
--- /dev/null
@@ -0,0 +1,268 @@
+package MemcachedTest;
+use strict;
+use IO::Socket::INET;
+use IO::Socket::UNIX;
+use Exporter 'import';
+use Carp qw(croak);
+use vars qw(@EXPORT);
+
+# Instead of doing the substitution with Autoconf, we assume that
+# cwd == builddir.
+use Cwd;
+my $builddir = getcwd;
+
+
+@EXPORT = qw(new_memcached sleep mem_get_is mem_gets mem_gets_is mem_stats
+             supports_sasl free_port);
+
+sub sleep {
+    my $n = shift;
+    select undef, undef, undef, $n;
+}
+
+sub mem_stats {
+    my ($sock, $type) = @_;
+    $type = $type ? " $type" : "";
+    print $sock "stats$type\r\n";
+    my $stats = {};
+    while (<$sock>) {
+        last if /^(\.|END)/;
+        /^(STAT|ITEM) (\S+)\s+([^\r\n]+)/;
+        #print " slabs: $_";
+        $stats->{$2} = $3;
+    }
+    return $stats;
+}
+
+sub mem_get_is {
+    # works on single-line values only.  no newlines in value.
+    my ($sock_opts, $key, $val, $msg) = @_;
+    my $opts = ref $sock_opts eq "HASH" ? $sock_opts : {};
+    my $sock = ref $sock_opts eq "HASH" ? $opts->{sock} : $sock_opts;
+
+    my $expect_flags = $opts->{flags} || 0;
+    my $dval = defined $val ? "'$val'" : "<undef>";
+    $msg ||= "$key == $dval";
+
+    print $sock "get $key\r\n";
+    if (! defined $val) {
+        my $line = scalar <$sock>;
+        if ($line =~ /^VALUE/) {
+            $line .= scalar(<$sock>) . scalar(<$sock>);
+        }
+        Test::More::is($line, "END\r\n", $msg);
+    } else {
+        my $len = length($val);
+        my $body = scalar(<$sock>);
+        my $expected = "VALUE $key $expect_flags $len\r\n$val\r\nEND\r\n";
+        if (!$body || $body =~ /^END/) {
+            Test::More::is($body, $expected, $msg);
+            return;
+        }
+        $body .= scalar(<$sock>) . scalar(<$sock>);
+        Test::More::is($body, $expected, $msg);
+    }
+}
+
+sub mem_gets {
+    # works on single-line values only.  no newlines in value.
+    my ($sock_opts, $key) = @_;
+    my $opts = ref $sock_opts eq "HASH" ? $sock_opts : {};
+    my $sock = ref $sock_opts eq "HASH" ? $opts->{sock} : $sock_opts;
+    my $val;
+    my $expect_flags = $opts->{flags} || 0;
+
+    print $sock "gets $key\r\n";
+    my $response = <$sock>;
+    if ($response =~ /^END/) {
+        return "NOT_FOUND";
+    }
+    else
+    {
+        $response =~ /VALUE (.*) (\d+) (\d+) (\d+)/;
+        my $flags = $2;
+        my $len = $3;
+        my $identifier = $4;
+        read $sock, $val , $len;
+        # get the END
+        $_ = <$sock>;
+        $_ = <$sock>;
+
+        return ($identifier,$val);
+    }
+
+}
+sub mem_gets_is {
+    # works on single-line values only.  no newlines in value.
+    my ($sock_opts, $identifier, $key, $val, $msg) = @_;
+    my $opts = ref $sock_opts eq "HASH" ? $sock_opts : {};
+    my $sock = ref $sock_opts eq "HASH" ? $opts->{sock} : $sock_opts;
+
+    my $expect_flags = $opts->{flags} || 0;
+    my $dval = defined $val ? "'$val'" : "<undef>";
+    $msg ||= "$key == $dval";
+
+    print $sock "gets $key\r\n";
+    if (! defined $val) {
+        my $line = scalar <$sock>;
+        if ($line =~ /^VALUE/) {
+            $line .= scalar(<$sock>) . scalar(<$sock>);
+        }
+        Test::More::is($line, "END\r\n", $msg);
+    } else {
+        my $len = length($val);
+        my $body = scalar(<$sock>);
+        my $expected = "VALUE $key $expect_flags $len $identifier\r\n$val\r\nEND\r\n";
+        if (!$body || $body =~ /^END/) {
+            Test::More::is($body, $expected, $msg);
+            return;
+        }
+        $body .= scalar(<$sock>) . scalar(<$sock>);
+        Test::More::is($body, $expected, $msg);
+    }
+}
+
+sub free_port {
+    my $type = shift || "tcp";
+    my $sock;
+    my $port;
+    while (!$sock) {
+        $port = int(rand(20000)) + 30000;
+        $sock = IO::Socket::INET->new(LocalAddr => '127.0.0.1',
+                                      LocalPort => $port,
+                                      Proto     => $type,
+                                      ReuseAddr => 1);
+    }
+    return $port;
+}
+
+sub supports_udp {
+    my $output = `$builddir/memcached-debug -h`;
+    return 0 if $output =~ /^memcached 1\.1\./;
+    return 1;
+}
+
+sub supports_sasl {
+    my $output = `$builddir/memcached-debug -h`;
+    return 1 if $output =~ /sasl/i;
+    return 0;
+}
+
+sub new_memcached {
+    my ($args, $passed_port) = @_;
+    my $port = $passed_port || free_port();
+    my $host = '127.0.0.1';
+
+    if ($ENV{T_MEMD_USE_DAEMON}) {
+        my ($host, $port) = ($ENV{T_MEMD_USE_DAEMON} =~ m/^([^:]+):(\d+)$/);
+        my $conn = IO::Socket::INET->new(PeerAddr => "$host:$port");
+        if ($conn) {
+            return Memcached::Handle->new(conn => $conn,
+                                          host => $host,
+                                          port => $port);
+        }
+        croak("Failed to connect to specified memcached server.") unless $conn;
+    }
+
+    my $udpport = free_port("udp");
+    $args .= " -p $port";
+    if (supports_udp()) {
+        $args .= " -U $udpport";
+    }
+    if ($< == 0) {
+        $args .= " -u root";
+    }
+
+    my $childpid = fork();
+
+    my $exe = "$builddir/memcached-debug";
+    croak("memcached binary doesn't exist.  Haven't run 'make' ?\n") unless -e $exe;
+    croak("memcached binary not executable\n") unless -x _;
+
+    unless ($childpid) {
+        exec "$builddir/timedrun 600 $exe $args";
+        exit; # never gets here.
+    }
+
+    # unix domain sockets
+    if ($args =~ /-s (\S+)/) {
+        sleep 1;
+        my $filename = $1;
+        my $conn = IO::Socket::UNIX->new(Peer => $filename) ||
+            croak("Failed to connect to unix domain socket: $! '$filename'");
+
+        return Memcached::Handle->new(pid  => $childpid,
+                                      conn => $conn,
+                                      domainsocket => $filename,
+                                      host => $host,
+                                      port => $port);
+    }
+
+    # try to connect / find open port, only if we're not using unix domain
+    # sockets
+
+    for (1..20) {
+        my $conn = IO::Socket::INET->new(PeerAddr => "127.0.0.1:$port");
+        if ($conn) {
+            return Memcached::Handle->new(pid  => $childpid,
+                                          conn => $conn,
+                                          udpport => $udpport,
+                                          host => $host,
+                                          port => $port);
+        }
+        select undef, undef, undef, 0.10;
+    }
+    croak("Failed to startup/connect to memcached server.");
+}
+
+############################################################################
+package Memcached::Handle;
+sub new {
+    my ($class, %params) = @_;
+    return bless \%params, $class;
+}
+
+sub DESTROY {
+    my $self = shift;
+    kill 2, $self->{pid};
+}
+
+sub stop {
+    my $self = shift;
+    kill 15, $self->{pid};
+}
+
+sub host { $_[0]{host} }
+sub port { $_[0]{port} }
+sub udpport { $_[0]{udpport} }
+
+sub sock {
+    my $self = shift;
+
+    if ($self->{conn} && ($self->{domainsocket} || getpeername($self->{conn}))) {
+        return $self->{conn};
+    }
+    return $self->new_sock;
+}
+
+sub new_sock {
+    my $self = shift;
+    if ($self->{domainsocket}) {
+        return IO::Socket::UNIX->new(Peer => $self->{domainsocket});
+    } else {
+        return IO::Socket::INET->new(PeerAddr => "$self->{host}:$self->{port}");
+    }
+}
+
+sub new_udp_sock {
+    my $self = shift;
+    return IO::Socket::INET->new(PeerAddr => '127.0.0.1',
+                                 PeerPort => $self->{udpport},
+                                 Proto    => 'udp',
+                                 LocalAddr => '127.0.0.1',
+                                 LocalPort => MemcachedTest::free_port('udp'),
+        );
+
+}
+
+1;
diff --git a/memcached/t/line-lengths.t b/memcached/t/line-lengths.t
new file mode 100755 (executable)
index 0000000..829f4d0
--- /dev/null
@@ -0,0 +1,25 @@
+#!/usr/bin/perl
+use strict;
+use FindBin qw($Bin);
+our @files;
+
+BEGIN {
+    chdir "$Bin/.." or die;
+    @files = ( "doc/protocol.txt" );
+}
+
+use Test::More tests => scalar(@files);
+
+foreach my $f (@files) {
+    open(my $fh, $f) or die("Can't open $f");
+    my @long_lines = ();
+    my $line_number = 0;
+    while(<$fh>) {
+        $line_number++;
+        if(length($_) > 80) {
+            push(@long_lines, $line_number);
+        }
+    }
+    close($fh);
+    ok(@long_lines == 0, "$f has a long lines:  @long_lines");
+}
diff --git a/memcached/t/lru.t b/memcached/t/lru.t
new file mode 100755 (executable)
index 0000000..86a8d1e
--- /dev/null
@@ -0,0 +1,58 @@
+#!/usr/bin/perl
+
+use strict;
+use Test::More tests => 149;
+use FindBin qw($Bin);
+use lib "$Bin/lib";
+use MemcachedTest;
+
+# assuming max slab is 1M and default mem is 64M
+my $server = new_memcached();
+my $sock = $server->sock;
+
+# create a big value for the largest slab
+my $max = 1024 * 1024;
+my $big = 'x' x (1024 * 1024 - 250);
+
+ok(length($big) > 512 * 1024);
+ok(length($big) < 1024 * 1024);
+
+# test that an even bigger value is rejected while we're here
+my $too_big = $big . $big . $big;
+my $len = length($too_big);
+print $sock "set too_big 0 0 $len\r\n$too_big\r\n";
+is(scalar <$sock>, "SERVER_ERROR object too large for cache\r\n", "too_big not stored");
+
+# set the big value
+my $len = length($big);
+print $sock "set big 0 0 $len\r\n$big\r\n";
+is(scalar <$sock>, "STORED\r\n", "stored big");
+mem_get_is($sock, "big", $big);
+
+# no evictions yet
+my $stats = mem_stats($sock);
+is($stats->{"evictions"}, "0", "no evictions to start");
+
+# set many big items, enough to get evictions
+for (my $i = 0; $i < 100; $i++) {
+  print $sock "set item_$i 0 0 $len\r\n$big\r\n";
+  is(scalar <$sock>, "STORED\r\n", "stored item_$i");
+}
+
+# some evictions should have happened
+my $stats = mem_stats($sock);
+my $evictions = int($stats->{"evictions"});
+ok($evictions == 37, "some evictions happened");
+
+# the first big value should be gone
+mem_get_is($sock, "big", undef);
+
+# the earliest items should be gone too
+for (my $i = 0; $i < $evictions - 1; $i++) {
+  mem_get_is($sock, "item_$i", undef);
+}
+
+# check that the non-evicted are the right ones
+for (my $i = $evictions - 1; $i < $evictions + 4; $i++) {
+  mem_get_is($sock, "item_$i", $big);
+}
diff --git a/memcached/t/maxconns.t b/memcached/t/maxconns.t
new file mode 100755 (executable)
index 0000000..14b5eae
--- /dev/null
@@ -0,0 +1,28 @@
+#!/usr/bin/perl
+# NOTE: This test never worked. Memcached would ignore maxconns requests lower
+# than the current ulimit. Test needs to be updated.
+
+use strict;
+use warnings;
+
+use Test::More tests => 11;
+
+use FindBin qw($Bin);
+use lib "$Bin/lib";
+use MemcachedTest;
+
+
+# start up a server with 10 maximum connections
+my $server = new_memcached('-c 100');
+my $sock = $server->sock;
+my @sockets;
+
+ok(defined($sock), 'Connection 0');
+push (@sockets, $sock);
+
+
+foreach my $conn (1..10) {
+  $sock = $server->new_sock;
+  ok(defined($sock), "Made connection $conn");
+  push(@sockets, $sock);
+}
diff --git a/memcached/t/multiversioning.t b/memcached/t/multiversioning.t
new file mode 100755 (executable)
index 0000000..df9eab4
--- /dev/null
@@ -0,0 +1,46 @@
+#!/usr/bin/perl
+
+use strict;
+use Test::More tests => 13;
+use FindBin qw($Bin);
+use lib "$Bin/lib";
+use MemcachedTest;
+
+my $server = new_memcached();
+my $sock  = $server->sock;
+my $sock2 = $server->new_sock;
+
+ok($sock != $sock2, "have two different connections open");
+
+# set large value
+my $size   = 256 * 1024;  # 256 kB
+my $bigval = "0123456789abcdef" x ($size / 16);
+$bigval =~ s/^0/\[/; $bigval =~ s/f$/\]/;
+my $bigval2 = uc($bigval);
+
+print $sock "set big 0 0 $size\r\n$bigval\r\n";
+is(scalar <$sock>, "STORED\r\n", "stored foo");
+mem_get_is($sock, "big", $bigval, "big value got correctly");
+
+print $sock "get big\r\n";
+my $buf;
+is(read($sock, $buf, $size / 2), $size / 2, "read half the answer back");
+like($buf, qr/VALUE big/, "buf has big value header in it");
+like($buf, qr/abcdef/, "buf has some data in it");
+unlike($buf, qr/abcde\]/, "buf doesn't yet close");
+
+# sock2 interrupts (maybe sock1 is slow) and deletes stuff:
+print $sock2 "delete big\r\n";
+is(scalar <$sock2>, "DELETED\r\n", "deleted big from sock2 while sock1's still reading it");
+mem_get_is($sock2, "big", undef, "nothing from sock2 now.  gone from namespace.");
+print $sock2 "set big 0 0 $size\r\n$bigval2\r\n";
+is(scalar <$sock2>, "STORED\r\n", "stored big w/ val2");
+mem_get_is($sock2, "big", $bigval2, "big value2 got correctly");
+
+# sock1 resumes reading...
+$buf .= <$sock>;
+$buf .= <$sock>;
+like($buf, qr/abcde\]/, "buf now closes");
+
+# and if sock1 reads again, it's the uppercase version:
+mem_get_is($sock, "big", $bigval2, "big value2 got correctly from sock1");
diff --git a/memcached/t/noreply.t b/memcached/t/noreply.t
new file mode 100644 (file)
index 0000000..54a3f13
--- /dev/null
@@ -0,0 +1,47 @@
+#!/usr/bin/perl
+
+use strict;
+use Test::More tests => 9;
+use FindBin qw($Bin);
+use lib "$Bin/lib";
+use MemcachedTest;
+
+
+my $server = new_memcached();
+my $sock = $server->sock;
+
+
+# Test that commands can take 'noreply' parameter.
+print $sock "flush_all noreply\r\n";
+print $sock "flush_all 0 noreply\r\n";
+
+print $sock "verbosity 0 noreply\r\n";
+
+print $sock "add noreply:foo 0 0 1 noreply\r\n1\r\n";
+mem_get_is($sock, "noreply:foo", "1");
+
+print $sock "set noreply:foo 0 0 1 noreply\r\n2\r\n";
+mem_get_is($sock, "noreply:foo", "2");
+
+print $sock "replace noreply:foo 0 0 1 noreply\r\n3\r\n";
+mem_get_is($sock, "noreply:foo", "3");
+
+print $sock "append noreply:foo 0 0 1 noreply\r\n4\r\n";
+mem_get_is($sock, "noreply:foo", "34");
+
+print $sock "prepend noreply:foo 0 0 1 noreply\r\n5\r\n";
+my @result = mem_gets($sock, "noreply:foo");
+ok($result[1] eq "534");
+
+print $sock "cas noreply:foo 0 0 1 $result[0] noreply\r\n6\r\n";
+mem_get_is($sock, "noreply:foo", "6");
+
+print $sock "incr noreply:foo 3 noreply\r\n";
+mem_get_is($sock, "noreply:foo", "9");
+
+print $sock "decr noreply:foo 2 noreply\r\n";
+mem_get_is($sock, "noreply:foo", "7");
+
+print $sock "delete noreply:foo noreply\r\n";
+mem_get_is($sock, "noreply:foo");
+
diff --git a/memcached/t/sasl/memcached.conf b/memcached/t/sasl/memcached.conf
new file mode 100644 (file)
index 0000000..aacc510
--- /dev/null
@@ -0,0 +1,3 @@
+mech_list: plain cram-md5
+log_level: 5
+sasldb_path: /tmp/test-memcached.sasldb
diff --git a/memcached/t/slabs_reassign.t b/memcached/t/slabs_reassign.t
new file mode 100644 (file)
index 0000000..cf4b6f8
--- /dev/null
@@ -0,0 +1,71 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use Test::More tests => 131;
+use FindBin qw($Bin);
+use lib "$Bin/lib";
+use MemcachedTest;
+
+# Enable manual slab reassign, cap at 6 slabs
+my $server = new_memcached('-o slab_reassign -m 4');
+my $stats = mem_stats($server->sock, ' settings');
+is($stats->{slab_reassign}, "yes");
+
+my $sock = $server->sock;
+
+# Fill a largeish slab until it evicts (honors the -m 6)
+my $bigdata = 'x' x 70000; # slab 31
+for (1 .. 60) {
+    print $sock "set bfoo$_ 0 0 70000\r\n", $bigdata, "\r\n";
+    is(scalar <$sock>, "STORED\r\n", "stored key");
+}
+
+# Fill a smaller slab until it evicts
+my $smalldata = 'y' x 20000; # slab 25
+for (1 .. 60) {
+    print $sock "set sfoo$_ 0 0 20000\r\n", $smalldata, "\r\n";
+    is(scalar <$sock>, "STORED\r\n", "stored key");
+}
+
+my $items_before = mem_stats($sock, "items");
+isnt($items_before->{"items:31:evicted"}, 0, "slab 31 evicted is nonzero");
+isnt($items_before->{"items:25:evicted"}, 0, "slab 25 evicted is nonzero");
+
+my $slabs_before = mem_stats($sock, "slabs");
+# Move a large slab to the smaller slab
+print $sock "slabs reassign 31 25\r\n";
+is(scalar <$sock>, "OK\r\n", "slab rebalancer started");
+
+# Still working out how/if to signal the thread. For now, just sleep.
+sleep 2;
+
+# Check that stats counters increased
+my $slabs_after = mem_stats($sock, "slabs");
+$stats = mem_stats($sock);
+
+isnt($stats->{slabs_moved}, 0, "slabs moved is nonzero");
+
+# Check that slab stats reflect the change
+ok($slabs_before->{"31:total_pages"} != $slabs_after->{"31:total_pages"},
+    "slab 31 pagecount changed");
+ok($slabs_before->{"25:total_pages"} != $slabs_after->{"25:total_pages"},
+    "slab 25 pagecount changed");
+
+# Try to move another slab, see that it complains
+print $sock "slabs reassign 31 25\r\n";
+like(scalar <$sock>, qr/^NOTFULL/, "Cannot re-run against class with empty space");
+
+# Try to move a page backwards. Should complain that source class isn't "safe"
+# to move from.
+print $sock "slabs reassign 25 31\r\n";
+like(scalar <$sock>, qr/^UNSAFE/, "Cannot move an unsafe slab back");
+
+# Try to insert items into both slabs
+print $sock "set bfoo51 0 0 70000\r\n", $bigdata, "\r\n";
+is(scalar <$sock>, "STORED\r\n", "stored key");
+
+print $sock "set sfoo51 0 0 20000\r\n", $smalldata, "\r\n";
+is(scalar <$sock>, "STORED\r\n", "stored key");
+
+# Do need to come up with better automated tests for this.
diff --git a/memcached/t/stats-detail.t b/memcached/t/stats-detail.t
new file mode 100644 (file)
index 0000000..b7099a2
--- /dev/null
@@ -0,0 +1,63 @@
+#!/usr/bin/perl
+
+use strict;
+use Test::More tests => 24;
+use FindBin qw($Bin);
+use lib "$Bin/lib";
+use MemcachedTest;
+
+my $server = new_memcached();
+my $sock = $server->sock;
+my $expire;
+
+print $sock "stats detail dump\r\n";
+is(scalar <$sock>, "END\r\n", "verified empty stats at start");
+
+print $sock "stats detail on\r\n";
+is(scalar <$sock>, "OK\r\n", "detail collection turned on");
+
+print $sock "set foo:123 0 0 6\r\nfooval\r\n";
+is(scalar <$sock>, "STORED\r\n", "stored foo");
+
+print $sock "stats detail dump\r\n";
+is(scalar <$sock>, "PREFIX foo get 0 hit 0 set 1 del 0\r\n", "details after set");
+is(scalar <$sock>, "END\r\n", "end of details");
+
+mem_get_is($sock, "foo:123", "fooval");
+print $sock "stats detail dump\r\n";
+is(scalar <$sock>, "PREFIX foo get 1 hit 1 set 1 del 0\r\n", "details after get with hit");
+is(scalar <$sock>, "END\r\n", "end of details");
+
+mem_get_is($sock, "foo:124", undef);
+
+print $sock "stats detail dump\r\n";
+is(scalar <$sock>, "PREFIX foo get 2 hit 1 set 1 del 0\r\n", "details after get without hit");
+is(scalar <$sock>, "END\r\n", "end of details");
+
+print $sock "delete foo:125\r\n";
+is(scalar <$sock>, "NOT_FOUND\r\n", "sent delete command");
+
+print $sock "stats detail dump\r\n";
+is(scalar <$sock>, "PREFIX foo get 2 hit 1 set 1 del 1\r\n", "details after delete");
+is(scalar <$sock>, "END\r\n", "end of details");
+
+print $sock "stats reset\r\n";
+is(scalar <$sock>, "RESET\r\n", "stats cleared");
+
+print $sock "stats detail dump\r\n";
+is(scalar <$sock>, "END\r\n", "empty stats after clear");
+
+mem_get_is($sock, "foo:123", "fooval");
+print $sock "stats detail dump\r\n";
+is(scalar <$sock>, "PREFIX foo get 1 hit 1 set 0 del 0\r\n", "details after clear and get");
+is(scalar <$sock>, "END\r\n", "end of details");
+
+print $sock "stats detail off\r\n";
+is(scalar <$sock>, "OK\r\n", "detail collection turned off");
+
+mem_get_is($sock, "foo:124", undef);
+
+mem_get_is($sock, "foo:123", "fooval");
+print $sock "stats detail dump\r\n";
+is(scalar <$sock>, "PREFIX foo get 1 hit 1 set 0 del 0\r\n", "details after stats turned off");
+is(scalar <$sock>, "END\r\n", "end of details");
diff --git a/memcached/t/stats.t b/memcached/t/stats.t
new file mode 100755 (executable)
index 0000000..97a5b7c
--- /dev/null
@@ -0,0 +1,196 @@
+#!/usr/bin/perl
+
+use strict;
+use Test::More tests => 95;
+use FindBin qw($Bin);
+use lib "$Bin/lib";
+use MemcachedTest;
+
+my $server = new_memcached();
+my $sock = $server->sock;
+
+
+## Output looks like this:
+##
+## STAT pid 22969
+## STAT uptime 13
+## STAT time 1259170891
+## STAT version 1.4.3
+## STAT libevent 1.4.13-stable.
+## STAT pointer_size 32
+## STAT rusage_user 0.001198
+## STAT rusage_system 0.003523
+## STAT curr_connections 10
+## STAT total_connections 11
+## STAT connection_structures 11
+## STAT cmd_get 0
+## STAT cmd_set 0
+## STAT cmd_flush 0
+## STAT get_hits 0
+## STAT get_misses 0
+## STAT delete_misses 0
+## STAT delete_hits 0
+## STAT incr_misses 0
+## STAT incr_hits 0
+## STAT decr_misses 0
+## STAT decr_hits 0
+## STAT cas_misses 0
+## STAT cas_hits 0
+## STAT cas_badval 0
+## STAT auth_cmds 0
+## STAT auth_unknowns 0
+## STAT bytes_read 7
+## STAT bytes_written 0
+## STAT limit_maxbytes 67108864
+## STAT accepting_conns 1
+## STAT listen_disabled_num 0
+## STAT threads 4
+## STAT conn_yields 0
+## STAT bytes 0
+## STAT curr_items 0
+## STAT total_items 0
+## STAT evictions 0
+## STAT reclaimed 0
+
+# note that auth stats are tested in auth specfic tests
+
+
+my $stats = mem_stats($sock);
+
+# Test number of keys
+is(scalar(keys(%$stats)), 48, "48 stats values");
+
+# Test initial state
+foreach my $key (qw(curr_items total_items bytes cmd_get cmd_set get_hits evictions get_misses
+                 bytes_written delete_hits delete_misses incr_hits incr_misses decr_hits
+                 decr_misses listen_disabled_num)) {
+    is($stats->{$key}, 0, "initial $key is zero");
+}
+is($stats->{accepting_conns}, 1, "initial accepting_conns is one");
+
+# Do some operations
+
+print $sock "set foo 0 0 6\r\nfooval\r\n";
+is(scalar <$sock>, "STORED\r\n", "stored foo");
+mem_get_is($sock, "foo", "fooval");
+
+my $stats = mem_stats($sock);
+
+foreach my $key (qw(total_items curr_items cmd_get cmd_set get_hits)) {
+    is($stats->{$key}, 1, "after one set/one get $key is 1");
+}
+
+my $cache_dump = mem_stats($sock, " cachedump 1 100");
+ok(defined $cache_dump->{'foo'}, "got foo from cachedump");
+
+print $sock "delete foo\r\n";
+is(scalar <$sock>, "DELETED\r\n", "deleted foo");
+
+my $stats = mem_stats($sock);
+is($stats->{delete_hits}, 1);
+is($stats->{delete_misses}, 0);
+
+print $sock "delete foo\r\n";
+is(scalar <$sock>, "NOT_FOUND\r\n", "shouldn't delete foo again");
+
+my $stats = mem_stats($sock);
+is($stats->{delete_hits}, 1);
+is($stats->{delete_misses}, 1);
+
+# incr stats
+
+sub check_incr_stats {
+    my ($ih, $im, $dh, $dm) = @_;
+    my $stats = mem_stats($sock);
+
+    is($stats->{incr_hits}, $ih);
+    is($stats->{incr_misses}, $im);
+    is($stats->{decr_hits}, $dh);
+    is($stats->{decr_misses}, $dm);
+}
+
+print $sock "incr i 1\r\n";
+is(scalar <$sock>, "NOT_FOUND\r\n", "shouldn't incr a missing thing");
+check_incr_stats(0, 1, 0, 0);
+
+print $sock "decr d 1\r\n";
+is(scalar <$sock>, "NOT_FOUND\r\n", "shouldn't decr a missing thing");
+check_incr_stats(0, 1, 0, 1);
+
+print $sock "set n 0 0 1\r\n0\r\n";
+is(scalar <$sock>, "STORED\r\n", "stored n");
+
+print $sock "incr n 3\r\n";
+is(scalar <$sock>, "3\r\n", "incr works");
+check_incr_stats(1, 1, 0, 1);
+
+print $sock "decr n 1\r\n";
+is(scalar <$sock>, "2\r\n", "decr works");
+check_incr_stats(1, 1, 1, 1);
+
+# cas stats
+
+sub check_cas_stats {
+    my ($ch, $cm, $cb) = @_;
+    my $stats = mem_stats($sock);
+
+    is($stats->{cas_hits}, $ch);
+    is($stats->{cas_misses}, $cm);
+    is($stats->{cas_badval}, $cb);
+}
+
+check_cas_stats(0, 0, 0);
+
+print $sock "cas c 0 0 1 99999999\r\nz\r\n";
+is(scalar <$sock>, "NOT_FOUND\r\n", "missed cas");
+check_cas_stats(0, 1, 0);
+
+print $sock "set c 0 0 1\r\nx\r\n";
+is(scalar <$sock>, "STORED\r\n", "stored c");
+my ($id, $v) = mem_gets($sock, 'c');
+is('x', $v, 'got the expected value');
+
+print $sock "cas c 0 0 1 99999999\r\nz\r\n";
+is(scalar <$sock>, "EXISTS\r\n", "missed cas");
+check_cas_stats(0, 1, 1);
+my ($newid, $v) = mem_gets($sock, 'c');
+is('x', $v, 'got the expected value');
+
+print $sock "cas c 0 0 1 $id\r\nz\r\n";
+is(scalar <$sock>, "STORED\r\n", "good cas");
+check_cas_stats(1, 1, 1);
+my ($newid, $v) = mem_gets($sock, 'c');
+is('z', $v, 'got the expected value');
+
+my $settings = mem_stats($sock, ' settings');
+is(1024, $settings->{'maxconns'});
+is('NULL', $settings->{'domain_socket'});
+is('on', $settings->{'evictions'});
+is('yes', $settings->{'cas_enabled'});
+is('no', $settings->{'auth_enabled_sasl'});
+
+print $sock "stats reset\r\n";
+is(scalar <$sock>, "RESET\r\n", "good stats reset");
+
+my $stats = mem_stats($sock);
+is(0, $stats->{'cmd_get'});
+is(0, $stats->{'cmd_set'});
+is(0, $stats->{'get_hits'});
+is(0, $stats->{'get_misses'});
+is(0, $stats->{'delete_misses'});
+is(0, $stats->{'delete_hits'});
+is(0, $stats->{'incr_misses'});
+is(0, $stats->{'incr_hits'});
+is(0, $stats->{'decr_misses'});
+is(0, $stats->{'decr_hits'});
+is(0, $stats->{'cas_misses'});
+is(0, $stats->{'cas_hits'});
+is(0, $stats->{'cas_badval'});
+is(0, $stats->{'evictions'});
+is(0, $stats->{'reclaimed'});
+
+print $sock "flush_all\r\n";
+is(scalar <$sock>, "OK\r\n", "flushed");
+
+my $stats = mem_stats($sock);
+is($stats->{cmd_flush}, 1, "after one flush cmd_flush is 1");
diff --git a/memcached/t/stress-memcached.pl b/memcached/t/stress-memcached.pl
new file mode 100755 (executable)
index 0000000..aeacbc2
--- /dev/null
@@ -0,0 +1,101 @@
+#!/usr/bin/perl
+#
+
+use strict;
+use lib '../../api/perl/lib';
+use Cache::Memcached;
+use Time::HiRes qw(time);
+
+unless (@ARGV == 2) {
+    die "Usage: stress-memcached.pl ip:port threads\n";
+}
+
+my $host = shift;
+my $threads = shift;
+
+my $memc = new Cache::Memcached;
+$memc->set_servers([$host]);
+
+unless ($memc->set("foo", "bar") &&
+        $memc->get("foo") eq "bar") {
+    die "memcached not running at $host ?\n";
+}
+$memc->disconnect_all();
+
+
+my $running = 0;
+while (1) {
+    if ($running < $threads) {
+        my $cpid = fork();
+        if ($cpid) {
+            $running++;
+            #print "Launched $cpid.  Running $running threads.\n";
+        } else {
+            stress();
+            exit 0;
+        }
+    } else {
+        wait();
+        $running--;
+    }
+}
+
+sub stress {
+    undef $memc;
+    $memc = new Cache::Memcached;
+    $memc->set_servers([$host]);
+
+    my ($t1, $t2);
+    my $start = sub { $t1 = time(); };
+    my $stop = sub {
+        my $op = shift;
+        $t2 = time();
+        my $td = sprintf("%0.3f", $t2 - $t1);
+        if ($td > 0.25) { print "Took $td seconds for: $op\n"; }
+    };
+
+    my $max = rand(50);
+    my $sets = 0;
+
+    for (my $i = 0; $i < $max; $i++) {
+        my $key = key($i);
+        my $set = $memc->set($key, $key);
+        $sets++ if $set;
+    }
+
+    for (1..int(rand(500))) {
+        my $rand = int(rand($max));
+        my $key = key($rand);
+        my $meth = int(rand(3));
+        my $exp = int(rand(3));
+        undef $exp unless $exp;
+        $start->();
+        if ($meth == 0) {
+            $memc->add($key, $key, $exp);
+            $stop->("add");
+        } elsif ($meth == 1) {
+            $memc->delete($key);
+            $stop->("delete");
+        } else {
+            $memc->set($key, $key, $exp);
+            $stop->("set");
+        }
+        $rand = int(rand($max));
+        $key = key($rand);
+        $start->();
+        my $v = $memc->get($key);
+        $stop->("get");
+        if ($v && $v ne $key) { die "Bogus: $v for key $rand\n"; }
+    }
+
+    $start->();
+    my $multi = $memc->get_multi(map { key(int(rand($max))) } (1..$max));
+    $stop->("get_multi");
+}
+
+sub key {
+    my $n = shift;
+    $_ = sprintf("%04d", $n);
+    if ($n % 2) { $_ .= "a"x20; }
+    $_;
+}
diff --git a/memcached/t/touch.t b/memcached/t/touch.t
new file mode 100755 (executable)
index 0000000..dd2eba1
--- /dev/null
@@ -0,0 +1,23 @@
+#!/usr/bin/perl
+
+use strict;
+use Test::More tests => 4;
+use FindBin qw($Bin);
+use lib "$Bin/lib";
+use MemcachedTest;
+
+
+my $server = new_memcached();
+my $sock = $server->sock;
+
+# set foo (and should get it)
+print $sock "set foo 0 2 6\r\nfooval\r\n";
+is(scalar <$sock>, "STORED\r\n", "stored foo");
+mem_get_is($sock, "foo", "fooval");
+
+# touch it
+print $sock "touch foo 10\r\n";
+is(scalar <$sock>, "TOUCHED\r\n", "touched foo");
+
+sleep 2;
+mem_get_is($sock, "foo", "fooval");
diff --git a/memcached/t/udp.t b/memcached/t/udp.t
new file mode 100755 (executable)
index 0000000..d79b07a
--- /dev/null
@@ -0,0 +1,260 @@
+#!/usr/bin/perl
+
+use strict;
+use Test::More tests => 48;
+use FindBin qw($Bin);
+use lib "$Bin/lib";
+use MemcachedTest;
+
+use constant IS_ASCII         => 0;
+use constant IS_BINARY        => 1;
+use constant ENTRY_EXISTS     => 0;
+use constant ENTRY_MISSING    => 1;
+use constant BIN_REQ_MAGIC    => 0x80;
+use constant BIN_RES_MAGIC    => 0x81;
+use constant CMD_GET          => 0x00;
+use constant CMD_SET          => 0x01;
+use constant CMD_ADD          => 0x02;
+use constant CMD_REPLACE      => 0x03;
+use constant CMD_DELETE       => 0x04;
+use constant CMD_INCR         => 0x05;
+use constant CMD_DECR         => 0x06;
+use constant CMD_APPEND       => 0x0E;
+use constant CMD_PREPEND      => 0x0F;
+use constant REQ_PKT_FMT      => "CCnCCnNNNN";
+use constant RES_PKT_FMT      => "CCnCCnNNNN";
+use constant INCRDECR_PKT_FMT => "NNNNN";
+use constant MIN_RECV_BYTES   => length(pack(RES_PKT_FMT));
+
+
+my $server = new_memcached();
+my $sock = $server->sock;
+
+# set foo (and should get it)
+print $sock "set foo 0 0 6\r\nfooval\r\n";
+is(scalar <$sock>, "STORED\r\n", "stored foo");
+mem_get_is($sock, "foo", "fooval");
+
+my $usock = $server->new_udp_sock
+    or die "Can't bind : $@\n";
+
+# testing sequence of request ids
+for my $offt (1, 1, 2) {
+    my $req = 160 + $offt;
+    my $res = send_udp_request($usock, $req, "get foo\r\n");
+    ok($res, "got result");
+    is(keys %$res, 1, "one key (one packet)");
+    ok($res->{0}, "only got seq number 0");
+    is(substr($res->{0}, 8), "VALUE foo 0 6\r\nfooval\r\nEND\r\n");
+    is(hexify(substr($res->{0}, 0, 2)), hexify(pack("n", $req)), "udp request number in response ($req) is correct");
+}
+
+# op tests
+for my $prot (::IS_ASCII,::IS_BINARY) {
+    udp_set_test($prot,45,"aval$prot","1",0,0);
+    udp_set_test($prot,45,"bval$prot","abcd" x 1024,0,0);
+    udp_get_test($prot,45,"aval$prot","1",::ENTRY_EXISTS);
+    udp_get_test($prot,45,"404$prot","1",::ENTRY_MISSING);
+    udp_incr_decr_test($prot,45,"aval$prot","1","incr",1);
+    udp_incr_decr_test($prot,45,"aval$prot","1","decr",2);
+    udp_delete_test($prot,45,"aval$prot");
+}
+
+sub udp_set_test {
+    my ($protocol, $req_id, $key, $value, $flags, $exp) = @_;
+    my $req = "";
+    my $val_len = length($value);
+
+    if ($protocol == ::IS_ASCII) {
+        $req = "set $key $flags $exp $val_len\r\n$value\r\n";
+    } elsif ($protocol == ::IS_BINARY) {
+        my $key_len = length($key);
+        my $extra = pack "NN",$flags,$exp;
+        my $extra_len = length($extra);
+        my $total_len = $val_len + $extra_len + $key_len;
+        $req = pack(::REQ_PKT_FMT, ::BIN_REQ_MAGIC, ::CMD_SET, $key_len, $extra_len, 0, 0, $total_len, 0, 0, 0);
+        $req .=  $extra . $key . $value;
+    }
+
+    my $datagrams = send_udp_request($usock, $req_id, $req);
+    my $resp = construct_udp_message($datagrams);
+
+    if ($protocol == ::IS_ASCII) {
+        is($resp,"STORED\r\n","Store key $key using ASCII protocol");
+    } elsif ($protocol == ::IS_BINARY) {
+        my ($resp_magic, $resp_op_code, $resp_key_len, $resp_extra_len, $resp_data_type, $resp_status, $resp_total_len,
+            $resp_opaque, $resp_ident_hi, $resp_ident_lo) = unpack(::RES_PKT_FMT, $resp);
+        is($resp_status,"0","Store key $key using binary protocol");
+    }
+}
+
+sub udp_get_test {
+    my ($protocol, $req_id, $key, $value, $exists) = @_;
+    my $key_len = length($key);
+    my $value_len = length($value);
+    my $req = "";
+
+    if ($protocol == ::IS_ASCII) {
+        $req = "get $key\r\n";
+    } elsif ($protocol == ::IS_BINARY) {
+        $req = pack(::REQ_PKT_FMT, ::BIN_REQ_MAGIC, ::CMD_GET, $key_len, 0, 0, 0, $key_len, 0, 0, 0);
+        $req .= $key;
+    }
+
+    my $datagrams = send_udp_request($usock, $req_id, $req);
+    my $resp = construct_udp_message($datagrams);
+
+    if ($protocol == ::IS_ASCII) {
+        if ($exists == ::ENTRY_EXISTS) {
+            is($resp,"VALUE $key 0 $value_len\r\n$value\r\nEND\r\n","Retrieve entry with key $key using ASCII protocol");
+        } else {
+            is($resp,"END\r\n","Retrieve non existing entry with key $key using ASCII protocol");
+        }
+    } elsif ($protocol == ::IS_BINARY) {
+        my ($resp_magic, $resp_op_code, $resp_key_len, $resp_extra_len, $resp_data_type, $resp_status, $resp_total_len,
+            $resp_opaque, $resp_ident_hi, $resp_ident_lo) = unpack(::RES_PKT_FMT, $resp);
+        if ($exists == ::ENTRY_EXISTS) {
+            is($resp_status,"0","Retrieve entry with key $key using binary protocol");
+            is(substr($resp,::MIN_RECV_BYTES + $resp_extra_len + $resp_key_len, $value_len),$value,"Value for key $key retrieved with binary protocol matches");
+        } else {
+            is($resp_status,"1","Retrieve non existing entry with key $key using binary protocol");
+        }
+    }
+}
+
+sub udp_delete_test {
+    my ($protocol, $req_id, $key) = @_;
+    my $req = "";
+    my $key_len = length($key);
+
+    if ($protocol == ::IS_ASCII) {
+        $req = "delete $key\r\n";
+    } elsif ($protocol == ::IS_BINARY) {
+        $req = pack(::REQ_PKT_FMT, ::BIN_REQ_MAGIC, ::CMD_DELETE, $key_len, 0, 0, 0, $key_len, 0, 0, 0);
+        $req .= $key;
+    }
+
+    my $datagrams = send_udp_request($usock, $req_id, $req);
+    my $resp = construct_udp_message($datagrams);
+
+    if ($protocol == ::IS_ASCII) {
+        is($resp,"DELETED\r\n","Delete key $key using ASCII protocol");
+    } elsif ($protocol == ::IS_BINARY) {
+        my ($resp_magic, $resp_op_code, $resp_key_len, $resp_extra_len, $resp_data_type, $resp_status, $resp_total_len,
+            $resp_opaque, $resp_ident_hi, $resp_ident_lo) = unpack(::RES_PKT_FMT, $resp);
+        is($resp_status,"0","Delete key $key using binary protocol");
+    }
+}
+
+sub udp_incr_decr_test {
+    my ($protocol, $req_id, $key, $val, $optype, $init_val) = @_;
+    my $req = "";
+    my $key_len = length($key);
+    my $expected_value = 0;
+    my $acmd = "incr";
+    my $bcmd = ::CMD_INCR;
+    if ($optype eq "incr") {
+        $expected_value = $init_val + $val;
+    } else {
+        $acmd = "decr";
+        $bcmd = ::CMD_DECR;
+        $expected_value = $init_val - $val;
+    }
+
+    if ($protocol == ::IS_ASCII) {
+        $req = "$acmd $key $val\r\n";
+    } elsif ($protocol == ::IS_BINARY) {
+        my $extra = pack(::INCRDECR_PKT_FMT, ($val / 2 ** 32),($val % 2 ** 32), 0, 0, 0);
+        my $extra_len = length($extra);
+        $req = pack(::REQ_PKT_FMT, ::BIN_REQ_MAGIC, $bcmd, $key_len, $extra_len, 0, 0, $key_len + $extra_len, 0, 0, 0);
+        $req .= $extra . $key;
+    }
+
+    my $datagrams = send_udp_request($usock, $req_id, $req);
+    my $resp = construct_udp_message($datagrams);
+
+    if ($protocol == ::IS_ASCII) {
+        is($resp,"$expected_value\r\n","perform $acmd math operation on key $key with ASCII protocol");
+    } elsif ($protocol == ::IS_BINARY) {
+        my ($resp_magic, $resp_op_code, $resp_key_len, $resp_extra_len, $resp_data_type, $resp_status, $resp_total_len,
+            $resp_opaque, $resp_ident_hi, $resp_ident_lo) = unpack(::RES_PKT_FMT, $resp);
+        is($resp_status,"0","perform $acmd math operation on key $key with binary protocol");
+        my ($resp_hi,$resp_lo) = unpack("NN",substr($resp,::MIN_RECV_BYTES + $resp_extra_len + $resp_key_len,
+                                                    $resp_total_len - $resp_extra_len - $resp_key_len));
+        is(($resp_hi * 2 ** 32) + $resp_lo,$expected_value,"validate result of binary protocol math operation $acmd . Expected value $expected_value")
+    }
+}
+
+sub construct_udp_message {
+    my $datagrams = shift;
+    my $num_datagram = keys (%$datagrams);
+    my $msg = "";
+    my $cur_dg ="";
+    my $cur_udp_header ="";
+    for (my $cur_dg_index = 0; $cur_dg_index < $num_datagram; $cur_dg_index++) {
+        $cur_dg = $datagrams->{$cur_dg_index};
+        isnt($cur_dg,"","missing datagram for segment $cur_dg_index");
+        $cur_udp_header=substr($cur_dg, 0, 8);
+        $msg .= substr($cur_dg,8);
+    }
+    return $msg;
+}
+
+sub hexify {
+    my $val = shift;
+    $val =~ s/(.)/sprintf("%02x", ord($1))/egs;
+    return $val;
+}
+
+# returns undef on select timeout, or hashref of "seqnum" -> payload (including headers)
+# verifies that resp_id is equal to id sent in request
+# ensures consistency in num packets that make up response
+sub send_udp_request {
+    my ($sock, $reqid, $req) = @_;
+
+    my $pkt = pack("nnnn", $reqid, 0, 1, 0);  # request id (opaque), seq num, #packets, reserved (must be 0)
+    $pkt .= $req;
+    my $fail = sub {
+        my $msg = shift;
+        warn "  FAILING send_udp because: $msg\n";
+        return undef;
+    };
+    return $fail->("send") unless send($sock, $pkt, 0);
+
+    my $ret = {};
+
+    my $got = 0;   # packets got
+    my $numpkts = undef;
+
+    while (!defined($numpkts) || $got < $numpkts) {
+        my $rin = '';
+        vec($rin, fileno($sock), 1) = 1;
+        my $rout;
+        return $fail->("timeout after $got packets") unless
+            select($rout = $rin, undef, undef, 1.5);
+
+        my $res;
+        my $sender = $sock->recv($res, 1500, 0);
+        my ($resid, $seq, $this_numpkts, $resv) = unpack("nnnn", substr($res, 0, 8));
+        die "Response ID of $resid doesn't match request if of $reqid" unless $resid == $reqid;
+        die "Reserved area not zero" unless $resv == 0;
+        die "num packets changed midstream!" if defined $numpkts && $this_numpkts != $numpkts;
+        $numpkts = $this_numpkts;
+        $ret->{$seq} = $res;
+        $got++;
+    }
+    return $ret;
+}
+
+
+__END__
+    $sender = recv($usock, $ans, 1050, 0);
+
+__END__
+    $usock->send
+
+
+    ($hispaddr = recv(SOCKET, $rtime, 4, 0))        || die "recv: $!";
+($port, $hisiaddr) = sockaddr_in($hispaddr);
+$host = gethostbyaddr($hisiaddr, AF_INET);
+$histime = unpack("N", $rtime) - $SECS_of_70_YEARS ;
diff --git a/memcached/t/unixsocket.t b/memcached/t/unixsocket.t
new file mode 100755 (executable)
index 0000000..492215e
--- /dev/null
@@ -0,0 +1,24 @@
+#!/usr/bin/perl
+
+use strict;
+use Test::More tests => 3;
+use FindBin qw($Bin);
+use lib "$Bin/lib";
+use MemcachedTest;
+
+my $filename = "/tmp/memcachetest$$";
+
+my $server = new_memcached("-s $filename");
+my $sock = $server->sock;
+
+ok(-S $filename, "creating unix domain socket $filename");
+
+# set foo (and should get it)
+print $sock "set foo 0 0 6\r\nfooval\r\n";
+
+is(scalar <$sock>, "STORED\r\n", "stored foo");
+mem_get_is($sock, "foo", "fooval");
+
+unlink($filename);
+
+## Just some basic stuff for now...
diff --git a/memcached/t/whitespace.t b/memcached/t/whitespace.t
new file mode 100755 (executable)
index 0000000..22c610c
--- /dev/null
@@ -0,0 +1,40 @@
+#!/usr/bin/perl
+use strict;
+use FindBin qw($Bin);
+our @files;
+
+BEGIN {
+    chdir "$Bin/.." or die;
+
+    my @exempted = qw(Makefile.am ChangeLog doc/Makefile.am);
+    push(@exempted, glob("doc/*.xml"));
+    push(@exempted, glob("doc/xml2rfc/*.xsl"));
+    push(@exempted, glob("m4/*backport*m4"));
+    my %exempted_hash = map { $_ => 1 } @exempted;
+
+    my @stuff = split /\0/, `git ls-files -z -c -m -o --exclude-standard`;
+    @files = grep { ! $exempted_hash{$_} } @stuff;
+
+    # We won't find any files if git isn't installed.  If git isn't
+    # installed, they're probably not doing any useful development, or
+    # at the very least am will clean up whitespace when we receive
+    # their patch.
+    unless (@files) {
+        use Test::More;
+        plan skip_all => "Skipping tests probably because you don't have git.";
+        exit 0;
+    }
+}
+
+use Test::More tests => scalar(@files);
+
+foreach my $f (@files) {
+    open(my $fh, $f) or die;
+    my $before = do { local $/; <$fh>; };
+    close ($fh);
+    my $after = $before;
+    $after =~ s/\t/    /g;
+    $after =~ s/ +$//mg;
+    $after .= "\n" unless $after =~ /\n$/;
+    ok ($after eq $before, "$f (see devtools/clean-whitespace.pl)");
+}
diff --git a/memcached/testapp.c b/memcached/testapp.c
new file mode 100644 (file)
index 0000000..aba0b90
--- /dev/null
@@ -0,0 +1,1938 @@
+/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+#undef NDEBUG
+#include <pthread.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <netdb.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <assert.h>
+#include <string.h>
+#include <unistd.h>
+#include <netinet/in.h>
+#include <fcntl.h>
+
+#include "config.h"
+#include "cache.h"
+#include "util.h"
+#include "protocol_binary.h"
+
+#define TMP_TEMPLATE "/tmp/test_file.XXXXXXX"
+
+enum test_return { TEST_SKIP, TEST_PASS, TEST_FAIL };
+
+static pid_t server_pid;
+static in_port_t port;
+static int sock;
+static bool allow_closed_read = false;
+
+static enum test_return cache_create_test(void)
+{
+    cache_t *cache = cache_create("test", sizeof(uint32_t), sizeof(char*),
+                                  NULL, NULL);
+    assert(cache != NULL);
+    cache_destroy(cache);
+    return TEST_PASS;
+}
+
+const uint64_t constructor_pattern = 0xdeadcafebabebeef;
+
+static int cache_constructor(void *buffer, void *notused1, int notused2) {
+    uint64_t *ptr = buffer;
+    *ptr = constructor_pattern;
+    return 0;
+}
+
+static enum test_return cache_constructor_test(void)
+{
+    cache_t *cache = cache_create("test", sizeof(uint64_t), sizeof(uint64_t),
+                                  cache_constructor, NULL);
+    assert(cache != NULL);
+    uint64_t *ptr = cache_alloc(cache);
+    uint64_t pattern = *ptr;
+    cache_free(cache, ptr);
+    cache_destroy(cache);
+    return (pattern == constructor_pattern) ? TEST_PASS : TEST_FAIL;
+}
+
+static int cache_fail_constructor(void *buffer, void *notused1, int notused2) {
+    return 1;
+}
+
+static enum test_return cache_fail_constructor_test(void)
+{
+    enum test_return ret = TEST_PASS;
+
+    cache_t *cache = cache_create("test", sizeof(uint64_t), sizeof(uint64_t),
+                                  cache_fail_constructor, NULL);
+    assert(cache != NULL);
+    uint64_t *ptr = cache_alloc(cache);
+    if (ptr != NULL) {
+        ret = TEST_FAIL;
+    }
+    cache_destroy(cache);
+    return ret;
+}
+
+static void *destruct_data = 0;
+
+static void cache_destructor(void *buffer, void *notused) {
+    destruct_data = buffer;
+}
+
+static enum test_return cache_destructor_test(void)
+{
+    cache_t *cache = cache_create("test", sizeof(uint32_t), sizeof(char*),
+                                  NULL, cache_destructor);
+    assert(cache != NULL);
+    char *ptr = cache_alloc(cache);
+    cache_free(cache, ptr);
+    cache_destroy(cache);
+
+    return (ptr == destruct_data) ? TEST_PASS : TEST_FAIL;
+}
+
+static enum test_return cache_reuse_test(void)
+{
+    int ii;
+    cache_t *cache = cache_create("test", sizeof(uint32_t), sizeof(char*),
+                                  NULL, NULL);
+    char *ptr = cache_alloc(cache);
+    cache_free(cache, ptr);
+    for (ii = 0; ii < 100; ++ii) {
+        char *p = cache_alloc(cache);
+        assert(p == ptr);
+        cache_free(cache, ptr);
+    }
+    cache_destroy(cache);
+    return TEST_PASS;
+}
+
+
+static enum test_return cache_bulkalloc(size_t datasize)
+{
+    cache_t *cache = cache_create("test", datasize, sizeof(char*),
+                                  NULL, NULL);
+#define ITERATIONS 1024
+    void *ptr[ITERATIONS];
+
+    for (int ii = 0; ii < ITERATIONS; ++ii) {
+        ptr[ii] = cache_alloc(cache);
+        assert(ptr[ii] != 0);
+        memset(ptr[ii], 0xff, datasize);
+    }
+
+    for (int ii = 0; ii < ITERATIONS; ++ii) {
+        cache_free(cache, ptr[ii]);
+    }
+
+#undef ITERATIONS
+    cache_destroy(cache);
+    return TEST_PASS;
+}
+
+static enum test_return test_issue_161(void)
+{
+    enum test_return ret = cache_bulkalloc(1);
+    if (ret == TEST_PASS) {
+        ret = cache_bulkalloc(512);
+    }
+
+    return ret;
+}
+
+static enum test_return cache_redzone_test(void)
+{
+#ifndef HAVE_UMEM_H
+    cache_t *cache = cache_create("test", sizeof(uint32_t), sizeof(char*),
+                                  NULL, NULL);
+
+    /* Ignore SIGABORT */
+    struct sigaction old_action;
+    struct sigaction action = { .sa_handler = SIG_IGN, .sa_flags = 0};
+    sigemptyset(&action.sa_mask);
+    sigaction(SIGABRT, &action, &old_action);
+
+    /* check memory debug.. */
+    char *p = cache_alloc(cache);
+    char old = *(p - 1);
+    *(p - 1) = 0;
+    cache_free(cache, p);
+    assert(cache_error == -1);
+    *(p - 1) = old;
+
+    p[sizeof(uint32_t)] = 0;
+    cache_free(cache, p);
+    assert(cache_error == 1);
+
+    /* restore signal handler */
+    sigaction(SIGABRT, &old_action, NULL);
+
+    cache_destroy(cache);
+
+    return TEST_PASS;
+#else
+    return TEST_SKIP;
+#endif
+}
+
+static enum test_return test_safe_strtoul(void) {
+    uint32_t val;
+    assert(safe_strtoul("123", &val));
+    assert(val == 123);
+    assert(safe_strtoul("+123", &val));
+    assert(val == 123);
+    assert(!safe_strtoul("", &val));  // empty
+    assert(!safe_strtoul("123BOGUS", &val));  // non-numeric
+    assert(!safe_strtoul(" issue221", &val));  // non-numeric
+    /* Not sure what it does, but this works with ICC :/
+       assert(!safe_strtoul("92837498237498237498029383", &val)); // out of range
+    */
+
+    // extremes:
+    assert(safe_strtoul("4294967295", &val)); // 2**32 - 1
+    assert(val == 4294967295L);
+    /* This actually works on 64-bit ubuntu
+       assert(!safe_strtoul("4294967296", &val)); // 2**32
+    */
+    assert(!safe_strtoul("-1", &val));  // negative
+    return TEST_PASS;
+}
+
+
+static enum test_return test_safe_strtoull(void) {
+    uint64_t val;
+    assert(safe_strtoull("123", &val));
+    assert(val == 123);
+    assert(safe_strtoull("+123", &val));
+    assert(val == 123);
+    assert(!safe_strtoull("", &val));  // empty
+    assert(!safe_strtoull("123BOGUS", &val));  // non-numeric
+    assert(!safe_strtoull("92837498237498237498029383", &val)); // out of range
+    assert(!safe_strtoull(" issue221", &val));  // non-numeric
+
+    // extremes:
+    assert(safe_strtoull("18446744073709551615", &val)); // 2**64 - 1
+    assert(val == 18446744073709551615ULL);
+    assert(!safe_strtoull("18446744073709551616", &val)); // 2**64
+    assert(!safe_strtoull("-1", &val));  // negative
+    return TEST_PASS;
+}
+
+static enum test_return test_safe_strtoll(void) {
+    int64_t val;
+    assert(safe_strtoll("123", &val));
+    assert(val == 123);
+    assert(safe_strtoll("+123", &val));
+    assert(val == 123);
+    assert(safe_strtoll("-123", &val));
+    assert(val == -123);
+    assert(!safe_strtoll("", &val));  // empty
+    assert(!safe_strtoll("123BOGUS", &val));  // non-numeric
+    assert(!safe_strtoll("92837498237498237498029383", &val)); // out of range
+    assert(!safe_strtoll(" issue221", &val));  // non-numeric
+
+    // extremes:
+    assert(!safe_strtoll("18446744073709551615", &val)); // 2**64 - 1
+    assert(safe_strtoll("9223372036854775807", &val)); // 2**63 - 1
+    assert(val == 9223372036854775807LL);
+    /*
+      assert(safe_strtoll("-9223372036854775808", &val)); // -2**63
+      assert(val == -9223372036854775808LL);
+    */
+    assert(!safe_strtoll("-9223372036854775809", &val)); // -2**63 - 1
+
+    // We'll allow space to terminate the string.  And leading space.
+    assert(safe_strtoll(" 123 foo", &val));
+    assert(val == 123);
+    return TEST_PASS;
+}
+
+static enum test_return test_safe_strtol(void) {
+    int32_t val;
+    assert(safe_strtol("123", &val));
+    assert(val == 123);
+    assert(safe_strtol("+123", &val));
+    assert(val == 123);
+    assert(safe_strtol("-123", &val));
+    assert(val == -123);
+    assert(!safe_strtol("", &val));  // empty
+    assert(!safe_strtol("123BOGUS", &val));  // non-numeric
+    assert(!safe_strtol("92837498237498237498029383", &val)); // out of range
+    assert(!safe_strtol(" issue221", &val));  // non-numeric
+
+    // extremes:
+    /* This actually works on 64-bit ubuntu
+       assert(!safe_strtol("2147483648", &val)); // (expt 2.0 31.0)
+    */
+    assert(safe_strtol("2147483647", &val)); // (- (expt 2.0 31) 1)
+    assert(val == 2147483647L);
+    /* This actually works on 64-bit ubuntu
+       assert(!safe_strtol("-2147483649", &val)); // (- (expt -2.0 31) 1)
+    */
+
+    // We'll allow space to terminate the string.  And leading space.
+    assert(safe_strtol(" 123 foo", &val));
+    assert(val == 123);
+    return TEST_PASS;
+}
+
+/**
+ * Function to start the server and let it listen on a random port
+ *
+ * @param port_out where to store the TCP port number the server is
+ *                 listening on
+ * @param daemon set to true if you want to run the memcached server
+ *               as a daemon process
+ * @return the pid of the memcached server
+ */
+static pid_t start_server(in_port_t *port_out, bool daemon, int timeout) {
+    char environment[80];
+    snprintf(environment, sizeof(environment),
+             "MEMCACHED_PORT_FILENAME=/tmp/ports.%lu", (long)getpid());
+    char *filename= environment + strlen("MEMCACHED_PORT_FILENAME=");
+    char pid_file[80];
+    snprintf(pid_file, sizeof(pid_file), "/tmp/pid.%lu", (long)getpid());
+
+    remove(filename);
+    remove(pid_file);
+
+#ifdef __sun
+    /* I want to name the corefiles differently so that they don't
+       overwrite each other
+    */
+    char coreadm[128];
+    snprintf(coreadm, sizeof(coreadm),
+             "coreadm -p core.%%f.%%p %lu", (unsigned long)getpid());
+    system(coreadm);
+#endif
+
+    pid_t pid = fork();
+    assert(pid != -1);
+
+    if (pid == 0) {
+        /* Child */
+        char *argv[20];
+        int arg = 0;
+        char tmo[24];
+        snprintf(tmo, sizeof(tmo), "%u", timeout);
+
+        putenv(environment);
+#ifdef __sun
+        putenv("LD_PRELOAD=watchmalloc.so.1");
+        putenv("MALLOC_DEBUG=WATCH");
+#endif
+
+        if (!daemon) {
+            argv[arg++] = "./timedrun";
+            argv[arg++] = tmo;
+        }
+        argv[arg++] = "./memcached-debug";
+        argv[arg++] = "-p";
+        argv[arg++] = "-1";
+        argv[arg++] = "-U";
+        argv[arg++] = "0";
+        /* Handle rpmbuild and the like doing this as root */
+        if (getuid() == 0) {
+            argv[arg++] = "-u";
+            argv[arg++] = "root";
+        }
+        if (daemon) {
+            argv[arg++] = "-d";
+            argv[arg++] = "-P";
+            argv[arg++] = pid_file;
+        }
+#ifdef MESSAGE_DEBUG
+         argv[arg++] = "-vvv";
+#endif
+        argv[arg++] = NULL;
+        assert(execv(argv[0], argv) != -1);
+    }
+
+    /* Yeah just let us "busy-wait" for the file to be created ;-) */
+    while (access(filename, F_OK) == -1) {
+        usleep(10);
+    }
+
+    FILE *fp = fopen(filename, "r");
+    if (fp == NULL) {
+        fprintf(stderr, "Failed to open the file containing port numbers: %s\n",
+                strerror(errno));
+        assert(false);
+    }
+
+    *port_out = (in_port_t)-1;
+    char buffer[80];
+    while ((fgets(buffer, sizeof(buffer), fp)) != NULL) {
+        if (strncmp(buffer, "TCP INET: ", 10) == 0) {
+            int32_t val;
+            assert(safe_strtol(buffer + 10, &val));
+            *port_out = (in_port_t)val;
+        }
+    }
+    fclose(fp);
+    assert(remove(filename) == 0);
+
+    if (daemon) {
+        /* loop and wait for the pid file.. There is a potential race
+         * condition that the server just created the file but isn't
+         * finished writing the content, but I'll take the chance....
+         */
+        while (access(pid_file, F_OK) == -1) {
+            usleep(10);
+        }
+
+        fp = fopen(pid_file, "r");
+        if (fp == NULL) {
+            fprintf(stderr, "Failed to open pid file: %s\n",
+                    strerror(errno));
+            assert(false);
+        }
+        assert(fgets(buffer, sizeof(buffer), fp) != NULL);
+        fclose(fp);
+
+        int32_t val;
+        assert(safe_strtol(buffer, &val));
+        pid = (pid_t)val;
+    }
+
+    return pid;
+}
+
+static enum test_return test_issue_44(void) {
+    in_port_t port;
+    pid_t pid = start_server(&port, true, 15);
+    assert(kill(pid, SIGHUP) == 0);
+    sleep(1);
+    assert(kill(pid, SIGTERM) == 0);
+
+    return TEST_PASS;
+}
+
+static struct addrinfo *lookuphost(const char *hostname, in_port_t port)
+{
+    struct addrinfo *ai = 0;
+    struct addrinfo hints = { .ai_family = AF_UNSPEC,
+                              .ai_protocol = IPPROTO_TCP,
+                              .ai_socktype = SOCK_STREAM };
+    char service[NI_MAXSERV];
+    int error;
+
+    (void)snprintf(service, NI_MAXSERV, "%d", port);
+    if ((error = getaddrinfo(hostname, service, &hints, &ai)) != 0) {
+       if (error != EAI_SYSTEM) {
+          fprintf(stderr, "getaddrinfo(): %s\n", gai_strerror(error));
+       } else {
+          perror("getaddrinfo()");
+       }
+    }
+
+    return ai;
+}
+
+static int connect_server(const char *hostname, in_port_t port, bool nonblock)
+{
+    struct addrinfo *ai = lookuphost(hostname, port);
+    int sock = -1;
+    if (ai != NULL) {
+       if ((sock = socket(ai->ai_family, ai->ai_socktype,
+                          ai->ai_protocol)) != -1) {
+          if (connect(sock, ai->ai_addr, ai->ai_addrlen) == -1) {
+             fprintf(stderr, "Failed to connect socket: %s\n",
+                     strerror(errno));
+             close(sock);
+             sock = -1;
+          } else if (nonblock) {
+              int flags = fcntl(sock, F_GETFL, 0);
+              if (flags < 0 || fcntl(sock, F_SETFL, flags | O_NONBLOCK) < 0) {
+                  fprintf(stderr, "Failed to enable nonblocking mode: %s\n",
+                          strerror(errno));
+                  close(sock);
+                  sock = -1;
+              }
+          }
+       } else {
+          fprintf(stderr, "Failed to create socket: %s\n", strerror(errno));
+       }
+
+       freeaddrinfo(ai);
+    }
+    return sock;
+}
+
+static enum test_return test_vperror(void) {
+    int rv = 0;
+    int oldstderr = dup(STDERR_FILENO);
+    char tmpl[sizeof(TMP_TEMPLATE)+1];
+    strncpy(tmpl, TMP_TEMPLATE, sizeof(TMP_TEMPLATE)+1);
+
+    int newfile = mkstemp(tmpl);
+    assert(newfile > 0);
+    rv = dup2(newfile, STDERR_FILENO);
+    assert(rv == STDERR_FILENO);
+    rv = close(newfile);
+    assert(rv == 0);
+
+    errno = EIO;
+    vperror("Old McDonald had a farm.  %s", "EI EIO");
+
+    /* Restore stderr */
+    rv = dup2(oldstderr, STDERR_FILENO);
+    assert(rv == STDERR_FILENO);
+
+
+    /* Go read the file */
+    char buf[80] = { 0 };
+    FILE *efile = fopen(tmpl, "r");
+    assert(efile);
+    char *prv = fgets(buf, sizeof(buf), efile);
+    assert(prv);
+    fclose(efile);
+
+    unlink(tmpl);
+
+    char expected[80] = { 0 };
+    snprintf(expected, sizeof(expected),
+             "Old McDonald had a farm.  EI EIO: %s\n", strerror(EIO));
+
+    /*
+    fprintf(stderr,
+            "\nExpected:  ``%s''"
+            "\nGot:       ``%s''\n", expected, buf);
+    */
+
+    return strcmp(expected, buf) == 0 ? TEST_PASS : TEST_FAIL;
+}
+
+static void send_ascii_command(const char *buf) {
+    off_t offset = 0;
+    const char* ptr = buf;
+    size_t len = strlen(buf);
+
+    do {
+        ssize_t nw = write(sock, ptr + offset, len - offset);
+        if (nw == -1) {
+            if (errno != EINTR) {
+                fprintf(stderr, "Failed to write: %s\n", strerror(errno));
+                abort();
+            }
+        } else {
+            offset += nw;
+        }
+    } while (offset < len);
+}
+
+/*
+ * This is a dead slow single byte read, but it should only read out
+ * _one_ response and I don't have an input buffer... The current
+ * implementation only supports single-line responses, so if you want to use
+ * it for get commands you need to implement that first ;-)
+ */
+static void read_ascii_response(char *buffer, size_t size) {
+    off_t offset = 0;
+    bool need_more = true;
+    do {
+        ssize_t nr = read(sock, buffer + offset, 1);
+        if (nr == -1) {
+            if (errno != EINTR) {
+                fprintf(stderr, "Failed to read: %s\n", strerror(errno));
+                abort();
+            }
+        } else {
+            assert(nr == 1);
+            if (buffer[offset] == '\n') {
+                need_more = false;
+                buffer[offset + 1] = '\0';
+            }
+            offset += nr;
+            assert(offset + 1 < size);
+        }
+    } while (need_more);
+}
+
+static enum test_return test_issue_92(void) {
+    char buffer[1024];
+
+    close(sock);
+    sock = connect_server("127.0.0.1", port, false);
+
+    send_ascii_command("stats cachedump 1 0 0\r\n");
+    read_ascii_response(buffer, sizeof(buffer));
+    assert(strncmp(buffer, "END", strlen("END")) == 0);
+
+    send_ascii_command("stats cachedump 200 0 0\r\n");
+    read_ascii_response(buffer, sizeof(buffer));
+    assert(strncmp(buffer, "CLIENT_ERROR", strlen("CLIENT_ERROR")) == 0);
+
+    close(sock);
+    sock = connect_server("127.0.0.1", port, false);
+    return TEST_PASS;
+}
+
+static enum test_return test_issue_102(void) {
+    char buffer[4096];
+    memset(buffer, ' ', sizeof(buffer));
+    buffer[sizeof(buffer) - 1] = '\0';
+
+    close(sock);
+    sock = connect_server("127.0.0.1", port, false);
+
+    send_ascii_command(buffer);
+    /* verify that the server closed the connection */
+    assert(read(sock, buffer, sizeof(buffer)) == 0);
+    close(sock);
+    sock = connect_server("127.0.0.1", port, false);
+
+    snprintf(buffer, sizeof(buffer), "gets ");
+    size_t offset = 5;
+    while (offset < 4000) {
+        offset += snprintf(buffer + offset, sizeof(buffer) - offset,
+                           "%010u ", (unsigned int)offset);
+    }
+
+    send_ascii_command(buffer);
+    usleep(250);
+
+    send_ascii_command("\r\n");
+    char rsp[80];
+    read_ascii_response(rsp, sizeof(rsp));
+    assert(strncmp(rsp, "END", strlen("END")) == 0);
+    buffer[3]= ' ';
+    send_ascii_command(buffer);
+    usleep(250);
+    send_ascii_command("\r\n");
+    read_ascii_response(rsp, sizeof(rsp));
+    assert(strncmp(rsp, "END", strlen("END")) == 0);
+
+    memset(buffer, ' ', sizeof(buffer));
+    int len = snprintf(buffer + 101, sizeof(buffer) - 101, "gets foo");
+    buffer[101 + len] = ' ';
+    buffer[sizeof(buffer) - 1] = '\0';
+    send_ascii_command(buffer);
+    /* verify that the server closed the connection */
+    assert(read(sock, buffer, sizeof(buffer)) == 0);
+
+    close(sock);
+    sock = connect_server("127.0.0.1", port, false);
+
+    return TEST_PASS;
+}
+
+static enum test_return start_memcached_server(void) {
+    server_pid = start_server(&port, false, 600);
+    sock = connect_server("127.0.0.1", port, false);
+    return TEST_PASS;
+}
+
+static enum test_return stop_memcached_server(void) {
+    close(sock);
+    assert(kill(server_pid, SIGTERM) == 0);
+    return TEST_PASS;
+}
+
+static void safe_send(const void* buf, size_t len, bool hickup)
+{
+    off_t offset = 0;
+    const char* ptr = buf;
+#ifdef MESSAGE_DEBUG
+    uint8_t val = *ptr;
+    assert(val == (uint8_t)0x80);
+    fprintf(stderr, "About to send %lu bytes:", (unsigned long)len);
+    for (int ii = 0; ii < len; ++ii) {
+        if (ii % 4 == 0) {
+            fprintf(stderr, "\n   ");
+        }
+        val = *(ptr + ii);
+        fprintf(stderr, " 0x%02x", val);
+    }
+    fprintf(stderr, "\n");
+    usleep(500);
+#endif
+
+    do {
+        size_t num_bytes = len - offset;
+        if (hickup) {
+            if (num_bytes > 1024) {
+                num_bytes = (rand() % 1023) + 1;
+            }
+        }
+
+        ssize_t nw = write(sock, ptr + offset, num_bytes);
+        if (nw == -1) {
+            if (errno != EINTR) {
+                fprintf(stderr, "Failed to write: %s\n", strerror(errno));
+                abort();
+            }
+        } else {
+            if (hickup) {
+                usleep(100);
+            }
+            offset += nw;
+        }
+    } while (offset < len);
+}
+
+static bool safe_recv(void *buf, size_t len) {
+    if (len == 0) {
+        return true;
+    }
+    off_t offset = 0;
+    do {
+        ssize_t nr = read(sock, ((char*)buf) + offset, len - offset);
+        if (nr == -1) {
+            if (errno != EINTR) {
+                fprintf(stderr, "Failed to read: %s\n", strerror(errno));
+                abort();
+            }
+        } else {
+            if (nr == 0 && allow_closed_read) {
+                return false;
+            }
+            assert(nr != 0);
+            offset += nr;
+        }
+    } while (offset < len);
+
+    return true;
+}
+
+static bool safe_recv_packet(void *buf, size_t size) {
+    protocol_binary_response_no_extras *response = buf;
+    assert(size > sizeof(*response));
+    if (!safe_recv(response, sizeof(*response))) {
+        return false;
+    }
+    response->message.header.response.keylen = ntohs(response->message.header.response.keylen);
+    response->message.header.response.status = ntohs(response->message.header.response.status);
+    response->message.header.response.bodylen = ntohl(response->message.header.response.bodylen);
+
+    size_t len = sizeof(*response);
+
+    char *ptr = buf;
+    ptr += len;
+    if (!safe_recv(ptr, response->message.header.response.bodylen)) {
+        return false;
+    }
+
+#ifdef MESSAGE_DEBUG
+    usleep(500);
+    ptr = buf;
+    len += response->message.header.response.bodylen;
+    uint8_t val = *ptr;
+    assert(val == (uint8_t)0x81);
+    fprintf(stderr, "Received %lu bytes:", (unsigned long)len);
+    for (int ii = 0; ii < len; ++ii) {
+        if (ii % 4 == 0) {
+            fprintf(stderr, "\n   ");
+        }
+        val = *(ptr + ii);
+        fprintf(stderr, " 0x%02x", val);
+    }
+    fprintf(stderr, "\n");
+#endif
+    return true;
+}
+
+static off_t storage_command(char*buf,
+                             size_t bufsz,
+                             uint8_t cmd,
+                             const void* key,
+                             size_t keylen,
+                             const void* dta,
+                             size_t dtalen,
+                             uint32_t flags,
+                             uint32_t exp) {
+    /* all of the storage commands use the same command layout */
+    protocol_binary_request_set *request = (void*)buf;
+    assert(bufsz > sizeof(*request) + keylen + dtalen);
+
+    memset(request, 0, sizeof(*request));
+    request->message.header.request.magic = PROTOCOL_BINARY_REQ;
+    request->message.header.request.opcode = cmd;
+    request->message.header.request.keylen = htons(keylen);
+    request->message.header.request.extlen = 8;
+    request->message.header.request.bodylen = htonl(keylen + 8 + dtalen);
+    request->message.header.request.opaque = 0xdeadbeef;
+    request->message.body.flags = flags;
+    request->message.body.expiration = exp;
+
+    off_t key_offset = sizeof(protocol_binary_request_no_extras) + 8;
+
+    memcpy(buf + key_offset, key, keylen);
+    if (dta != NULL) {
+        memcpy(buf + key_offset + keylen, dta, dtalen);
+    }
+
+    return key_offset + keylen + dtalen;
+}
+
+static off_t raw_command(char* buf,
+                         size_t bufsz,
+                         uint8_t cmd,
+                         const void* key,
+                         size_t keylen,
+                         const void* dta,
+                         size_t dtalen) {
+    /* all of the storage commands use the same command layout */
+    protocol_binary_request_no_extras *request = (void*)buf;
+    assert(bufsz > sizeof(*request) + keylen + dtalen);
+
+    memset(request, 0, sizeof(*request));
+    request->message.header.request.magic = PROTOCOL_BINARY_REQ;
+    request->message.header.request.opcode = cmd;
+    request->message.header.request.keylen = htons(keylen);
+    request->message.header.request.bodylen = htonl(keylen + dtalen);
+    request->message.header.request.opaque = 0xdeadbeef;
+
+    off_t key_offset = sizeof(protocol_binary_request_no_extras);
+
+    if (key != NULL) {
+        memcpy(buf + key_offset, key, keylen);
+    }
+    if (dta != NULL) {
+        memcpy(buf + key_offset + keylen, dta, dtalen);
+    }
+
+    return sizeof(*request) + keylen + dtalen;
+}
+
+static off_t flush_command(char* buf, size_t bufsz, uint8_t cmd, uint32_t exptime, bool use_extra) {
+    protocol_binary_request_flush *request = (void*)buf;
+    assert(bufsz > sizeof(*request));
+
+    memset(request, 0, sizeof(*request));
+    request->message.header.request.magic = PROTOCOL_BINARY_REQ;
+    request->message.header.request.opcode = cmd;
+
+    off_t size = sizeof(protocol_binary_request_no_extras);
+    if (use_extra) {
+        request->message.header.request.extlen = 4;
+        request->message.body.expiration = htonl(exptime);
+        request->message.header.request.bodylen = htonl(4);
+        size += 4;
+    }
+
+    request->message.header.request.opaque = 0xdeadbeef;
+
+    return size;
+}
+
+
+static off_t touch_command(char* buf,
+                           size_t bufsz,
+                           uint8_t cmd,
+                           const void* key,
+                           size_t keylen,
+                           uint32_t exptime) {
+    protocol_binary_request_touch *request = (void*)buf;
+    assert(bufsz > sizeof(*request));
+
+    memset(request, 0, sizeof(*request));
+    request->message.header.request.magic = PROTOCOL_BINARY_REQ;
+    request->message.header.request.opcode = cmd;
+
+    request->message.header.request.keylen = htons(keylen);
+    request->message.header.request.extlen = 4;
+    request->message.body.expiration = htonl(exptime);
+    request->message.header.request.bodylen = htonl(keylen + 4);
+
+    request->message.header.request.opaque = 0xdeadbeef;
+
+    off_t key_offset = sizeof(protocol_binary_request_no_extras) + 4;
+
+    memcpy(buf + key_offset, key, keylen);
+    return sizeof(protocol_binary_request_no_extras) + 4 + keylen;
+}
+
+static off_t arithmetic_command(char* buf,
+                                size_t bufsz,
+                                uint8_t cmd,
+                                const void* key,
+                                size_t keylen,
+                                uint64_t delta,
+                                uint64_t initial,
+                                uint32_t exp) {
+    protocol_binary_request_incr *request = (void*)buf;
+    assert(bufsz > sizeof(*request) + keylen);
+
+    memset(request, 0, sizeof(*request));
+    request->message.header.request.magic = PROTOCOL_BINARY_REQ;
+    request->message.header.request.opcode = cmd;
+    request->message.header.request.keylen = htons(keylen);
+    request->message.header.request.extlen = 20;
+    request->message.header.request.bodylen = htonl(keylen + 20);
+    request->message.header.request.opaque = 0xdeadbeef;
+    request->message.body.delta = htonll(delta);
+    request->message.body.initial = htonll(initial);
+    request->message.body.expiration = htonl(exp);
+
+    off_t key_offset = sizeof(protocol_binary_request_no_extras) + 20;
+
+    memcpy(buf + key_offset, key, keylen);
+    return key_offset + keylen;
+}
+
+static void validate_response_header(protocol_binary_response_no_extras *response,
+                                     uint8_t cmd, uint16_t status)
+{
+    assert(response->message.header.response.magic == PROTOCOL_BINARY_RES);
+    assert(response->message.header.response.opcode == cmd);
+    assert(response->message.header.response.datatype == PROTOCOL_BINARY_RAW_BYTES);
+    assert(response->message.header.response.status == status);
+    assert(response->message.header.response.opaque == 0xdeadbeef);
+
+    if (status == PROTOCOL_BINARY_RESPONSE_SUCCESS) {
+        switch (cmd) {
+        case PROTOCOL_BINARY_CMD_ADDQ:
+        case PROTOCOL_BINARY_CMD_APPENDQ:
+        case PROTOCOL_BINARY_CMD_DECREMENTQ:
+        case PROTOCOL_BINARY_CMD_DELETEQ:
+        case PROTOCOL_BINARY_CMD_FLUSHQ:
+        case PROTOCOL_BINARY_CMD_INCREMENTQ:
+        case PROTOCOL_BINARY_CMD_PREPENDQ:
+        case PROTOCOL_BINARY_CMD_QUITQ:
+        case PROTOCOL_BINARY_CMD_REPLACEQ:
+        case PROTOCOL_BINARY_CMD_SETQ:
+            assert("Quiet command shouldn't return on success" == NULL);
+        default:
+            break;
+        }
+
+        switch (cmd) {
+        case PROTOCOL_BINARY_CMD_ADD:
+        case PROTOCOL_BINARY_CMD_REPLACE:
+        case PROTOCOL_BINARY_CMD_SET:
+        case PROTOCOL_BINARY_CMD_APPEND:
+        case PROTOCOL_BINARY_CMD_PREPEND:
+            assert(response->message.header.response.keylen == 0);
+            assert(response->message.header.response.extlen == 0);
+            assert(response->message.header.response.bodylen == 0);
+            assert(response->message.header.response.cas != 0);
+            break;
+        case PROTOCOL_BINARY_CMD_FLUSH:
+        case PROTOCOL_BINARY_CMD_NOOP:
+        case PROTOCOL_BINARY_CMD_QUIT:
+        case PROTOCOL_BINARY_CMD_DELETE:
+            assert(response->message.header.response.keylen == 0);
+            assert(response->message.header.response.extlen == 0);
+            assert(response->message.header.response.bodylen == 0);
+            assert(response->message.header.response.cas == 0);
+            break;
+
+        case PROTOCOL_BINARY_CMD_DECREMENT:
+        case PROTOCOL_BINARY_CMD_INCREMENT:
+            assert(response->message.header.response.keylen == 0);
+            assert(response->message.header.response.extlen == 0);
+            assert(response->message.header.response.bodylen == 8);
+            assert(response->message.header.response.cas != 0);
+            break;
+
+        case PROTOCOL_BINARY_CMD_STAT:
+            assert(response->message.header.response.extlen == 0);
+            /* key and value exists in all packets except in the terminating */
+            assert(response->message.header.response.cas == 0);
+            break;
+
+        case PROTOCOL_BINARY_CMD_VERSION:
+            assert(response->message.header.response.keylen == 0);
+            assert(response->message.header.response.extlen == 0);
+            assert(response->message.header.response.bodylen != 0);
+            assert(response->message.header.response.cas == 0);
+            break;
+
+        case PROTOCOL_BINARY_CMD_GET:
+        case PROTOCOL_BINARY_CMD_GETQ:
+            assert(response->message.header.response.keylen == 0);
+            assert(response->message.header.response.extlen == 4);
+            assert(response->message.header.response.cas != 0);
+            break;
+
+        case PROTOCOL_BINARY_CMD_GETK:
+        case PROTOCOL_BINARY_CMD_GETKQ:
+            assert(response->message.header.response.keylen != 0);
+            assert(response->message.header.response.extlen == 4);
+            assert(response->message.header.response.cas != 0);
+            break;
+
+        default:
+            /* Undefined command code */
+            break;
+        }
+    } else {
+        assert(response->message.header.response.cas == 0);
+        assert(response->message.header.response.extlen == 0);
+        if (cmd != PROTOCOL_BINARY_CMD_GETK &&
+            cmd != PROTOCOL_BINARY_CMD_GATK) {
+            assert(response->message.header.response.keylen == 0);
+        }
+    }
+}
+
+static enum test_return test_binary_noop(void) {
+    union {
+        protocol_binary_request_no_extras request;
+        protocol_binary_response_no_extras response;
+        char bytes[1024];
+    } buffer;
+
+    size_t len = raw_command(buffer.bytes, sizeof(buffer.bytes),
+                             PROTOCOL_BINARY_CMD_NOOP,
+                             NULL, 0, NULL, 0);
+
+    safe_send(buffer.bytes, len, false);
+    safe_recv_packet(buffer.bytes, sizeof(buffer.bytes));
+    validate_response_header(&buffer.response, PROTOCOL_BINARY_CMD_NOOP,
+                             PROTOCOL_BINARY_RESPONSE_SUCCESS);
+
+    return TEST_PASS;
+}
+
+static enum test_return test_binary_quit_impl(uint8_t cmd) {
+    union {
+        protocol_binary_request_no_extras request;
+        protocol_binary_response_no_extras response;
+        char bytes[1024];
+    } buffer;
+    size_t len = raw_command(buffer.bytes, sizeof(buffer.bytes),
+                             cmd, NULL, 0, NULL, 0);
+
+    safe_send(buffer.bytes, len, false);
+    if (cmd == PROTOCOL_BINARY_CMD_QUIT) {
+        safe_recv_packet(buffer.bytes, sizeof(buffer.bytes));
+        validate_response_header(&buffer.response, PROTOCOL_BINARY_CMD_QUIT,
+                                 PROTOCOL_BINARY_RESPONSE_SUCCESS);
+    }
+
+    /* Socket should be closed now, read should return 0 */
+    assert(read(sock, buffer.bytes, sizeof(buffer.bytes)) == 0);
+    close(sock);
+    sock = connect_server("127.0.0.1", port, false);
+
+    return TEST_PASS;
+}
+
+static enum test_return test_binary_quit(void) {
+    return test_binary_quit_impl(PROTOCOL_BINARY_CMD_QUIT);
+}
+
+static enum test_return test_binary_quitq(void) {
+    return test_binary_quit_impl(PROTOCOL_BINARY_CMD_QUITQ);
+}
+
+static enum test_return test_binary_set_impl(const char *key, uint8_t cmd) {
+    union {
+        protocol_binary_request_no_extras request;
+        protocol_binary_response_no_extras response;
+        char bytes[1024];
+    } send, receive;
+    uint64_t value = 0xdeadbeefdeadcafe;
+    size_t len = storage_command(send.bytes, sizeof(send.bytes), cmd,
+                                 key, strlen(key), &value, sizeof(value),
+                                 0, 0);
+
+    /* Set should work over and over again */
+    int ii;
+    for (ii = 0; ii < 10; ++ii) {
+        safe_send(send.bytes, len, false);
+        if (cmd == PROTOCOL_BINARY_CMD_SET) {
+            safe_recv_packet(receive.bytes, sizeof(receive.bytes));
+            validate_response_header(&receive.response, cmd,
+                                     PROTOCOL_BINARY_RESPONSE_SUCCESS);
+        }
+    }
+
+    if (cmd == PROTOCOL_BINARY_CMD_SETQ) {
+        return test_binary_noop();
+    }
+
+    send.request.message.header.request.cas = receive.response.message.header.response.cas;
+    safe_send(send.bytes, len, false);
+    if (cmd == PROTOCOL_BINARY_CMD_SET) {
+        safe_recv_packet(receive.bytes, sizeof(receive.bytes));
+        validate_response_header(&receive.response, cmd,
+                                 PROTOCOL_BINARY_RESPONSE_SUCCESS);
+        assert(receive.response.message.header.response.cas != send.request.message.header.request.cas);
+    } else {
+        return test_binary_noop();
+    }
+
+    return TEST_PASS;
+}
+
+static enum test_return test_binary_set(void) {
+    return test_binary_set_impl("test_binary_set", PROTOCOL_BINARY_CMD_SET);
+}
+
+static enum test_return test_binary_setq(void) {
+    return test_binary_set_impl("test_binary_setq", PROTOCOL_BINARY_CMD_SETQ);
+}
+
+
+static enum test_return test_binary_add_impl(const char *key, uint8_t cmd) {
+    uint64_t value = 0xdeadbeefdeadcafe;
+    union {
+        protocol_binary_request_no_extras request;
+        protocol_binary_response_no_extras response;
+        char bytes[1024];
+    } send, receive;
+    size_t len = storage_command(send.bytes, sizeof(send.bytes), cmd, key,
+                                 strlen(key), &value, sizeof(value),
+                                 0, 0);
+
+    /* Add should only work the first time */
+    int ii;
+    for (ii = 0; ii < 10; ++ii) {
+        safe_send(send.bytes, len, false);
+        if (ii == 0) {
+            if (cmd == PROTOCOL_BINARY_CMD_ADD) {
+                safe_recv_packet(receive.bytes, sizeof(receive.bytes));
+                validate_response_header(&receive.response, cmd,
+                                         PROTOCOL_BINARY_RESPONSE_SUCCESS);
+            }
+        } else {
+            safe_recv_packet(receive.bytes, sizeof(receive.bytes));
+            validate_response_header(&receive.response, cmd,
+                                     PROTOCOL_BINARY_RESPONSE_KEY_EEXISTS);
+        }
+    }
+
+    return TEST_PASS;
+}
+
+static enum test_return test_binary_add(void) {
+    return test_binary_add_impl("test_binary_add", PROTOCOL_BINARY_CMD_ADD);
+}
+
+static enum test_return test_binary_addq(void) {
+    return test_binary_add_impl("test_binary_addq", PROTOCOL_BINARY_CMD_ADDQ);
+}
+
+static enum test_return test_binary_replace_impl(const char* key, uint8_t cmd) {
+    uint64_t value = 0xdeadbeefdeadcafe;
+    union {
+        protocol_binary_request_no_extras request;
+        protocol_binary_response_no_extras response;
+        char bytes[1024];
+    } send, receive;
+    size_t len = storage_command(send.bytes, sizeof(send.bytes), cmd,
+                                 key, strlen(key), &value, sizeof(value),
+                                 0, 0);
+    safe_send(send.bytes, len, false);
+    safe_recv_packet(receive.bytes, sizeof(receive.bytes));
+    validate_response_header(&receive.response, cmd,
+                             PROTOCOL_BINARY_RESPONSE_KEY_ENOENT);
+    len = storage_command(send.bytes, sizeof(send.bytes),
+                          PROTOCOL_BINARY_CMD_ADD,
+                          key, strlen(key), &value, sizeof(value), 0, 0);
+    safe_send(send.bytes, len, false);
+    safe_recv_packet(receive.bytes, sizeof(receive.bytes));
+    validate_response_header(&receive.response, PROTOCOL_BINARY_CMD_ADD,
+                             PROTOCOL_BINARY_RESPONSE_SUCCESS);
+
+    len = storage_command(send.bytes, sizeof(send.bytes), cmd,
+                          key, strlen(key), &value, sizeof(value), 0, 0);
+    int ii;
+    for (ii = 0; ii < 10; ++ii) {
+        safe_send(send.bytes, len, false);
+        if (cmd == PROTOCOL_BINARY_CMD_REPLACE) {
+            safe_recv_packet(receive.bytes, sizeof(receive.bytes));
+            validate_response_header(&receive.response,
+                                     PROTOCOL_BINARY_CMD_REPLACE,
+                                     PROTOCOL_BINARY_RESPONSE_SUCCESS);
+        }
+    }
+
+    if (cmd == PROTOCOL_BINARY_CMD_REPLACEQ) {
+        test_binary_noop();
+    }
+
+    return TEST_PASS;
+}
+
+static enum test_return test_binary_replace(void) {
+    return test_binary_replace_impl("test_binary_replace",
+                                    PROTOCOL_BINARY_CMD_REPLACE);
+}
+
+static enum test_return test_binary_replaceq(void) {
+    return test_binary_replace_impl("test_binary_replaceq",
+                                    PROTOCOL_BINARY_CMD_REPLACEQ);
+}
+
+static enum test_return test_binary_delete_impl(const char *key, uint8_t cmd) {
+    union {
+        protocol_binary_request_no_extras request;
+        protocol_binary_response_no_extras response;
+        char bytes[1024];
+    } send, receive;
+    size_t len = raw_command(send.bytes, sizeof(send.bytes), cmd,
+                             key, strlen(key), NULL, 0);
+
+    safe_send(send.bytes, len, false);
+    safe_recv_packet(receive.bytes, sizeof(receive.bytes));
+    validate_response_header(&receive.response, cmd,
+                             PROTOCOL_BINARY_RESPONSE_KEY_ENOENT);
+    len = storage_command(send.bytes, sizeof(send.bytes),
+                          PROTOCOL_BINARY_CMD_ADD,
+                          key, strlen(key), NULL, 0, 0, 0);
+    safe_send(send.bytes, len, false);
+    safe_recv_packet(receive.bytes, sizeof(receive.bytes));
+    validate_response_header(&receive.response, PROTOCOL_BINARY_CMD_ADD,
+                             PROTOCOL_BINARY_RESPONSE_SUCCESS);
+
+    len = raw_command(send.bytes, sizeof(send.bytes),
+                      cmd, key, strlen(key), NULL, 0);
+    safe_send(send.bytes, len, false);
+
+    if (cmd == PROTOCOL_BINARY_CMD_DELETE) {
+        safe_recv_packet(receive.bytes, sizeof(receive.bytes));
+        validate_response_header(&receive.response, PROTOCOL_BINARY_CMD_DELETE,
+                                 PROTOCOL_BINARY_RESPONSE_SUCCESS);
+    }
+
+    safe_send(send.bytes, len, false);
+    safe_recv_packet(receive.bytes, sizeof(receive.bytes));
+    validate_response_header(&receive.response, cmd,
+                             PROTOCOL_BINARY_RESPONSE_KEY_ENOENT);
+
+    return TEST_PASS;
+}
+
+static enum test_return test_binary_delete(void) {
+    return test_binary_delete_impl("test_binary_delete",
+                                   PROTOCOL_BINARY_CMD_DELETE);
+}
+
+static enum test_return test_binary_deleteq(void) {
+    return test_binary_delete_impl("test_binary_deleteq",
+                                   PROTOCOL_BINARY_CMD_DELETEQ);
+}
+
+static enum test_return test_binary_get_impl(const char *key, uint8_t cmd) {
+    union {
+        protocol_binary_request_no_extras request;
+        protocol_binary_response_no_extras response;
+        char bytes[1024];
+    } send, receive;
+    size_t len = raw_command(send.bytes, sizeof(send.bytes), cmd,
+                             key, strlen(key), NULL, 0);
+
+    safe_send(send.bytes, len, false);
+    safe_recv_packet(receive.bytes, sizeof(receive.bytes));
+    validate_response_header(&receive.response, cmd,
+                             PROTOCOL_BINARY_RESPONSE_KEY_ENOENT);
+
+    len = storage_command(send.bytes, sizeof(send.bytes),
+                          PROTOCOL_BINARY_CMD_ADD,
+                          key, strlen(key), NULL, 0,
+                          0, 0);
+    safe_send(send.bytes, len, false);
+    safe_recv_packet(receive.bytes, sizeof(receive.bytes));
+    validate_response_header(&receive.response, PROTOCOL_BINARY_CMD_ADD,
+                             PROTOCOL_BINARY_RESPONSE_SUCCESS);
+
+    /* run a little pipeline test ;-) */
+    len = 0;
+    int ii;
+    for (ii = 0; ii < 10; ++ii) {
+        union {
+            protocol_binary_request_no_extras request;
+            char bytes[1024];
+        } temp;
+        size_t l = raw_command(temp.bytes, sizeof(temp.bytes),
+                               cmd, key, strlen(key), NULL, 0);
+        memcpy(send.bytes + len, temp.bytes, l);
+        len += l;
+    }
+
+    safe_send(send.bytes, len, false);
+    for (ii = 0; ii < 10; ++ii) {
+        safe_recv_packet(receive.bytes, sizeof(receive.bytes));
+        validate_response_header(&receive.response, cmd,
+                                 PROTOCOL_BINARY_RESPONSE_SUCCESS);
+    }
+
+    return TEST_PASS;
+}
+
+static enum test_return test_binary_get(void) {
+    return test_binary_get_impl("test_binary_get", PROTOCOL_BINARY_CMD_GET);
+}
+
+static enum test_return test_binary_getk(void) {
+    return test_binary_get_impl("test_binary_getk", PROTOCOL_BINARY_CMD_GETK);
+}
+
+static enum test_return test_binary_getq_impl(const char *key, uint8_t cmd) {
+    const char *missing = "test_binary_getq_missing";
+    union {
+        protocol_binary_request_no_extras request;
+        protocol_binary_response_no_extras response;
+        char bytes[1024];
+    } send, temp, receive;
+    size_t len = storage_command(send.bytes, sizeof(send.bytes),
+                                 PROTOCOL_BINARY_CMD_ADD,
+                                 key, strlen(key), NULL, 0,
+                                 0, 0);
+    size_t len2 = raw_command(temp.bytes, sizeof(temp.bytes), cmd,
+                             missing, strlen(missing), NULL, 0);
+    /* I need to change the first opaque so that I can separate the two
+     * return packets */
+    temp.request.message.header.request.opaque = 0xfeedface;
+    memcpy(send.bytes + len, temp.bytes, len2);
+    len += len2;
+
+    len2 = raw_command(temp.bytes, sizeof(temp.bytes), cmd,
+                       key, strlen(key), NULL, 0);
+    memcpy(send.bytes + len, temp.bytes, len2);
+    len += len2;
+
+    safe_send(send.bytes, len, false);
+    safe_recv_packet(receive.bytes, sizeof(receive.bytes));
+    validate_response_header(&receive.response, PROTOCOL_BINARY_CMD_ADD,
+                             PROTOCOL_BINARY_RESPONSE_SUCCESS);
+    /* The first GETQ shouldn't return anything */
+    safe_recv_packet(receive.bytes, sizeof(receive.bytes));
+    validate_response_header(&receive.response, cmd,
+                             PROTOCOL_BINARY_RESPONSE_SUCCESS);
+
+    return TEST_PASS;
+}
+
+static enum test_return test_binary_getq(void) {
+    return test_binary_getq_impl("test_binary_getq", PROTOCOL_BINARY_CMD_GETQ);
+}
+
+static enum test_return test_binary_getkq(void) {
+    return test_binary_getq_impl("test_binary_getkq", PROTOCOL_BINARY_CMD_GETKQ);
+}
+
+static enum test_return test_binary_incr_impl(const char* key, uint8_t cmd) {
+    union {
+        protocol_binary_request_no_extras request;
+        protocol_binary_response_no_extras response_header;
+        protocol_binary_response_incr response;
+        char bytes[1024];
+    } send, receive;
+    size_t len = arithmetic_command(send.bytes, sizeof(send.bytes), cmd,
+                                    key, strlen(key), 1, 0, 0);
+
+    int ii;
+    for (ii = 0; ii < 10; ++ii) {
+        safe_send(send.bytes, len, false);
+        if (cmd == PROTOCOL_BINARY_CMD_INCREMENT) {
+            safe_recv_packet(receive.bytes, sizeof(receive.bytes));
+            validate_response_header(&receive.response_header, cmd,
+                                     PROTOCOL_BINARY_RESPONSE_SUCCESS);
+            assert(ntohll(receive.response.message.body.value) == ii);
+        }
+    }
+
+    if (cmd == PROTOCOL_BINARY_CMD_INCREMENTQ) {
+        test_binary_noop();
+    }
+    return TEST_PASS;
+}
+
+static enum test_return test_binary_incr(void) {
+    return test_binary_incr_impl("test_binary_incr",
+                                 PROTOCOL_BINARY_CMD_INCREMENT);
+}
+
+static enum test_return test_binary_incrq(void) {
+    return test_binary_incr_impl("test_binary_incrq",
+                                 PROTOCOL_BINARY_CMD_INCREMENTQ);
+}
+
+static enum test_return test_binary_decr_impl(const char* key, uint8_t cmd) {
+    union {
+        protocol_binary_request_no_extras request;
+        protocol_binary_response_no_extras response_header;
+        protocol_binary_response_decr response;
+        char bytes[1024];
+    } send, receive;
+    size_t len = arithmetic_command(send.bytes, sizeof(send.bytes), cmd,
+                                    key, strlen(key), 1, 9, 0);
+
+    int ii;
+    for (ii = 9; ii >= 0; --ii) {
+        safe_send(send.bytes, len, false);
+        if (cmd == PROTOCOL_BINARY_CMD_DECREMENT) {
+            safe_recv_packet(receive.bytes, sizeof(receive.bytes));
+            validate_response_header(&receive.response_header, cmd,
+                                     PROTOCOL_BINARY_RESPONSE_SUCCESS);
+            assert(ntohll(receive.response.message.body.value) == ii);
+        }
+    }
+
+    /* decr on 0 should not wrap */
+    safe_send(send.bytes, len, false);
+    if (cmd == PROTOCOL_BINARY_CMD_DECREMENT) {
+        safe_recv_packet(receive.bytes, sizeof(receive.bytes));
+        validate_response_header(&receive.response_header, cmd,
+                                 PROTOCOL_BINARY_RESPONSE_SUCCESS);
+        assert(ntohll(receive.response.message.body.value) == 0);
+    } else {
+        test_binary_noop();
+    }
+
+    return TEST_PASS;
+}
+
+static enum test_return test_binary_decr(void) {
+    return test_binary_decr_impl("test_binary_decr",
+                                 PROTOCOL_BINARY_CMD_DECREMENT);
+}
+
+static enum test_return test_binary_decrq(void) {
+    return test_binary_decr_impl("test_binary_decrq",
+                                 PROTOCOL_BINARY_CMD_DECREMENTQ);
+}
+
+static enum test_return test_binary_version(void) {
+    union {
+        protocol_binary_request_no_extras request;
+        protocol_binary_response_no_extras response;
+        char bytes[1024];
+    } buffer;
+
+    size_t len = raw_command(buffer.bytes, sizeof(buffer.bytes),
+                             PROTOCOL_BINARY_CMD_VERSION,
+                             NULL, 0, NULL, 0);
+
+    safe_send(buffer.bytes, len, false);
+    safe_recv_packet(buffer.bytes, sizeof(buffer.bytes));
+    validate_response_header(&buffer.response, PROTOCOL_BINARY_CMD_VERSION,
+                             PROTOCOL_BINARY_RESPONSE_SUCCESS);
+
+    return TEST_PASS;
+}
+
+static enum test_return test_binary_flush_impl(const char *key, uint8_t cmd) {
+    union {
+        protocol_binary_request_no_extras request;
+        protocol_binary_response_no_extras response;
+        char bytes[1024];
+    } send, receive;
+
+    size_t len = storage_command(send.bytes, sizeof(send.bytes),
+                                 PROTOCOL_BINARY_CMD_ADD,
+                                 key, strlen(key), NULL, 0, 0, 0);
+    safe_send(send.bytes, len, false);
+    safe_recv_packet(receive.bytes, sizeof(receive.bytes));
+    validate_response_header(&receive.response, PROTOCOL_BINARY_CMD_ADD,
+                             PROTOCOL_BINARY_RESPONSE_SUCCESS);
+
+    len = flush_command(send.bytes, sizeof(send.bytes), cmd, 2, true);
+    safe_send(send.bytes, len, false);
+    if (cmd == PROTOCOL_BINARY_CMD_FLUSH) {
+        safe_recv_packet(receive.bytes, sizeof(receive.bytes));
+        validate_response_header(&receive.response, cmd,
+                                 PROTOCOL_BINARY_RESPONSE_SUCCESS);
+    }
+
+    len = raw_command(send.bytes, sizeof(send.bytes), PROTOCOL_BINARY_CMD_GET,
+                      key, strlen(key), NULL, 0);
+    safe_send(send.bytes, len, false);
+    safe_recv_packet(receive.bytes, sizeof(receive.bytes));
+    validate_response_header(&receive.response, PROTOCOL_BINARY_CMD_GET,
+                             PROTOCOL_BINARY_RESPONSE_SUCCESS);
+
+    sleep(2);
+    safe_send(send.bytes, len, false);
+    safe_recv_packet(receive.bytes, sizeof(receive.bytes));
+    validate_response_header(&receive.response, PROTOCOL_BINARY_CMD_GET,
+                             PROTOCOL_BINARY_RESPONSE_KEY_ENOENT);
+
+    int ii;
+    for (ii = 0; ii < 2; ++ii) {
+        len = storage_command(send.bytes, sizeof(send.bytes),
+                              PROTOCOL_BINARY_CMD_ADD,
+                              key, strlen(key), NULL, 0, 0, 0);
+        safe_send(send.bytes, len, false);
+        safe_recv_packet(receive.bytes, sizeof(receive.bytes));
+        validate_response_header(&receive.response, PROTOCOL_BINARY_CMD_ADD,
+                                 PROTOCOL_BINARY_RESPONSE_SUCCESS);
+
+        len = flush_command(send.bytes, sizeof(send.bytes), cmd, 0, ii == 0);
+        safe_send(send.bytes, len, false);
+        if (cmd == PROTOCOL_BINARY_CMD_FLUSH) {
+            safe_recv_packet(receive.bytes, sizeof(receive.bytes));
+            validate_response_header(&receive.response, cmd,
+                                     PROTOCOL_BINARY_RESPONSE_SUCCESS);
+        }
+
+        len = raw_command(send.bytes, sizeof(send.bytes),
+                          PROTOCOL_BINARY_CMD_GET,
+                          key, strlen(key), NULL, 0);
+        safe_send(send.bytes, len, false);
+        safe_recv_packet(receive.bytes, sizeof(receive.bytes));
+        validate_response_header(&receive.response, PROTOCOL_BINARY_CMD_GET,
+                                 PROTOCOL_BINARY_RESPONSE_KEY_ENOENT);
+    }
+
+    return TEST_PASS;
+}
+
+static enum test_return test_binary_flush(void) {
+    return test_binary_flush_impl("test_binary_flush",
+                                  PROTOCOL_BINARY_CMD_FLUSH);
+}
+
+static enum test_return test_binary_flushq(void) {
+    return test_binary_flush_impl("test_binary_flushq",
+                                  PROTOCOL_BINARY_CMD_FLUSHQ);
+}
+
+static enum test_return test_binary_concat_impl(const char *key, uint8_t cmd) {
+    union {
+        protocol_binary_request_no_extras request;
+        protocol_binary_response_no_extras response;
+        char bytes[1024];
+    } send, receive;
+    const char *value = "world";
+
+    size_t len = raw_command(send.bytes, sizeof(send.bytes), cmd,
+                              key, strlen(key), value, strlen(value));
+
+
+    safe_send(send.bytes, len, false);
+    safe_recv_packet(receive.bytes, sizeof(receive.bytes));
+    validate_response_header(&receive.response, cmd,
+                             PROTOCOL_BINARY_RESPONSE_NOT_STORED);
+
+    len = storage_command(send.bytes, sizeof(send.bytes),
+                          PROTOCOL_BINARY_CMD_ADD,
+                          key, strlen(key), value, strlen(value), 0, 0);
+    safe_send(send.bytes, len, false);
+    safe_recv_packet(receive.bytes, sizeof(receive.bytes));
+    validate_response_header(&receive.response, PROTOCOL_BINARY_CMD_ADD,
+                             PROTOCOL_BINARY_RESPONSE_SUCCESS);
+
+    len = raw_command(send.bytes, sizeof(send.bytes), cmd,
+                      key, strlen(key), value, strlen(value));
+    safe_send(send.bytes, len, false);
+
+    if (cmd == PROTOCOL_BINARY_CMD_APPEND || cmd == PROTOCOL_BINARY_CMD_PREPEND) {
+        safe_recv_packet(receive.bytes, sizeof(receive.bytes));
+        validate_response_header(&receive.response, cmd,
+                                 PROTOCOL_BINARY_RESPONSE_SUCCESS);
+    } else {
+        len = raw_command(send.bytes, sizeof(send.bytes), PROTOCOL_BINARY_CMD_NOOP,
+                          NULL, 0, NULL, 0);
+        safe_send(send.bytes, len, false);
+        safe_recv_packet(receive.bytes, sizeof(receive.bytes));
+        validate_response_header(&receive.response, PROTOCOL_BINARY_CMD_NOOP,
+                                 PROTOCOL_BINARY_RESPONSE_SUCCESS);
+    }
+
+    len = raw_command(send.bytes, sizeof(send.bytes), PROTOCOL_BINARY_CMD_GETK,
+                      key, strlen(key), NULL, 0);
+
+    safe_send(send.bytes, len, false);
+    safe_recv_packet(receive.bytes, sizeof(receive.bytes));
+    validate_response_header(&receive.response, PROTOCOL_BINARY_CMD_GETK,
+                             PROTOCOL_BINARY_RESPONSE_SUCCESS);
+
+    assert(receive.response.message.header.response.keylen == strlen(key));
+    assert(receive.response.message.header.response.bodylen == (strlen(key) + 2*strlen(value) + 4));
+
+    char *ptr = receive.bytes;
+    ptr += sizeof(receive.response);
+    ptr += 4;
+
+    assert(memcmp(ptr, key, strlen(key)) == 0);
+    ptr += strlen(key);
+    assert(memcmp(ptr, value, strlen(value)) == 0);
+    ptr += strlen(value);
+    assert(memcmp(ptr, value, strlen(value)) == 0);
+
+    return TEST_PASS;
+}
+
+static enum test_return test_binary_append(void) {
+    return test_binary_concat_impl("test_binary_append",
+                                   PROTOCOL_BINARY_CMD_APPEND);
+}
+
+static enum test_return test_binary_prepend(void) {
+    return test_binary_concat_impl("test_binary_prepend",
+                                   PROTOCOL_BINARY_CMD_PREPEND);
+}
+
+static enum test_return test_binary_appendq(void) {
+    return test_binary_concat_impl("test_binary_appendq",
+                                   PROTOCOL_BINARY_CMD_APPENDQ);
+}
+
+static enum test_return test_binary_prependq(void) {
+    return test_binary_concat_impl("test_binary_prependq",
+                                   PROTOCOL_BINARY_CMD_PREPENDQ);
+}
+
+static enum test_return test_binary_stat(void) {
+    union {
+        protocol_binary_request_no_extras request;
+        protocol_binary_response_no_extras response;
+        char bytes[1024];
+    } buffer;
+
+    size_t len = raw_command(buffer.bytes, sizeof(buffer.bytes),
+                             PROTOCOL_BINARY_CMD_STAT,
+                             NULL, 0, NULL, 0);
+
+    safe_send(buffer.bytes, len, false);
+    do {
+        safe_recv_packet(buffer.bytes, sizeof(buffer.bytes));
+        validate_response_header(&buffer.response, PROTOCOL_BINARY_CMD_STAT,
+                                 PROTOCOL_BINARY_RESPONSE_SUCCESS);
+    } while (buffer.response.message.header.response.keylen != 0);
+
+    return TEST_PASS;
+}
+
+static enum test_return test_binary_illegal(void) {
+    uint8_t cmd = 0x25;
+    while (cmd != 0x00) {
+        union {
+            protocol_binary_request_no_extras request;
+            protocol_binary_response_no_extras response;
+            char bytes[1024];
+        } buffer;
+        size_t len = raw_command(buffer.bytes, sizeof(buffer.bytes),
+                                 cmd, NULL, 0, NULL, 0);
+        safe_send(buffer.bytes, len, false);
+        safe_recv_packet(buffer.bytes, sizeof(buffer.bytes));
+        validate_response_header(&buffer.response, cmd,
+                                 PROTOCOL_BINARY_RESPONSE_UNKNOWN_COMMAND);
+        ++cmd;
+    }
+
+    return TEST_PASS;
+}
+
+volatile bool hickup_thread_running;
+
+static void *binary_hickup_recv_verification_thread(void *arg) {
+    protocol_binary_response_no_extras *response = malloc(65*1024);
+    if (response != NULL) {
+        while (safe_recv_packet(response, 65*1024)) {
+            /* Just validate the packet format */
+            validate_response_header(response,
+                                     response->message.header.response.opcode,
+                                     response->message.header.response.status);
+        }
+        free(response);
+    }
+    hickup_thread_running = false;
+    allow_closed_read = false;
+    return NULL;
+}
+
+static enum test_return test_binary_pipeline_hickup_chunk(void *buffer, size_t buffersize) {
+    off_t offset = 0;
+    char *key[256];
+    uint64_t value = 0xfeedfacedeadbeef;
+
+    while (hickup_thread_running &&
+           offset + sizeof(protocol_binary_request_no_extras) < buffersize) {
+        union {
+            protocol_binary_request_no_extras request;
+            char bytes[65 * 1024];
+        } command;
+        uint8_t cmd = (uint8_t)(rand() & 0xff);
+        size_t len;
+        size_t keylen = (rand() % 250) + 1;
+
+        switch (cmd) {
+        case PROTOCOL_BINARY_CMD_ADD:
+        case PROTOCOL_BINARY_CMD_ADDQ:
+        case PROTOCOL_BINARY_CMD_REPLACE:
+        case PROTOCOL_BINARY_CMD_REPLACEQ:
+        case PROTOCOL_BINARY_CMD_SET:
+        case PROTOCOL_BINARY_CMD_SETQ:
+            len = storage_command(command.bytes, sizeof(command.bytes), cmd,
+                                  key, keylen , &value, sizeof(value),
+                                  0, 0);
+            break;
+        case PROTOCOL_BINARY_CMD_APPEND:
+        case PROTOCOL_BINARY_CMD_APPENDQ:
+        case PROTOCOL_BINARY_CMD_PREPEND:
+        case PROTOCOL_BINARY_CMD_PREPENDQ:
+            len = raw_command(command.bytes, sizeof(command.bytes), cmd,
+                              key, keylen, &value, sizeof(value));
+            break;
+        case PROTOCOL_BINARY_CMD_FLUSH:
+        case PROTOCOL_BINARY_CMD_FLUSHQ:
+            len = raw_command(command.bytes, sizeof(command.bytes), cmd,
+                              NULL, 0, NULL, 0);
+            break;
+        case PROTOCOL_BINARY_CMD_NOOP:
+            len = raw_command(command.bytes, sizeof(command.bytes), cmd,
+                              NULL, 0, NULL, 0);
+            break;
+        case PROTOCOL_BINARY_CMD_DELETE:
+        case PROTOCOL_BINARY_CMD_DELETEQ:
+            len = raw_command(command.bytes, sizeof(command.bytes), cmd,
+                             key, keylen, NULL, 0);
+            break;
+        case PROTOCOL_BINARY_CMD_DECREMENT:
+        case PROTOCOL_BINARY_CMD_DECREMENTQ:
+        case PROTOCOL_BINARY_CMD_INCREMENT:
+        case PROTOCOL_BINARY_CMD_INCREMENTQ:
+            len = arithmetic_command(command.bytes, sizeof(command.bytes), cmd,
+                                     key, keylen, 1, 0, 0);
+            break;
+        case PROTOCOL_BINARY_CMD_VERSION:
+            len = raw_command(command.bytes, sizeof(command.bytes),
+                             PROTOCOL_BINARY_CMD_VERSION,
+                             NULL, 0, NULL, 0);
+            break;
+        case PROTOCOL_BINARY_CMD_GET:
+        case PROTOCOL_BINARY_CMD_GETK:
+        case PROTOCOL_BINARY_CMD_GETKQ:
+        case PROTOCOL_BINARY_CMD_GETQ:
+            len = raw_command(command.bytes, sizeof(command.bytes), cmd,
+                             key, keylen, NULL, 0);
+            break;
+
+        case PROTOCOL_BINARY_CMD_TOUCH:
+        case PROTOCOL_BINARY_CMD_GAT:
+        case PROTOCOL_BINARY_CMD_GATQ:
+        case PROTOCOL_BINARY_CMD_GATK:
+        case PROTOCOL_BINARY_CMD_GATKQ:
+            len = touch_command(command.bytes, sizeof(command.bytes), cmd,
+                                key, keylen, 10);
+            break;
+
+        case PROTOCOL_BINARY_CMD_STAT:
+            len = raw_command(command.bytes, sizeof(command.bytes),
+                              PROTOCOL_BINARY_CMD_STAT,
+                              NULL, 0, NULL, 0);
+            break;
+
+        case PROTOCOL_BINARY_CMD_SASL_LIST_MECHS:
+        case PROTOCOL_BINARY_CMD_SASL_AUTH:
+        case PROTOCOL_BINARY_CMD_SASL_STEP:
+            /* Ignoring SASL */
+        case PROTOCOL_BINARY_CMD_QUITQ:
+        case PROTOCOL_BINARY_CMD_QUIT:
+            /* I don't want to pass on the quit commands ;-) */
+            cmd |= 0xf0;
+            /* FALLTHROUGH */
+        default:
+            len = raw_command(command.bytes, sizeof(command.bytes),
+                              cmd, NULL, 0, NULL, 0);
+        }
+
+        if ((len + offset) < buffersize) {
+            memcpy(((char*)buffer) + offset, command.bytes, len);
+            offset += len;
+        } else {
+            break;
+        }
+    }
+    safe_send(buffer, offset, true);
+
+    return TEST_PASS;
+}
+
+static enum test_return test_binary_pipeline_hickup(void)
+{
+    size_t buffersize = 65 * 1024;
+    void *buffer = malloc(buffersize);
+    int ii;
+
+    pthread_t tid;
+    int ret;
+    allow_closed_read = true;
+    hickup_thread_running = true;
+    if ((ret = pthread_create(&tid, NULL,
+                              binary_hickup_recv_verification_thread, NULL)) != 0) {
+        fprintf(stderr, "Can't create thread: %s\n", strerror(ret));
+        return TEST_FAIL;
+    }
+
+    /* Allow the thread to start */
+    usleep(250);
+
+    srand((int)time(NULL));
+    for (ii = 0; ii < 2; ++ii) {
+        test_binary_pipeline_hickup_chunk(buffer, buffersize);
+    }
+
+    /* send quitq to shut down the read thread ;-) */
+    size_t len = raw_command(buffer, buffersize, PROTOCOL_BINARY_CMD_QUITQ,
+                             NULL, 0, NULL, 0);
+    safe_send(buffer, len, false);
+
+    pthread_join(tid, NULL);
+    free(buffer);
+    return TEST_PASS;
+}
+
+
+static enum test_return test_issue_101(void) {
+    const int max = 2;
+    enum test_return ret = TEST_PASS;
+    int fds[max];
+    int ii = 0;
+    pid_t child = 0;
+
+    if (getenv("SKIP_TEST_101") != NULL) {
+        return TEST_SKIP;
+    }
+
+    const char *command = "stats\r\nstats\r\nstats\r\nstats\r\nstats\r\n";
+    size_t cmdlen = strlen(command);
+
+    server_pid = start_server(&port, false, 1000);
+
+    for (ii = 0; ii < max; ++ii) {
+        fds[ii] = connect_server("127.0.0.1", port, true);
+        assert(fds[ii] > 0);
+    }
+
+    /* Send command on the connection until it blocks */
+    for (ii = 0; ii < max; ++ii) {
+        bool more = true;
+        do {
+            ssize_t err = write(fds[ii], command, cmdlen);
+            if (err == -1) {
+                switch (errno) {
+                case EINTR:
+                    break;
+                case ENOMEM:
+                case EWOULDBLOCK:
+                    more = false;
+                    break;
+                default:
+                    ret = TEST_FAIL;
+                    goto cleanup;
+                }
+            }
+        } while (more);
+    }
+
+    child = fork();
+    if (child == (pid_t)-1) {
+        abort();
+    } else if (child > 0) {
+        int stat;
+        pid_t c;
+        while ((c = waitpid(child, &stat, 0)) == (pid_t)-1 && errno == EINTR);
+        assert(c == child);
+        assert(stat == 0);
+    } else {
+        sock = connect_server("127.0.0.1", port, false);
+        ret = test_binary_noop();
+        close(sock);
+        exit(0);
+    }
+
+ cleanup:
+    /* close all connections */
+    for (ii = 0; ii < max; ++ii) {
+        close(fds[ii]);
+    }
+
+    assert(kill(server_pid, SIGTERM) == 0);
+
+    return ret;
+}
+
+typedef enum test_return (*TEST_FUNC)(void);
+struct testcase {
+    const char *description;
+    TEST_FUNC function;
+};
+
+struct testcase testcases[] = {
+    { "cache_create", cache_create_test },
+    { "cache_constructor", cache_constructor_test },
+    { "cache_constructor_fail", cache_fail_constructor_test },
+    { "cache_destructor", cache_destructor_test },
+    { "cache_reuse", cache_reuse_test },
+    { "cache_redzone", cache_redzone_test },
+    { "issue_161", test_issue_161 },
+    { "strtol", test_safe_strtol },
+    { "strtoll", test_safe_strtoll },
+    { "strtoul", test_safe_strtoul },
+    { "strtoull", test_safe_strtoull },
+    { "issue_44", test_issue_44 },
+    { "vperror", test_vperror },
+    { "issue_101", test_issue_101 },
+    /* The following tests all run towards the same server */
+    { "start_server", start_memcached_server },
+    { "issue_92", test_issue_92 },
+    { "issue_102", test_issue_102 },
+    { "binary_noop", test_binary_noop },
+    { "binary_quit", test_binary_quit },
+    { "binary_quitq", test_binary_quitq },
+    { "binary_set", test_binary_set },
+    { "binary_setq", test_binary_setq },
+    { "binary_add", test_binary_add },
+    { "binary_addq", test_binary_addq },
+    { "binary_replace", test_binary_replace },
+    { "binary_replaceq", test_binary_replaceq },
+    { "binary_delete", test_binary_delete },
+    { "binary_deleteq", test_binary_deleteq },
+    { "binary_get", test_binary_get },
+    { "binary_getq", test_binary_getq },
+    { "binary_getk", test_binary_getk },
+    { "binary_getkq", test_binary_getkq },
+    { "binary_incr", test_binary_incr },
+    { "binary_incrq", test_binary_incrq },
+    { "binary_decr", test_binary_decr },
+    { "binary_decrq", test_binary_decrq },
+    { "binary_version", test_binary_version },
+    { "binary_flush", test_binary_flush },
+    { "binary_flushq", test_binary_flushq },
+    { "binary_append", test_binary_append },
+    { "binary_appendq", test_binary_appendq },
+    { "binary_prepend", test_binary_prepend },
+    { "binary_prependq", test_binary_prependq },
+    { "binary_stat", test_binary_stat },
+    { "binary_illegal", test_binary_illegal },
+    { "binary_pipeline_hickup", test_binary_pipeline_hickup },
+    { "stop_server", stop_memcached_server },
+    { NULL, NULL }
+};
+
+int main(int argc, char **argv)
+{
+    int exitcode = 0;
+    int ii = 0, num_cases = 0;
+
+    for (num_cases = 0; testcases[num_cases].description; num_cases++) {
+        /* Just counting */
+    }
+
+    printf("1..%d\n", num_cases);
+
+    for (ii = 0; testcases[ii].description != NULL; ++ii) {
+        fflush(stdout);
+#ifndef DEBUG
+        /* the test program shouldn't run longer than 10 minutes... */
+        alarm(600);
+#endif
+        enum test_return ret = testcases[ii].function();
+        if (ret == TEST_SKIP) {
+            fprintf(stdout, "ok # SKIP %d - %s\n", ii + 1, testcases[ii].description);
+        } else if (ret == TEST_PASS) {
+            fprintf(stdout, "ok %d - %s\n", ii + 1, testcases[ii].description);
+        } else {
+            fprintf(stdout, "not ok %d - %s\n", ii + 1, testcases[ii].description);
+            exitcode = 1;
+        }
+        fflush(stdout);
+    }
+
+    return exitcode;
+}
diff --git a/memcached/thread.c b/memcached/thread.c
new file mode 100644 (file)
index 0000000..e362249
--- /dev/null
@@ -0,0 +1,743 @@
+/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Thread management for memcached.
+ */
+#include "memcached.h"
+#include <assert.h>
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <pthread.h>
+
+#ifdef __sun
+#include <atomic.h>
+#endif
+
+#define ITEMS_PER_ALLOC 64
+
+/* An item in the connection queue. */
+typedef struct conn_queue_item CQ_ITEM;
+struct conn_queue_item {
+    int               sfd;
+    enum conn_states  init_state;
+    int               event_flags;
+    int               read_buffer_size;
+    enum network_transport     transport;
+    CQ_ITEM          *next;
+};
+
+/* A connection queue. */
+typedef struct conn_queue CQ;
+struct conn_queue {
+    CQ_ITEM *head;
+    CQ_ITEM *tail;
+    pthread_mutex_t lock;
+    pthread_cond_t  cond;
+};
+
+/* Lock for cache operations (item_*, assoc_*) */
+pthread_mutex_t cache_lock;
+
+/* Connection lock around accepting new connections */
+pthread_mutex_t conn_lock = PTHREAD_MUTEX_INITIALIZER;
+
+#if !defined(HAVE_GCC_ATOMICS) && !defined(__sun)
+pthread_mutex_t atomics_mutex = PTHREAD_MUTEX_INITIALIZER;
+#endif
+
+/* Lock for global stats */
+static pthread_mutex_t stats_lock;
+
+/* Free list of CQ_ITEM structs */
+static CQ_ITEM *cqi_freelist;
+static pthread_mutex_t cqi_freelist_lock;
+
+static pthread_mutex_t *item_locks;
+/* size of the item lock hash table */
+static uint32_t item_lock_count;
+/* size - 1 for lookup masking */
+static uint32_t item_lock_mask;
+
+static LIBEVENT_DISPATCHER_THREAD dispatcher_thread;
+
+/*
+ * Each libevent instance has a wakeup pipe, which other threads
+ * can use to signal that they've put a new connection on its queue.
+ */
+static LIBEVENT_THREAD *threads;
+
+/*
+ * Number of worker threads that have finished setting themselves up.
+ */
+static int init_count = 0;
+static pthread_mutex_t init_lock;
+static pthread_cond_t init_cond;
+
+
+static void thread_libevent_process(int fd, short which, void *arg);
+
+unsigned short refcount_incr(unsigned short *refcount) {
+#ifdef HAVE_GCC_ATOMICS
+    return __sync_add_and_fetch(refcount, 1);
+#elif defined(__sun)
+    return atomic_inc_ushort_nv(refcount);
+#else
+    unsigned short res;
+    mutex_lock(&atomics_mutex);
+    (*refcount)++;
+    res = *refcount;
+    pthread_mutex_unlock(&atomics_mutex);
+    return res;
+#endif
+}
+
+unsigned short refcount_decr(unsigned short *refcount) {
+#ifdef HAVE_GCC_ATOMICS
+    return __sync_sub_and_fetch(refcount, 1);
+#elif defined(__sun)
+    return atomic_dec_ushort_nv(refcount);
+#else
+    unsigned short res;
+    mutex_lock(&atomics_mutex);
+    (*refcount)--;
+    res = *refcount;
+    pthread_mutex_unlock(&atomics_mutex);
+    return res;
+#endif
+}
+
+void item_lock(uint32_t hv) {
+    mutex_lock(&item_locks[hv & item_lock_mask]);
+}
+
+void item_unlock(uint32_t hv) {
+    pthread_mutex_unlock(&item_locks[hv & item_lock_mask]);
+}
+
+/*
+ * Initializes a connection queue.
+ */
+static void cq_init(CQ *cq) {
+    pthread_mutex_init(&cq->lock, NULL);
+    pthread_cond_init(&cq->cond, NULL);
+    cq->head = NULL;
+    cq->tail = NULL;
+}
+
+/*
+ * Looks for an item on a connection queue, but doesn't block if there isn't
+ * one.
+ * Returns the item, or NULL if no item is available
+ */
+static CQ_ITEM *cq_pop(CQ *cq) {
+    CQ_ITEM *item;
+
+    pthread_mutex_lock(&cq->lock);
+    item = cq->head;
+    if (NULL != item) {
+        cq->head = item->next;
+        if (NULL == cq->head)
+            cq->tail = NULL;
+    }
+    pthread_mutex_unlock(&cq->lock);
+
+    return item;
+}
+
+/*
+ * Adds an item to a connection queue.
+ */
+static void cq_push(CQ *cq, CQ_ITEM *item) {
+    item->next = NULL;
+
+    pthread_mutex_lock(&cq->lock);
+    if (NULL == cq->tail)
+        cq->head = item;
+    else
+        cq->tail->next = item;
+    cq->tail = item;
+    pthread_cond_signal(&cq->cond);
+    pthread_mutex_unlock(&cq->lock);
+}
+
+/*
+ * Returns a fresh connection queue item.
+ */
+static CQ_ITEM *cqi_new(void) {
+    CQ_ITEM *item = NULL;
+    pthread_mutex_lock(&cqi_freelist_lock);
+    if (cqi_freelist) {
+        item = cqi_freelist;
+        cqi_freelist = item->next;
+    }
+    pthread_mutex_unlock(&cqi_freelist_lock);
+
+    if (NULL == item) {
+        int i;
+
+        /* Allocate a bunch of items at once to reduce fragmentation */
+        item = malloc(sizeof(CQ_ITEM) * ITEMS_PER_ALLOC);
+        if (NULL == item)
+            return NULL;
+
+        /*
+         * Link together all the new items except the first one
+         * (which we'll return to the caller) for placement on
+         * the freelist.
+         */
+        for (i = 2; i < ITEMS_PER_ALLOC; i++)
+            item[i - 1].next = &item[i];
+
+        pthread_mutex_lock(&cqi_freelist_lock);
+        item[ITEMS_PER_ALLOC - 1].next = cqi_freelist;
+        cqi_freelist = &item[1];
+        pthread_mutex_unlock(&cqi_freelist_lock);
+    }
+
+    return item;
+}
+
+
+/*
+ * Frees a connection queue item (adds it to the freelist.)
+ */
+static void cqi_free(CQ_ITEM *item) {
+    pthread_mutex_lock(&cqi_freelist_lock);
+    item->next = cqi_freelist;
+    cqi_freelist = item;
+    pthread_mutex_unlock(&cqi_freelist_lock);
+}
+
+
+/*
+ * Creates a worker thread.
+ */
+static void create_worker(void *(*func)(void *), void *arg) {
+    pthread_t       thread;
+    pthread_attr_t  attr;
+    int             ret;
+
+    pthread_attr_init(&attr);
+
+    if ((ret = pthread_create(&thread, &attr, func, arg)) != 0) {
+        fprintf(stderr, "Can't create thread: %s\n",
+                strerror(ret));
+        exit(1);
+    }
+}
+
+/*
+ * Sets whether or not we accept new connections.
+ */
+void accept_new_conns(const bool do_accept) {
+    pthread_mutex_lock(&conn_lock);
+    do_accept_new_conns(do_accept);
+    pthread_mutex_unlock(&conn_lock);
+}
+/****************************** LIBEVENT THREADS *****************************/
+
+/*
+ * Set up a thread's information.
+ */
+static void setup_thread(LIBEVENT_THREAD *me) {
+    me->base = event_init();
+    if (! me->base) {
+        fprintf(stderr, "Can't allocate event base\n");
+        exit(1);
+    }
+
+    /* Listen for notifications from other threads */
+    event_set(&me->notify_event, me->notify_receive_fd,
+              EV_READ | EV_PERSIST, thread_libevent_process, me);
+    event_base_set(me->base, &me->notify_event);
+
+    if (event_add(&me->notify_event, 0) == -1) {
+        fprintf(stderr, "Can't monitor libevent notify pipe\n");
+        exit(1);
+    }
+
+    me->new_conn_queue = malloc(sizeof(struct conn_queue));
+    if (me->new_conn_queue == NULL) {
+        perror("Failed to allocate memory for connection queue");
+        exit(EXIT_FAILURE);
+    }
+    cq_init(me->new_conn_queue);
+
+    if (pthread_mutex_init(&me->stats.mutex, NULL) != 0) {
+        perror("Failed to initialize mutex");
+        exit(EXIT_FAILURE);
+    }
+
+    me->suffix_cache = cache_create("suffix", SUFFIX_SIZE, sizeof(char*),
+                                    NULL, NULL);
+    if (me->suffix_cache == NULL) {
+        fprintf(stderr, "Failed to create suffix cache\n");
+        exit(EXIT_FAILURE);
+    }
+}
+
+
+/*
+ * Worker thread: main event loop
+ */
+static void *worker_libevent(void *arg) {
+    LIBEVENT_THREAD *me = arg;
+
+    /* Any per-thread setup can happen here; thread_init() will block until
+     * all threads have finished initializing.
+     */
+
+    pthread_mutex_lock(&init_lock);
+    init_count++;
+    pthread_cond_signal(&init_cond);
+    pthread_mutex_unlock(&init_lock);
+
+    event_base_loop(me->base, 0);
+    return NULL;
+}
+
+
+#ifndef __INTEL_COMPILER
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#endif
+/*
+ * Processes an incoming "handle a new connection" item. This is called when
+ * input arrives on the libevent wakeup pipe.
+ */
+static void thread_libevent_process(int fd, short which, void *arg) {
+    LIBEVENT_THREAD *me = arg;
+    CQ_ITEM *item;
+    char buf[1];
+
+    if (read(fd, buf, 1) != 1)
+        if (settings.verbose > 0)
+            fprintf(stderr, "Can't read from libevent pipe\n");
+
+    item = cq_pop(me->new_conn_queue);
+
+    if (NULL != item) {
+        conn *c = conn_new(item->sfd, item->init_state, item->event_flags,
+                           item->read_buffer_size, item->transport, me->base);
+        if (c == NULL) {
+            if (IS_UDP(item->transport)) {
+                fprintf(stderr, "Can't listen for events on UDP socket\n");
+                exit(1);
+            } else {
+                if (settings.verbose > 0) {
+                    fprintf(stderr, "Can't listen for events on fd %d\n",
+                        item->sfd);
+                }
+                close(item->sfd);
+            }
+        } else {
+            c->thread = me;
+        }
+        cqi_free(item);
+    }
+}
+
+/* Which thread we assigned a connection to most recently. */
+static int last_thread = -1;
+
+/*
+ * Dispatches a new connection to another thread. This is only ever called
+ * from the main thread, either during initialization (for UDP) or because
+ * of an incoming connection.
+ */
+void dispatch_conn_new(int sfd, enum conn_states init_state, int event_flags,
+                       int read_buffer_size, enum network_transport transport) {
+    CQ_ITEM *item = cqi_new();
+    int tid = (last_thread + 1) % settings.num_threads;
+
+    LIBEVENT_THREAD *thread = threads + tid;
+
+    last_thread = tid;
+
+    item->sfd = sfd;
+    item->init_state = init_state;
+    item->event_flags = event_flags;
+    item->read_buffer_size = read_buffer_size;
+    item->transport = transport;
+
+    cq_push(thread->new_conn_queue, item);
+
+    MEMCACHED_CONN_DISPATCH(sfd, thread->thread_id);
+    if (write(thread->notify_send_fd, "", 1) != 1) {
+        perror("Writing to thread notify pipe");
+    }
+}
+
+/*
+ * Returns true if this is the thread that listens for new TCP connections.
+ */
+int is_listen_thread() {
+    return pthread_self() == dispatcher_thread.thread_id;
+}
+
+/********************************* ITEM ACCESS *******************************/
+
+/*
+ * Allocates a new item.
+ */
+item *item_alloc(char *key, size_t nkey, int flags, rel_time_t exptime, int nbytes) {
+    item *it;
+    /* do_item_alloc handles its own locks */
+    it = do_item_alloc(key, nkey, flags, exptime, nbytes);
+    return it;
+}
+
+/*
+ * Returns an item if it hasn't been marked as expired,
+ * lazy-expiring as needed.
+ */
+item *item_get(const char *key, const size_t nkey) {
+    item *it;
+    uint32_t hv;
+    hv = hash(key, nkey, 0);
+    item_lock(hv);
+    it = do_item_get(key, nkey, hv);
+    item_unlock(hv);
+    return it;
+}
+
+item *item_touch(const char *key, size_t nkey, uint32_t exptime) {
+    item *it;
+    uint32_t hv;
+    hv = hash(key, nkey, 0);
+    item_lock(hv);
+    it = do_item_touch(key, nkey, exptime, hv);
+    item_unlock(hv);
+    return it;
+}
+
+/*
+ * Links an item into the LRU and hashtable.
+ */
+int item_link(item *item) {
+    int ret;
+    uint32_t hv;
+
+    hv = hash(ITEM_key(item), item->nkey, 0);
+    item_lock(hv);
+    ret = do_item_link(item, hv);
+    item_unlock(hv);
+    return ret;
+}
+
+/*
+ * Decrements the reference count on an item and adds it to the freelist if
+ * needed.
+ */
+void item_remove(item *item) {
+    uint32_t hv;
+    hv = hash(ITEM_key(item), item->nkey, 0);
+
+    item_lock(hv);
+    do_item_remove(item);
+    item_unlock(hv);
+}
+
+/*
+ * Replaces one item with another in the hashtable.
+ * Unprotected by a mutex lock since the core server does not require
+ * it to be thread-safe.
+ */
+int item_replace(item *old_it, item *new_it, const uint32_t hv) {
+    return do_item_replace(old_it, new_it, hv);
+}
+
+/*
+ * Unlinks an item from the LRU and hashtable.
+ */
+void item_unlink(item *item) {
+    uint32_t hv;
+    hv = hash(ITEM_key(item), item->nkey, 0);
+    item_lock(hv);
+    do_item_unlink(item, hv);
+    item_unlock(hv);
+}
+
+/*
+ * Moves an item to the back of the LRU queue.
+ */
+void item_update(item *item) {
+    uint32_t hv;
+    hv = hash(ITEM_key(item), item->nkey, 0);
+
+    item_lock(hv);
+    do_item_update(item);
+    item_unlock(hv);
+}
+
+/*
+ * Does arithmetic on a numeric item value.
+ */
+enum delta_result_type add_delta(conn *c, const char *key,
+                                 const size_t nkey, int incr,
+                                 const int64_t delta, char *buf,
+                                 uint64_t *cas) {
+    enum delta_result_type ret;
+    uint32_t hv;
+
+    hv = hash(key, nkey, 0);
+    item_lock(hv);
+    ret = do_add_delta(c, key, nkey, incr, delta, buf, cas, hv);
+    item_unlock(hv);
+    return ret;
+}
+
+/*
+ * Stores an item in the cache (high level, obeys set/add/replace semantics)
+ */
+enum store_item_type store_item(item *item, int comm, conn* c) {
+    enum store_item_type ret;
+    uint32_t hv;
+
+    hv = hash(ITEM_key(item), item->nkey, 0);
+    item_lock(hv);
+    ret = do_store_item(item, comm, c, hv);
+    item_unlock(hv);
+    return ret;
+}
+
+/*
+ * Flushes expired items after a flush_all call
+ */
+void item_flush_expired() {
+    mutex_lock(&cache_lock);
+    do_item_flush_expired();
+    pthread_mutex_unlock(&cache_lock);
+}
+
+/*
+ * Dumps part of the cache
+ */
+char *item_cachedump(unsigned int slabs_clsid, unsigned int limit, unsigned int *bytes) {
+    char *ret;
+
+    mutex_lock(&cache_lock);
+    ret = do_item_cachedump(slabs_clsid, limit, bytes);
+    pthread_mutex_unlock(&cache_lock);
+    return ret;
+}
+
+/*
+ * Dumps statistics about slab classes
+ */
+void  item_stats(ADD_STAT add_stats, void *c) {
+    mutex_lock(&cache_lock);
+    do_item_stats(add_stats, c);
+    pthread_mutex_unlock(&cache_lock);
+}
+
+/*
+ * Dumps a list of objects of each size in 32-byte increments
+ */
+void  item_stats_sizes(ADD_STAT add_stats, void *c) {
+    mutex_lock(&cache_lock);
+    do_item_stats_sizes(add_stats, c);
+    pthread_mutex_unlock(&cache_lock);
+}
+
+/******************************* GLOBAL STATS ******************************/
+
+void STATS_LOCK() {
+    pthread_mutex_lock(&stats_lock);
+}
+
+void STATS_UNLOCK() {
+    pthread_mutex_unlock(&stats_lock);
+}
+
+void threadlocal_stats_reset(void) {
+    int ii, sid;
+    for (ii = 0; ii < settings.num_threads; ++ii) {
+        pthread_mutex_lock(&threads[ii].stats.mutex);
+
+        threads[ii].stats.get_cmds = 0;
+        threads[ii].stats.get_misses = 0;
+        threads[ii].stats.touch_cmds = 0;
+        threads[ii].stats.touch_misses = 0;
+        threads[ii].stats.delete_misses = 0;
+        threads[ii].stats.incr_misses = 0;
+        threads[ii].stats.decr_misses = 0;
+        threads[ii].stats.cas_misses = 0;
+        threads[ii].stats.bytes_read = 0;
+        threads[ii].stats.bytes_written = 0;
+        threads[ii].stats.flush_cmds = 0;
+        threads[ii].stats.conn_yields = 0;
+        threads[ii].stats.auth_cmds = 0;
+        threads[ii].stats.auth_errors = 0;
+
+        for(sid = 0; sid < MAX_NUMBER_OF_SLAB_CLASSES; sid++) {
+            threads[ii].stats.slab_stats[sid].set_cmds = 0;
+            threads[ii].stats.slab_stats[sid].get_hits = 0;
+            threads[ii].stats.slab_stats[sid].touch_hits = 0;
+            threads[ii].stats.slab_stats[sid].delete_hits = 0;
+            threads[ii].stats.slab_stats[sid].incr_hits = 0;
+            threads[ii].stats.slab_stats[sid].decr_hits = 0;
+            threads[ii].stats.slab_stats[sid].cas_hits = 0;
+            threads[ii].stats.slab_stats[sid].cas_badval = 0;
+        }
+
+        pthread_mutex_unlock(&threads[ii].stats.mutex);
+    }
+}
+
+void threadlocal_stats_aggregate(struct thread_stats *stats) {
+    int ii, sid;
+
+    /* The struct has a mutex, but we can safely set the whole thing
+     * to zero since it is unused when aggregating. */
+    memset(stats, 0, sizeof(*stats));
+
+    for (ii = 0; ii < settings.num_threads; ++ii) {
+        pthread_mutex_lock(&threads[ii].stats.mutex);
+
+        stats->get_cmds += threads[ii].stats.get_cmds;
+        stats->get_misses += threads[ii].stats.get_misses;
+        stats->touch_cmds += threads[ii].stats.touch_cmds;
+        stats->touch_misses += threads[ii].stats.touch_misses;
+        stats->delete_misses += threads[ii].stats.delete_misses;
+        stats->decr_misses += threads[ii].stats.decr_misses;
+        stats->incr_misses += threads[ii].stats.incr_misses;
+        stats->cas_misses += threads[ii].stats.cas_misses;
+        stats->bytes_read += threads[ii].stats.bytes_read;
+        stats->bytes_written += threads[ii].stats.bytes_written;
+        stats->flush_cmds += threads[ii].stats.flush_cmds;
+        stats->conn_yields += threads[ii].stats.conn_yields;
+        stats->auth_cmds += threads[ii].stats.auth_cmds;
+        stats->auth_errors += threads[ii].stats.auth_errors;
+
+        for (sid = 0; sid < MAX_NUMBER_OF_SLAB_CLASSES; sid++) {
+            stats->slab_stats[sid].set_cmds +=
+                threads[ii].stats.slab_stats[sid].set_cmds;
+            stats->slab_stats[sid].get_hits +=
+                threads[ii].stats.slab_stats[sid].get_hits;
+            stats->slab_stats[sid].touch_hits +=
+                threads[ii].stats.slab_stats[sid].touch_hits;
+            stats->slab_stats[sid].delete_hits +=
+                threads[ii].stats.slab_stats[sid].delete_hits;
+            stats->slab_stats[sid].decr_hits +=
+                threads[ii].stats.slab_stats[sid].decr_hits;
+            stats->slab_stats[sid].incr_hits +=
+                threads[ii].stats.slab_stats[sid].incr_hits;
+            stats->slab_stats[sid].cas_hits +=
+                threads[ii].stats.slab_stats[sid].cas_hits;
+            stats->slab_stats[sid].cas_badval +=
+                threads[ii].stats.slab_stats[sid].cas_badval;
+        }
+
+        pthread_mutex_unlock(&threads[ii].stats.mutex);
+    }
+}
+
+void slab_stats_aggregate(struct thread_stats *stats, struct slab_stats *out) {
+    int sid;
+
+    out->set_cmds = 0;
+    out->get_hits = 0;
+    out->touch_hits = 0;
+    out->delete_hits = 0;
+    out->incr_hits = 0;
+    out->decr_hits = 0;
+    out->cas_hits = 0;
+    out->cas_badval = 0;
+
+    for (sid = 0; sid < MAX_NUMBER_OF_SLAB_CLASSES; sid++) {
+        out->set_cmds += stats->slab_stats[sid].set_cmds;
+        out->get_hits += stats->slab_stats[sid].get_hits;
+        out->touch_hits += stats->slab_stats[sid].touch_hits;
+        out->delete_hits += stats->slab_stats[sid].delete_hits;
+        out->decr_hits += stats->slab_stats[sid].decr_hits;
+        out->incr_hits += stats->slab_stats[sid].incr_hits;
+        out->cas_hits += stats->slab_stats[sid].cas_hits;
+        out->cas_badval += stats->slab_stats[sid].cas_badval;
+    }
+}
+
+#ifndef __INTEL_COMPILER
+#pragma GCC diagnostic ignored "-Wsign-compare"
+#endif
+/*
+ * Initializes the thread subsystem, creating various worker threads.
+ *
+ * nthreads  Number of worker event handler threads to spawn
+ * main_base Event base for main thread
+ */
+void thread_init(int nthreads, struct event_base *main_base) {
+    int         i;
+    int         power;
+
+    pthread_mutex_init(&cache_lock, NULL);
+    pthread_mutex_init(&stats_lock, NULL);
+
+    pthread_mutex_init(&init_lock, NULL);
+    pthread_cond_init(&init_cond, NULL);
+
+    pthread_mutex_init(&cqi_freelist_lock, NULL);
+    cqi_freelist = NULL;
+
+    /* Want a wide lock table, but don't waste memory */
+    if (nthreads < 3) {
+        power = 10;
+    } else if (nthreads < 4) {
+        power = 11;
+    } else if (nthreads < 5) {
+        power = 12;
+    } else {
+        /* 8192 buckets, and central locks don't scale much past 5 threads */
+        power = 13;
+    }
+
+    item_lock_count = ((unsigned long int)1 << (power));
+    item_lock_mask  = item_lock_count - 1;
+
+    item_locks = calloc(item_lock_count, sizeof(pthread_mutex_t));
+    if (! item_locks) {
+        perror("Can't allocate item locks");
+        exit(1);
+    }
+    for (i = 0; i < item_lock_count; i++) {
+        pthread_mutex_init(&item_locks[i], NULL);
+    }
+
+    threads = calloc(nthreads, sizeof(LIBEVENT_THREAD));
+    if (! threads) {
+        perror("Can't allocate thread descriptors");
+        exit(1);
+    }
+
+    dispatcher_thread.base = main_base;
+    dispatcher_thread.thread_id = pthread_self();
+
+    for (i = 0; i < nthreads; i++) {
+        int fds[2];
+        if (pipe(fds)) {
+            perror("Can't create notify pipe");
+            exit(1);
+        }
+
+        threads[i].notify_receive_fd = fds[0];
+        threads[i].notify_send_fd = fds[1];
+
+        setup_thread(&threads[i]);
+        /* Reserve three fds for the libevent base, and two for the pipe */
+        stats.reserved_fds += 5;
+    }
+
+    /* Create threads after we've done all the libevent setup. */
+    for (i = 0; i < nthreads; i++) {
+        create_worker(worker_libevent, &threads[i]);
+    }
+
+    /* Wait for all the threads to set themselves up before returning. */
+    pthread_mutex_lock(&init_lock);
+    while (init_count < nthreads) {
+        pthread_cond_wait(&init_cond, &init_lock);
+    }
+    pthread_mutex_unlock(&init_lock);
+}
+
diff --git a/memcached/timedrun.c b/memcached/timedrun.c
new file mode 100644 (file)
index 0000000..6d7afb3
--- /dev/null
@@ -0,0 +1,102 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <signal.h>
+#include <sys/wait.h>
+#include <sysexits.h>
+
+#include <assert.h>
+
+static int caught = 0;
+
+static void caught_signal(int which)
+{
+    caught = which;
+}
+
+static int wait_for_process(pid_t pid)
+{
+    int rv = EX_SOFTWARE;
+    int stats = 0;
+    int i = 0;
+    struct sigaction sig_handler;
+
+    sig_handler.sa_handler = caught_signal;
+    sig_handler.sa_flags = 0;
+
+    sigaction(SIGALRM, &sig_handler, NULL);
+    sigaction(SIGHUP, &sig_handler, NULL);
+    sigaction(SIGINT, &sig_handler, NULL);
+    sigaction(SIGTERM, &sig_handler, NULL);
+    sigaction(SIGPIPE, &sig_handler, NULL);
+
+    /* Loop forever waiting for the process to quit */
+    for (i = 0; ;i++) {
+        pid_t p = waitpid(pid, &stats, 0);
+        if (p == pid) {
+            /* child exited.  Let's get out of here */
+            rv = WIFEXITED(stats) ?
+                WEXITSTATUS(stats) :
+                (0x80 | WTERMSIG(stats));
+            break;
+        } else {
+            int sig = 0;
+            switch (i) {
+            case 0:
+                /* On the first iteration, pass the signal through */
+                sig = caught > 0 ? caught : SIGTERM;
+                if (caught == SIGALRM) {
+                   fprintf(stderr, "Timeout.. killing the process\n");
+                }
+                break;
+            case 1:
+                sig = SIGTERM;
+                break;
+            default:
+                sig = SIGKILL;
+                break;
+            }
+            if (kill(pid, sig) < 0) {
+                /* Kill failed.  Must have lost the process. :/ */
+                perror("lost child when trying to kill");
+            }
+            /* Wait up to 5 seconds for the pid */
+            alarm(5);
+        }
+    }
+    return rv;
+}
+
+static int spawn_and_wait(char **argv)
+{
+    int rv = EX_SOFTWARE;
+    pid_t pid = fork();
+
+    switch (pid) {
+    case -1:
+        perror("fork");
+        rv = EX_OSERR;
+        break; /* NOTREACHED */
+    case 0:
+        execvp(argv[0], argv);
+        perror("exec");
+        rv = EX_SOFTWARE;
+        break; /* NOTREACHED */
+    default:
+        rv = wait_for_process(pid);
+    }
+    return rv;
+}
+
+int main(int argc, char **argv)
+{
+    int naptime = 0;
+    assert(argc > 2);
+
+    naptime = atoi(argv[1]);
+    assert(naptime > 0 && naptime < 1800);
+
+    alarm(naptime);
+
+    return spawn_and_wait(argv+2);
+}
diff --git a/memcached/trace.h b/memcached/trace.h
new file mode 100644 (file)
index 0000000..dc792a0
--- /dev/null
@@ -0,0 +1,71 @@
+#ifndef TRACE_H
+#define TRACE_H
+
+#ifdef ENABLE_DTRACE
+#include "memcached_dtrace.h"
+#else
+#define MEMCACHED_ASSOC_DELETE(arg0, arg1, arg2)
+#define MEMCACHED_ASSOC_DELETE_ENABLED() (0)
+#define MEMCACHED_ASSOC_FIND(arg0, arg1, arg2)
+#define MEMCACHED_ASSOC_FIND_ENABLED() (0)
+#define MEMCACHED_ASSOC_INSERT(arg0, arg1, arg2)
+#define MEMCACHED_ASSOC_INSERT_ENABLED() (0)
+#define MEMCACHED_COMMAND_ADD(arg0, arg1, arg2, arg3, arg4)
+#define MEMCACHED_COMMAND_ADD_ENABLED() (0)
+#define MEMCACHED_COMMAND_APPEND(arg0, arg1, arg2, arg3, arg4)
+#define MEMCACHED_COMMAND_APPEND_ENABLED() (0)
+#define MEMCACHED_COMMAND_CAS(arg0, arg1, arg2, arg3, arg4)
+#define MEMCACHED_COMMAND_CAS_ENABLED() (0)
+#define MEMCACHED_COMMAND_DECR(arg0, arg1, arg2, arg3)
+#define MEMCACHED_COMMAND_DECR_ENABLED() (0)
+#define MEMCACHED_COMMAND_DELETE(arg0, arg1, arg2)
+#define MEMCACHED_COMMAND_DELETE_ENABLED() (0)
+#define MEMCACHED_COMMAND_GET(arg0, arg1, arg2, arg3, arg4)
+#define MEMCACHED_COMMAND_GET_ENABLED() (0)
+#define MEMCACHED_COMMAND_TOUCH(arg0, arg1, arg2, arg3, arg4)
+#define MEMCACHED_COMMAND_TOUCH_ENABLED() (0)
+#define MEMCACHED_COMMAND_INCR(arg0, arg1, arg2, arg3)
+#define MEMCACHED_COMMAND_INCR_ENABLED() (0)
+#define MEMCACHED_COMMAND_PREPEND(arg0, arg1, arg2, arg3, arg4)
+#define MEMCACHED_COMMAND_PREPEND_ENABLED() (0)
+#define MEMCACHED_COMMAND_REPLACE(arg0, arg1, arg2, arg3, arg4)
+#define MEMCACHED_COMMAND_REPLACE_ENABLED() (0)
+#define MEMCACHED_COMMAND_SET(arg0, arg1, arg2, arg3, arg4)
+#define MEMCACHED_COMMAND_SET_ENABLED() (0)
+#define MEMCACHED_CONN_ALLOCATE(arg0)
+#define MEMCACHED_CONN_ALLOCATE_ENABLED() (0)
+#define MEMCACHED_CONN_CREATE(arg0)
+#define MEMCACHED_CONN_CREATE_ENABLED() (0)
+#define MEMCACHED_CONN_DESTROY(arg0)
+#define MEMCACHED_CONN_DESTROY_ENABLED() (0)
+#define MEMCACHED_CONN_DISPATCH(arg0, arg1)
+#define MEMCACHED_CONN_DISPATCH_ENABLED() (0)
+#define MEMCACHED_CONN_RELEASE(arg0)
+#define MEMCACHED_CONN_RELEASE_ENABLED() (0)
+#define MEMCACHED_ITEM_LINK(arg0, arg1, arg2)
+#define MEMCACHED_ITEM_LINK_ENABLED() (0)
+#define MEMCACHED_ITEM_REMOVE(arg0, arg1, arg2)
+#define MEMCACHED_ITEM_REMOVE_ENABLED() (0)
+#define MEMCACHED_ITEM_REPLACE(arg0, arg1, arg2, arg3, arg4, arg5)
+#define MEMCACHED_ITEM_REPLACE_ENABLED() (0)
+#define MEMCACHED_ITEM_UNLINK(arg0, arg1, arg2)
+#define MEMCACHED_ITEM_UNLINK_ENABLED() (0)
+#define MEMCACHED_ITEM_UPDATE(arg0, arg1, arg2)
+#define MEMCACHED_ITEM_UPDATE_ENABLED() (0)
+#define MEMCACHED_PROCESS_COMMAND_END(arg0, arg1, arg2)
+#define MEMCACHED_PROCESS_COMMAND_END_ENABLED() (0)
+#define MEMCACHED_PROCESS_COMMAND_START(arg0, arg1, arg2)
+#define MEMCACHED_PROCESS_COMMAND_START_ENABLED() (0)
+#define MEMCACHED_SLABS_ALLOCATE(arg0, arg1, arg2, arg3)
+#define MEMCACHED_SLABS_ALLOCATE_ENABLED() (0)
+#define MEMCACHED_SLABS_ALLOCATE_FAILED(arg0, arg1)
+#define MEMCACHED_SLABS_ALLOCATE_FAILED_ENABLED() (0)
+#define MEMCACHED_SLABS_FREE(arg0, arg1, arg2)
+#define MEMCACHED_SLABS_FREE_ENABLED() (0)
+#define MEMCACHED_SLABS_SLABCLASS_ALLOCATE(arg0)
+#define MEMCACHED_SLABS_SLABCLASS_ALLOCATE_ENABLED() (0)
+#define MEMCACHED_SLABS_SLABCLASS_ALLOCATE_FAILED(arg0)
+#define MEMCACHED_SLABS_SLABCLASS_ALLOCATE_FAILED_ENABLED() (0)
+#endif
+
+#endif
diff --git a/memcached/util.c b/memcached/util.c
new file mode 100644 (file)
index 0000000..d92e621
--- /dev/null
@@ -0,0 +1,144 @@
+#include "memcached.h"
+
+#include <stdio.h>
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdarg.h>
+
+/* Avoid warnings on solaris, where isspace() is an index into an array, and gcc uses signed chars */
+#define xisspace(c) isspace((unsigned char)c)
+
+bool safe_strtoull(const char *str, uint64_t *out) {
+    assert(out != NULL);
+    errno = 0;
+    *out = 0;
+    char *endptr;
+    unsigned long long ull = strtoull(str, &endptr, 10);
+    if ((errno == ERANGE) || (str == endptr)) {
+        return false;
+    }
+
+    if (xisspace(*endptr) || (*endptr == '\0' && endptr != str)) {
+        if ((long long) ull < 0) {
+            /* only check for negative signs in the uncommon case when
+             * the unsigned number is so big that it's negative as a
+             * signed number. */
+            if (strchr(str, '-') != NULL) {
+                return false;
+            }
+        }
+        *out = ull;
+        return true;
+    }
+    return false;
+}
+
+bool safe_strtoll(const char *str, int64_t *out) {
+    assert(out != NULL);
+    errno = 0;
+    *out = 0;
+    char *endptr;
+    long long ll = strtoll(str, &endptr, 10);
+    if ((errno == ERANGE) || (str == endptr)) {
+        return false;
+    }
+
+    if (xisspace(*endptr) || (*endptr == '\0' && endptr != str)) {
+        *out = ll;
+        return true;
+    }
+    return false;
+}
+
+bool safe_strtoul(const char *str, uint32_t *out) {
+    char *endptr = NULL;
+    unsigned long l = 0;
+    assert(out);
+    assert(str);
+    *out = 0;
+    errno = 0;
+
+    l = strtoul(str, &endptr, 10);
+    if ((errno == ERANGE) || (str == endptr)) {
+        return false;
+    }
+
+    if (xisspace(*endptr) || (*endptr == '\0' && endptr != str)) {
+        if ((long) l < 0) {
+            /* only check for negative signs in the uncommon case when
+             * the unsigned number is so big that it's negative as a
+             * signed number. */
+            if (strchr(str, '-') != NULL) {
+                return false;
+            }
+        }
+        *out = l;
+        return true;
+    }
+
+    return false;
+}
+
+bool safe_strtol(const char *str, int32_t *out) {
+    assert(out != NULL);
+    errno = 0;
+    *out = 0;
+    char *endptr;
+    long l = strtol(str, &endptr, 10);
+    if ((errno == ERANGE) || (str == endptr)) {
+        return false;
+    }
+
+    if (xisspace(*endptr) || (*endptr == '\0' && endptr != str)) {
+        *out = l;
+        return true;
+    }
+    return false;
+}
+
+void vperror(const char *fmt, ...) {
+    int old_errno = errno;
+    char buf[1024];
+    va_list ap;
+
+    va_start(ap, fmt);
+    if (vsnprintf(buf, sizeof(buf), fmt, ap) == -1) {
+        buf[sizeof(buf) - 1] = '\0';
+    }
+    va_end(ap);
+
+    errno = old_errno;
+
+    perror(buf);
+}
+
+#ifndef HAVE_HTONLL
+static uint64_t mc_swap64(uint64_t in) {
+#ifdef ENDIAN_LITTLE
+    /* Little endian, flip the bytes around until someone makes a faster/better
+    * way to do this. */
+    int64_t rv = 0;
+    int i = 0;
+     for(i = 0; i<8; i++) {
+        rv = (rv << 8) | (in & 0xff);
+        in >>= 8;
+     }
+    return rv;
+#else
+    /* big-endian machines don't need byte swapping */
+    return in;
+#endif
+}
+
+uint64_t ntohll(uint64_t val) {
+   return mc_swap64(val);
+}
+
+uint64_t htonll(uint64_t val) {
+   return mc_swap64(val);
+}
+#endif
+
diff --git a/memcached/util.h b/memcached/util.h
new file mode 100644 (file)
index 0000000..1ec1a52
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * Wrappers around strtoull/strtoll that are safer and easier to
+ * use.  For tests and assumptions, see internal_tests.c.
+ *
+ * str   a NULL-terminated base decimal 10 unsigned integer
+ * out   out parameter, if conversion succeeded
+ *
+ * returns true if conversion succeeded.
+ */
+bool safe_strtoull(const char *str, uint64_t *out);
+bool safe_strtoll(const char *str, int64_t *out);
+bool safe_strtoul(const char *str, uint32_t *out);
+bool safe_strtol(const char *str, int32_t *out);
+
+#ifndef HAVE_HTONLL
+extern uint64_t htonll(uint64_t);
+extern uint64_t ntohll(uint64_t);
+#endif
+
+#ifdef __GCC
+# define __gcc_attribute__ __attribute__
+#else
+# define __gcc_attribute__(x)
+#endif
+
+/**
+ * Vararg variant of perror that makes for more useful error messages
+ * when reporting with parameters.
+ *
+ * @param fmt a printf format
+ */
+void vperror(const char *fmt, ...)
+    __gcc_attribute__ ((format (printf, 1, 2)));
diff --git a/memcached/version.m4 b/memcached/version.m4
new file mode 100644 (file)
index 0000000..5914292
--- /dev/null
@@ -0,0 +1 @@
+m4_define([VERSION_NUMBER], [1.4.13])
diff --git a/memcached/version.pl b/memcached/version.pl
new file mode 100755 (executable)
index 0000000..6f08faf
--- /dev/null
@@ -0,0 +1,59 @@
+#!/usr/bin/perl
+# If you think this is stupid/overkill, blame dormando
+
+use warnings;
+use strict;
+
+my $version = `git describe`;
+chomp $version;
+# Test the various versions.
+#my $version = 'foob';
+#my $version = '1.4.2-30-gf966dba';
+#my $version = '1.4.3-rc1';
+#my $version = '1.4.3';
+unless ($version =~ m/^\d+\.\d+\.\d+/) {
+    write_file('version.m4', "m4_define([VERSION_NUMBER], [UNKNOWN])\n");
+    exit;
+}
+
+$version =~ s/-/_/g;
+write_file('version.m4', "m4_define([VERSION_NUMBER], [$version])\n");
+my ($VERSION, $FULLVERSION, $RELEASE);
+
+if ($version =~ m/^(\d+\.\d+\.\d+)_rc(\d+)$/) {
+    $VERSION = $1;
+    $FULLVERSION = $version;
+    $RELEASE = '0.1.rc' . $2;
+} elsif ($version =~ m/^(\d+\.\d+\.\d+)_(.+)$/) {
+    $VERSION = $1;
+    $FULLVERSION = $version;
+    $RELEASE = '1.' . $2;
+} elsif ($version =~ m/^(\d+\.\d+\.\d+)$/) {
+    $VERSION = $1;
+    $FULLVERSION = $version;
+    $RELEASE = '1';
+}
+
+my $spec = read_file('memcached.spec.in');
+$spec =~ s/\@VERSION\@/$VERSION/gm;
+$spec =~ s/\@FULLVERSION\@/$FULLVERSION/gm;
+$spec =~ s/\@RELEASE\@/$RELEASE/gm;
+
+write_file('memcached.spec', $spec);
+
+sub write_file {
+    my $file = shift;
+    my $data = shift;
+    open(my $fh, "> $file") or die "Can't open $file: $!";
+    print $fh $data;
+    close($fh);
+}
+
+sub read_file {
+    my $file = shift;
+    local $/ = undef;
+    open(my $fh, "< $file") or die "Can't open $file: $!";
+    my $data = <$fh>;
+    close($fh);
+    return $data;
+}
diff --git a/memcached/version.sh b/memcached/version.sh
new file mode 100755 (executable)
index 0000000..a286017
--- /dev/null
@@ -0,0 +1,11 @@
+#!/bin/sh
+
+if git describe | sed s/-/_/g > version.num.tmp
+then
+    mv version.num.tmp version.num
+    echo "m4_define([VERSION_NUMBER], [`tr -d '\n' < version.num`])" \
+        > version.m4
+    sed s/@VERSION@/`cat version.num`/ < memcached.spec.in > memcached.spec
+else
+    rm version.num.tmp
+fi