From b3ca13d4192fabe6b6bedb9b9bedba311a40ea68 Mon Sep 17 00:00:00 2001 From: minfrin Date: Sun, 1 Dec 2019 21:28:27 +0000 Subject: [PATCH] Initial import of mod_rrd. --- AUTHORS | 3 + COPYING | 178 +++ ChangeLog | 5 + INSTALL | 368 ++++++ Makefile.am | 13 + NEWS | 3 + README.md | 28 +- config.h.in | 61 + configure.ac | 53 + debian/changelog | 5 + debian/compat | 1 + debian/control | 13 + debian/copyright | 26 + debian/docs | 2 + debian/mod-rrd.dirs | 1 + debian/mod-rrd.substvars | 1 + debian/rules | 13 + debian/source/format | 1 + mod_rrd.c | 2619 ++++++++++++++++++++++++++++++++++++++ mod_rrd.spec.in | 35 + 20 files changed, 3428 insertions(+), 1 deletion(-) create mode 100644 AUTHORS create mode 100644 COPYING create mode 100644 ChangeLog create mode 100644 INSTALL create mode 100644 Makefile.am create mode 100644 NEWS create mode 100644 config.h.in create mode 100644 configure.ac create mode 100644 debian/changelog create mode 100644 debian/compat create mode 100644 debian/control create mode 100644 debian/copyright create mode 100644 debian/docs create mode 100644 debian/mod-rrd.dirs create mode 100644 debian/mod-rrd.substvars create mode 100755 debian/rules create mode 100644 debian/source/format create mode 100644 mod_rrd.c create mode 100644 mod_rrd.spec.in diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..efa8eb2 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,3 @@ + +Written by Graham Leggett + diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..4909afd --- /dev/null +++ b/COPYING @@ -0,0 +1,178 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..bd6ed04 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,5 @@ + +Changes with v1.0.0 + + *) Initial release. [Graham Leggett ] + diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..8865734 --- /dev/null +++ b/INSTALL @@ -0,0 +1,368 @@ +Installation Instructions +************************* + + Copyright (C) 1994-1996, 1999-2002, 2004-2016 Free Software +Foundation, Inc. + + Copying and distribution of this file, with or without modification, +are permitted in any medium without royalty provided the copyright +notice and this notice are preserved. This file is offered as-is, +without warranty of any kind. + +Basic Installation +================== + + Briefly, the shell command './configure && make && make install' +should configure, build, and install this package. The following +more-detailed instructions are generic; see the 'README' file for +instructions specific to this package. Some packages provide this +'INSTALL' file but do not implement all of the features documented +below. The lack of an optional feature in a given package is not +necessarily a bug. More recommendations for GNU packages can be found +in *note Makefile Conventions: (standards)Makefile Conventions. + + The 'configure' shell script attempts to guess correct values for +various system-dependent variables used during compilation. It uses +those values to create a 'Makefile' in each directory of the package. +It may also create one or more '.h' files containing system-dependent +definitions. Finally, it creates a shell script 'config.status' that +you can run in the future to recreate the current configuration, and a +file 'config.log' containing compiler output (useful mainly for +debugging 'configure'). + + It can also use an optional file (typically called 'config.cache' and +enabled with '--cache-file=config.cache' or simply '-C') that saves the +results of its tests to speed up reconfiguring. Caching is disabled by +default to prevent problems with accidental use of stale cache files. + + If you need to do unusual things to compile the package, please try +to figure out how 'configure' could check whether to do them, and mail +diffs or instructions to the address given in the 'README' so they can +be considered for the next release. If you are using the cache, and at +some point 'config.cache' contains results you don't want to keep, you +may remove or edit it. + + The file 'configure.ac' (or 'configure.in') is used to create +'configure' by a program called 'autoconf'. You need 'configure.ac' if +you want to change it or regenerate 'configure' using a newer version of +'autoconf'. + + The simplest way to compile this package is: + + 1. 'cd' to the directory containing the package's source code and type + './configure' to configure the package for your system. + + Running 'configure' might take a while. While running, it prints + some messages telling which features it is checking for. + + 2. Type 'make' to compile the package. + + 3. Optionally, type 'make check' to run any self-tests that come with + the package, generally using the just-built uninstalled binaries. + + 4. Type 'make install' to install the programs and any data files and + documentation. When installing into a prefix owned by root, it is + recommended that the package be configured and built as a regular + user, and only the 'make install' phase executed with root + privileges. + + 5. Optionally, type 'make installcheck' to repeat any self-tests, but + this time using the binaries in their final installed location. + This target does not install anything. Running this target as a + regular user, particularly if the prior 'make install' required + root privileges, verifies that the installation completed + correctly. + + 6. You can remove the program binaries and object files from the + source code directory by typing 'make clean'. To also remove the + files that 'configure' created (so you can compile the package for + a different kind of computer), type 'make distclean'. There is + also a 'make maintainer-clean' target, but that is intended mainly + for the package's developers. If you use it, you may have to get + all sorts of other programs in order to regenerate files that came + with the distribution. + + 7. Often, you can also type 'make uninstall' to remove the installed + files again. In practice, not all packages have tested that + uninstallation works correctly, even though it is required by the + GNU Coding Standards. + + 8. Some packages, particularly those that use Automake, provide 'make + distcheck', which can by used by developers to test that all other + targets like 'make install' and 'make uninstall' work correctly. + This target is generally not run by end users. + +Compilers and Options +===================== + + Some systems require unusual options for compilation or linking that +the 'configure' script does not know about. Run './configure --help' +for details on some of the pertinent environment variables. + + You can give 'configure' initial values for configuration parameters +by setting variables in the command line or in the environment. Here is +an example: + + ./configure CC=c99 CFLAGS=-g LIBS=-lposix + + *Note Defining Variables::, for more details. + +Compiling For Multiple Architectures +==================================== + + You can compile the package for more than one kind of computer at the +same time, by placing the object files for each architecture in their +own directory. To do this, you can use GNU 'make'. 'cd' to the +directory where you want the object files and executables to go and run +the 'configure' script. 'configure' automatically checks for the source +code in the directory that 'configure' is in and in '..'. This is known +as a "VPATH" build. + + With a non-GNU 'make', it is safer to compile the package for one +architecture at a time in the source code directory. After you have +installed the package for one architecture, use 'make distclean' before +reconfiguring for another architecture. + + On MacOS X 10.5 and later systems, you can create libraries and +executables that work on multiple system types--known as "fat" or +"universal" binaries--by specifying multiple '-arch' options to the +compiler but only a single '-arch' option to the preprocessor. Like +this: + + ./configure CC="gcc -arch i386 -arch x86_64 -arch ppc -arch ppc64" \ + CXX="g++ -arch i386 -arch x86_64 -arch ppc -arch ppc64" \ + CPP="gcc -E" CXXCPP="g++ -E" + + This is not guaranteed to produce working output in all cases, you +may have to build one architecture at a time and combine the results +using the 'lipo' tool if you have problems. + +Installation Names +================== + + By default, 'make install' installs the package's commands under +'/usr/local/bin', include files under '/usr/local/include', etc. You +can specify an installation prefix other than '/usr/local' by giving +'configure' the option '--prefix=PREFIX', where PREFIX must be an +absolute file name. + + You can specify separate installation prefixes for +architecture-specific files and architecture-independent files. If you +pass the option '--exec-prefix=PREFIX' to 'configure', the package uses +PREFIX as the prefix for installing programs and libraries. +Documentation and other data files still use the regular prefix. + + In addition, if you use an unusual directory layout you can give +options like '--bindir=DIR' to specify different values for particular +kinds of files. Run 'configure --help' for a list of the directories +you can set and what kinds of files go in them. In general, the default +for these options is expressed in terms of '${prefix}', so that +specifying just '--prefix' will affect all of the other directory +specifications that were not explicitly provided. + + The most portable way to affect installation locations is to pass the +correct locations to 'configure'; however, many packages provide one or +both of the following shortcuts of passing variable assignments to the +'make install' command line to change installation locations without +having to reconfigure or recompile. + + The first method involves providing an override variable for each +affected directory. For example, 'make install +prefix=/alternate/directory' will choose an alternate location for all +directory configuration variables that were expressed in terms of +'${prefix}'. Any directories that were specified during 'configure', +but not in terms of '${prefix}', must each be overridden at install time +for the entire installation to be relocated. The approach of makefile +variable overrides for each directory variable is required by the GNU +Coding Standards, and ideally causes no recompilation. However, some +platforms have known limitations with the semantics of shared libraries +that end up requiring recompilation when using this method, particularly +noticeable in packages that use GNU Libtool. + + The second method involves providing the 'DESTDIR' variable. For +example, 'make install DESTDIR=/alternate/directory' will prepend +'/alternate/directory' before all installation names. The approach of +'DESTDIR' overrides is not required by the GNU Coding Standards, and +does not work on platforms that have drive letters. On the other hand, +it does better at avoiding recompilation issues, and works well even +when some directory options were not specified in terms of '${prefix}' +at 'configure' time. + +Optional Features +================= + + If the package supports it, you can cause programs to be installed +with an extra prefix or suffix on their names by giving 'configure' the +option '--program-prefix=PREFIX' or '--program-suffix=SUFFIX'. + + Some packages pay attention to '--enable-FEATURE' options to +'configure', where FEATURE indicates an optional part of the package. +They may also pay attention to '--with-PACKAGE' options, where PACKAGE +is something like 'gnu-as' or 'x' (for the X Window System). The +'README' should mention any '--enable-' and '--with-' options that the +package recognizes. + + For packages that use the X Window System, 'configure' can usually +find the X include and library files automatically, but if it doesn't, +you can use the 'configure' options '--x-includes=DIR' and +'--x-libraries=DIR' to specify their locations. + + Some packages offer the ability to configure how verbose the +execution of 'make' will be. For these packages, running './configure +--enable-silent-rules' sets the default to minimal output, which can be +overridden with 'make V=1'; while running './configure +--disable-silent-rules' sets the default to verbose, which can be +overridden with 'make V=0'. + +Particular systems +================== + + On HP-UX, the default C compiler is not ANSI C compatible. If GNU CC +is not installed, it is recommended to use the following options in +order to use an ANSI C compiler: + + ./configure CC="cc -Ae -D_XOPEN_SOURCE=500" + +and if that doesn't work, install pre-built binaries of GCC for HP-UX. + + HP-UX 'make' updates targets which have the same time stamps as their +prerequisites, which makes it generally unusable when shipped generated +files such as 'configure' are involved. Use GNU 'make' instead. + + On OSF/1 a.k.a. Tru64, some versions of the default C compiler cannot +parse its '' header file. The option '-nodtk' can be used as a +workaround. If GNU CC is not installed, it is therefore recommended to +try + + ./configure CC="cc" + +and if that doesn't work, try + + ./configure CC="cc -nodtk" + + On Solaris, don't put '/usr/ucb' early in your 'PATH'. This +directory contains several dysfunctional programs; working variants of +these programs are available in '/usr/bin'. So, if you need '/usr/ucb' +in your 'PATH', put it _after_ '/usr/bin'. + + On Haiku, software installed for all users goes in '/boot/common', +not '/usr/local'. It is recommended to use the following options: + + ./configure --prefix=/boot/common + +Specifying the System Type +========================== + + There may be some features 'configure' cannot figure out +automatically, but needs to determine by the type of machine the package +will run on. Usually, assuming the package is built to be run on the +_same_ architectures, 'configure' can figure that out, but if it prints +a message saying it cannot guess the machine type, give it the +'--build=TYPE' option. TYPE can either be a short name for the system +type, such as 'sun4', or a canonical name which has the form: + + CPU-COMPANY-SYSTEM + +where SYSTEM can have one of these forms: + + OS + KERNEL-OS + + See the file 'config.sub' for the possible values of each field. If +'config.sub' isn't included in this package, then this package doesn't +need to know the machine type. + + If you are _building_ compiler tools for cross-compiling, you should +use the option '--target=TYPE' to select the type of system they will +produce code for. + + If you want to _use_ a cross compiler, that generates code for a +platform different from the build platform, you should specify the +"host" platform (i.e., that on which the generated programs will +eventually be run) with '--host=TYPE'. + +Sharing Defaults +================ + + If you want to set default values for 'configure' scripts to share, +you can create a site shell script called 'config.site' that gives +default values for variables like 'CC', 'cache_file', and 'prefix'. +'configure' looks for 'PREFIX/share/config.site' if it exists, then +'PREFIX/etc/config.site' if it exists. Or, you can set the +'CONFIG_SITE' environment variable to the location of the site script. +A warning: not all 'configure' scripts look for a site script. + +Defining Variables +================== + + Variables not defined in a site shell script can be set in the +environment passed to 'configure'. However, some packages may run +configure again during the build, and the customized values of these +variables may be lost. In order to avoid this problem, you should set +them in the 'configure' command line, using 'VAR=value'. For example: + + ./configure CC=/usr/local2/bin/gcc + +causes the specified 'gcc' to be used as the C compiler (unless it is +overridden in the site shell script). + +Unfortunately, this technique does not work for 'CONFIG_SHELL' due to an +Autoconf limitation. Until the limitation is lifted, you can use this +workaround: + + CONFIG_SHELL=/bin/bash ./configure CONFIG_SHELL=/bin/bash + +'configure' Invocation +====================== + + 'configure' recognizes the following options to control how it +operates. + +'--help' +'-h' + Print a summary of all of the options to 'configure', and exit. + +'--help=short' +'--help=recursive' + Print a summary of the options unique to this package's + 'configure', and exit. The 'short' variant lists options used only + in the top level, while the 'recursive' variant lists options also + present in any nested packages. + +'--version' +'-V' + Print the version of Autoconf used to generate the 'configure' + script, and exit. + +'--cache-file=FILE' + Enable the cache: use and save the results of the tests in FILE, + traditionally 'config.cache'. FILE defaults to '/dev/null' to + disable caching. + +'--config-cache' +'-C' + Alias for '--cache-file=config.cache'. + +'--quiet' +'--silent' +'-q' + Do not print messages saying which checks are being made. To + suppress all normal output, redirect it to '/dev/null' (any error + messages will still be shown). + +'--srcdir=DIR' + Look for the package's source code in directory DIR. Usually + 'configure' can determine that directory automatically. + +'--prefix=DIR' + Use DIR as the installation prefix. *note Installation Names:: for + more details, including other options available for fine-tuning the + installation locations. + +'--no-create' +'-n' + Run the configure checks, but stop before creating any output + files. + +'configure' also accepts some other, not widely useful, options. Run +'configure --help' for more details. diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..fecbfd3 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,13 @@ + +AM_CFLAGS = ${apr_CFLAGS} ${apu_CFLAGS} +AM_LDFLAGS = ${apr_LDFLAGS} ${apu_LDFLAGS} + +EXTRA_DIST = mod_rrd.c mod_rrd.spec debian/changelog debian/compat debian/control debian/copyright debian/docs debian/mod-rrd.substvars debian/mod-rrd.dirs debian/rules debian/source/format README.md + +all-local: + $(APXS) -c $(DEF_LDLIBS) -Wc,"$(CFLAGS)" -Wc,"$(AM_CFLAGS)" -Wl,"$(LDFLAGS)" -Wl,"$(AM_LDFLAGS)" $(LIBS) @srcdir@/mod_rrd.c + +install-exec-local: + mkdir -p $(DESTDIR)`$(APXS) -q LIBEXECDIR` + $(APXS) -S LIBEXECDIR=$(DESTDIR)`$(APXS) -q LIBEXECDIR` -c -i $(DEF_LDLIBS) -Wc,"$(CFLAGS)" -Wc,"$(AM_CFLAGS)" -Wl,"$(LDFLAGS)" -Wl,"$(AM_LDFLAGS)" $(LIBS) @srcdir@/mod_rrd.c + diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..593e6be --- /dev/null +++ b/NEWS @@ -0,0 +1,3 @@ + +No news is good news. + diff --git a/README.md b/README.md index d72f850..54904f5 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,28 @@ # mod_rrd -Apache httpd module to render RRD graphs via a webserver +Apache httpd module to render RRD graphs via a webserver. + +Depends on rrdtool from https://oss.oetiker.ch/rrdtool/. + +Example config: + + + + Require all granted + + Alias /rrd /var/lib/collectd/rrd + + RRDGraph on + RRDGraphEnv METHODS %{REQUEST_METHOD} + RRDGraphOption title :%{SERVER_NAME} + RRDGraphElement DEF:ifOutOctets=monitor*.rrd:ifOutOctets:AVERAGE + RRDGraphElement VDEF:ifOutOctetsmax=ifOutOctets,MAXIMUM + RRDGraphElement CDEF:combined=ifOutOctets,1,+ + RRDGraphElement LINE1:ifOutOctets#00ff00:Out+Octets :%{SERVER_NAME} + RRDGraphElement AREA:ifOutOctets#00ff00:Out+Octets :%{SERVER_NAME} + RRDGraphElement TICK:ifOutOctets#00ff00:1.0:Failures :%{SERVER_NAME} + RRDGraphElement "VRULE:0#FF0000:dashed line:dashes" :%{SERVER_NAME} + RRDGraphElement "HRULE:0#FF0000:dashed line:dashes" :%{SERVER_NAME} + RRDGraphElement "COMMENT:Foo" %{env:METHODS} + + + diff --git a/config.h.in b/config.h.in new file mode 100644 index 0000000..d31374c --- /dev/null +++ b/config.h.in @@ -0,0 +1,61 @@ +/* config.h.in. Generated from configure.ac by autoheader. */ + +/* Define to 1 if you have the header file. */ +#undef HAVE_INTTYPES_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_MEMORY_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STDINT_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STDLIB_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STRINGS_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STRING_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_STAT_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_TYPES_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_XATTR_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_UNISTD_H + +/* Name of package */ +#undef PACKAGE + +/* Define to the address where bug reports for this package should be sent. */ +#undef PACKAGE_BUGREPORT + +/* Define to the full name of this package. */ +#undef PACKAGE_NAME + +/* Define to the full name and version of this package. */ +#undef PACKAGE_STRING + +/* Define to the one symbol short name of this package. */ +#undef PACKAGE_TARNAME + +/* Define to the home page for this package. */ +#undef PACKAGE_URL + +/* Define to the version of this package. */ +#undef PACKAGE_VERSION + +/* Define to 1 if you have the ANSI C header files. */ +#undef STDC_HEADERS + +/* Version number of package */ +#undef VERSION + +/* Define to `unsigned int' if does not define. */ +#undef size_t diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..9c94326 --- /dev/null +++ b/configure.ac @@ -0,0 +1,53 @@ +# -*- Autoconf -*- +# Process this file with autoconf to produce a configure script. + +AC_PREREQ(2.61) +AC_INIT(mod_rrd, 1.0.0, minfrin@sharp.fm) +AM_INIT_AUTOMAKE([dist-bzip2]) +AC_CONFIG_FILES([Makefile mod_rrd.spec]) +AC_CONFIG_SRCDIR([mod_rrd.c]) +AC_CONFIG_HEADER(config.h) + +# Checks for programs. +AC_PROG_CC +AC_ARG_WITH(apxs, + [ --with-apxs=PATH path to Apache apxs], + [ + if test "$withval" = "yes"; then + AC_PATH_PROGS(APXS, apxs apxs2, reject, $PATH:/usr/sbin) + else + APXS=$withval + AC_SUBST(APXS) + fi + ], + [ + AC_PATH_PROGS(APXS, apxs apxs2, reject, $PATH:/usr/sbin) + ]) +if test "$APXS" = "reject"; then + AC_MSG_ERROR([Could not find apxs on the path.]) +fi + +# Make sure the Apache include files are found +CPPFLAGS="$CPPFLAGS -I`$APXS -q INCLUDEDIR`" +CFLAGS="$CFLAGS -I`$APXS -q INCLUDEDIR`" + +# Checks for libraries. +PKG_CHECK_MODULES(apr, apr-1 >= 1.2) +PKG_CHECK_MODULES(apu, apr-util-1 >= 1.2) +PKG_CHECK_MODULES(librrd, librrd >= 1.4) + +# Make sure we use rrdtool paths +CFLAGS="$CFLAGS $librrd_CFLAGS" +CPPFLAGS="$CPPFLAGS $librrd_CPPFLAGS" +LDFLAGS="$LDFLAGS $librrd_LIBS" + +# Checks for header files. +AC_CHECK_HEADERS(sys/xattr.h) + +# Checks for typedefs, structures, and compiler characteristics. +AC_TYPE_SIZE_T + +# Checks for library functions. + +AC_SUBST(PACKAGE_VERSION) +AC_OUTPUT diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..7b2886e --- /dev/null +++ b/debian/changelog @@ -0,0 +1,5 @@ +mod-rrd (1.0.0) stable; urgency=low + + * Initial Release. + + -- Graham Leggett Tue, 22 Oct 2019 00:00:00 +0000 diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..45a4fb7 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +8 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..68ef2c3 --- /dev/null +++ b/debian/control @@ -0,0 +1,13 @@ +Source: mod-rrd +Priority: extra +Maintainer: Graham Leggett +Build-Depends: debhelper (>= 8.0.0), autotools-dev, apache2-dev, rrdtool-dev +Standards-Version: 3.9.2 +Section: libs + +Package: mod-rrd +Section: libs +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends} +Description: The Apache mod_rrd module provides a set of filters and handlers to manipulate and display RRD graphs. + diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..1fff78b --- /dev/null +++ b/debian/copyright @@ -0,0 +1,26 @@ +Format: http://dep.debian.net/deps/dep5 +Upstream-Name: mod-scrape +Source: http://source/mod_scrape/ + +Files: * +Copyright: 2019 Graham Leggett +License: Apache-2.0 + +License: Apache-2.0 + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + . + http://www.apache.org/licenses/LICENSE-2.0 + . + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + . + On Debian systems, the complete text of the Apache version 2.0 license + can be found in "/usr/share/common-licenses/Apache-2.0". + +# Please also look if there are files or directories which have a +# different copyright/license attached and list them here. diff --git a/debian/docs b/debian/docs new file mode 100644 index 0000000..50bd824 --- /dev/null +++ b/debian/docs @@ -0,0 +1,2 @@ +NEWS +README diff --git a/debian/mod-rrd.dirs b/debian/mod-rrd.dirs new file mode 100644 index 0000000..6845771 --- /dev/null +++ b/debian/mod-rrd.dirs @@ -0,0 +1 @@ +usr/lib diff --git a/debian/mod-rrd.substvars b/debian/mod-rrd.substvars new file mode 100644 index 0000000..abd3ebe --- /dev/null +++ b/debian/mod-rrd.substvars @@ -0,0 +1 @@ +misc:Depends= diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..b760bee --- /dev/null +++ b/debian/rules @@ -0,0 +1,13 @@ +#!/usr/bin/make -f +# -*- makefile -*- +# Sample debian/rules that uses debhelper. +# This file was originally written by Joey Hess and Craig Small. +# As a special exception, when this file is copied by dh-make into a +# dh-make output file, you may use that output file without restriction. +# This special exception was added by Craig Small in version 0.37 of dh-make. + +# Uncomment this to turn on verbose mode. +#export DH_VERBOSE=1 + +%: + dh $@ diff --git a/debian/source/format b/debian/source/format new file mode 100644 index 0000000..89ae9db --- /dev/null +++ b/debian/source/format @@ -0,0 +1 @@ +3.0 (native) diff --git a/mod_rrd.c b/mod_rrd.c new file mode 100644 index 0000000..5d46d32 --- /dev/null +++ b/mod_rrd.c @@ -0,0 +1,2619 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * mod_rrd.c --- The Apache mod_rrd module provides a set of handlers to + * manipulate and display RRD graphs. + * + * Example config: + * + * + * + * Require all granted + * + * Alias /rrd /var/lib/collectd/rrd + * + * RRDGraph on + * RRDGraphOption title %{SERVER_NAME} + * RRDGraphEnv METHODS %{REQUEST_METHOD} + * RRDGraphElement DEF:xifOutOctets=monitor*.rrd:ifOutOctets:AVERAGE + * RRDGraphElement VDEF:xifOutOctetsmax=xifOutOctets+,MAXIMUM + * RRDGraphElement CDEF:xcombined=xifOutOctets,1,+ + * RRDGraphElement LINE1:xifOutOctets#00ff00:Out+Octets :%{SERVER_NAME} + * RRDGraphElement AREA:xifOutOctets#00ff00:Out+Octets :%{SERVER_NAME} + * RRDGraphElement TICK:xifOutOctets#00ff00:1.0:Failures :%{SERVER_NAME} + * RRDGraphElement "VRULE:0#FF0000:dashed line:dashes" :%{SERVER_NAME} + * RRDGraphElement "HRULE:0#FF0000:dashed line:dashes" :%{SERVER_NAME} + * RRDGraphElement "COMMENT:Foo" %{env:METHODS} + * + * + * + * Returns a dynamically generated graph file with the format controlled + * by the given suffix. The graph file in the URL must not already exist, + * otherwise the existing file will be returned. + * + * Options are passed as query parameters, either as a name value pair, or + * a name only for options that do not take a parameter. + * + * Graph elements are passed between & characters. + * + * The parameters in the query string must be URLEncoded. Most notably the + * '+' character is not decoded. + * + * All RRD files are checked against Apache httpd permissions, and if not + * accessible the DEF line is ignored. + * + * Unlike rrdgraph, DEF lines can accept wildcard filenames. A CDEF is + * generated automatically to add the wildcard RRDs together. + * + * When a LINE, AREA or TICK is rendered, each RRD file that matches the + * wildcard will form the basis of the expressions parsed. + * + * Example call: + * curl "http://localhost/rrd/monitor.png?DEF:ifOutOctets=monitor*.rrd:ifOutOctets:AVERAGE&LINE1:ifOutOctets%2300ff00:Out+Octets" + * + * Notes: + * - Write as a handler, not a filter (alas) + * - Use rrd_graph_v() to return images in memory buffer + * + * - GET with Accept of xml - map to rrdtool dump + * - PUT with Content-Type of XML - map to rrdtool restore + * - PATCH - map to update/updatev + * - PROPFIND - map to rrdtool info? + * + * - Graph handler - map specific path to specific graph. + * - Option to expand each wildcard to one line per rrd, to + * supporting a combined syntax, eg ifOutOctets+ for all + * the DEFs added together using a CDEF, to ifOutOctets* for all the + * DEFs multiplied together using a CDEF. + * + * - rrd_fetch_cb_register / rrd_fetch_fn_cb are too limited - we'll + * need to build the DEF values ourselves. + */ + +#include "apr.h" +#include "apr_escape.h" +#include "apr_strings.h" +#include "apr_buckets.h" +#include "apr_fnmatch.h" +#include "apr_lib.h" +#include "apr_hash.h" +#include "apr_tables.h" +#include "apr_cstr.h" +#include "apr_uuid.h" + +#include "ap_config.h" +#include "ap_expr.h" +#include "ap_mpm.h" +#include "util_filter.h" +#include "httpd.h" +#include "http_config.h" +#include "http_log.h" +#include "http_protocol.h" +#include "http_request.h" + +#undef PACKAGE_BUGREPORT +#undef PACKAGE_NAME +#undef PACKAGE_STRING +#undef PACKAGE_TARNAME +#undef PACKAGE_VERSION + +#include "config.h" + +#include "rrd.h" + +#if HAVE_SYS_XATTR_H +#include +#endif + +#if APR_HAS_THREADS +static apr_thread_mutex_t *rrd_mutex = NULL; +#endif + +module AP_MODULE_DECLARE_DATA rrd_module; + +typedef struct rrd_conf { + const char *location; + apr_array_header_t *options; + apr_array_header_t *elements; + apr_hash_t *env; + int graph; + unsigned int location_set:1; + unsigned int graph_set:1; +} rrd_conf; + +typedef struct rrd_ctx { + apr_file_t *file; + apr_bucket_brigade *bb; +} rrd_ctx; + +typedef enum rrd_conf_e { + RRD_CONF_DEF, + RRD_CONF_CDEF, + RRD_CONF_VDEF, + RRD_CONF_PRINT, + RRD_CONF_GPRINT, + RRD_CONF_COMMENT, + RRD_CONF_VRULE, + RRD_CONF_HRULE, + RRD_CONF_LINE, + RRD_CONF_AREA, + RRD_CONF_TICK, + RRD_CONF_SHIFT, + RRD_CONF_TEXTALIGN +} rrd_conf_e; + +typedef struct rrd_cmd_t rrd_cmd_t; + +typedef struct rrd_def_t { + const char *vname; + const char *path; + const char *dsname; + const char *cf; + apr_pool_t *pool; + apr_array_header_t *requests; +} rrd_def_t; + +typedef struct rrd_vdef_t { + const char *vname; + const char *dsname; + const char *rpn; + rrd_cmd_t *ref; +} rrd_vdef_t; + +typedef struct rrd_cdef_t { + const char *vname; + apr_array_header_t *rpns; + const char *rpn; + rrd_cmd_t *ref; +} rrd_cdef_t; + +typedef struct rrd_rpn_t { + const char *rpn; + rrd_cmd_t *def; +} rrd_rpn_t; + +typedef struct rrd_line_t { + const char *line; + const char *vname; + const char *colour; + const char *legend; + ap_expr_info_t *elegend; + const char *args; +} rrd_line_t; + +typedef struct rrd_area_t { + const char *vname; + const char *colour; + const char *legend; + ap_expr_info_t *elegend; + const char *args; +} rrd_area_t; + +typedef struct rrd_tick_t { + const char *vname; + const char *colour; + const char *fraction; + const char *legend; + ap_expr_info_t *elegend; + const char *args; +} rrd_tick_t; + +typedef struct rrd_shift_t { + const char *vname; + const char *shift; +} rrd_shift_t; + +typedef struct rrd_print_t { + const char *vname; + const char *format; +} rrd_print_t; + +typedef struct rrd_rule_t { + const char *val; + const char *colour; + const char *legend; + ap_expr_info_t *elegend; + const char *args; +} rrd_rule_t; + +typedef struct rrd_element_t { + const char *element; + const char *legend; + ap_expr_info_t *elegend; +} rrd_element_t; + +typedef struct rrd_cmd_t { + rrd_conf_e type; + int num; + rrd_cmd_t *def; + union { + rrd_def_t d; + rrd_vdef_t v; + rrd_cdef_t c; + rrd_line_t l; + rrd_area_t a; + rrd_rule_t r; + rrd_tick_t t; + rrd_shift_t s; + rrd_element_t e; + rrd_print_t p; + }; +} rrd_cmd_t; + +typedef struct rrd_opt_t { + const char *key; + const char *val; + ap_expr_info_t *eval; +} rrd_opt_t; + +typedef struct rrd_cmds_t { + apr_array_header_t *cmds; + apr_array_header_t *opts; + apr_hash_t *names; +} rrd_cmds_t; + +typedef struct rrd_cb_t { + request_rec *r; + rrd_cmd_t *cmd; +} rrd_cb_t; + +static char *substring_quote(apr_pool_t *p, const char *start, int len, + char quote) +{ + char *result = apr_palloc(p, len + 1); + char *resp = result; + int i; + + for (i = 0; i < len; ++i) { + if (start[i] == '\\' && (start[i + 1] == '\\' + || (quote && start[i + 1] == quote))) + *resp++ = start[++i]; + else + *resp++ = start[i]; + } + + *resp++ = '\0'; + return result; +} + +static char *getword_quote(apr_pool_t *p, const char **line, char stop) +{ + const char *str = *line, *strend; + char *res; + char quote; + + if (!*str) { + *line = str; + return ""; + } + + if ((quote = *str) == '"' || quote == '\'') { + strend = str + 1; + while (*strend && *strend != quote) { + if (*strend == '\\' && strend[1] && + (strend[1] == quote || strend[1] == '\\')) { + strend += 2; + } + else { + ++strend; + } + } + res = substring_quote(p, str + 1, strend - str - 1, quote); + + if (*strend == quote) { + ++strend; + } + while (*strend && *strend != stop) { + ++strend; + } + } + else { + strend = str; + while (*strend && *strend != stop) { + ++strend; + } + + res = substring_quote(p, str, strend - str, 0); + } + + if (*strend == stop) { + ++strend; + } + *line = strend; + return res; +} + +static apr_status_t escape_colon(char *escaped, const char *str, + apr_ssize_t slen, apr_size_t *len) +{ + unsigned char *d; + const unsigned char *s; + apr_size_t size = 1; + int found = 0; + + d = (unsigned char *) escaped; + s = (const unsigned char *) str; + + if (s) { + if (d) { + for (; *s && slen; ++s, slen--) { + if (':' == *s) { + *d++ = '\\'; + size++; + found = 1; + } + *d++ = *s; + size++; + } + *d = '\0'; + } + else { + for (; *s && slen; ++s, slen--) { + if (':' == *s) { + size++; + found = 1; + } + size++; + } + } + } + + if (len) { + *len = size; + } + if (!found) { + return APR_NOTFOUND; + } + + return APR_SUCCESS; +} + +static const char *pescape_colon(apr_pool_t *p, const char *str) +{ + apr_size_t len; + + switch (escape_colon(NULL, str, APR_ESCAPE_STRING, &len)) { + case APR_SUCCESS: { + char *cmd = apr_palloc(p, len); + escape_colon(cmd, str, APR_ESCAPE_STRING, NULL); + return cmd; + } + case APR_NOTFOUND: { + break; + } + } + + return str; +} + +static void log_message(request_rec *r, apr_status_t status, + const char *message, const char *err) +{ + + /* Allow "error-notes" string to be printed by ap_send_error_response() */ + apr_table_setn(r->notes, "verbose-error-to", "*"); + + if (err) { + + apr_table_setn(r->notes, "error-notes", + ap_escape_html(r->pool, + apr_pstrcat(r->pool, "RRD error: ", message, ": ", err, + NULL))); + + ap_log_rerror( + APLOG_MARK, APLOG_ERR, status, r, "mod_rrd: " + "%s (%s)", message, err); + } + else { + + apr_table_setn(r->notes, "error-notes", + ap_escape_html(r->pool, + apr_pstrcat(r->pool, "RRD error: ", message, NULL))); + + ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, "mod_rrd: " + "%s", message); + } + +} + +static int options_wadl(request_rec *r, rrd_conf *conf) +{ + int rv; + + /* discard the request body */ + if ((rv = ap_discard_request_body(r)) != OK) { + return rv; + } + + ap_set_content_type(r, "application/vnd.sun.wadl+xml"); + + ap_rprintf(r, + "\n" + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n", + conf->location ? conf->location : + apr_pstrcat(r->pool, ap_http_scheme(r), "://", + r->server->server_hostname, r->uri, NULL)); + + return OK; +} + +static const char *parse_rrdgraph_suffix(request_rec *r) +{ + const char *fname = ap_strrchr_c(r->filename, '/'); + + if (fname) { + /* PNG|SVG|EPS|PDF|XML|XMLENUM|JSON|JSONTIME|CSV|TSV|SSV */ + const char *suffix = ap_strrchr_c(fname, '.'); + if (suffix) { + switch (suffix[1]) { + case 'p': + case 'P': + if (strcasecmp(suffix, ".png") == 0) { + return "PNG"; + } + if (strcasecmp(suffix, "pdf") == 0) { + return "PDF"; + } + break; + case 's': + case 'S': + if (strcasecmp(suffix, ".svg") == 0) { + return "SVG"; + } + if (strcasecmp(suffix, "ssv") == 0) { + return "SSV"; + } + break; + case 'e': + case 'E': + if (strcasecmp(suffix, ".eps") == 0) { + return "EPS"; + } + break; + case 'x': + case 'X': + if (strcasecmp(suffix, ".xml") == 0) { + return "XML"; + } + if (strcasecmp(suffix, ".xmlenum") == 0) { + return "XMLENUM"; + } + break; + case 'j': + case 'J': + if (strcasecmp(suffix, ".json") == 0) { + return "JSON"; + } + if (strcasecmp(suffix, ".jsontime") == 0) { + return "JSONTIME"; + } + break; + case 'c': + case 'C': + if (strcasecmp(suffix, ".csv") == 0) { + return "CSV"; + } + break; + case 't': + case 'T': + if (strcasecmp(suffix, ".tsv") == 0) { + return "TSV"; + } + break; + } + } + } + return NULL; +} + +static int parse_element(apr_pool_t *p, const char *element, ap_expr_info_t *elegend, + apr_array_header_t *cmds) +{ + switch (element[0]) { + case 'A': + /* handle AREA sections */ + if (strncmp(element, "AREA:", 5) == 0) { + char *vncol; + rrd_cmd_t *cmd = apr_array_push(cmds); + cmd->type = RRD_CONF_AREA; + element += 5; + vncol = ap_getword(p, &element, ':'); + cmd->a.legend = getword_quote(p, &element, ':'); + cmd->a.elegend = elegend; + cmd->a.args = element; + cmd->a.vname = apr_cstr_tokenize("#", &vncol); + cmd->a.colour = vncol; + return 1; + } + break; + case 'C': + /* handle CDEF sections */ + if (strncmp(element, "CDEF:", 5) == 0) { + char *rpn, *rpns; + rrd_cmd_t *cmd = apr_array_push(cmds); + cmd->type = RRD_CONF_CDEF; + element += 5; + cmd->c.vname = ap_getword(p, &element, '='); + cmd->c.rpns = apr_array_make(p, 4, sizeof(rrd_rpn_t)); + cmd->c.rpn = element; + rpns = apr_pstrdup(p, element); + while ((rpn = apr_cstr_tokenize(",", &rpns))) { + rrd_rpn_t *rp = apr_array_push(cmd->c.rpns); + rp->rpn = rpn; + } + return 1; + } + /* handle COMMENT sections */ + if (strncmp(element, "COMMENT:", 7) == 0) { + rrd_cmd_t *cmd = apr_array_push(cmds); + cmd->type = RRD_CONF_COMMENT; + cmd->e.element = ap_getword(p, &element, ':'); + cmd->a.legend = getword_quote(p, &element, ':'); + cmd->e.elegend = elegend; + return 1; + } + break; + case 'D': + /* handle DEF sections */ + if (strncmp(element, "DEF:", 4) == 0) { + rrd_cmd_t *cmd = apr_array_push(cmds); + cmd->type = RRD_CONF_DEF; + element += 4; + cmd->d.vname = ap_getword(p, &element, '='); + cmd->d.path = ap_getword(p, &element, ':'); + cmd->d.dsname = ap_getword(p, &element, ':'); + cmd->d.cf = element; + cmd->d.pool = p; + cmd->d.requests = apr_array_make(p, 10, sizeof(request_rec *)); + return 1; + } + break; + case 'G': + /* handle GPRINT sections */ + if (strncmp(element, "GPRINT:", 7) == 0) { + rrd_cmd_t *cmd = apr_array_push(cmds); + cmd->type = RRD_CONF_GPRINT; + element += 7; + cmd->p.vname = ap_getword(p, &element, ':'); + cmd->p.format = element; + return 1; + } + break; + case 'H': + /* handle HRULE sections */ + if (strncmp(element, "HRULE:", 6) == 0) { + char *vncol; + rrd_cmd_t *cmd = apr_array_push(cmds); + cmd->type = RRD_CONF_HRULE; + element += 6; + vncol = ap_getword(p, &element, ':'); + cmd->r.legend = getword_quote(p, &element, ':'); + cmd->r.elegend = elegend; + cmd->r.args = element; + cmd->r.val = apr_cstr_tokenize("#", &vncol); + cmd->r.colour = vncol; + return 1; + } + break; + case 'L': + /* handle LINE sections */ + if (strncmp(element, "LINE", 4) == 0) { + char *vncol; + rrd_cmd_t *cmd = apr_array_push(cmds); + cmd->type = RRD_CONF_LINE; + cmd->l.line = ap_getword(p, &element, ':'); + vncol = ap_getword(p, &element, ':'); + cmd->l.legend = getword_quote(p, &element, ':'); + cmd->l.elegend = elegend; + cmd->l.args = element; + cmd->l.vname = apr_cstr_tokenize("#", &vncol); + cmd->l.colour = vncol; + cmd->l.elegend = elegend; + return 1; + } + break; + case 'P': + /* handle PRINT sections */ + if (strncmp(element, "PRINT:", 6) == 0) { + rrd_cmd_t *cmd = apr_array_push(cmds); + cmd->type = RRD_CONF_PRINT; + element += 6; + cmd->p.vname = ap_getword(p, &element, ':'); + cmd->p.format = element; + return 1; + } + break; + case 'S': + /* handle SHIFT sections */ + if (strncmp(element, "SHIFT:", 6) == 0) { + rrd_cmd_t *cmd = apr_array_push(cmds); + cmd->type = RRD_CONF_SHIFT; + element += 6; + cmd->s.vname = ap_getword(p, &element, ':'); + cmd->s.shift = element; + return 1; + } + break; + case 'T': + /* handle TICK sections */ + if (strncmp(element, "TICK:", 5) == 0) { + char *vncol; + rrd_cmd_t *cmd = apr_array_push(cmds); + cmd->type = RRD_CONF_TICK; + element += 5; + vncol = ap_getword(p, &element, ':'); + cmd->t.fraction = ap_getword(p, &element, ':'); + cmd->t.legend = getword_quote(p, &element, ':'); + cmd->t.elegend = elegend; + cmd->t.args = element; + cmd->t.vname = apr_cstr_tokenize("#", &vncol); + cmd->t.colour = vncol; + return 1; + } + /* handle TEXTALIGN sections */ + else if (strncmp(element, "TEXTALIGN:", 10) == 0) { + rrd_cmd_t *cmd = apr_array_push(cmds); + cmd->type = RRD_CONF_TEXTALIGN; + cmd->e.element = ap_getword(p, &element, ':'); + cmd->a.legend = getword_quote(p, &element, ':'); + cmd->e.elegend = elegend; + return 1; + } + break; + case 'V': + /* handle VDEF sections */ + if (strncmp(element, "VDEF:", 5) == 0) { + rrd_cmd_t *cmd = apr_array_push(cmds); + cmd->type = RRD_CONF_VDEF; + element += 5; + cmd->v.vname = ap_getword(p, &element, '='); + cmd->v.dsname = ap_getword(p, &element, ','); + cmd->v.rpn = element; + return 1; + } + /* handle VRULE sections */ + if (strncmp(element, "VRULE:", 6) == 0) { + char *vncol; + rrd_cmd_t *cmd = apr_array_push(cmds); + cmd->type = RRD_CONF_VRULE; + element += 6; + vncol = ap_getword(p, &element, ':'); + cmd->r.legend = getword_quote(p, &element, ':'); + cmd->r.elegend = elegend; + cmd->r.args = element; + cmd->r.val = apr_cstr_tokenize("#", &vncol); + cmd->r.colour = vncol; + return 1; + } + break; + } + return 0; +} + +static int parse_option(apr_pool_t *p, const char *key, const char *val, + ap_expr_info_t *eval, apr_array_header_t *opts) +{ + /* with value */ + if (val) { + switch (key[0]) { + case 'b': + /* [--border width] */ + if (strcmp(key, "border") == 0) { + rrd_opt_t *opt = apr_array_push(opts); + opt->key = key; + opt->val = val; + opt->eval = eval; + return 1; + } + break; + case 'c': + /* [-c|--color COLORTAG#rrggbb[aa]] */ + if (strcmp(key, "color") == 0) { + rrd_opt_t *opt = apr_array_push(opts); + opt->key = key; + opt->val = val; + opt->eval = eval; + return 1; + } + break; + case 'f': + /* [-n|--font FONTTAG:size:font] */ + if (strcmp(key, "font") == 0) { + rrd_opt_t *opt = apr_array_push(opts); + opt->key = key; + opt->val = val; + opt->eval = eval; + return 1; + } + /* [-R|--font-render-mode {normal,light,mono}] */ + if (strcmp(key, "font-render-mode") == 0) { + rrd_opt_t *opt = apr_array_push(opts); + opt->key = key; + opt->val = val; + opt->eval = eval; + return 1; + } + /* [-B|--font-smoothing-threshold size] */ + if (strcmp(key, "font-smoothing-threshold") == 0) { + rrd_opt_t *opt = apr_array_push(opts); + opt->key = key; + opt->val = val; + opt->eval = eval; + return 1; + } + break; + case 'g': + /* [-G|--graph-render-mode {normal,mono}] */ + if (strcmp(key, "graph-render-mode") == 0) { + rrd_opt_t *opt = apr_array_push(opts); + opt->key = key; + opt->val = val; + opt->eval = eval; + return 1; + } + break; + case 'h': + /* [-h|--height pixels] */ + if (strcmp(key, "height") == 0) { + rrd_opt_t *opt = apr_array_push(opts); + opt->key = key; + opt->val = val; + opt->eval = eval; + return 1; + } + break; + case 'l': + /* [--left-axis-format format] */ + if (strcmp(key, "left-axis-format") == 0) { + rrd_opt_t *opt = apr_array_push(opts); + opt->key = key; + opt->val = val; + opt->eval = eval; + return 1; + } + /* [-l|--lower-limit value] */ + if (strcmp(key, "lower-limit") == 0) { + rrd_opt_t *opt = apr_array_push(opts); + opt->key = key; + opt->val = val; + opt->eval = eval; + return 1; + } + break; + case 'r': + /* [--right-axis scale:shift] */ + if (strcmp(key, "right-axis") == 0) { + rrd_opt_t *opt = apr_array_push(opts); + opt->key = key; + opt->val = val; + opt->eval = eval; + return 1; + } + /* [--right-axis-label label] */ + if (strcmp(key, "right-axis-label") == 0) { + rrd_opt_t *opt = apr_array_push(opts); + opt->key = key; + opt->val = val; + opt->eval = eval; + return 1; + } + /* [--right-axis-format format] */ + if (strcmp(key, "right-axis-format") == 0) { + rrd_opt_t *opt = apr_array_push(opts); + opt->key = key; + opt->val = val; + opt->eval = eval; + return 1; + } + break; + case 's': + /* [-S|--step seconds] */ + if (strcmp(key, "step") == 0) { + rrd_opt_t *opt = apr_array_push(opts); + opt->key = key; + opt->val = val; + opt->eval = eval; + return 1; + } + break; + case 't': + /* [-T|--tabwidth width] */ + if (strcmp(key, "tabwidth") == 0) { + rrd_opt_t *opt = apr_array_push(opts); + opt->key = key; + opt->val = val; + opt->eval = eval; + return 1; + } + /* [-t|--title string] */ + if (strcmp(key, "title") == 0) { + rrd_opt_t *opt = apr_array_push(opts); + opt->key = key; + opt->val = val; + opt->eval = eval; + return 1; + } + break; + case 'u': + /* [-X|--units-exponent value] */ + if (strcmp(key, "units-exponent") == 0) { + rrd_opt_t *opt = apr_array_push(opts); + opt->key = key; + opt->val = val; + opt->eval = eval; + return 1; + } + /* [-L|--units-length value] */ + if (strcmp(key, "units-length") == 0) { + rrd_opt_t *opt = apr_array_push(opts); + opt->key = key; + opt->val = val; + opt->eval = eval; + return 1; + } + break; + case 'v': + /* [-v|--vertical-label string] */ + if (strcmp(key, "vertical-label") == 0) { + rrd_opt_t *opt = apr_array_push(opts); + opt->key = key; + opt->val = val; + opt->eval = eval; + return 1; + } + break; + case 'w': + /* [-w|--width pixels] */ + if (strcmp(key, "width") == 0) { + rrd_opt_t *opt = apr_array_push(opts); + opt->key = key; + opt->val = val; + opt->eval = eval; + return 1; + } + /* [-W|--watermark string] */ + if (strcmp(key, "watermark") == 0) { + rrd_opt_t *opt = apr_array_push(opts); + opt->key = key; + opt->val = val; + opt->eval = eval; + return 1; + } + break; + case 'x': + /* [-x|--x-grid x-axis grid and label] */ + if (strcmp(key, "x-grid") == 0) { + rrd_opt_t *opt = apr_array_push(opts); + opt->key = key; + opt->val = val; + opt->eval = eval; + return 1; + } + break; + case 'y': + /* [-y|--y-grid y-axis grid and label] */ + if (strcmp(key, "y-grid") == 0) { + rrd_opt_t *opt = apr_array_push(opts); + opt->key = key; + opt->val = val; + opt->eval = eval; + return 1; + } + break; + case 'z': + /* [-m|--zoom factor] */ + if (strcmp(key, "zoom") == 0) { + rrd_opt_t *opt = apr_array_push(opts); + opt->key = key; + opt->val = val; + opt->eval = eval; + return 1; + } + break; + } + } + + /* no value */ + else { + switch (key[0]) { + case 'a': + /* [-Y|--alt-y-grid] */ + if (strcmp(key, "alt-y-grid") == 0) { + rrd_opt_t *opt = apr_array_push(opts); + opt->key = key; + return 1; + } + /* [-A|--alt-autoscale] */ + if (strcmp(key, "alt-autoscale") == 0) { + rrd_opt_t *opt = apr_array_push(opts); + opt->key = key; + return 1; + } + /* [-M|--alt-autoscale-max] */ + if (strcmp(key, "alt-autoscale-max") == 0) { + rrd_opt_t *opt = apr_array_push(opts); + opt->key = key; + return 1; + } + break; + case 'f': + /* [--full-size-mode] */ + if (strcmp(key, "full-size-mode") == 0) { + rrd_opt_t *opt = apr_array_push(opts); + opt->key = key; + return 1; + } + /* [-F|--force-rules-legend] */ + if (strcmp(key, "force-rules-legend") == 0) { + rrd_opt_t *opt = apr_array_push(opts); + opt->key = key; + return 1; + } + break; + case 'l': + /* [-o|--logarithmic] */ + if (strcmp(key, "logarithmic") == 0) { + rrd_opt_t *opt = apr_array_push(opts); + opt->key = key; + return 1; + } + /* [-z|--lazy] */ + if (strcmp(key, "lazy") == 0) { + rrd_opt_t *opt = apr_array_push(opts); + opt->key = key; + return 1; + } + break; + case 'n': + /* [-g|--no-legend] */ + if (strcmp(key, "no-legend") == 0) { + rrd_opt_t *opt = apr_array_push(opts); + opt->key = key; + return 1; + } + /* [-N|--no-gridfit] */ + if (strcmp(key, "no-gridfit") == 0) { + rrd_opt_t *opt = apr_array_push(opts); + opt->key = key; + return 1; + } + break; + case 'o': + /* [-j|--only-graph] */ + if (strcmp(key, "only-graph") == 0) { + rrd_opt_t *opt = apr_array_push(opts); + opt->key = key; + return 1; + } + break; + case 'p': + /* [-P|--pango-markup] */ + if (strcmp(key, "pango-markup") == 0) { + rrd_opt_t *opt = apr_array_push(opts); + opt->key = key; + return 1; + } + break; + case 'r': + /* [-r|--rigid] */ + if (strcmp(key, "rigid") == 0) { + rrd_opt_t *opt = apr_array_push(opts); + opt->key = key; + return 1; + } + break; + case 's': + /* [-E|--slope-mode] */ + if (strcmp(key, "slope-mode") == 0) { + rrd_opt_t *opt = apr_array_push(opts); + opt->key = key; + return 1; + } + break; + case 'u': + /* [-u|--upper-limit value] */ + if (strcmp(key, "upper-limit") == 0) { + rrd_opt_t *opt = apr_array_push(opts); + opt->key = key; + return 1; + } + /* [-Z|--use-nan-for-all-missing-data] */ + if (strcmp(key, "use-nan-for-all-missing-data") == 0) { + rrd_opt_t *opt = apr_array_push(opts); + opt->key = key; + return 1; + } + break; + } + + } + return 0; +} + +static int parse_query(request_rec *r, rrd_cmds_t **pcmds) +{ + rrd_conf *conf = ap_get_module_config(r->per_dir_config, + &rrd_module); + + char *arg, *args; + rrd_cmds_t *cmds = *pcmds = apr_pcalloc(r->pool, sizeof(rrd_cmds_t)); + int optnum = 0, cmdnum = 0; + + cmds->names = apr_hash_make(r->pool); + + /* count the query string */ + args = apr_pstrdup(r->pool, r->args); + while ((arg = apr_cstr_tokenize("&", &args))) { + if (apr_islower(arg[0])) { + optnum++; + } + else { + cmdnum++; + } + } + + cmds->opts = apr_array_make(r->pool, optnum + conf->options->nelts, sizeof(rrd_opt_t)); + cmds->cmds = apr_array_make(r->pool, cmdnum + conf->elements->nelts, sizeof(rrd_cmd_t)); + + /* pass the system wide options */ + apr_array_cat(cmds->opts, conf->options); + apr_array_cat(cmds->cmds, conf->elements); + + /* parse the query string */ + args = apr_pstrdup(r->pool, r->args); + while ((arg = apr_cstr_tokenize("&", &args))) { + const char *key, *val; + char *element; + + if (!arg[0]) { + continue; + } + + element = apr_pstrdup(r->pool, apr_punescape_url(r->pool, arg, NULL, NULL, 0)); + if (!element) { + log_message(r, APR_SUCCESS, + apr_psprintf(r->pool, + "The following element could not be unescaped: %s", arg), NULL); + return HTTP_BAD_REQUEST; + } + + if (parse_element(r->pool, element, NULL, cmds->cmds)) { + continue; + } + + /* try parse options that take the form of name value pairs */ + key = apr_cstr_tokenize("=", &element); + val = element; + + if (parse_option(r->pool, key, val, NULL, cmds->opts)) { + continue; + } + + /* else unrecognised option */ + log_message(r, APR_SUCCESS, + apr_psprintf(r->pool, + "Query was not recognised: %s", arg), NULL); + return HTTP_BAD_REQUEST; + } + + return OK; +} + +static const char *resolve_def_cb(ap_dir_match_t *w, const char *fname) +{ + rrd_cb_t *ctx = w->ctx; + request_rec *rr; + + rr = ap_sub_req_lookup_file(fname, ctx->r, NULL); + + if (rr->status == HTTP_OK) { + APR_ARRAY_PUSH(ctx->cmd->d.requests, request_rec *) = rr; + ctx->cmd->num++; + } + else { + ap_log_rerror( + APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, rr, "mod_rrd: Access to path returned %d, ignoring: %s", + rr->status, fname); + } + + return NULL; +} + +static int resolve_def(request_rec *r, rrd_cmd_t *cmd, rrd_cmds_t *cmds) +{ + ap_dir_match_t w; + rrd_cb_t ctx; + apr_pool_t *ptemp; + const char *last, *dirpath = r->filename; + apr_hash_index_t *hi, *hi2; + apr_hash_t *set; + + rrd_conf *conf = ap_get_module_config(r->per_dir_config, + &rrd_module); + + apr_pool_create(&ptemp, r->pool); + + /* process the wildcards */ + ctx.r = r; + ctx.cmd = cmd; + + w.prefix = "rrd path: "; + w.p = r->pool; + w.ptemp = ptemp; + w.flags = AP_DIR_FLAG_OPTIONAL | AP_DIR_FLAG_RECURSIVE; + w.cb = resolve_def_cb; + w.ctx = &ctx; + w.depth = 0; + + last = strrchr(r->filename, '/'); + if (last) { + dirpath = apr_pstrndup(ptemp, r->filename, last - r->filename); + } + + const char *err = ap_dir_fnmatch(&w, dirpath, cmd->d.path); + if (err) { + log_message(r, APR_SUCCESS, + apr_psprintf(r->pool, + "While parsing DEF '%s': %s", cmd->d.path, err), NULL); + return HTTP_BAD_REQUEST; + } + + /* process the environment lookup */ + set = apr_hash_make(ptemp); + for (hi = apr_hash_first(NULL, conf->env); hi; hi = apr_hash_next(hi)) { + const char *err = NULL, *key, *val; + ap_expr_info_t *eval; + void *v; + const void *k; + int j; + + apr_hash_this(hi, &k, NULL, &v); + key = k; + eval = v; + + for (j = 0; j < cmd->d.requests->nelts; ++j) { + request_rec *rr = APR_ARRAY_IDX(cmd->d.requests, j, request_rec *); + + val = ap_expr_str_exec(rr, eval, &err); + if (err) { + log_message(r, APR_SUCCESS, + apr_psprintf(r->pool, + "While evaluating an element expression: %s", err), NULL); + return HTTP_INTERNAL_SERVER_ERROR; + } + if (val && val[0]) { + apr_hash_set(set, val, APR_HASH_KEY_STRING, val); + } + + } + + if (apr_hash_count(set)) { + apr_array_header_t *arr = apr_array_make(ptemp, apr_hash_count(set), sizeof(const char *)); + for (hi2 = apr_hash_first(NULL, set); hi2; hi2 = apr_hash_next(hi2)) { + apr_hash_this(hi2, apr_array_push(arr), NULL, NULL); + } + apr_table_setn(r->subprocess_env, key, apr_array_pstrcat(r->pool, arr, ',')); + + } + apr_hash_clear(set); + } + + apr_pool_destroy(ptemp); + + cmd->def = cmd; + apr_hash_set(cmds->names, cmd->d.vname, APR_HASH_KEY_STRING, cmd); + + return OK; +} + +static int resolve_vdef(request_rec *r, rrd_cmd_t *cmd, rrd_cmds_t *cmds) +{ + cmd->v.ref = apr_hash_get(cmds->names, cmd->v.dsname, APR_HASH_KEY_STRING); + if (cmd->v.ref) { + cmd->def = cmd->v.ref->def; + } + + if (!cmd->v.ref) { + log_message(r, APR_SUCCESS, + apr_psprintf(r->pool, + "While parsing VDEF '%s': '%s' was not found", cmd->v.vname, cmd->v.dsname), NULL); + return HTTP_BAD_REQUEST; + } + else { + cmd->num = cmd->v.ref->num; + } + + apr_hash_set(cmds->names, cmd->v.vname, APR_HASH_KEY_STRING, cmd); + return OK; +} + +static int resolve_cdef(request_rec *r, rrd_cmd_t *cmd, rrd_cmds_t *cmds) +{ + int i; + + for (i = 0; i < cmd->c.rpns->nelts; ++i) { + rrd_rpn_t *rp = &((rrd_rpn_t *) cmd->c.rpns->elts)[i]; + + if (!cmd->c.ref) { + rrd_cmd_t *ref = apr_hash_get(cmds->names, rp->rpn, + APR_HASH_KEY_STRING); + if (ref) { + cmd->c.ref = ref; + rp->def = cmd->def = ref->def; + } + } + + } + if (cmd->c.ref) { + cmd->num = cmd->c.ref->num; + } + apr_hash_set(cmds->names, cmd->c.vname, APR_HASH_KEY_STRING, cmd); + return OK; +} + +static int resolve_area(request_rec *r, rrd_cmd_t *cmd, rrd_cmds_t *cmds) +{ + rrd_cmd_t *ref; + + ref = apr_hash_get(cmds->names, cmd->a.vname, APR_HASH_KEY_STRING); + if (ref) { + cmd->def = ref->def; + } + else { + log_message(r, APR_SUCCESS, + apr_psprintf(r->pool, + "While parsing AREA: '%s' was not found", cmd->a.vname), NULL); + return HTTP_BAD_REQUEST; + } + + return OK; +} + +static int resolve_line(request_rec *r, rrd_cmd_t *cmd, rrd_cmds_t *cmds) +{ + rrd_cmd_t *ref; + + ref = apr_hash_get(cmds->names, cmd->l.vname, APR_HASH_KEY_STRING); + if (ref) { + cmd->def = ref->def; + } + else { + log_message(r, APR_SUCCESS, + apr_psprintf(r->pool, + "While parsing LINE: '%s' was not found", cmd->l.vname), NULL); + return HTTP_BAD_REQUEST; + } + + return OK; +} + +static int resolve_tick(request_rec *r, rrd_cmd_t *cmd, rrd_cmds_t *cmds) +{ + rrd_cmd_t *ref; + + ref = apr_hash_get(cmds->names, cmd->t.vname, APR_HASH_KEY_STRING); + if (ref) { + cmd->def = ref->def; + } + else { + log_message(r, APR_SUCCESS, + apr_psprintf(r->pool, + "While parsing TICK: '%s' was not found", cmd->t.vname), NULL); + return HTTP_BAD_REQUEST; + } + + return OK; +} + +static int resolve_shift(request_rec *r, rrd_cmd_t *cmd, rrd_cmds_t *cmds) +{ + rrd_cmd_t *ref; + + ref = apr_hash_get(cmds->names, cmd->s.vname, APR_HASH_KEY_STRING); + if (ref) { + cmd->def = ref->def; + } + else { + log_message(r, APR_SUCCESS, + apr_psprintf(r->pool, + "While parsing SHIFT: '%s' was not found", cmd->s.vname), NULL); + return HTTP_BAD_REQUEST; + } + + return OK; +} + +static int resolve_gprint(request_rec *r, rrd_cmd_t *cmd, rrd_cmds_t *cmds) +{ + rrd_cmd_t *ref; + + ref = apr_hash_get(cmds->names, cmd->p.vname, APR_HASH_KEY_STRING); + if (ref) { + cmd->def = ref->def; + } + else { + log_message(r, APR_SUCCESS, + apr_psprintf(r->pool, + "While parsing GPRINT: '%s' was not found", cmd->p.vname), NULL); + return HTTP_BAD_REQUEST; + } + + return OK; +} + +static int resolve_print(request_rec *r, rrd_cmd_t *cmd, rrd_cmds_t *cmds) +{ + rrd_cmd_t *ref; + + ref = apr_hash_get(cmds->names, cmd->p.vname, APR_HASH_KEY_STRING); + if (ref) { + cmd->def = ref->def; + } + else { + log_message(r, APR_SUCCESS, + apr_psprintf(r->pool, + "While parsing PRINT: '%s' was not found", cmd->p.vname), NULL); + return HTTP_BAD_REQUEST; + } + + return OK; +} + +static int resolve_rrds(request_rec *r, rrd_cmds_t *cmds) +{ + rrd_cmd_t *cmd; + int i, ret; + + for (i = 0; i < cmds->cmds->nelts; ++i) { + + cmd = &((rrd_cmd_t *)cmds->cmds->elts)[i]; + + switch (cmd->type) { + case RRD_CONF_DEF: + + ret = resolve_def(r, cmd, cmds); + if (OK != ret) { + return ret; + } + + break; + case RRD_CONF_CDEF: + + ret = resolve_cdef(r, cmd, cmds); + if (OK != ret) { + return ret; + } + + break; + case RRD_CONF_VDEF: + + ret = resolve_vdef(r, cmd, cmds); + if (OK != ret) { + return ret; + } + + break; + case RRD_CONF_AREA: + + ret = resolve_area(r, cmd, cmds); + if (OK != ret) { + return ret; + } + + break; + case RRD_CONF_LINE: + + ret = resolve_line(r, cmd, cmds); + if (OK != ret) { + return ret; + } + + break; + case RRD_CONF_TICK: + + ret = resolve_tick(r, cmd, cmds); + if (OK != ret) { + return ret; + } + + break; + case RRD_CONF_SHIFT: + + ret = resolve_shift(r, cmd, cmds); + if (OK != ret) { + return ret; + } + + break; + case RRD_CONF_GPRINT: + + ret = resolve_gprint(r, cmd, cmds); + if (OK != ret) { + return ret; + } + + break; + case RRD_CONF_PRINT: + + ret = resolve_print(r, cmd, cmds); + if (OK != ret) { + return ret; + } + + break; + default: + break; + } + + } + + return OK; +} + +static int generate_element(request_rec *r, rrd_cmd_t *cmd, + apr_array_header_t *args) +{ + /* one result */ + const char *arg; + + const char *legend = cmd->e.legend; + if (cmd->e.elegend) { + const char *err = NULL; + legend = ap_expr_str_exec(r, cmd->e.elegend, &err); + if (err) { + log_message(r, APR_SUCCESS, + apr_psprintf(r->pool, + "While evaluating an element expression: %s", err), NULL); + return HTTP_INTERNAL_SERVER_ERROR; + } + legend = pescape_colon(r->pool, legend); + } + + arg = apr_psprintf(r->pool, "%s:%s", + cmd->e.element, legend); + APR_ARRAY_PUSH(args, const char *) = arg; + + return OK; +} + +static int generate_gprint(request_rec *r, rrd_cmd_t *cmd, apr_array_header_t *args) +{ + int j; + + /* no reference */ + if (!cmd->def) { + log_message(r, APR_SUCCESS, + apr_psprintf(r->pool, + "GPRINT element referred to '%s', which does not exist", + cmd->p.vname), NULL); + return HTTP_BAD_REQUEST; + } + + /* no results */ + else if (cmd->def->num == 0) { + /* output nothing */ + } + + /* one result */ + else if (cmd->def->num == 1) { + const char *arg = apr_psprintf(r->pool, "GPRINT:%s:%s", + cmd->p.vname, cmd->p.format); + APR_ARRAY_PUSH(args, const char *) = arg; + } + + /* more than one result */ + else { + /* handle each PRINT: line */ + for (j = 0; j < cmd->def->num; ++j) { + const char *arg = apr_psprintf(r->pool, "GPRINT:%sw%d:%s", + cmd->p.vname, j, cmd->p.format); + APR_ARRAY_PUSH(args, const char *) = arg; + } + } + + return OK; +} + +static int generate_print(request_rec *r, rrd_cmd_t *cmd, apr_array_header_t *args) +{ + int j; + + /* no reference */ + if (!cmd->def) { + log_message(r, APR_SUCCESS, + apr_psprintf(r->pool, + "PRINT element referred to '%s', which does not exist", + cmd->p.vname), NULL); + return HTTP_BAD_REQUEST; + } + + /* no results */ + else if (cmd->def->num == 0) { + /* output nothing */ + } + + /* one result */ + else if (cmd->def->num == 1) { + const char *arg = apr_psprintf(r->pool, "PRINT:%s:%s", + cmd->p.vname, cmd->p.format); + APR_ARRAY_PUSH(args, const char *) = arg; + } + + /* more than one result */ + else { + /* handle each PRINT: line */ + for (j = 0; j < cmd->def->num; ++j) { + const char *arg = apr_psprintf(r->pool, "PRINT:%sw%d:%s", + cmd->p.vname, j, cmd->p.format); + APR_ARRAY_PUSH(args, const char *) = arg; + } + } + + return OK; +} + +static int generate_shift(request_rec *r, rrd_cmd_t *cmd, apr_array_header_t *args) +{ + int j; + + /* no reference */ + if (!cmd->def) { + log_message(r, APR_SUCCESS, + apr_psprintf(r->pool, + "SHIFT element referred to '%s', which does not exist", + cmd->s.vname), NULL); + return HTTP_BAD_REQUEST; + } + + /* no results */ + else if (cmd->def->num == 0) { + /* output nothing */ + } + + /* one result */ + else if (cmd->def->num == 1) { + const char *arg = apr_psprintf(r->pool, "SHIFT:%s:%s", + cmd->s.vname, cmd->s.shift); + APR_ARRAY_PUSH(args, const char *) = arg; + } + + /* more than one result */ + else { + /* handle each LINE: line */ + for (j = 0; j < cmd->def->num; ++j) { + const char *arg = apr_psprintf(r->pool, "SHIFT:%sw%d:%s", + cmd->s.vname, j, cmd->s.shift); + APR_ARRAY_PUSH(args, const char *) = arg; + } + } + + return OK; +} + +static int generate_hrule(request_rec *r, rrd_cmd_t *cmd, + apr_array_header_t *args) +{ + /* one result */ + const char *arg; + + const char *legend = cmd->r.legend; + if (cmd->r.elegend) { + const char *err = NULL; + legend = ap_expr_str_exec(r, cmd->r.elegend, &err); + if (err) { + log_message(r, APR_SUCCESS, + apr_psprintf(r->pool, + "While evaluating an element expression: %s", err), NULL); + return HTTP_INTERNAL_SERVER_ERROR; + } + legend = pescape_colon(r->pool, legend); + } + + arg = apr_psprintf(r->pool, "HRULE:%s%s%s:%s%s%s", + cmd->r.val, + cmd->r.colour ? "#" : "", cmd->r.colour ? cmd->r.colour : "", + legend, + cmd->r.args[0] ? ":" : "", cmd->r.args); + APR_ARRAY_PUSH(args, const char *) = arg; + + return OK; +} + +static int generate_vrule(request_rec *r, rrd_cmd_t *cmd, + apr_array_header_t *args) +{ + /* one result */ + const char *arg; + + const char *legend = cmd->r.legend; + if (cmd->r.elegend) { + const char *err = NULL; + legend = ap_expr_str_exec(r, cmd->r.elegend, &err); + if (err) { + log_message(r, APR_SUCCESS, + apr_psprintf(r->pool, + "While evaluating an element expression: %s", err), NULL); + return HTTP_INTERNAL_SERVER_ERROR; + } + legend = pescape_colon(r->pool, legend); + } + + arg = apr_psprintf(r->pool, "VRULE:%s%s%s:%s%s%s", + cmd->r.val, + cmd->r.colour ? "#" : "", cmd->r.colour ? cmd->r.colour : "", + legend, + cmd->r.args[0] ? ":" : "", cmd->r.args); + APR_ARRAY_PUSH(args, const char *) = arg; + + return OK; +} + +static int generate_tick(request_rec *r, rrd_cmd_t *cmd, rrd_cmds_t *cmds, + apr_array_header_t *args, int *i) +{ + int j, k; + + /* no reference */ + if (!cmd->def) { + log_message(r, APR_SUCCESS, + apr_psprintf(r->pool, + "TICK element referred to '%s', which does not exist", + cmd->t.vname), NULL); + return HTTP_BAD_REQUEST; + } + + /* no results */ + else if (cmd->def->num == 0) { + /* output nothing */ + } + + /* one result */ + else if (cmd->def->num == 1) { + const char *arg; + request_rec *rr = ((request_rec **)cmd->def->d.requests->elts)[0]; + + const char *legend = cmd->t.legend; + if (cmd->t.elegend) { + const char *err = NULL; + legend = ap_expr_str_exec(rr, cmd->t.elegend, &err); + if (err) { + log_message(r, APR_SUCCESS, + apr_psprintf(r->pool, + "While evaluating an element expression: %s", err), NULL); + return HTTP_INTERNAL_SERVER_ERROR; + } + legend = pescape_colon(r->pool, legend); + } + arg = apr_psprintf(r->pool, "TICK:%s%s%s:%s:%s%s%s", + cmd->t.vname, + cmd->t.colour ? "#" : "", cmd->t.colour ? cmd->t.colour : "", + cmd->t.fraction, + legend, + cmd->t.args[0] ? ":" : "", cmd->t.args); + APR_ARRAY_PUSH(args, const char *) = arg; + } + + /* more than one result */ + else { + int skip = 0; + + /* handle each TICK: line */ + for (j = 0; j < cmd->def->num; ++j) { + const char *arg; + request_rec *rr = ((request_rec **)cmd->def->d.requests->elts)[j]; + + const char *legend = cmd->t.legend; + if (cmd->t.elegend) { + const char *err = NULL; + legend = ap_expr_str_exec(rr, cmd->t.elegend, &err); + if (err) { + log_message(r, APR_SUCCESS, + apr_psprintf(r->pool, + "While evaluating an element expression: %s", err), NULL); + return HTTP_INTERNAL_SERVER_ERROR; + } + legend = pescape_colon(r->pool, legend); + } + + arg = apr_psprintf(r->pool, "TICK:%sw%d%s%s:%s:%s%s%s", + cmd->t.vname, j, + cmd->t.colour ? "#" : "", cmd->t.colour ? cmd->t.colour : "", + cmd->t.fraction, + legend, + cmd->t.args[0] ? ":" : "", cmd->t.args); + APR_ARRAY_PUSH(args, const char *) = arg; + + for (k = *i + 1; k < cmds->cmds->nelts; ++k) { + rrd_cmd_t *pcmd = &((rrd_cmd_t *)cmds->cmds->elts)[k]; + if (pcmd->def == cmd->def) { + switch (pcmd->type) { + case RRD_CONF_PRINT: + + APR_ARRAY_PUSH(args, const char *) = + apr_psprintf(r->pool, "PRINT:%sw%d:%s", + pcmd->p.vname, j, pcmd->p.format); + + break; + case RRD_CONF_GPRINT: + + APR_ARRAY_PUSH(args, const char *) = + apr_psprintf(r->pool, "GPRINT:%sw%d:%s", + pcmd->p.vname, j, pcmd->p.format); + + break; + default: + /* skip the print/grint */ + skip = k - *i - 1; + /* jump out of the loop */ + k = cmds->cmds->nelts; + } + } + else { + /* skip the print/grint */ + skip = k - *i - 1; + /* jump out of the loop */ + k = cmds->cmds->nelts; + } + } + + } + *i += skip; + } + + return OK; +} + +static int generate_area(request_rec *r, rrd_cmd_t *cmd, rrd_cmds_t *cmds, + apr_array_header_t *args, int *i) +{ + int j, k; + + /* no reference */ + if (!cmd->def) { + log_message(r, APR_SUCCESS, + apr_psprintf(r->pool, + "AREA element referred to '%s', which does not exist", + cmd->a.vname), NULL); + return HTTP_BAD_REQUEST; + } + + /* no results */ + else if (cmd->def->num == 0) { + /* output nothing */ + } + + /* one result */ + else if (cmd->def->num == 1) { + const char *arg; + request_rec *rr = ((request_rec **)cmd->def->d.requests->elts)[0]; + + const char *legend = cmd->a.legend; + if (cmd->a.elegend) { + const char *err = NULL; + legend = ap_expr_str_exec(rr, cmd->a.elegend, &err); + if (err) { + log_message(r, APR_SUCCESS, + apr_psprintf(r->pool, + "While evaluating an element expression: %s", err), NULL); + return HTTP_INTERNAL_SERVER_ERROR; + } + legend = pescape_colon(r->pool, legend); + } + + arg = apr_psprintf(r->pool, "AREA:%s%s%s:%s%s%s", + cmd->a.vname, + cmd->a.colour ? "#" : "", cmd->a.colour ? cmd->a.colour : "", + legend, + cmd->a.args[0] ? ":" : "", cmd->a.args); + APR_ARRAY_PUSH(args, const char *) = arg; + } + + /* more than one result */ + else { + int skip = 0; + + /* handle each AREA: line */ + for (j = 0; j < cmd->def->num; ++j) { + const char *arg; + request_rec *rr = ((request_rec **)cmd->def->d.requests->elts)[j]; + + const char *legend = cmd->a.legend; + if (cmd->a.elegend) { + const char *err = NULL; + legend = ap_expr_str_exec(rr, cmd->a.elegend, &err); + if (err) { + log_message(r, APR_SUCCESS, + apr_psprintf(r->pool, + "While evaluating an element expression: %s", err), NULL); + return HTTP_INTERNAL_SERVER_ERROR; + } + legend = pescape_colon(r->pool, legend); + } + + arg = apr_psprintf(r->pool, "AREA:%sw%d%s%s:%s%s%s", + cmd->a.vname, j, + cmd->a.colour ? "#" : "", cmd->a.colour ? cmd->a.colour : "", + legend, + cmd->a.args[0] ? ":" : "", cmd->a.args); + APR_ARRAY_PUSH(args, const char *) = arg; + + for (k = *i + 1; k < cmds->cmds->nelts; ++k) { + rrd_cmd_t *pcmd = &((rrd_cmd_t *)cmds->cmds->elts)[k]; + if (pcmd->def == cmd->def) { + switch (pcmd->type) { + case RRD_CONF_PRINT: + + APR_ARRAY_PUSH(args, const char *) = + apr_psprintf(r->pool, "PRINT:%sw%d:%s", + pcmd->p.vname, j, pcmd->p.format); + + break; + case RRD_CONF_GPRINT: + + APR_ARRAY_PUSH(args, const char *) = + apr_psprintf(r->pool, "GPRINT:%sw%d:%s", + pcmd->p.vname, j, pcmd->p.format); + + break; + default: + /* skip the print/grint */ + skip = k - *i - 1; + /* jump out of the loop */ + k = cmds->cmds->nelts; + } + } + else { + /* skip the print/grint */ + skip = k - *i - 1; + /* jump out of the loop */ + k = cmds->cmds->nelts; + } + } + + } + *i += skip; + } + + return OK; +} + +static int generate_line(request_rec *r, rrd_cmd_t *cmd, rrd_cmds_t *cmds, + apr_array_header_t *args, int *i) +{ + int j, k; + + /* no reference */ + if (!cmd->def) { + log_message(r, APR_SUCCESS, + apr_psprintf(r->pool, + "LINE element referred to '%s', which does not exist", + cmd->l.vname), NULL); + return HTTP_BAD_REQUEST; + } + + /* no results */ + else if (cmd->def->num == 0) { + /* output nothing */ + } + + /* one result */ + else if (cmd->def->num == 1) { + const char *arg; + request_rec *rr = ((request_rec **)cmd->def->d.requests->elts)[0]; + + const char *legend = cmd->l.legend; + if (cmd->l.elegend) { + const char *err = NULL; + legend = ap_expr_str_exec(rr, cmd->l.elegend, &err); + if (err) { + log_message(r, APR_SUCCESS, + apr_psprintf(r->pool, + "While evaluating an element expression: %s", err), NULL); + return HTTP_INTERNAL_SERVER_ERROR; + } + legend = pescape_colon(r->pool, legend); + } + arg = apr_psprintf(r->pool, "%s:%s%s%s:%s%s%s", + cmd->l.line, cmd->l.vname, + cmd->l.colour ? "#" : "", cmd->l.colour ? cmd->l.colour : "", + legend, + cmd->l.args[0] ? ":" : "", cmd->l.args); + APR_ARRAY_PUSH(args, const char *) = arg; + } + + /* more than one result */ + else { + int skip = 0; + + /* handle each LINE: line */ + for (j = 0; j < cmd->def->num; ++j) { + const char *arg; + request_rec *rr = ((request_rec **)cmd->def->d.requests->elts)[j]; + + const char *legend = cmd->l.legend; + if (cmd->l.elegend) { + const char *err = NULL; + legend = ap_expr_str_exec(rr, cmd->l.elegend, &err); + if (err) { + log_message(r, APR_SUCCESS, + apr_psprintf(r->pool, + "While evaluating an element expression: %s", err), NULL); + return HTTP_INTERNAL_SERVER_ERROR; + } + legend = pescape_colon(r->pool, legend); + } + + arg = apr_psprintf(r->pool, "%s:%sw%d%s%s:%s%s%s", + cmd->l.line, cmd->l.vname, j, + cmd->l.colour ? "#" : "", cmd->l.colour ? cmd->l.colour : "", + legend, + cmd->l.args[0] ? ":" : "", cmd->l.args); + APR_ARRAY_PUSH(args, const char *) = arg; + + for (k = *i + 1; k < cmds->cmds->nelts; ++k) { + rrd_cmd_t *pcmd = &((rrd_cmd_t *)cmds->cmds->elts)[k]; + if (pcmd->def == cmd->def) { + switch (pcmd->type) { + case RRD_CONF_PRINT: + + APR_ARRAY_PUSH(args, const char *) = + apr_psprintf(r->pool, "PRINT:%sw%d:%s", + pcmd->p.vname, j, pcmd->p.format); + + break; + case RRD_CONF_GPRINT: + + APR_ARRAY_PUSH(args, const char *) = + apr_psprintf(r->pool, "GPRINT:%sw%d:%s", + pcmd->p.vname, j, pcmd->p.format); + + break; + default: + /* skip the print/grint */ + skip = k - *i - 1; + /* jump out of the loop */ + k = cmds->cmds->nelts; + } + } + else { + /* skip the print/grint */ + skip = k - *i - 1; + /* jump out of the loop */ + k = cmds->cmds->nelts; + } + } + + } + *i += skip; + } + + return OK; +} + +static int generate_vdef(request_rec *r, rrd_cmd_t *cmd, apr_array_header_t *args) +{ + int j; + + /* no reference */ + if (!cmd->def) { + log_message(r, APR_SUCCESS, + apr_psprintf(r->pool, + "VDEF element referred to '%s', which does not exist", + cmd->v.vname), NULL); + return HTTP_BAD_REQUEST; + } + + /* no results */ + else if (cmd->def->num == 0) { + /* output nothing */ + } + + /* one result */ + else if (cmd->def->num == 1) { + APR_ARRAY_PUSH(args, const char *) = + apr_pstrcat(r->pool, "VDEF:", cmd->v.vname, "=", + cmd->v.dsname, ",", cmd->v.rpn, NULL); + } + + /* more than one result */ + else { + /* handle each VDEF: line */ + for (j = 0; j < cmd->def->num; ++j) { + const char *arg = apr_psprintf(r->pool, "VDEF:%sw%d=%sw%d,%s", cmd->v.vname, + j, cmd->v.dsname, j, cmd->v.rpn); + APR_ARRAY_PUSH(args, const char *) = arg; + } + } + + return OK; +} + +static int generate_cdef(request_rec *r, rrd_cmd_t *cmd, apr_array_header_t *args) +{ + int j, k; + + /* no reference */ + if (!cmd->def) { + log_message(r, APR_SUCCESS, + apr_psprintf(r->pool, + "CDEF element '%s' referred to no existing definitions", + cmd->c.vname), NULL); + return HTTP_BAD_REQUEST; + } + + /* no results */ + else if (cmd->def->num == 0) { + /* output nothing */ + } + + /* one result */ + else if (cmd->def->num == 1) { + APR_ARRAY_PUSH(args, const char *) = + apr_pstrcat(r->pool, "CDEF:", cmd->c.vname, "=", + cmd->c.rpn, NULL); + } + + /* more than one result */ + else { + /* handle each CDEF: line */ + for (j = 0; j < cmd->num; ++j) { + char *cdef; + int len; + + /* first pass - work out the length */ + len = apr_snprintf(NULL, 0, "CDEF:%sw%d=", cmd->c.vname, j); + for (k = 0; k < cmd->c.rpns->nelts; ++k) { + rrd_rpn_t *rp = (((rrd_rpn_t *)cmd->c.rpns->elts) + k); + if (k) { + len++; + } + if (!rp->def || rp->def->num < 2) { + len += apr_snprintf(NULL, 0, "%s", rp->rpn); + } + else { + len += apr_snprintf(NULL, 0, "%sw%d", rp->rpn, j); + } + } + + /* second pass, write the cdef */ + cdef = apr_palloc(r->pool, len + 1); + APR_ARRAY_PUSH(args, const char *) = cdef; + cdef += apr_snprintf(cdef, len, "CDEF:%sw%d=", cmd->c.vname, j); + for (k = 0; k < cmd->c.rpns->nelts; ++k) { + rrd_rpn_t *rp = (((rrd_rpn_t *)cmd->c.rpns->elts) + k); + if (k) { + *cdef++ = ','; + } + if (!rp->def || rp->def->num < 2) { + cdef += apr_snprintf(cdef, len, "%s", rp->rpn); + } + else { + cdef += apr_snprintf(cdef, len, "%sw%d", rp->rpn, j); + } + } + } + } + + return OK; +} + +static int generate_def(request_rec *r, rrd_cmd_t *cmd, apr_array_header_t *args) +{ + int j; + + /* safety check - reject anything trying to set the daemon */ + if (ap_strstr_c(cmd->d.cf, ":daemon=")) { + log_message(r, APR_SUCCESS, + "DEF elements must not contain a 'daemon' parameter", NULL); + return HTTP_BAD_REQUEST; + } + + /* no results */ + if (cmd->d.requests->nelts == 0) { + /* output nothing */ + } + + /* one result */ + else if (cmd->d.requests->nelts == 1) { + request_rec *rr = APR_ARRAY_IDX(cmd->d.requests, 0, request_rec *); + const char *arg = apr_psprintf(r->pool, "DEF:%s=%s:%s:%s", cmd->d.vname, + rr->filename, cmd->d.dsname, cmd->d.cf); + APR_ARRAY_PUSH(args, const char *) = arg; + } + + /* more than one result */ + else { + char *cdef; + int len = apr_snprintf(NULL, 0, "CDEF:%s=", cmd->d.vname); + + /* handle each DEF: line */ + for (j = 0; j < cmd->d.requests->nelts; ++j) { + request_rec *rr = APR_ARRAY_IDX(cmd->d.requests, j, request_rec *); + const char *arg = apr_psprintf(r->pool, "DEF:%sw%d=%s:%s:%s", cmd->d.vname, + j, rr->filename, cmd->d.dsname, cmd->d.cf); + APR_ARRAY_PUSH(args, const char *) = arg; + len += apr_snprintf(NULL, 0, "%s%sw%d%s", j ? "," : "", cmd->d.vname, j, j ? ",+" : ""); + } + + /* calculate the CDEF summary line */ + cdef = apr_palloc(r->pool, len + 1); + APR_ARRAY_PUSH(args, const char *) = cdef; + cdef += apr_snprintf(cdef, len, "CDEF:%s=", cmd->d.vname); + for (j = 0; j < cmd->d.requests->nelts; ++j) { + cdef += apr_snprintf(cdef, len, "%s%sw%d%s", j ? "," : "", cmd->d.vname, j, j ? ",+" : ""); + } + + } + + return OK; +} + +static int generate_args(request_rec *r, rrd_cmds_t *cmds, apr_array_header_t **pargs) +{ + apr_array_header_t *args; + rrd_cmd_t *cmd; + rrd_opt_t *opt; + int i, num = 4, ret = OK; + + /* count the options */ + for (i = 0; i < cmds->opts->nelts; ++i) { + + opt = &((rrd_opt_t *)cmds->opts->elts)[i]; + + if (opt->val) { + num++; + } + + num++; + } + /* count the number of elements we need */ + for (i = 0; i < cmds->cmds->nelts; ++i) { + + cmd = &APR_ARRAY_IDX(cmds->cmds, i, rrd_cmd_t); + + if (cmd->def) { + num += cmd->def->d.requests->nelts; + } + + num++; + } + + /* create arguments of the correct size */ + args = *pargs = apr_array_make(r->pool, num, sizeof(const char *)); + + /* the argv array */ + APR_ARRAY_PUSH(args, const char *) = "rrdgraph"; + APR_ARRAY_PUSH(args, const char *) = "-"; + APR_ARRAY_PUSH(args, const char *) = "--imgformat"; + APR_ARRAY_PUSH(args, const char *) = parse_rrdgraph_suffix(r); + + /* first create the options */ + for (i = 0; i < cmds->opts->nelts; ++i) { + + opt = &((rrd_opt_t *)cmds->opts->elts)[i]; + + APR_ARRAY_PUSH(args, const char *) = + apr_pstrcat(r->pool, "--", opt->key, NULL); + if (opt->eval) { + const char *err = NULL; + + APR_ARRAY_PUSH(args, const char *) = ap_expr_str_exec(r, opt->eval, &err); + if (err) { + log_message(r, APR_SUCCESS, + apr_psprintf(r->pool, + "While evaluating expressions for '%s': %s", opt->key, err), NULL); + return HTTP_INTERNAL_SERVER_ERROR; + } + + } + else if (opt->val) { + APR_ARRAY_PUSH(args, const char *) = opt->val; + } + + } + + /* and finally create the elements */ + for (i = 0; i < cmds->cmds->nelts; ++i) { + + cmd = &((rrd_cmd_t *)cmds->cmds->elts)[i]; + + switch (cmd->type) { + case RRD_CONF_DEF: + + ret = generate_def(r, cmd, args); + + break; + case RRD_CONF_CDEF: + + ret = generate_cdef(r, cmd, args); + + break; + case RRD_CONF_VDEF: + + ret = generate_vdef(r, cmd, args); + + break; + case RRD_CONF_LINE: + + ret = generate_line(r, cmd, cmds, args, &i); + + break; + case RRD_CONF_AREA: + + ret = generate_area(r, cmd, cmds, args, &i); + + break; + case RRD_CONF_TICK: + + ret = generate_tick(r, cmd, cmds, args, &i); + + break; + case RRD_CONF_SHIFT: + + ret = generate_shift(r, cmd, args); + + break; + case RRD_CONF_PRINT: + + ret = generate_print(r, cmd, args); + + break; + case RRD_CONF_GPRINT: + + ret = generate_gprint(r, cmd, args); + + break; + case RRD_CONF_HRULE: + + ret = generate_hrule(r, cmd, args); + + break; + case RRD_CONF_VRULE: + + ret = generate_vrule(r, cmd, args); + + break; + case RRD_CONF_COMMENT: + case RRD_CONF_TEXTALIGN: + + ret = generate_element(r, cmd, args); + + break; + } + + if (OK != ret) { + return ret; + } + + } + + for (i = 0; i < args->nelts; ++i) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, "mod_rrd: rrdgraph:%d: %s", + i, ((const char **) args->elts)[i]); + } + + return OK; +} + +static int cleanup_args(request_rec *r, rrd_cmds_t *cmds) +{ + rrd_cmd_t *cmd; + int i; + + for (i = 0; i < cmds->cmds->nelts; ++i) { + request_rec **rr; + + cmd = &APR_ARRAY_IDX(cmds->cmds, i, rrd_cmd_t); + + /* free all the saved requests */ + if (RRD_CONF_DEF == cmd->type && cmd->d.requests) { + while ((rr = apr_array_pop(cmd->d.requests))) { + apr_pool_destroy((*rr)->pool); + } + } + + } + + return OK; +} + +static int get_rrdgraph(request_rec *r) +{ + rrd_info_t *grinfo = NULL; + apr_array_header_t *args; + apr_bucket_brigade *bb = apr_brigade_create(r->pool, + r->connection->bucket_alloc); + rrd_cmds_t *cmds; + + apr_status_t rv; + int ret; + + /* pull apart the query string, reject unrecognised options */ + ret = parse_query(r, &cmds); + if (OK != ret) { + return ret; + } + + /* resolve permissions and wildcards of rrd files */ + ret = resolve_rrds(r, cmds); + if (OK != ret) { + return ret; + } + + /* create the args string for rrd_graph */ + ret = generate_args(r, cmds, &args); + if (OK != ret) { + return ret; + } + + /* rrd_graph_v is not thread safe */ +#if APR_HAS_THREADS + if (rrd_mutex) { + apr_thread_mutex_lock(rrd_mutex); + } +#endif + + /* we're ready, let's generate the graph */ + grinfo = rrd_graph_v(args->nelts, (char **)args->elts); + if (grinfo == NULL) { + log_message(r, APR_SUCCESS, "Call to rrd_graph_v failed", rrd_get_error()); + ret = HTTP_INTERNAL_SERVER_ERROR; + } + else { + /* grab the image data from the results */ + while (grinfo) { + if (strcmp(grinfo->key, "image") == 0) { + apr_brigade_write(bb, NULL, NULL, (const char *)grinfo->value.u_blo.ptr, + grinfo->value.u_blo.size); + break; + } + /* skip anything else */ + grinfo = grinfo->next; + } + rrd_info_free(grinfo); + } + rrd_clear_error(); + +#if APR_HAS_THREADS + if (rrd_mutex) { + apr_thread_mutex_unlock(rrd_mutex); + } +#endif + + /* trigger an early cleanup to save memory */ + ret = cleanup_args(r, cmds); + if (OK != ret) { + return ret; + } + + /* send our response down the stack */ + if (OK == ret) { + rv = ap_pass_brigade(r->output_filters, bb); + if (rv == APR_SUCCESS || r->status != HTTP_OK + || r->connection->aborted) { + return OK; + } + else { + /* no way to know what type of error occurred */ + ap_log_rerror( + APLOG_MARK, APLOG_DEBUG, rv, r, "rrd_handler: ap_pass_brigade returned %i", rv); + return HTTP_INTERNAL_SERVER_ERROR; + } + } + + return ret; +} + +static int get_rrd(request_rec *r) +{ + /* + * if a file does not exist, assume it is a request for a graph, otherwise + * go with the original file. + */ + if (r->filename && r->finfo.filetype == APR_NOFILE && parse_rrdgraph_suffix(r)) { + return get_rrdgraph(r); + } + + return DECLINED; +} + +static int rrd_handler(request_rec *r) +{ + + rrd_conf *conf = ap_get_module_config(r->per_dir_config, + &rrd_module); + + if (!conf || !conf->graph) { + return DECLINED; + } + + /* A GET should return the CRL, OPTIONS should return the WADL */ + ap_allow_methods(r, 1, "GET", "OPTIONS", NULL); + if (!strcmp(r->method, "GET")) { + return get_rrd(r); + } + else if (!strcmp(r->method, "OPTIONS")) { + return options_wadl(r, conf); + } + else { + return HTTP_METHOD_NOT_ALLOWED; + } + +} + +static void rrd_child_init(apr_pool_t *pchild, server_rec *s) +{ +#if APR_HAS_THREADS + int threaded_mpm; + if (ap_mpm_query(AP_MPMQ_IS_THREADED, &threaded_mpm) == APR_SUCCESS + && threaded_mpm) + { + apr_thread_mutex_create(&rrd_mutex, APR_THREAD_MUTEX_DEFAULT, pchild); + } +#endif +} + +static void *create_rrd_config(apr_pool_t *p, char *dummy) +{ + rrd_conf *new = (rrd_conf *) apr_pcalloc(p, sizeof(rrd_conf)); + + new->options = apr_array_make(p, 10, sizeof(rrd_opt_t)); + new->elements = apr_array_make(p, 10, sizeof(rrd_cmd_t)); + new->env = apr_hash_make(p); + + return (void *) new; +} + +static void *merge_rrd_config(apr_pool_t *p, void *basev, void *addv) +{ + rrd_conf *new = (rrd_conf *) apr_pcalloc(p, sizeof(rrd_conf)); + rrd_conf *add = (rrd_conf *) addv; + rrd_conf *base = (rrd_conf *) basev; + + new->options = apr_array_append(p, add->options, base->options); + new->elements = apr_array_append(p, add->elements, base->elements); + new->env = apr_hash_overlay(p, add->env, base->env); + + new->location = (add->location_set == 0) ? base->location : add->location; + new->location_set = add->location_set || base->location_set; + + new->graph = (add->graph_set == 0) ? base->graph : add->graph; + new->graph_set = add->graph_set || base->graph_set; + + return new; +} + +static const char *set_rrd_graph_option(cmd_parms *cmd, void *dconf, const char *key, const char *val) +{ + rrd_conf *conf = dconf; + ap_expr_info_t *eval = NULL; + const char *expr_err = NULL; + + if (val) { + + eval = ap_expr_parse_cmd(cmd, val, AP_EXPR_FLAG_STRING_RESULT, + &expr_err, NULL); + + if (expr_err) { + return apr_pstrcat(cmd->temp_pool, + "Cannot parse expression '", val, "': ", + expr_err, NULL); + } + + } + + if (!parse_option(cmd->pool, key, val, eval, conf->options)) { + return apr_pstrcat(cmd->pool, "Could not recognise option: ", key, NULL); + } + + return NULL; +} + +static const char *set_rrd_graph_element(cmd_parms *cmd, void *dconf, + const char *element, const char *legend) +{ + rrd_conf *conf = dconf; + ap_expr_info_t *elegend = NULL; + const char *expr_err = NULL; + + if (legend) { + + elegend = ap_expr_parse_cmd(cmd, legend, AP_EXPR_FLAG_STRING_RESULT, + &expr_err, NULL); + + if (expr_err) { + return apr_pstrcat(cmd->temp_pool, + "Cannot parse expression '", legend, "': ", + expr_err, NULL); + } + + } + + if (!parse_element(cmd->pool, element, elegend, conf->elements)) { + return apr_psprintf(cmd->pool, + "RRDGraphElement was not recognised: %s", element); + } + + return NULL; +} + +static const char *set_rrd_graph_env(cmd_parms *cmd, void *dconf, +const char *key, const char *val) +{ + rrd_conf *conf = dconf; + ap_expr_info_t *eval; + const char *expr_err = NULL; + + eval = ap_expr_parse_cmd(cmd, val, AP_EXPR_FLAG_STRING_RESULT, + &expr_err, NULL); + + if (expr_err) { + return apr_pstrcat(cmd->temp_pool, + "Cannot parse expression '", val, "': ", + expr_err, NULL); + } + + apr_hash_set(conf->env, key, APR_HASH_KEY_STRING, eval); + + return NULL; +} + +static const char *set_rrd_graph(cmd_parms *cmd, void *dconf, int flag) +{ + rrd_conf *conf = dconf; + + conf->graph = flag; + conf->graph_set = 1; + + return NULL; +} + +static const command_rec rrd_cmds[] = { + AP_INIT_FLAG("RRDGraph", set_rrd_graph, NULL, RSRC_CONF | ACCESS_CONF, + "Enable the rrdgraph image generator."), + AP_INIT_TAKE12("RRDGraphOption", set_rrd_graph_option, NULL, RSRC_CONF | ACCESS_CONF, + "Options for the rrdgraph image generator."), + AP_INIT_TAKE12("RRDGraphElement", set_rrd_graph_element, NULL, RSRC_CONF | ACCESS_CONF, + "Elements for the rrdgraph image generator. If specified, an optional expression can be set for the legend where appropriate."), + AP_INIT_TAKE2("RRDGraphEnv", set_rrd_graph_env, NULL, RSRC_CONF | ACCESS_CONF, + "Summarise environment variables from the RRD file requests."), { NULL } +}; + +static void register_hooks(apr_pool_t *p) +{ + ap_hook_child_init(rrd_child_init,NULL,NULL,APR_HOOK_MIDDLE); + ap_hook_handler(rrd_handler, NULL, NULL, APR_HOOK_MIDDLE); +} + +AP_DECLARE_MODULE(rrd) = { + STANDARD20_MODULE_STUFF, + create_rrd_config, /* create per-directory config structure */ + merge_rrd_config, /* merge per-directory config structures */ + NULL, /* create per-server config structure */ + NULL, /* merge per-server config structures */ + rrd_cmds, /* command apr_table_t */ + register_hooks /* register hooks */ +}; diff --git a/mod_rrd.spec.in b/mod_rrd.spec.in new file mode 100644 index 0000000..4d52a0c --- /dev/null +++ b/mod_rrd.spec.in @@ -0,0 +1,35 @@ +# RPM Spec file for mod_rrd + +Name: mod_rrd +Version: @PACKAGE_VERSION@ +Release: 1%{?dist} +Summary: Apache httpd rrd filter module +License: ASL 2.0 +Group: System Environment/Daemons +Source: https://github.com/minfrin/%{name}/releases/download/%{name}-%{version}/%{name}-%{version}.tar.bz2 +Url: https://github.com/minfrin/%{name} +BuildRequires: gcc, pkgconfig(apr-1), pkgconfig(apr-util-1), httpd-devel +Requires: httpd + +%description +The Apache mod_rrd module provides a set of filters and handlers +to manipulate and display RRD graphs. + +%prep +%setup -q +%build +%configure +make %{?_smp_mflags} + +%install +%make_install + +%files +%{_libdir}/httpd/modules/mod_rrd.so + +%doc AUTHORS ChangeLog README.md + +%changelog +* Tue Oct 22 2019 Graham Leggett - 1.0.0-1 +- Initial version of the package +