diff options
-rw-r--r-- | .gitignore | 16 | ||||
-rw-r--r-- | LICENSE | 19 | ||||
-rw-r--r-- | Makefile.am | 34 | ||||
-rw-r--r-- | README | 121 | ||||
-rw-r--r-- | TODO | 7 | ||||
-rw-r--r-- | cgroups.c | 537 | ||||
-rw-r--r-- | cgroups.h | 30 | ||||
-rw-r--r-- | configure.ac | 66 | ||||
-rw-r--r-- | debug.c | 56 | ||||
-rw-r--r-- | debug.h | 33 | ||||
-rw-r--r-- | dispatch.c | 73 | ||||
-rw-r--r-- | dispatch.h | 30 | ||||
-rw-r--r-- | m4/ax_pthread.m4 | 332 | ||||
-rw-r--r-- | main.c | 273 | ||||
-rw-r--r-- | uri.c | 34 | ||||
-rw-r--r-- | uri.h | 29 |
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 @@ -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 + @@ -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] + @@ -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 + @@ -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 (<ime)); + + 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); +} @@ -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 @@ -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); +} @@ -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; @@ -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 |