From: Tobias Oetiker Date: Tue, 30 Jun 2015 08:46:15 +0000 (+0200) Subject: trheadsafe extension X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=4e177d8c0a8d1bcac5ae1413923adede3ea589c3;p=thirdparty%2Frrdtool-1.x.git trheadsafe extension --- diff --git a/configure.ac b/configure.ac index 059ca8b1..8f974365 100644 --- a/configure.ac +++ b/configure.ac @@ -525,6 +525,11 @@ AM_CONDITIONAL(BUILD_RRDGRAPH,[test $enable_rrd_graph != no]) CORE_LIBS="$LIBS" +EX_CHECK_ALL(glib-2.0, glib_check_version, glib.h, glib-2.0, 2.28.7, ftp://ftp.gtk.org/pub/glib/2.28/, "") +GLIB_LIBS=$THIS_LIB +AC_SUBST(GLIB_LIBS) + + if test $enable_rrd_graph != no; then dnl EX_CHECK_ALL(z, zlibVersion, zlib.h, zlib, 1.2.5, http://zlib.net/, "") dnl EX_CHECK_ALL(png, png_access_version_number, png.h, libpng, 1.4.8, ftp://ftp.simplesystems.org/pub/libpng/png/src/, "") @@ -537,7 +542,6 @@ EX_CHECK_ALL(cairo, cairo_ps_surface_create, cairo-ps.h, EX_CHECK_ALL(pangocairo-1.0, pango_cairo_context_set_font_options, pango/pango.h, pangocairo, 1.28.4, http://ftp.gnome.org/pub/GNOME/sources/pango/1.28, "") fi -EX_CHECK_ALL(glib-2.0, glib_check_version, glib.h, glib-2.0, 2.28.7, ftp://ftp.gtk.org/pub/glib/2.28/, "") EX_CHECK_ALL(xml2, xmlParseFile, libxml/parser.h, libxml-2.0, 2.7.8, http://xmlsoft.org/downloads.html, /usr/include/libxml2) if test "$EX_CHECK_ALL_ERR" = "YES"; then diff --git a/m4/acinclude.m4 b/m4/acinclude.m4 index 6aed2238..5eff6de6 100644 --- a/m4/acinclude.m4 +++ b/m4/acinclude.m4 @@ -35,6 +35,7 @@ AC_DEFUN([EX_CHECK_ALL], LDFLAGS=${LDFLAGS}" "`$PKGCONFIG --libs-only-L $4` LDFLAGS=${LDFLAGS}" "`$PKGCONFIG --libs-only-other $4` LIBS=${LIBS}" "`$PKGCONFIG --libs-only-l $4` + THIS_LIB=`$PKGCONFIG --libs-only-l $4` dnl remove the cached value and test again unset ac_cv_lib_`echo $1 | sed ['s/[^_a-zA-Z0-9]/_/g;s/^[0-9]/_/']`_$2 AC_CHECK_LIB($1,$2,[ diff --git a/src/Makefile.am b/src/Makefile.am index f0a54485..da65ab76 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -31,6 +31,7 @@ UPD_C_FILES = \ rrd_nan_inf.c \ rrd_rpncalc.c \ rrd_utils.c \ + mutex.c \ rrd_update.c RRD_C_FILES = \ @@ -48,7 +49,7 @@ RRD_C_FILES = \ if BUILD_RRDGRAPH RRD_C_FILES += rrd_graph.c \ rrd_graph_helper.c \ - rrd_xport.c \ + rrd_xport.c optparse.c \ rrd_gfx.c \ pngsize.c endif @@ -56,9 +57,10 @@ endif noinst_HEADERS = \ unused.h \ gettext.h \ + mutex.h \ rrd_getopt.h rrd_parsetime.h \ rrd_config_bottom.h rrd_i18n.h \ - rrd_format.h rrd_tool.h rrd_xport.h rrd.h rrd_rpncalc.h \ + rrd_format.h rrd_tool.h rrd_xport.h optparse.h rrd.h rrd_rpncalc.h \ rrd_hw.h rrd_hw_math.h rrd_hw_update.h \ fnv.h rrd_graph.h \ rrd_is_thread_safe.h @@ -104,7 +106,7 @@ bin_PROGRAMS += rrdcgi endif rrdcgi_SOURCES = rrd_cgi.c -rrdcgi_LDADD = librrd.la +rrdcgi_LDADD = librrd.la rrdupdate_SOURCES = rrdupdate.c rrdupdate_LDADD = librrdupd.la @@ -116,7 +118,7 @@ rrdtool_LDADD = librrd.la rrdcached_SOURCES = rrd_daemon.c rrdcached_DEPENDENCIES = librrd_th.la rrdcached_CPPFLAGS = -DVERSION='"$(VERSION)"' -DLOCALSTATEDIR='"$(localstatedir)"' -rrdcached_LDADD = librrd_th.la +rrdcached_LDADD = librrd_th.la $(GLIB_LIBS) pkgconfigdir = $(libdir)/pkgconfig pkgconfig_DATA = librrd.pc diff --git a/src/mutex.c b/src/mutex.c new file mode 100644 index 00000000..c359b6fc --- /dev/null +++ b/src/mutex.c @@ -0,0 +1,56 @@ +/* + * + * mutex.c + * + * Cross platform mutex + * + */ + +#include "mutex.h" + +int mutex_init(mutex_t *mutex) +{ +#ifdef WIN32 + *mutex = CreateMutex(NULL, FALSE, NULL); + return (*mutex == NULL); +#else + return pthread_mutex_init(mutex, NULL);; +#endif +} + +int mutex_lock(mutex_t *mutex) +{ +#ifdef WIN32 + if (*mutex == NULL) { /* static initializer? */ + HANDLE p = CreateMutex(NULL, FALSE, NULL); + if (InterlockedCompareExchangePointer((PVOID*)mutex, (PVOID)p, NULL) != NULL) + CloseHandle(p); + } + return (WaitForSingleObject(*mutex, INFINITE) == WAIT_FAILED); +#else + return pthread_mutex_lock(mutex); +#endif +} + +int mutex_unlock(mutex_t *mutex) +{ +#ifdef WIN32 + return (ReleaseMutex(*mutex) == 0); +#else + return pthread_mutex_unlock(mutex); +#endif +} + +int mutex_cleanup(mutex_t *mutex) +{ +#ifdef WIN32 + return (CloseHandle(mutex) == 0); +#else + return pthread_mutex_destroy(mutex); +#endif +} + +/* + * vim: set sw=2 sts=2 ts=8 et fdm=marker : + */ + diff --git a/src/mutex.h b/src/mutex.h new file mode 100644 index 00000000..17664db8 --- /dev/null +++ b/src/mutex.h @@ -0,0 +1,33 @@ +/* + * mutex.h - Cross platform mutex + */ + +#ifndef MUTEX_H_B13C67AB432C4C39AF823A339537CA40 +#define MUTEX_H_B13C67AB432C4C39AF823A339537CA40 + +#ifdef WIN32 +#include +#include +#else +#include +#endif + +#ifndef WIN32 +#define mutex_t pthread_mutex_t +#define MUTEX_INITIALIZER PTHREAD_MUTEX_INITIALIZER +#else +#define mutex_t HANDLE +#define MUTEX_INITIALIZER NULL +#endif + +int mutex_init(mutex_t *mutex); +int mutex_lock(mutex_t *mutex); +int mutex_unlock(mutex_t *mutex); +int mutex_cleanup(mutex_t *mutex); + +#endif /* MUTEX__H */ + +/* + * vim: set sw=2 sts=2 ts=8 et fdm=marker : + */ + diff --git a/src/optparse.c b/src/optparse.c new file mode 100644 index 00000000..8889b947 --- /dev/null +++ b/src/optparse.c @@ -0,0 +1,257 @@ +#include +#include "optparse.h" + +#define opterror(options, format, args...) \ + snprintf(options->errmsg, sizeof(options->errmsg), format, args); + +void optparse_init(struct optparse *options, char **argv) +{ + options->argv = argv; + options->permute = 1; + options->optind = 1; + options->subopt = 0; + options->optarg = NULL; + options->errmsg[0] = '\0'; +} + +static inline int +is_dashdash(const char *arg) +{ + return arg != NULL && arg[0] == '-' && arg[1] == '-' && arg[2] == '\0'; +} + +static inline int +is_shortopt(const char *arg) +{ + return arg != NULL && arg[0] == '-' && arg[1] != '-' && arg[1] != '\0'; +} + +static inline int +is_longopt(const char *arg) +{ + return arg != NULL && arg[0] == '-' && arg[1] == '-' && arg[2] != '\0'; +} + +static void +permute(struct optparse *options, int index) +{ + char *nonoption = options->argv[index]; + for (int i = index; i < options->optind - 1; i++) + options->argv[i] = options->argv[i + 1]; + options->argv[options->optind - 1] = nonoption; +} + +static enum optparse_argtype +argtype(const char *optstring, char c) +{ + if (c == ':') + return -1; + for (; *optstring && c != *optstring; optstring++); + if (!*optstring) + return -1; + enum optparse_argtype count = OPTPARSE_NONE; + if (optstring[1] == ':') + count += optstring[2] == ':' ? 2 : 1; + return count; +} + +int optparse(struct optparse *options, const char *optstring) +{ + options->errmsg[0] = '\0'; + options->optopt = 0; + options->optarg = NULL; + char *option = options->argv[options->optind]; + if (option == NULL) { + return -1; + } else if (is_dashdash(option)) { + options->optind++; // consume "--" + return -1; + } else if (!is_shortopt(option)) { + if (options->permute) { + int index = options->optind; + options->optind++; + int r = optparse(options, optstring); + permute(options, index); + options->optind--; + return r; + } else { + return -1; + } + } + option += options->subopt + 1; + options->optopt = option[0]; + int type = argtype(optstring, option[0]); + char *next = options->argv[options->optind + 1]; + switch (type) { + case -1: + opterror(options, "invalid option -- '%c'", option[0]); + options->optind++; + return '?'; + case OPTPARSE_NONE: + if (option[1]) { + options->subopt++; + } else { + options->subopt = 0; + options->optind++; + } + return option[0]; + case OPTPARSE_REQUIRED: + options->subopt = 0; + options->optind++; + if (option[1]) { + options->optarg = option + 1; + } else if (next != NULL) { + options->optarg = next; + options->optind++; + } else { + opterror(options, "option requires an argument -- '%c'", option[0]); + options->optarg = NULL; + return '?'; + } + return option[0]; + case OPTPARSE_OPTIONAL: + options->subopt = 0; + options->optind++; + if (option[1]) + options->optarg = option + 1; + else + options->optarg = NULL; + return option[0]; + } + return 0; +} + +char *optparse_arg(struct optparse *options) +{ + options->subopt = 0; + char *option = options->argv[options->optind]; + if (option != NULL) + options->optind++; + return option; +} + +static inline int +longopts_end(const struct optparse_long *longopts, int i) +{ + return !longopts[i].longname && !longopts[i].shortname; +} + +static size_t +optstring_length(const struct optparse_long *longopts) +{ + int length = 0; + for (int i = 0; !longopts_end(longopts, i); i++, length++) + length += longopts[i].argtype; + return length + 1; +} + +static void +optstring_from_long(const struct optparse_long *longopts, char *optstring) +{ + char *p = optstring; + for (int i = 0; !longopts_end(longopts, i); i++) { + if (longopts[i].shortname) { + *p++ = longopts[i].shortname; + for (int a = 0; a < longopts[i].argtype; a++) + *p++ = ':'; + } + } + *p = '\0'; +} + +/* Unlike strcmp(), handles options containing "=". */ +static int +longopts_match(const char *longname, const char *option) +{ + if (longname == NULL) + return 0; + const char *a = option, *n = longname; + for (; *a && *n && *a != '='; a++, n++) + if (*a != *n) + return 0; + return *n == '\0' && (*a == '\0' || *a == '='); +} + +/* Return the part after "=", or NULL. */ +static const char * +longopts_arg(const char *option) +{ + for (; *option && *option != '='; option++); + if (*option == '=') + return option + 1; + else + return NULL; +} + +static int +long_fallback(struct optparse *options, + const struct optparse_long *longopts, + int *longindex) +{ + char optstring[optstring_length(longopts)]; + optstring_from_long(longopts, optstring); + int result = optparse(options, optstring); + if (longindex != NULL) { + *longindex = -1; + if (result != -1) + for (int i = 0; !longopts_end(longopts, i); i++) + if (longopts[i].shortname == options->optopt) + *longindex = i; + } + return result; +} + +int +optparse_long(struct optparse *options, + const struct optparse_long *longopts, + int *longindex) +{ + char *option = options->argv[options->optind]; + if (option == NULL) { + return -1; + } else if (is_shortopt(option)) { + return long_fallback(options, longopts, longindex); + } else if (!is_longopt(option)) { + if (options->permute) { + int index = options->optind; + options->optind++; + int r = optparse_long(options, longopts, longindex); + permute(options, index); + options->optind--; + return r; + } else { + return -1; + } + } + + /* Parse as long option. */ + options->errmsg[0] = '\0'; + options->optopt = 0; + options->optarg = NULL; + option += 2; // skip "--" + options->optind++; + for (int i = 0; !longopts_end(longopts, i); i++) { + const char *name = longopts[i].longname; + if (longopts_match(name, option)) { + if (longindex) + *longindex = i; + options->optopt = longopts[i].shortname; + const char *arg = longopts_arg(option); + if (longopts[i].argtype == OPTPARSE_NONE && arg != NULL) { + opterror(options, "option takes no arguments -- '%s'", name); + return '?'; + } if (arg != NULL) { + options->optarg = arg; + } else if (longopts[i].argtype == OPTPARSE_REQUIRED) { + options->optarg = options->argv[options->optind++]; + if (options->optarg == NULL) { + opterror(options, "option requires argument -- '%s'", name); + return '?'; + } + } + return options->optopt; + } + } + opterror(options, "invalid option -- '%s'", option); + return '?'; +} diff --git a/src/optparse.h b/src/optparse.h new file mode 100644 index 00000000..3e70dc04 --- /dev/null +++ b/src/optparse.h @@ -0,0 +1,102 @@ +#ifndef OPTPARSE_H +#define OPTPARSE_H + +/** + * Optparse -- portable, reentrant, embeddable, getopt-like option parser + * + * The POSIX getopt() option parser has three fatal flaws. These flaws + * are solved by Optparse. + * + * 1) Parser state is stored entirely in global variables, some of + * which are static and inaccessible. This means only one thread can + * use getopt(). It also means it's not possible to recursively parse + * nested sub-arguments while in the middle of argument parsing. + * Optparse fixes this by storing all state on a local struct. + * + * 2) The POSIX standard provides no way to properly reset the parser. + * This means for portable code that getopt() is only good for one + * run, over one argv with one optstring. It also means subcommand + * options cannot be processed with getopt(). Most implementations + * provide a method to reset the parser, but it's not portable. + * Optparse provides an optparse_arg() function for stepping over + * subcommands and continuing parsing of options with another + * optstring. The Optparse struct itself can be passed around to + * subcommand handlers for additional subcommand option parsing. A + * full reset can be achieved by with an additional optparse_init(). + * + * 3) Error messages are printed to stderr. This can be disabled with + * opterr, but the messages themselves are still inaccessible. + * Optparse solves this by writing an error message in its errmsg + * field. The downside to Optparse is that this error message will + * always be in English rather than the current locale. + * + * Optparse should be familiar with anyone accustomed to getopt(), and + * it could be a nearly drop-in replacement. The optstring is the same + * and the fields have the same names as the getopt() global variables + * (optarg, optind, optopt). + * + * Optparse also supports GNU-style long options with optparse_long(). + * The interface is slightly different and simpler than getopt_long(). + * + * By default, argv is permuted as it is parsed, moving non-option + * arguments to the end. This can be disabled by setting the `permute` + * field to 0 after initialization. + */ + +struct optparse { + char **argv; + int permute; + int optind; + int optopt; + const char *optarg; + char errmsg[48]; + int subopt; +}; + +enum optparse_argtype { OPTPARSE_NONE, OPTPARSE_REQUIRED, OPTPARSE_OPTIONAL }; + +struct optparse_long { + const char *longname; + int shortname; + enum optparse_argtype argtype; +}; + +/** + * Initializes the parser state. + */ +void optparse_init(struct optparse *options, char **argv); + +/** + * Read the next option in the argv array. + * @param optstring a getopt()-formatted option string. + * @return the next option character, -1 for done, or '?' for error + * + * Just like getopt(), a character followed by a colon means no + * argument. One colon means the option has a required argument. Two + * colons means the option takes an optional argument. + */ +int optparse(struct optparse *options, const char *optstring); + +/** + * Handles GNU-style long options in addition to getopt() options. + * This works a lot like GNU's getopt_long(). The last option in + * longopts must be all zeros, marking the end of the array. The + * longindex argument may be NULL. + */ +int +optparse_long(struct optparse *options, + const struct optparse_long *longopts, + int *longindex); + +/** + * Used for stepping over non-option arguments. + * @return the next non-option argument, or -1 for no more arguments + * + * Argument parsing can continue with optparse() after using this + * function. That would be used to parse the options for the + * subcommand returned by optparse_arg(). This function allows you to + * ignore the value of optind. + */ +char *optparse_arg(struct optparse *options); + +#endif diff --git a/src/rrd_graph.c b/src/rrd_graph.c index d783a358..4a0f246c 100644 --- a/src/rrd_graph.c +++ b/src/rrd_graph.c @@ -3995,7 +3995,7 @@ rrd_info_t *rrd_graph_v( im.graphfile[0] = '\0'; } - rrd_graph_script(argc, argv, &im, 1); + rrd_graph_script(argc, argv, &im, optind+1); setlocale(LC_NUMERIC, old_locale); /* reenable locale for rendering the graph */ if (rrd_test_error()) { diff --git a/src/rrd_graph_helper.c b/src/rrd_graph_helper.c index e7ec9022..8ea141a5 100644 --- a/src/rrd_graph_helper.c +++ b/src/rrd_graph_helper.c @@ -1090,7 +1090,7 @@ void rrd_graph_script( enum gf_en last_gf = GF_PRINT; float last_linewidth = 0.0; - for (i = optind + optno; i < argc; i++) { + for (i = optno; i < argc; i++) { graph_desc_t *gdp; unsigned int eaten = 0; diff --git a/src/rrd_parsetime.c b/src/rrd_parsetime.c index a5f1db89..9162884f 100644 --- a/src/rrd_parsetime.c +++ b/src/rrd_parsetime.c @@ -113,7 +113,7 @@ /* System Headers */ /* Local headers */ - +#include "mutex.h" #include #include #include @@ -837,9 +837,14 @@ char *rrd_parsetime( const char *tspec, rrd_time_value_t * ptv) { + static mutex_t parsetime_mutex = MUTEX_INITIALIZER; time_t now = time(NULL); int hr = 0; + /* yes this code is non re-entrant ... so lets make sure we do not run + in twice */ + mutex_lock(&parsetime_mutex); + /* this MUST be initialized to zero for midnight/noon/teatime */ Specials = VariousWords; /* initialize special words context */ @@ -990,6 +995,9 @@ char *rrd_parsetime( panic(e("the specified time is incorrect (out of range?)")); } EnsureMemFree(); + /* ok done ... drop the mutex lock */ + mutex_unlock(&parsetime_mutex); + return TIME_OK; } /* rrd_parsetime */ diff --git a/src/rrd_xport.c b/src/rrd_xport.c index 7708be6e..2c153258 100644 --- a/src/rrd_xport.c +++ b/src/rrd_xport.c @@ -11,6 +11,7 @@ #include "rrd_xport.h" #include "unused.h" #include "rrd_client.h" +#include "optparse.h" #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__) #include @@ -58,55 +59,49 @@ int rrd_xport( time_t start_tmp = 0, end_tmp = 0; rrd_time_value_t start_tv, end_tv; char *parsetime_error = NULL; - - struct option long_options[] = { - {"start", required_argument, 0, 's'}, - {"end", required_argument, 0, 'e'}, - {"maxrows", required_argument, 0, 'm'}, - {"step", required_argument, 0, 261}, - {"enumds", no_argument, 0, 262}, /* these are handled in the frontend ... */ - {"json", no_argument, 0, 263}, /* these are handled in the frontend ... */ - {"daemon", required_argument, 0, 'd'}, - {0, 0, 0, 0} + struct optparse options; + optparse_init(&options, argv); + + struct optparse_long longopts[] = { + {"start", 's', OPTPARSE_REQUIRED}, + {"end", 'e', OPTPARSE_REQUIRED}, + {"maxrows",'m', OPTPARSE_REQUIRED}, + {"step", 261, OPTPARSE_REQUIRED}, + {"enumds", 262, OPTPARSE_NONE}, /* these are handled in the frontend ... */ + {"json", 263, OPTPARSE_NONE}, /* these are handled in the frontend ... */ + {"daemon", 'd', OPTPARSE_REQUIRED}, + {0} }; - optind = 0; - opterr = 0; /* initialize getopt */ - rrd_graph_init(&im); rrd_parsetime("end-24h", &start_tv); rrd_parsetime("now", &end_tv); - while (1) { - int option_index = 0; - int opt; - opt = getopt_long(argc, argv, "s:e:m:d:", long_options, &option_index); - if (opt == EOF) - break; + + int opt; + while ((opt = optparse_long(&options,longopts,NULL)) != -1){ switch (opt) { case 261: - im.step = atoi(optarg); - break; - case 262: + im.step = atoi(options.optarg); break; case 's': - if ((parsetime_error = rrd_parsetime(optarg, &start_tv))) { + if ((parsetime_error = rrd_parsetime(options.optarg, &start_tv))) { rrd_set_error("start time: %s", parsetime_error); return -1; } break; case 'e': - if ((parsetime_error = rrd_parsetime(optarg, &end_tv))) { + if ((parsetime_error = rrd_parsetime(options.optarg, &end_tv))) { rrd_set_error("end time: %s", parsetime_error); return -1; } break; case 'm': - im.xsize = atol(optarg); + im.xsize = atol(options.optarg); if (im.xsize < 10) { rrd_set_error("maxrows below 10 rows"); return -1; @@ -121,7 +116,7 @@ int rrd_xport( return (-1); } - im.daemon_addr = strdup(optarg); + im.daemon_addr = strdup(options.optarg); if (im.daemon_addr == NULL) { rrd_set_error("strdup error"); @@ -131,7 +126,7 @@ int rrd_xport( } case '?': - rrd_set_error("unknown option '%s'", argv[optind - 1]); + rrd_set_error("%s: %s", argv[0], options.errmsg); return -1; } } @@ -156,7 +151,7 @@ int rrd_xport( im.end = end_tmp; im.step = max((long) im.step, (im.end - im.start) / im.xsize); - rrd_graph_script(argc, argv, &im, 0); + rrd_graph_script(argc, argv, &im, options.optind); if (rrd_test_error()) { im_free(&im); return -1;