aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIgor Pashev <pashev.igor@gmail.com>2014-01-18 17:26:45 +0400
committerIgor Pashev <pashev.igor@gmail.com>2014-01-18 17:26:45 +0400
commit5e37235d8726bf81ef8a343c62dc0b06de344337 (patch)
treea7b41ef2237be29e3198098d375db8967ab094ba
downloadfastcgi-5e37235d8726bf81ef8a343c62dc0b06de344337.tar.gz
Initial version0.1.0
-rw-r--r--.gitignore16
-rw-r--r--LICENSE19
-rw-r--r--Makefile.am34
-rw-r--r--README121
-rw-r--r--TODO7
-rw-r--r--cgroups.c537
-rw-r--r--cgroups.h30
-rw-r--r--configure.ac66
-rw-r--r--debug.c56
-rw-r--r--debug.h33
-rw-r--r--dispatch.c73
-rw-r--r--dispatch.h30
-rw-r--r--m4/ax_pthread.m4332
-rw-r--r--main.c273
-rw-r--r--uri.c34
-rw-r--r--uri.h29
16 files changed, 1690 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..0b2d3d6
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,16 @@
+*.o
+*~
+/aclocal.m4
+/autom4te.cache
+/compile
+/config.guess
+/config.h.in
+/config.sub
+/configure
+/depcomp
+/install-sh
+/missing
+/stamp-h1
+Makefile
+Makefile.in
+license.c
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..104e959
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2014 Igor Pashev <pashev.igor@gmail.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..b1d36f6
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,34 @@
+AM_CFLAGS = $(PTHREAD_CFLAGS)
+AM_LDFLAGS = $(PTHREAD_LIBS)
+
+bin_PROGRAMS = fcgi
+
+fcgi_SOURCES = \
+debug.h \
+dispatch.c \
+dispatch.h \
+main.c \
+uri.c \
+uri.h
+
+if ENABLE_DEBUG
+fcgi_SOURCES += debug.c
+endif
+
+if ENABLE_CGROUPS
+fcgi_SOURCES += cgroups.c cgroups.h
+endif
+
+if HAVE_XXD
+fcgi_SOURCES += license.c
+
+license.c: LICENSE
+ $(AM_V_GEN)$(XXD) -i $< > $@
+
+CLEANFILES = license.c
+endif
+
+doc_DATA = LICENSE README
+
+EXTRA_DIST = $(doc_DATA) TODO
+
diff --git a/README b/README
new file mode 100644
index 0000000..38a9951
--- /dev/null
+++ b/README
@@ -0,0 +1,121 @@
+I. BUILDING
+-------------------------
+
+1. Requirements:
+
+ libfcgi - http://www.nongnu.org/fastcgi/
+ libcgroup - http://libcg.sourceforge.net/
+
+
+2. Preconfigure if using git clone (with autoconf & automake)
+
+ # autoreconf -fiv
+
+
+3. Configure and make
+
+ # ./configure --enable-debug
+ # make
+
+
+
+II. RUNNING
+-------------------------
+
+1. For lighttpd use
+
+ fastcgi.server = (
+ "/fcgi/" =>
+ ((
+ "host" => "127.0.0.1",
+ "port" => "9000",
+ "check-local" => "disable",
+ ))
+ )
+
+
+2. Run ./fcgi from the directory where is was built
+
+ # ./fcgi
+ 2014-01-18 17:06:28 +0400 main.c:193 (init_libraries): initializing libfcgi
+ 2014-01-18 17:06:28 +0400 main.c:200 (init_libraries): initializing libcgroup
+ ./fcgi: socket `:9000', backlog 16, 5 workers, URI prefix `/fcgi'
+ 2014-01-18 17:06:28 +0400 main.c:230 (main): allocating space for 5 threads
+ 2014-01-18 17:06:28 +0400 main.c:239 (main): starting threads
+ 2014-01-18 17:06:28 +0400 main.c:245 (main): starting thread #0
+ 2014-01-18 17:06:28 +0400 main.c:245 (main): starting thread #1
+ 2014-01-18 17:06:28 +0400 main.c:63 (worker): thread #0 started
+ 2014-01-18 17:06:28 +0400 main.c:245 (main): starting thread #2
+ 2014-01-18 17:06:28 +0400 main.c:63 (worker): thread #1 started
+ 2014-01-18 17:06:28 +0400 main.c:245 (main): starting thread #3
+ 2014-01-18 17:06:28 +0400 main.c:63 (worker): thread #2 started
+ 2014-01-18 17:06:28 +0400 main.c:245 (main): starting thread #4
+ 2014-01-18 17:06:28 +0400 main.c:63 (worker): thread #3 started
+ 2014-01-18 17:06:28 +0400 main.c:63 (worker): thread #4 started
+
+
+
+III. API
+-------------------------
+
+0. Setup used in examples:
+
+# lscgroup
+cpu:/
+cpu:/hello
+cpu:/hello/world
+blkio:/
+blkio:/hello
+blkio:/hello/man
+cpuacct,devices,freezer:/
+net_cls,perf_event:/
+
+
+1. List all groups hierarhies
+
+# curl 'http://localhost/fcgi/cgroups/'
+[
+ {controllers: ["cpu"], groups: ["/", "/hello", "/hello/world"]},
+ {controllers: ["blkio"], groups: ["/", "/hello", "/hello/man"]},
+ {controllers: ["cpuacct", "devices", "freezer"], groups: ["/"]},
+ {controllers: ["net_cls", "perf_event"], groups: ["/"]}
+]
+
+
+2. List particular hierarhies
+
+# curl 'http://localhost/fcgi/cgroups/cpu:/'
+[{controllers: ["cpu"], groups: ["/", "/hello", "/hello/world"]}]
+
+# curl 'http://localhost/fcgi/cgroups/cpu,blkio:/'
+[{controllers: ["cpu"], groups: ["/", "/hello", "/hello/world"]},
+ {controllers: ["blkio"], groups: ["/", "/hello", "/hello/man"]}]
+
+# curl 'http://localhost/fcgi/cgroups/cpu,blkio:/hello'
+[{controllers: ["cpu"], groups: ["/hello/", "/hello/world"]},
+ {controllers: ["blkio"], groups: ["/hello/", "/hello/man"]}]
+
+# curl 'http://localhost/fcgi/cgroups/cpu,devices:/hello'
+[{controllers: ["cpu"], groups: ["/hello/", "/hello/world"]}]
+
+
+3. Listing tasks
+
+# curl 'http://localhost/fcgi/cgroups/blkio:/hello?list-tasks'
+[24086]
+
+# curl 'http://localhost/fcgi/cgroups/cpu:/hello?list-tasks'
+[1, 24086, 24099]
+
+# curl 'http://localhost/fcgi/cgroups/cpu,blkio:/hello?list-tasks'
+[24086]
+
+
+4. Attaching (moving) a task to a group
+
+# curl 'http://localhost/fcgi/cgroups/blkio:/hello?attach-task=24099'
+{}
+
+# curl 'http://localhost/fcgi/cgroups/cpu,blkio:/hello?list-tasks'
+[24086, 24099]
+
diff --git a/TODO b/TODO
new file mode 100644
index 0000000..fc1f2b5
--- /dev/null
+++ b/TODO
@@ -0,0 +1,7 @@
+1. Make output abstraction to allow different formats (JSON, XML, plain text)
+
+ E. g. replace FCGX_FPrintF (request->out, ...);
+ with report_error(request->out, ...);
+
+2. Add support for Solaris projects
+
diff --git a/cgroups.c b/cgroups.c
new file mode 100644
index 0000000..c4f3324
--- /dev/null
+++ b/cgroups.c
@@ -0,0 +1,537 @@
+/*
+Copyright (c) 2014 Igor Pashev <pashev.igor@gmail.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+
+#include <fcgiapp.h>
+#include <libcgroup.h>
+
+#include "debug.h"
+
+
+// trim traling slashes, but not if str == "/"
+static void
+trim_trailing_slashes (char *str)
+{
+ if (NULL != str)
+ {
+ size_t len = strlen (str);
+ if (len > 1)
+ {
+ char *last_symbol = (char *) str + len - 1;
+ while ('/' == *last_symbol)
+ last_symbol--;
+ last_symbol[1] = '\0';
+ }
+ }
+}
+
+
+// Is name in "name1,name2,name3" ?
+static bool
+controller_is_in_list (const char *controllers, const char *name)
+{
+ if ((NULL == controllers) || ('\0' == controllers[0])
+ || ('*' == controllers[0]))
+ return true;
+
+ const char *p = strstr (controllers, name);
+
+ if (NULL == p)
+ return false;
+
+ if ((controllers != p) && (',' != p[-1]))
+ return false;
+
+ p += strlen (name);
+ if ((',' != *p) && ('\0' != *p))
+ return false;
+
+ return true;
+}
+
+
+// "name1,name2" => 2
+// "name1,name2,name3" => 3
+static int
+count_controllers (const char *controllers)
+{
+ size_t number_of_controllers = 0;
+ size_t controllers_len = strlen (controllers) + 1;
+ char *tail = NULL;
+ char controllers_copy[controllers_len];
+ memcpy (controllers_copy, controllers, controllers_len);
+
+ char *next_controller = strtok_r (controllers_copy, ",", &tail);
+ do
+ {
+ debug ("next controller `%s'", next_controller);
+ number_of_controllers++;
+ next_controller = strtok_r (NULL, ",", &tail);
+ }
+ while (NULL != next_controller);
+ debug ("number of controllers in `%s': %d", controllers,
+ number_of_controllers);
+ return number_of_controllers;
+}
+
+
+static bool
+group_has_controller (const char *controller, const char *group)
+{
+ int rc;
+ void *handle = NULL;
+ int base_level = 0;
+ struct cgroup_file_info info;
+
+ rc =
+ cgroup_walk_tree_begin (controller, group, 0, &handle, &info,
+ &base_level);
+
+ if (0 == rc)
+ {
+ cgroup_walk_tree_end (&handle);
+ if (CGROUP_FILE_TYPE_DIR == info.type)
+ return true;
+ }
+
+ return false;
+}
+
+
+static bool
+group_exists (const char *controllers, const char *group)
+{
+ size_t controllers_len = strlen (controllers) + 1;
+ char *tail = NULL;
+ char controllers_copy[controllers_len];
+ memcpy (controllers_copy, controllers, controllers_len);
+
+ char *controller = strtok_r (controllers_copy, ",", &tail);
+ do
+ {
+ debug ("controller `%s'", controller);
+ if (!group_has_controller (controller, group))
+ return false;
+ controller = strtok_r (NULL, ",", &tail);
+ }
+ while (NULL != controller);
+ return true;
+}
+
+
+static bool
+group_has_pid (const char *controllers, const char *group, pid_t pid)
+{
+ bool ret = true;
+
+ char proc_cgroup_path[sizeof ("/proc/2147483647/cgroup")];
+ char pid_controllers[FILENAME_MAX];
+
+ snprintf (proc_cgroup_path, sizeof (proc_cgroup_path), "/proc/%d/cgroup",
+ pid);
+ debug ("reading `%s'", proc_cgroup_path);
+
+ FILE *proc_cgroup_file = fopen (proc_cgroup_path, "r");
+ if (NULL != proc_cgroup_file)
+ {
+ char *next_controller = pid_controllers;
+ while (!feof (proc_cgroup_file))
+ {
+ char cntrls[FILENAME_MAX];
+ char path[FILENAME_MAX];
+ int rc = fscanf (proc_cgroup_file, "%*d:%[^:]:%s\n", cntrls, path);
+ if (2 != rc)
+ {
+ debug ("fscanf() returned %d != 2", rc);
+ continue;
+ }
+
+ debug ("pid %d is in `%s:%s'", pid, cntrls, path);
+ if (0 == strcmp (path, group))
+ {
+ if (next_controller != pid_controllers)
+ {
+ *next_controller = ',';
+ next_controller++;
+ }
+ size_t l = strlen (cntrls);
+ memcpy (next_controller, cntrls, l);
+ next_controller += l;
+ }
+ }
+ fclose (proc_cgroup_file);
+
+ *next_controller = '\0';
+ debug ("all pid %d controllers `%s'", pid, pid_controllers);
+
+ // all controllers must be in pid_controllers:
+ size_t l = strlen (controllers) + 1;
+ char controllers_copy[l];
+ char *tail = NULL;
+ memcpy (controllers_copy, controllers, l);
+ next_controller = strtok_r (controllers_copy, ",", &tail);
+ while (NULL != next_controller)
+ {
+ debug ("checking controller `%s'", next_controller);
+ if (!controller_is_in_list (pid_controllers, next_controller))
+ {
+ ret = false;
+ break;
+ }
+ next_controller = strtok_r (NULL, ",", &tail);
+ }
+ }
+ else
+ {
+ debug ("fopen() failed: %s ", strerror (errno));
+ ret = false;
+ }
+
+ debug ("pid %d is%s under `%s' controllers", pid, (ret ? "" : " not"),
+ controllers);
+ return ret;
+}
+
+
+static void
+fcgi_cgroups_list_controllers_by_mountpoint (FCGX_Request * request,
+ const char *mountpoint)
+{
+ int rc;
+ void *handle = NULL;
+ struct cgroup_mount_point controller;
+ int contr_count = 0;
+
+ FCGX_PutS ("[", request->out);
+
+ rc = cgroup_get_controller_begin (&handle, &controller);
+ debug ("cgroup_get_controller_begin() returned %d", rc);
+
+ while (0 == rc)
+ {
+ debug ("controller `%s', mount point `%s'", controller.name,
+ controller.path);
+ if (0 == strcmp (mountpoint, controller.path))
+ {
+ if (contr_count > 0)
+ FCGX_PutS (", ", request->out);
+ FCGX_FPrintF (request->out, "\"%s\"", controller.name);
+ contr_count++;
+ }
+ rc = cgroup_get_controller_next (&handle, &controller);
+ }
+ debug ("exit from loop with %d", rc);
+
+ cgroup_get_controller_end (&handle);
+
+ FCGX_PutS ("]", request->out);
+}
+
+
+static void
+fcgi_cgroups_list_groups (FCGX_Request * request, const char *controller,
+ const char *path, const char *mountpoint)
+{
+ int rc;
+ void *handle = NULL;
+ int base_level = 0;
+ struct cgroup_file_info info;
+ int group_count = 0;
+
+ FCGX_PutS ("[", request->out);
+
+ size_t mountpoint_len = strlen (mountpoint);
+
+ rc =
+ cgroup_walk_tree_begin (controller, path, 0, &handle, &info, &base_level);
+ debug ("cgroup_walk_tree_begin() returned %d", rc);
+
+ while (0 == rc)
+ {
+ if (CGROUP_FILE_TYPE_DIR == info.type)
+ {
+ if (group_count > 0)
+ FCGX_PutS (", ", request->out);
+
+ const char *rel_path = info.full_path + mountpoint_len;
+ while ('/' == *rel_path)
+ rel_path++;
+
+ FCGX_FPrintF (request->out, "\"/%s\"", rel_path);
+
+ debug ("full path = `%s', rel path = `%s'", info.full_path,
+ rel_path);
+
+ group_count++;
+ }
+ rc = cgroup_walk_tree_next (0, &handle, &info, base_level);
+ }
+ debug ("exit from loop with %d", rc);
+
+ cgroup_walk_tree_end (&handle);
+
+ FCGX_PutS ("]", request->out);
+}
+
+
+static void
+fcgi_cgroups_heirarchy (FCGX_Request * request, const char *controller,
+ const char *path, const char *mountpoint)
+{
+ FCGX_PutS ("{", request->out);
+
+ FCGX_PutS ("controllers: ", request->out);
+ fcgi_cgroups_list_controllers_by_mountpoint (request, mountpoint);
+
+ FCGX_PutS (", ", request->out);
+
+ FCGX_PutS ("groups: ", request->out);
+ fcgi_cgroups_list_groups (request, controller, path, mountpoint);
+
+ FCGX_PutS ("}", request->out);
+}
+
+
+static void
+fcgi_cgroups_list_hierarhies (FCGX_Request * request, const char *controllers,
+ const char *path)
+{
+ int rc;
+ void *handle = NULL;
+ struct cgroup_mount_point controller;
+ char mountpoint[FILENAME_MAX]; // it's a sizeof cgroup_mount_point.path
+ int hier_count = 0;
+
+ FCGX_PutS ("[", request->out);
+
+ mountpoint[0] = '\0';
+
+ debug ("controllers `%s', path `%s'", controllers, path);
+
+ rc = cgroup_get_controller_begin (&handle, &controller);
+ debug ("cgroup_get_controller_begin() returned %d", rc);
+
+ while (0 == rc)
+ {
+ debug ("controller `%s', mount point `%s'", controller.name,
+ controller.path);
+
+ bool use_controller =
+ controller_is_in_list (controllers, controller.name)
+ && group_has_controller (controller.name, path);
+
+ if (use_controller)
+ {
+ // XXX are controllers sorted by mount point?
+ // XXX use stat() and st_dev/st_ino ?
+ if (0 != strcmp (mountpoint, controller.path)) // new mount point (hierarchy)
+ {
+ strcpy (mountpoint, controller.path);
+ if (hier_count > 0)
+ FCGX_PutS (", ", request->out);
+ fcgi_cgroups_heirarchy (request, controller.name, path,
+ mountpoint);
+ hier_count++;
+ }
+ }
+ rc = cgroup_get_controller_next (&handle, &controller);
+ }
+ debug ("exit from loop with %d", rc);
+
+ cgroup_get_controller_end (&handle);
+
+ FCGX_PutS ("]", request->out);
+}
+
+
+static void
+fcgi_cgroups_list_tasks (FCGX_Request * request, const char *controllers,
+ const char *path)
+{
+ int rc;
+ pid_t pid;
+ void *handle = NULL;
+ int pid_count = 0;
+
+ FCGX_PutS ("[", request->out);
+
+ if (group_exists (controllers, path))
+ {
+ const char *first_controller;
+ char *other_controllers;
+ size_t l = strlen (controllers) + 1;
+ char cntlrs[l];
+ memcpy (cntlrs, controllers, l);
+ first_controller = strtok_r (cntlrs, ",", &other_controllers);
+ rc = cgroup_get_task_begin (path, first_controller, &handle, &pid);
+ debug ("cgroup_get_task_begin() returned %d", rc);
+
+ while (0 == rc)
+ {
+ if ((NULL == other_controllers)
+ || group_has_pid (other_controllers, path, pid))
+ {
+ if (pid_count > 0)
+ FCGX_PutS (", ", request->out);
+ FCGX_FPrintF (request->out, "%d", pid);
+ pid_count++;
+ }
+ rc = cgroup_get_task_next (&handle, &pid);
+ }
+ debug ("exit from loop with %d", rc);
+
+ cgroup_get_task_end (&handle);
+ }
+ else
+ {
+ debug ("group `%s:%s' does not exist", controllers, path);
+ }
+
+ FCGX_PutS ("]", request->out);
+}
+
+
+static void
+fcgi_cgroups_attach_task (FCGX_Request * request, const char *controllers,
+ const char *path, const char *pid_s)
+{
+ char *p;
+ pid_t pid = strtoul (pid_s, &p, 10);
+
+ if ((pid <= 0) || ('\0' != *p))
+ {
+ debug ("invalid pid: %s", pid_s);
+ FCGX_FPrintF (request->out, "{error: \"Invalid pid\"}");
+ return;
+ }
+
+ size_t controllers_len = strlen (controllers) + 1;
+ char controllers_copy[controllers_len];
+ memcpy (controllers_copy, controllers, controllers_len);
+
+ size_t number_of_controllers = count_controllers (controllers);
+ const char *controllers_list[number_of_controllers + 1]; // + sentinel
+ memset (controllers_list, 0, sizeof (controllers_list));
+
+ char *tail = NULL;
+ int controller_index = 0;
+ char *next_controller = strtok_r (controllers_copy, ",", &tail);
+ do
+ {
+ debug ("controller[%d] = %s", controller_index, next_controller);
+ controllers_list[controller_index] = next_controller;
+ next_controller = strtok_r (NULL, ",", &tail);
+ controller_index++;
+ }
+ while (NULL != next_controller);
+
+ int rc = cgroup_change_cgroup_path (path, pid, controllers_list);
+ if (0 != rc)
+ {
+ debug ("cgroup_change_cgroup_path() returned %d (%s)", rc,
+ cgroup_strerror (rc));
+ FCGX_FPrintF (request->out, "{error: \"%s\"}", cgroup_strerror (rc));
+ }
+ else
+ FCGX_FPrintF (request->out, "{}");
+}
+
+
+static void
+fcgi_cgroups_action (FCGX_Request * request, const char *controllers,
+ const char *path, const char *action)
+{
+ size_t l = strlen (action) + 1;
+
+ char act[l];
+
+ memcpy (act, action, l);
+
+ char *arg = strchr (act, '=');
+ if (NULL != arg)
+ {
+ *arg = '\0';
+ arg++;
+ }
+
+ debug ("action `%s', argument `%s'", act, arg);
+
+ if (0 == strcmp ("list", act))
+ fcgi_cgroups_list_hierarhies (request, controllers, path);
+ else if (0 == strcmp ("list-tasks", act))
+ fcgi_cgroups_list_tasks (request, controllers, path);
+ else if (0 == strcmp ("attach-task", act))
+ fcgi_cgroups_attach_task (request, controllers, path, arg);
+}
+
+
+void
+fcgi_cgroups (FCGX_Request * request, char **uri_tail)
+{
+ // controllers:path?action
+ char *controllers;
+ char *path = NULL;
+ char *action = NULL;
+
+ while ('/' == **uri_tail)
+ (*uri_tail)++;
+
+ char *colon = strchr (*uri_tail, ':');
+ if (NULL != colon)
+ {
+ *colon = '\0';
+ path = colon + 1;
+ controllers = *uri_tail;
+ }
+ else
+ {
+ controllers = "*";
+ path = *uri_tail;
+ }
+
+ char *ques = strchr (path, '?');
+ if (NULL != ques)
+ {
+ *ques = '\0';
+ action = ques + 1;
+ }
+
+ trim_trailing_slashes (path);
+
+ debug ("controllers `%s', path `%s', action `%s'", controllers, path,
+ action);
+
+ if ((NULL == action) || ('\0' == action[0]))
+ fcgi_cgroups_list_hierarhies (request, controllers, path);
+ else
+ fcgi_cgroups_action (request, controllers, path, action);
+}
diff --git a/cgroups.h b/cgroups.h
new file mode 100644
index 0000000..251983c
--- /dev/null
+++ b/cgroups.h
@@ -0,0 +1,30 @@
+/*
+Copyright (c) 2014 Igor Pashev <pashev.igor@gmail.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+
+#ifndef _CGROUPS_H
+#define _CGROUPS_H
+
+#include <fcgiapp.h>
+
+void fcgi_cgroups (FCGX_Request *, char **);
+
+#endif // _CGROUPS_H
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..581f916
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,66 @@
+# -*- Autoconf -*-
+# Process this file with autoconf to produce a configure script.
+
+AC_PREREQ([2.69])
+AC_INIT([fcgi], [0.1.0], [pashev.igor@gmail.com])
+AC_CONFIG_SRCDIR([main.c])
+AC_CONFIG_HEADERS([config.h])
+AM_INIT_AUTOMAKE([subdir-objects foreign dist-xz])
+AC_CONFIG_MACRO_DIR([m4])
+
+
+AC_PROG_CC_C99
+AC_USE_SYSTEM_EXTENSIONS
+AX_PTHREAD
+
+AC_CHECK_PROGS([XXD], [xxd], [none])
+AM_CONDITIONAL([HAVE_XXD], [test x$XXD != xnone])
+AS_IF([test x$XXD != xnone],
+ [AC_DEFINE([HAVE_XXD], [1], [Define to 1 if have an xxd generated source with license text])])
+
+AC_CHECK_HEADERS([fcgiapp.h], [],
+ [AC_MSG_ERROR([Missing the fcgiapp.h header file from the libfcgi library])]
+)
+AC_CHECK_LIB([fcgi], [FCGX_Init], [],
+ [AC_MSG_ERROR([Missing the libfcgi library])]
+)
+
+
+AC_ARG_ENABLE([debug], [AS_HELP_STRING([--enable-debug],
+ [Enable debug messages @<:@disable by default@:>@])])
+AC_MSG_CHECKING([whether to enable debug])
+AS_IF([test x$enable_debug = xyes],
+ [ AC_DEFINE([ENABLE_DEBUG], [1], [Set to 1 to enable debug messages])
+ AC_MSG_RESULT([yes])
+ ],
+ [ AC_MSG_RESULT([no])
+ ]
+)
+AM_CONDITIONAL([ENABLE_DEBUG], [test x$enable_debug = xyes])
+
+
+AC_ARG_ENABLE([cgroups], [AS_HELP_STRING([--enable-cgroups],
+ [Enable cgroups REST api @<:@enabled by default on linux@:>@])],
+ [], [AS_CASE([$host], [*linux*], [enable_cgroups=yes])])
+AC_MSG_CHECKING([whether to enable cgroups])
+AS_IF([test x$enable_cgroups = xyes],
+ [ AC_DEFINE([ENABLE_CGROUPS], [1], [Set to 1 to enable cgroups])
+ AC_MSG_RESULT([yes])
+ AC_CHECK_HEADER([libcgroup.h], [], [AC_MSG_ERROR([Missing libcgroup headers])])
+ AC_CHECK_LIB([cgroup], [cgroup_walk_tree_begin], [], [AC_MSG_ERROR([Missing the libcgroup library])])
+ ],
+ [ AC_MSG_RESULT([no])
+ ]
+)
+AM_CONDITIONAL([ENABLE_CGROUPS], [test x$enable_cgroups = xyes])
+
+AC_ARG_ENABLE([uri-prefix], [AS_HELP_STRING([--enable-uri-prefix=/fcgi],
+ [REST URI prefix, this prefix will be stripped from the request URI, default is /fcgi])],
+ [enable_uri_prefix=$enableval], [enable_uri_prefix=/fcgi])
+AC_MSG_CHECKING([for REST URI prefix])
+AC_MSG_RESULT([$enable_uri_prefix])
+AC_DEFINE_UNQUOTED([URI_PREFIX], ["$enable_uri_prefix"], [REST URI prefix])
+
+AC_CONFIG_FILES([Makefile])
+AC_OUTPUT
+
diff --git a/debug.c b/debug.c
new file mode 100644
index 0000000..4263002
--- /dev/null
+++ b/debug.c
@@ -0,0 +1,56 @@
+/*
+Copyright (c) 2014 Igor Pashev <pashev.igor@gmail.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <pthread.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <time.h>
+
+static pthread_mutex_t debug_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+void
+__debug (const char *file, int line, const char *func, const char *msg, ...)
+{
+ va_list ap;
+ time_t ltime;
+ char time_str[32];
+
+ ltime = time (NULL);
+ strftime (time_str, sizeof (time_str), "%Y-%m-%d %T %z",
+ localtime (&ltime));
+
+ va_start (ap, msg);
+
+ pthread_mutex_lock (&debug_mutex);
+
+ fprintf (stderr, "%s %s:%d (%s): ", time_str, file, line, func);
+ vfprintf (stderr, msg, ap);
+ fprintf (stderr, "\n");
+
+ pthread_mutex_unlock (&debug_mutex);
+
+ va_end (ap);
+}
diff --git a/debug.h b/debug.h
new file mode 100644
index 0000000..3371597
--- /dev/null
+++ b/debug.h
@@ -0,0 +1,33 @@
+/*
+Copyright (c) 2014 Igor Pashev <pashev.igor@gmail.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+
+#ifndef _DEBUG_H
+#define _DEBUG_H
+
+#ifdef ENABLE_DEBUG
+#define debug(...) __debug(__FILE__, __LINE__, __func__, __VA_ARGS__)
+void __debug (const char *, int, const char *, const char *, ...);
+#else // ! ENABLE_DEBUG
+#define debug(...) ((void)0)
+#endif
+
+#endif // _DEBUG_H
diff --git a/dispatch.c b/dispatch.c
new file mode 100644
index 0000000..d3dd128
--- /dev/null
+++ b/dispatch.c
@@ -0,0 +1,73 @@
+/*
+Copyright (c) 2014 Igor Pashev <pashev.igor@gmail.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <string.h>
+
+#include <fcgiapp.h>
+
+#include "uri.h"
+#include "debug.h"
+
+#ifdef ENABLE_CGROUPS
+#include "cgroups.h"
+#endif
+
+void
+dispatch (FCGX_Request * request)
+{
+ char *uri_tail;
+ char *uri = FCGX_GetParam ("REQUEST_URI", request->envp);
+
+ debug ("request uri = `%s'", uri);
+
+ FCGX_PutS ("Content-type: application/json\r\n", request->out);
+ FCGX_PutS ("\r\n", request->out);
+
+ if (strstr (uri, uri_prefix) != uri)
+ {
+ FCGX_FPrintF (request->out, "{error: \"Request must start with %s\"}",
+ uri_prefix);
+ return;
+ }
+ else
+ uri += uri_prefix_len;
+ debug ("stripped uri = `%s'", uri);
+
+ const char *driver = strtok_r (uri, "/", &uri_tail);
+ debug ("driver = `%s'", driver);
+
+ if (NULL == driver)
+ FCGX_PutS ("{}", request->out);
+#ifdef ENABLE_CGROUPS
+ else if (0 == strcmp ("cgroups", driver))
+ fcgi_cgroups (request, &uri_tail);
+#endif
+ else
+ {
+ debug ("unknown request: `%s'", driver);
+ FCGX_FPrintF (request->out, "{error: \"Unknown request: %s\"}", driver);
+ }
+}
diff --git a/dispatch.h b/dispatch.h
new file mode 100644
index 0000000..a7e9cc1
--- /dev/null
+++ b/dispatch.h
@@ -0,0 +1,30 @@
+/*
+Copyright (c) 2014 Igor Pashev <pashev.igor@gmail.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+
+#ifndef _DISPATCH_H
+#define _DISPATCH_H
+
+#include <fcgiapp.h>
+
+void dispatch (FCGX_Request *);
+
+#endif // _DISPATCH_H
diff --git a/m4/ax_pthread.m4 b/m4/ax_pthread.m4
new file mode 100644
index 0000000..d383ad5
--- /dev/null
+++ b/m4/ax_pthread.m4
@@ -0,0 +1,332 @@
+# ===========================================================================
+# http://www.gnu.org/software/autoconf-archive/ax_pthread.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+# AX_PTHREAD([ACTION-IF-FOUND[, ACTION-IF-NOT-FOUND]])
+#
+# DESCRIPTION
+#
+# This macro figures out how to build C programs using POSIX threads. It
+# sets the PTHREAD_LIBS output variable to the threads library and linker
+# flags, and the PTHREAD_CFLAGS output variable to any special C compiler
+# flags that are needed. (The user can also force certain compiler
+# flags/libs to be tested by setting these environment variables.)
+#
+# Also sets PTHREAD_CC to any special C compiler that is needed for
+# multi-threaded programs (defaults to the value of CC otherwise). (This
+# is necessary on AIX to use the special cc_r compiler alias.)
+#
+# NOTE: You are assumed to not only compile your program with these flags,
+# but also link it with them as well. e.g. you should link with
+# $PTHREAD_CC $CFLAGS $PTHREAD_CFLAGS $LDFLAGS ... $PTHREAD_LIBS $LIBS
+#
+# If you are only building threads programs, you may wish to use these
+# variables in your default LIBS, CFLAGS, and CC:
+#
+# LIBS="$PTHREAD_LIBS $LIBS"
+# CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
+# CC="$PTHREAD_CC"
+#
+# In addition, if the PTHREAD_CREATE_JOINABLE thread-attribute constant
+# has a nonstandard name, defines PTHREAD_CREATE_JOINABLE to that name
+# (e.g. PTHREAD_CREATE_UNDETACHED on AIX).
+#
+# Also HAVE_PTHREAD_PRIO_INHERIT is defined if pthread is found and the
+# PTHREAD_PRIO_INHERIT symbol is defined when compiling with
+# PTHREAD_CFLAGS.
+#
+# ACTION-IF-FOUND is a list of shell commands to run if a threads library
+# is found, and ACTION-IF-NOT-FOUND is a list of commands to run it if it
+# is not found. If ACTION-IF-FOUND is not specified, the default action
+# will define HAVE_PTHREAD.
+#
+# Please let the authors know if this macro fails on any platform, or if
+# you have any other suggestions or comments. This macro was based on work
+# by SGJ on autoconf scripts for FFTW (http://www.fftw.org/) (with help
+# from M. Frigo), as well as ac_pthread and hb_pthread macros posted by
+# Alejandro Forero Cuervo to the autoconf macro repository. We are also
+# grateful for the helpful feedback of numerous users.
+#
+# Updated for Autoconf 2.68 by Daniel Richard G.
+#
+# LICENSE
+#
+# Copyright (c) 2008 Steven G. Johnson <stevenj@alum.mit.edu>
+# Copyright (c) 2011 Daniel Richard G. <skunk@iSKUNK.ORG>
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation, either version 3 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+# Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+# As a special exception, the respective Autoconf Macro's copyright owner
+# gives unlimited permission to copy, distribute and modify the configure
+# scripts that are the output of Autoconf when processing the Macro. You
+# need not follow the terms of the GNU General Public License when using
+# or distributing such scripts, even though portions of the text of the
+# Macro appear in them. The GNU General Public License (GPL) does govern
+# all other use of the material that constitutes the Autoconf Macro.
+#
+# This special exception to the GPL applies to versions of the Autoconf
+# Macro released by the Autoconf Archive. When you make and distribute a
+# modified version of the Autoconf Macro, you may extend this special
+# exception to the GPL to apply to your modified version as well.
+
+#serial 21
+
+AU_ALIAS([ACX_PTHREAD], [AX_PTHREAD])
+AC_DEFUN([AX_PTHREAD], [
+AC_REQUIRE([AC_CANONICAL_HOST])
+AC_LANG_PUSH([C])
+ax_pthread_ok=no
+
+# We used to check for pthread.h first, but this fails if pthread.h
+# requires special compiler flags (e.g. on True64 or Sequent).
+# It gets checked for in the link test anyway.
+
+# First of all, check if the user has set any of the PTHREAD_LIBS,
+# etcetera environment variables, and if threads linking works using
+# them:
+if test x"$PTHREAD_LIBS$PTHREAD_CFLAGS" != x; then
+ save_CFLAGS="$CFLAGS"
+ CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
+ save_LIBS="$LIBS"
+ LIBS="$PTHREAD_LIBS $LIBS"
+ AC_MSG_CHECKING([for pthread_join in LIBS=$PTHREAD_LIBS with CFLAGS=$PTHREAD_CFLAGS])
+ AC_TRY_LINK_FUNC([pthread_join], [ax_pthread_ok=yes])
+ AC_MSG_RESULT([$ax_pthread_ok])
+ if test x"$ax_pthread_ok" = xno; then
+ PTHREAD_LIBS=""
+ PTHREAD_CFLAGS=""
+ fi
+ LIBS="$save_LIBS"
+ CFLAGS="$save_CFLAGS"
+fi
+
+# We must check for the threads library under a number of different
+# names; the ordering is very important because some systems
+# (e.g. DEC) have both -lpthread and -lpthreads, where one of the
+# libraries is broken (non-POSIX).
+
+# Create a list of thread flags to try. Items starting with a "-" are
+# C compiler flags, and other items are library names, except for "none"
+# which indicates that we try without any flags at all, and "pthread-config"
+# which is a program returning the flags for the Pth emulation library.
+
+ax_pthread_flags="pthreads none -Kthread -kthread lthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config"
+
+# The ordering *is* (sometimes) important. Some notes on the
+# individual items follow:
+
+# pthreads: AIX (must check this before -lpthread)
+# none: in case threads are in libc; should be tried before -Kthread and
+# other compiler flags to prevent continual compiler warnings
+# -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h)
+# -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able)
+# lthread: LinuxThreads port on FreeBSD (also preferred to -pthread)
+# -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads)
+# -pthreads: Solaris/gcc
+# -mthreads: Mingw32/gcc, Lynx/gcc
+# -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it
+# doesn't hurt to check since this sometimes defines pthreads too;
+# also defines -D_REENTRANT)
+# ... -mt is also the pthreads flag for HP/aCC
+# pthread: Linux, etcetera
+# --thread-safe: KAI C++
+# pthread-config: use pthread-config program (for GNU Pth library)
+
+case ${host_os} in
+ solaris*)
+
+ # On Solaris (at least, for some versions), libc contains stubbed
+ # (non-functional) versions of the pthreads routines, so link-based
+ # tests will erroneously succeed. (We need to link with -pthreads/-mt/
+ # -lpthread.) (The stubs are missing pthread_cleanup_push, or rather
+ # a function called by this macro, so we could check for that, but
+ # who knows whether they'll stub that too in a future libc.) So,
+ # we'll just look for -pthreads and -lpthread first:
+
+ ax_pthread_flags="-pthreads pthread -mt -pthread $ax_pthread_flags"
+ ;;
+
+ darwin*)
+ ax_pthread_flags="-pthread $ax_pthread_flags"
+ ;;
+esac
+
+# Clang doesn't consider unrecognized options an error unless we specify
+# -Werror. We throw in some extra Clang-specific options to ensure that
+# this doesn't happen for GCC, which also accepts -Werror.
+
+AC_MSG_CHECKING([if compiler needs -Werror to reject unknown flags])
+save_CFLAGS="$CFLAGS"
+ax_pthread_extra_flags="-Werror"
+CFLAGS="$CFLAGS $ax_pthread_extra_flags -Wunknown-warning-option -Wsizeof-array-argument"
+AC_COMPILE_IFELSE([AC_LANG_PROGRAM([int foo(void);],[foo()])],
+ [AC_MSG_RESULT([yes])],
+ [ax_pthread_extra_flags=
+ AC_MSG_RESULT([no])])
+CFLAGS="$save_CFLAGS"
+
+if test x"$ax_pthread_ok" = xno; then
+for flag in $ax_pthread_flags; do
+
+ case $flag in
+ none)
+ AC_MSG_CHECKING([whether pthreads work without any flags])
+ ;;
+
+ -*)
+ AC_MSG_CHECKING([whether pthreads work with $flag])
+ PTHREAD_CFLAGS="$flag"
+ ;;
+
+ pthread-config)
+ AC_CHECK_PROG([ax_pthread_config], [pthread-config], [yes], [no])
+ if test x"$ax_pthread_config" = xno; then continue; fi
+ PTHREAD_CFLAGS="`pthread-config --cflags`"
+ PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`"
+ ;;
+
+ *)
+ AC_MSG_CHECKING([for the pthreads library -l$flag])
+ PTHREAD_LIBS="-l$flag"
+ ;;
+ esac
+
+ save_LIBS="$LIBS"
+ save_CFLAGS="$CFLAGS"
+ LIBS="$PTHREAD_LIBS $LIBS"
+ CFLAGS="$CFLAGS $PTHREAD_CFLAGS $ax_pthread_extra_flags"
+
+ # Check for various functions. We must include pthread.h,
+ # since some functions may be macros. (On the Sequent, we
+ # need a special flag -Kthread to make this header compile.)
+ # We check for pthread_join because it is in -lpthread on IRIX
+ # while pthread_create is in libc. We check for pthread_attr_init
+ # due to DEC craziness with -lpthreads. We check for
+ # pthread_cleanup_push because it is one of the few pthread
+ # functions on Solaris that doesn't have a non-functional libc stub.
+ # We try pthread_create on general principles.
+ AC_LINK_IFELSE([AC_LANG_PROGRAM([#include <pthread.h>
+ static void routine(void *a) { a = 0; }
+ static void *start_routine(void *a) { return a; }],
+ [pthread_t th; pthread_attr_t attr;
+ pthread_create(&th, 0, start_routine, 0);
+ pthread_join(th, 0);
+ pthread_attr_init(&attr);
+ pthread_cleanup_push(routine, 0);
+ pthread_cleanup_pop(0) /* ; */])],
+ [ax_pthread_ok=yes],
+ [])
+
+ LIBS="$save_LIBS"
+ CFLAGS="$save_CFLAGS"
+
+ AC_MSG_RESULT([$ax_pthread_ok])
+ if test "x$ax_pthread_ok" = xyes; then
+ break;
+ fi
+
+ PTHREAD_LIBS=""
+ PTHREAD_CFLAGS=""
+done
+fi
+
+# Various other checks:
+if test "x$ax_pthread_ok" = xyes; then
+ save_LIBS="$LIBS"
+ LIBS="$PTHREAD_LIBS $LIBS"
+ save_CFLAGS="$CFLAGS"
+ CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
+
+ # Detect AIX lossage: JOINABLE attribute is called UNDETACHED.
+ AC_MSG_CHECKING([for joinable pthread attribute])
+ attr_name=unknown
+ for attr in PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_UNDETACHED; do
+ AC_LINK_IFELSE([AC_LANG_PROGRAM([#include <pthread.h>],
+ [int attr = $attr; return attr /* ; */])],
+ [attr_name=$attr; break],
+ [])
+ done
+ AC_MSG_RESULT([$attr_name])
+ if test "$attr_name" != PTHREAD_CREATE_JOINABLE; then
+ AC_DEFINE_UNQUOTED([PTHREAD_CREATE_JOINABLE], [$attr_name],
+ [Define to necessary symbol if this constant
+ uses a non-standard name on your system.])
+ fi
+
+ AC_MSG_CHECKING([if more special flags are required for pthreads])
+ flag=no
+ case ${host_os} in
+ aix* | freebsd* | darwin*) flag="-D_THREAD_SAFE";;
+ osf* | hpux*) flag="-D_REENTRANT";;
+ solaris*)
+ if test "$GCC" = "yes"; then
+ flag="-D_REENTRANT"
+ else
+ # TODO: What about Clang on Solaris?
+ flag="-mt -D_REENTRANT"
+ fi
+ ;;
+ esac
+ AC_MSG_RESULT([$flag])
+ if test "x$flag" != xno; then
+ PTHREAD_CFLAGS="$flag $PTHREAD_CFLAGS"
+ fi
+
+ AC_CACHE_CHECK([for PTHREAD_PRIO_INHERIT],
+ [ax_cv_PTHREAD_PRIO_INHERIT], [
+ AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include <pthread.h>]],
+ [[int i = PTHREAD_PRIO_INHERIT;]])],
+ [ax_cv_PTHREAD_PRIO_INHERIT=yes],
+ [ax_cv_PTHREAD_PRIO_INHERIT=no])
+ ])
+ AS_IF([test "x$ax_cv_PTHREAD_PRIO_INHERIT" = "xyes"],
+ [AC_DEFINE([HAVE_PTHREAD_PRIO_INHERIT], [1], [Have PTHREAD_PRIO_INHERIT.])])
+
+ LIBS="$save_LIBS"
+ CFLAGS="$save_CFLAGS"
+
+ # More AIX lossage: compile with *_r variant
+ if test "x$GCC" != xyes; then
+ case $host_os in
+ aix*)
+ AS_CASE(["x/$CC"],
+ [x*/c89|x*/c89_128|x*/c99|x*/c99_128|x*/cc|x*/cc128|x*/xlc|x*/xlc_v6|x*/xlc128|x*/xlc128_v6],
+ [#handle absolute path differently from PATH based program lookup
+ AS_CASE(["x$CC"],
+ [x/*],
+ [AS_IF([AS_EXECUTABLE_P([${CC}_r])],[PTHREAD_CC="${CC}_r"])],
+ [AC_CHECK_PROGS([PTHREAD_CC],[${CC}_r],[$CC])])])
+ ;;
+ esac
+ fi
+fi
+
+test -n "$PTHREAD_CC" || PTHREAD_CC="$CC"
+
+AC_SUBST([PTHREAD_LIBS])
+AC_SUBST([PTHREAD_CFLAGS])
+AC_SUBST([PTHREAD_CC])
+
+# Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND:
+if test x"$ax_pthread_ok" = xyes; then
+ ifelse([$1],,[AC_DEFINE([HAVE_PTHREAD],[1],[Define if you have POSIX threads libraries and header files.])],[$1])
+ :
+else
+ ax_pthread_ok=no
+ $2
+fi
+AC_LANG_POP
+])dnl AX_PTHREAD
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..3a42639
--- /dev/null
+++ b/main.c
@@ -0,0 +1,273 @@
+/*
+Copyright (c) 2014 Igor Pashev <pashev.igor@gmail.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <errno.h>
+#include <getopt.h>
+#include <inttypes.h>
+#include <pthread.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <fcgiapp.h>
+
+#ifdef ENABLE_CGROUPS
+#include <libcgroup.h>
+#endif
+
+#include "dispatch.h"
+#include "uri.h"
+#include "debug.h"
+
+static const char *progname;
+
+/* Tunable parameters: */
+static int number_of_workers = 5;
+static const char *socket_path = ":9000";
+static int backlog = 16;
+
+static pthread_mutex_t accept_mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_t *pthread_ids = NULL;
+static int socket = -1;
+
+
+static void *
+worker (void *param)
+{
+ FCGX_Request request;
+
+ debug ("thread #%" PRIdPTR " started", (intptr_t) param);
+ if (0 != FCGX_InitRequest (&request, socket, /* int flags */ 0))
+ {
+ return (NULL);
+ }
+
+ while (1)
+ {
+ pthread_mutex_lock (&accept_mutex);
+ int rc = FCGX_Accept_r (&request);
+ pthread_mutex_unlock (&accept_mutex);
+ debug ("thread #%" PRIdPTR " accepted request", (intptr_t) param);
+
+ if (rc < 0)
+ {
+ debug ("thread #%" PRIdPTR " FCGX_Accept_r() failed: %s",
+ (intptr_t) param, strerror (errno));
+ break;
+ }
+
+ dispatch (&request);
+
+ FCGX_Finish_r (&request);
+ }
+ return NULL;
+}
+
+
+static void
+version (void)
+{
+ printf (PACKAGE " " VERSION "\n");
+#ifdef HAVE_XXD
+ extern unsigned char LICENSE[];
+ extern unsigned int LICENSE_len;
+ printf ("%.*s\n", LICENSE_len, LICENSE);
+#endif
+ printf ("Report bugs to <" PACKAGE_BUGREPORT ">\n");
+ exit (0);
+}
+
+
+static void
+usage (void)
+{
+ printf ("Usage: %s [options]\n", progname);
+ printf ("FastCGI REST server\n\n");
+ printf
+ ("Mandatory arguments to long options are mandatory for short options too.\n");
+ printf (" -s, --socket={path|:port} a socket or a port number (%s)\n",
+ socket_path);
+ printf (" -b, --backlog=number listen queue depth (%d)\n", backlog);
+ printf (" -w, --threads=number number of threads to run (%d)\n",
+ number_of_workers);
+ printf (" -u, --uri-prefix=string URI prefix to trim (%s)\n",
+ uri_prefix);
+ printf (" -h, --help show this help message\n");
+ printf (" -v, --version show version\n");
+ exit (0);
+}
+
+
+static void
+parse_options (int argc, char **argv)
+{
+ static const char *short_options = "s:b:w:u:hv";
+
+ static const struct option long_options[] = {
+ {"socket", required_argument, NULL, 's'},
+ {"backlog", required_argument, NULL, 'b'},
+ {"threads", required_argument, NULL, 'w'},
+ {"uri-prefix", required_argument, NULL, 'u'},
+ {"help", no_argument, NULL, 'h'},
+ {"version", no_argument, NULL, 'v'},
+ {NULL, 0, NULL, 0}
+ };
+
+ progname = argv[0];
+
+ int option_index;
+ int opt;
+ while ((opt = getopt_long (argc, argv, short_options,
+ long_options, &option_index)) != -1)
+ switch (opt)
+ {
+ case 's':
+ socket_path = optarg;
+ break;
+ case 'b':
+ backlog = atoi (optarg);
+ if (backlog <= 0)
+ {
+ fprintf (stderr,
+ "%s: backlog number must be a positive integer\n",
+ progname);
+ exit (1);
+ }
+ break;
+ case 'w':
+ number_of_workers = atoi (optarg);
+ if (number_of_workers <= 0)
+ {
+ fprintf (stderr,
+ "%s: number of workers must be a positive integer\n",
+ progname);
+ exit (1);
+ }
+ break;
+ case 'u':
+ uri_prefix = optarg;
+ uri_prefix_len = strlen (uri_prefix);
+ break;
+ case 'h':
+ usage ();
+ break;
+ case 'v':
+ version ();
+ break;
+ default:
+ fprintf (stderr, "Use `%s --help' to get help\n", progname);
+ exit (1);
+ break;
+ }
+}
+
+
+static void
+init_libraries (void)
+{
+ int rc;
+ debug ("initializing libfcgi");
+ if (0 != (rc = FCGX_Init ()))
+ {
+ fprintf (stderr, "%s: FCGX_Init() failed. Exiting.\n", progname);
+ exit (EXIT_FAILURE);
+ }
+#ifdef ENABLE_CGROUPS
+ debug ("initializing libcgroup");
+ if (0 != (rc = cgroup_init ()))
+ {
+ fprintf (stderr, "%s: cgroup_init() failed. Exiting.\n", progname);
+ exit (EXIT_FAILURE);
+ }
+#endif
+}
+
+
+int
+main (int argc, char **argv)
+{
+ parse_options (argc, argv);
+
+ init_libraries ();
+
+ fprintf (stderr,
+ "%s: socket `%s', backlog %d, %d worker%s, URI prefix `%s'\n",
+ progname, socket_path, backlog, number_of_workers,
+ (number_of_workers == 1 ? "" : "s"), uri_prefix);
+
+ socket = FCGX_OpenSocket (socket_path, backlog);
+ if (socket < 0)
+ {
+ fprintf (stderr, "%s: FCGX_OpenSocket() failed: %s. Exiting.\n",
+ progname, strerror (errno));
+ return (EXIT_FAILURE);
+ }
+
+ debug ("allocating space for %d threads", number_of_workers);
+ pthread_ids = (pthread_t *) malloc (sizeof (pthread_t) * number_of_workers);
+ if (NULL == pthread_ids)
+ {
+ fprintf (stderr, "%s: malloc() failed: %s. Exiting.\n", progname,
+ strerror (errno));
+ return (EXIT_FAILURE);
+ }
+
+ debug ("starting threads");
+ for (int thr = 0; thr < number_of_workers; ++thr)
+ {
+ int rc;
+ do
+ {
+ debug ("starting thread #%d", thr);
+ errno = 0;
+ rc =
+ pthread_create (&(pthread_ids[thr]), NULL, worker,
+ (void *) ((intptr_t) thr));
+ }
+ while ((0 != rc) && (EAGAIN == errno));
+
+ if (0 != rc)
+ {
+ fprintf (stderr, "%s: pthread_create() failed: %s. Exiting.\n",
+ progname, strerror (errno));
+ return (EXIT_FAILURE);
+ }
+ }
+
+ for (int thr = 0; thr < number_of_workers; ++thr)
+ {
+ int rc = pthread_join (pthread_ids[thr], NULL);
+ if (0 != rc)
+ {
+ fprintf (stderr, "%s: pthread_join() failed: %s. Exiting.\n",
+ progname, strerror (errno));
+ return (EXIT_FAILURE);
+ }
+ }
+
+ return (EXIT_SUCCESS);
+}
diff --git a/uri.c b/uri.c
new file mode 100644
index 0000000..6f0c470
--- /dev/null
+++ b/uri.c
@@ -0,0 +1,34 @@
+/*
+Copyright (c) 2014 Igor Pashev <pashev.igor@gmail.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifndef URI_PREFIX
+#define URI_PREFIX "/fcgi"
+#endif
+
+static const char uri_prefix_default[] = URI_PREFIX;
+
+const char *uri_prefix = uri_prefix_default;
+int uri_prefix_len = sizeof (uri_prefix_default) - 1;
diff --git a/uri.h b/uri.h
new file mode 100644
index 0000000..a2cd9f3
--- /dev/null
+++ b/uri.h
@@ -0,0 +1,29 @@
+/*
+Copyright (c) 2014 Igor Pashev <pashev.igor@gmail.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+
+#ifndef _URI_H
+#define _URI_H
+
+extern const char *uri_prefix;
+extern int uri_prefix_len;
+
+#endif // _URI_H