Commit fb0d4a74 authored by Geoff Simmons's avatar Geoff Simmons

Initial commit, passes a first test for reader.get().

parents
Pipeline #458 canceled with stages
Makefile
Makefile.in
.deps/
.libs/
*.o
*.lo
*.la
*.gcda
*.gcno
*~
*.[1-9]
.dirstamp
/aclocal.m4
/ar-lib
/autom4te.cache/
/compile
/config.guess
/config.h
/config.h.in
/config.log
/config.status
/config.sub
/configure
/depcomp
/install-sh
/libtool
/ltmain.sh
/missing
/stamp-h1
/test-driver
/m4/
/src/vcc_if.c
/src/vcc_if.h
/src/vmod_*rst
/src/tests/*.log
/src/tests/*.trs
/src/test-suite.log
/src/coverage
CONTRIBUTING
============
To contribute code or documentation, submit a pull request at the
`source repository website
<https://code.uplex.de/uplex-varnish/libvmod-file>`_.
If you have a problem or discover a bug, you can post an `issue
<https://code.uplex.de/uplex-varnish/libvmod-file/issues>`_ at
the website. You can also write to <varnish-support@uplex.de>.
For developers
--------------
The VMOD source code is in C, and compilation has been tested with gcc
and clang. The code MUST always compile successfully with both of
them.
The build specifies C99 conformance for C sources (``-std=c99``). All
compiler warnings are turned on, and all warnings are considered
errors (``-Werror -Wall -Wextra``). The code MUST always build
without warnings or errors under these constraints.
By default, ``CFLAGS`` is set to ``-g -O2``, so that symbols are
included in the shared library, and optimization is at level
``O2``. To change or disable these options, set ``CFLAGS`` explicitly
before calling ``configure`` (it may be set to the empty string).
For development/debugging cycles, the ``configure`` option
``--enable-debugging`` is recommended (off by default). This will turn
off optimizations and function inlining, so that a debugger will step
through the code as expected.
Experience has shown that adding ``-ggdb3`` to ``CFLAGS`` is
beneficial if you need to examine the VMOD with the gdb debugger. The
shared object for a VMOD is loaded from a directory relative to the
Varnish home directory (by default ``/usr/local/var/$INSTANCE`` for
development builds). A debugger needs to locate the shared object from
that relative path to load its symbols, so the Varnish home directory
should be the current working directory when the debugger is run. For
example::
# To run gdb and examine a coredump
$ cd /usr/local/var/myinstance
$ gdb /usr/local/sbin/varnishd /path/to/coredump
By default, the VMOD is built with the stack protector enabled
(compile option ``-fstack-protector``), but it can be disabled with
the ``configure`` option ``--disable-stack-protector``.
See LICENSE for details.
INSTALLATION
============
Building from source
~~~~~~~~~~~~~~~~~~~~
The VMOD is built against a Varnish installation, and the autotools
use ``pkg-config(1)`` to locate the necessary header files and other
resources. This sequence will install the VMOD::
> ./autogen.sh # for builds from the git repo
> ./configure
> make
> make check # to run unit tests in src/tests/*.vtc
> make distcheck # run check and prepare a distribution tarball
> sudo make install
See `CONTRIBUTING.rst <CONTRIBUTING.rst>`_ for notes about building
from source.
If you have installed Varnish in non-standard directories, call
``autogen.sh`` and ``configure`` with the ``PKG_CONFIG_PATH``
environment variable set to include the paths where the ``.pc`` file
can be located for ``varnishapi``. For example, when varnishd
configure was called with ``--prefix=$PREFIX``, use::
> PKG_CONFIG_PATH=${PREFIX}/lib/pkgconfig
> export PKG_CONFIG_PATH
By default, the vmod ``configure`` script installs the vmod in
the same directory as Varnish, determined via ``pkg-config(1)``. The
vmod installation directory can be overridden by passing the
``VMOD_DIR`` variable to ``configure``.
Other files such as the man-page are installed in the locations
determined by ``configure``, which inherits its default ``--prefix``
setting from Varnish.
Copyright (c) 2018 UPLEX Nils Goroll Systemoptimierung
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
ACLOCAL_AMFLAGS = -I m4 -I ${VARNISHAPI_DATAROOTDIR}/aclocal
SUBDIRS = src
DISTCHECK_CONFIGURE_FLAGS = \
VMOD_DIR='$${libdir}/varnish/vmods'
coverage:
$(MAKE) $(AM_MAKEFLAGS) -C src coverage
EXTRA_DIST = README.rst LICENSE COPYING CONTRIBUTING.rst INSTALL.rst
doc_DATA = README.rst LICENSE COPYING CONTRIBUTING.rst INSTALL.rst
README.rst: src/vmod_file.vcc
$(MAKE) $(AM_MAKEFLAGS) -C src vmod_file.man.rst
cp src/vmod_file.man.rst README.rst
%.1 %.2 %.3 %.4 %.5 %.6 %.7 %.8 %.9:
if HAVE_RST2MAN
${RST2MAN} $< $@
else
@echo "========================================"
@echo "You need rst2man installed to make dist"
@echo "========================================"
@false
endif
..
.. NB: This file is machine generated, DO NOT EDIT!
..
.. Edit vmod.vcc and run make instead
..
.. role:: ref(emphasis)
=========
VMOD file
=========
-----------------------------------------------------------------
Varnish Module for reading files that may be updated at intervals
-----------------------------------------------------------------
:Manual section: 3
SYNOPSIS
========
::
import file;
# VMOD version
STRING file.version()
DESCRIPTION
===========
VMOD file is a Varnish module for reading the contents of a file and
caching its contents, returning the contents for use in the Varnish
Configuration Language (VCL), and checking if the file has changed
after specified time intervals elapse. If the file has changed, then
the new contents are read and cached, and are then available in VCL.
XXX ...
.. _file.reader():
new xreader = file.reader(STRING name, DURATION ttl=120)
--------------------------------------------------------
XXX ...
.. _xreader.get():
STRING xreader.get()
--------------------
Retrieves the contents of file specified in the constructor. If the
``ttl`` has elapsed, then ``.get()`` checks if the file has changed;
if so, the new contents of the file are read, cached and returned. If
the ``ttl`` has not elapsed, or if the file is unchanged, then the
cached contents are returned.
XXX ...
.. _file.version():
STRING version()
----------------
Return the version string for this VMOD.
Example::
std.log("Using VMOD file version: " + file.version());
ERRORS
======
XXX ...
REQUIREMENTS
============
The VMOD requires the Varnish master branch. See the source repository for
versions that are compatible with released Varnish versions.
XXX ...
INSTALLATION
============
See `INSTALL.rst <INSTALL.rst>`_ in the source repository.
LIMITATIONS
===========
XXX ...
SEE ALSO
========
* varnishd(1)
* vcl(7)
COPYRIGHT
=========
::
Copyright (c) 2019 UPLEX Nils Goroll Systemoptimierung
All rights reserved
Author: Geoffrey Simmons <geoffrey.simmons@uplex.de>
See LICENSE
#!/bin/sh
warn() {
echo "WARNING: $@" 1>&2
}
case `uname -s` in
Darwin)
LIBTOOLIZE=glibtoolize
;;
FreeBSD)
LIBTOOLIZE=libtoolize
;;
Linux)
LIBTOOLIZE=libtoolize
;;
SunOS)
LIBTOOLIZE=libtoolize
;;
*)
warn "unrecognized platform:" `uname -s`
LIBTOOLIZE=libtoolize
esac
automake_version=`automake --version | tr ' ' '\n' | egrep '^[0-9]\.[0-9a-z.-]+'`
if [ -z "$automake_version" ] ; then
warn "unable to determine automake version"
else
case $automake_version in
0.*|1.[0-8]|1.[0-8][.-]*)
warn "automake ($automake_version) detected; 1.9 or newer recommended"
;;
*)
;;
esac
fi
# check for varnishapi.m4 in custom paths
dataroot=$(pkg-config --variable=datarootdir varnishapi 2>/dev/null)
if [ -z "$dataroot" ] ; then
cat >&2 <<'EOF'
Package varnishapi was not found in the pkg-config search path.
Perhaps you should add the directory containing `varnishapi.pc'
to the PKG_CONFIG_PATH environment variable
EOF
exit 1
fi
set -ex
aclocal -I m4 -I ${dataroot}/aclocal
$LIBTOOLIZE --copy --force
autoheader
automake --add-missing --copy --foreign
autoconf
AC_PREREQ(2.68)
AC_COPYRIGHT([Copyright (c) 2019 UPLEX - Nils Goroll Systemoptimierung])
AC_INIT([libvmod-file], [trunk], [varnish-support@uplex.de], [vmod-file])
AC_CONFIG_MACRO_DIR([m4])
AC_CONFIG_SRCDIR(src/vmod_file.vcc)
AM_CONFIG_HEADER(config.h)
AC_CANONICAL_SYSTEM
AC_LANG(C)
AM_INIT_AUTOMAKE([1.12 -Wall -Werror foreign parallel-tests])
AM_SILENT_RULES([yes])
AM_PROG_AR
LT_PREREQ([2.2.6])
LT_INIT([dlopen disable-static])
AC_PROG_CC
AC_PROG_CC_STDC
if test "x$ac_cv_prog_cc_c99" = xno; then
AC_MSG_ERROR([Could not find a C99 compatible compiler])
fi
AC_PROG_CPP
AX_PTHREAD(,[AC_MSG_ERROR([Could not configure pthreads support])])
LIBS="$PTHREAD_LIBS $LIBS"
CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
CC="$PTHREAD_CC"
AC_ARG_WITH([rst2man],
AS_HELP_STRING(
[--with-rst2man=PATH],
[Location of rst2man (auto)]),
[RST2MAN="$withval"],
AC_CHECK_PROGS(RST2MAN, [rst2man rst2man.py], []))
AM_CONDITIONAL(HAVE_RST2MAN, [test "x$RST2MAN" != "xno"])
AC_ARG_WITH([lcov],
AS_HELP_STRING(
[--with-lcov=PATH],
[Location of lcov to generate coverage data (auto)]),
[LCOV="$withval"],
AC_CHECK_PROGS(LCOV, [lcov], []))
AM_CONDITIONAL(HAVE_LCOV, [test -n "$LCOV"])
AC_ARG_WITH([genhtml],
AS_HELP_STRING(
[--with-genhtml=PATH],
[Location of genhtml to generate coverage reports (auto)]),
[GENHTML="$withval"],
AC_CHECK_PROGS(GENHTML, [genhtml], []))
AM_CONDITIONAL(HAVE_GENHTML, [test -n "$GENHTML"])
m4_ifndef([VARNISH_PREREQ], AC_MSG_ERROR([Need varnish.m4 -- see README.rst]))
VARNISH_PREREQ([trunk])
VARNISH_VMODS([file])
VMOD_TESTS="$(cd $srcdir/src && echo tests/*.vtc)"
AC_SUBST(VMOD_TESTS)
PKG_CHECK_VAR([LIBVARNISHAPI_LIBDIR], [varnishapi], [libdir])
AC_SUBST([VARNISH_LIBRARY_PATH],
[$LIBVARNISHAPI_LIBDIR:$LIBVARNISHAPI_LIBDIR/varnish])
# Checks for C sources
# XXX
# --enable-stack-protector
AC_ARG_ENABLE(stack-protector,
AS_HELP_STRING([--enable-stack-protector],[enable stack protector (default is YES)]),
[],
[enable_stack_protector=yes])
if test "x$enable_stack_protector" != "xno"; then
AX_CHECK_COMPILE_FLAG([-fstack-protector],
AX_CHECK_LINK_FLAG([-fstack-protector],
[CFLAGS="${CFLAGS} -fstack-protector"], [], []),
[], [])
fi
# --enable-debugging
AC_ARG_ENABLE(debugging,
AS_HELP_STRING([--enable-debugging],[enable debugging (default is NO)]),
[],
[enable_debugging=no])
# AC_PROG_CC sets CFLAGS to '-g -O2' unless already set, so there's no
# need to add -g. Disable or change by explicitly setting CFLAGS. If
# this option is enabled, then -Og or -O0 becomes the last
# optimization option, and hence takes precedence.
if test "x$enable_debugging" != "xno"; then
CFLAGS="${CFLAGS} -fno-inline"
AX_CHECK_COMPILE_FLAG([-Og],
[CFLAGS="${CFLAGS} -O0 -Og"],
[CFLAGS="${CFLAGS} -O0"],
[])
fi
AC_CONFIG_FILES([
Makefile
src/Makefile
])
AC_OUTPUT
((nil . ((indent-tabs-mode . t)))
(c-mode . ((c-file-style . "BSD"))))
AUTOMAKE_OPTIONS = subdir-objects
AM_CFLAGS = $(VARNISHAPI_CFLAGS) -Wall -Werror -Wextra -std=c99
AM_LDFLAGS = $(VARNISHAPI_LIBS) -ldl
vmod_LTLIBRARIES = libvmod_file.la
libvmod_file_la_SOURCES = \
vmod_file.c
nodist_libvmod_file_la_SOURCES = \
vcc_if.c \
vcc_if.h
vmod_file.lo: $(nodist_libvmod_file_la_SOURCES)
dist_man_MANS = vmod_file.3
vcc_if.h vmod_file.rst vmod_file.man.rst: vcc_if.c
vcc_if.c: vmod_file.vcc
$(AM_V_VMODTOOL) $(PYTHON) $(VMODTOOL) -o vcc_if $(srcdir)/vmod_file.vcc
vmod_file.3: vmod_file.man.rst
$(AM_V_GEN) $(RST2MAN) vmod_file.man.rst vmod_file.3
AM_TESTS_ENVIRONMENT = \
PATH="$(VMOD_TEST_PATH)" \
LD_LIBRARY_PATH="$(VARNISH_LIBRARY_PATH)"
TEST_EXTENSIONS = .vtc
VTC_LOG_COMPILER = varnishtest -v
AM_VTC_LOG_FLAGS = -Dvmod_file="$(VMOD_FILE)"
TESTS = @VMOD_TESTS@
# To test an individual VTC test named test.vtc:
# $ cd src/
# $ make check TESTS=tests/test.vtc
gcov: clean
$(AM_V_at)$(MAKE) $(AM_MAKEFLAGS) CC=gcc \
CFLAGS="${AM_CFLAGS} --coverage -fno-inline -g -O0" check
# Set QUIET=-q for non-verbose builds, otherwise set to empty.
QUIET_0 = -q
QUIET_ = $(QUIET_@AM_DEFAULT_V@)
QUIET = $(QUIET_@AM_V@)
coverage/lcov.info: gcov
if HAVE_LCOV
$(AM_V_at)@mkdir $(builddir)/coverage
$(AM_V_GEN) $(LCOV) $(QUIET) -c -d . -o $(builddir)/coverage/lcov.info
else
@echo "================================================="
@echo "You need lcov installed to generate coverage data"
@echo "================================================="
@false
endif
coverage: coverage/lcov.info
if HAVE_GENHTML
$(AM_V_GEN) $(GENHTML) $(QUIET) $(builddir)/coverage/lcov.info \
-o $(builddir)/coverage
else
@echo "======================================================="
@echo "You need genhtml installed to generate coverage reports"
@echo "======================================================="
@false
endif
EXTRA_DIST = \
vmod_file.vcc \
$(VMOD_TESTS)
CLEANFILES = \
$(builddir)/vcc_if.c \
$(builddir)/vcc_if.h \
$(builddir)/vmod_file.rst \
$(builddir)/vmod_file.man.rst \
$(builddir)/vmod_file.3 \
$(builddir)/*.gcda \
$(builddir)/*.gcno
clean-local:
@rm -rf $(builddir)/coverage
# looks like -*- vcl -*-
varnishtest "vcl.use and .discard, and version strings"
varnish v1 -vcl {
import ${vmod_file};
backend b { .host = "${bad_ip}"; }
} -start
varnish v1 -vcl {backend b { .host = "${bad_ip}"; }}
varnish v1 -cli "vcl.list"
varnish v1 -cli "vcl.use vcl1"
varnish v1 -cli "vcl.use vcl2"
varnish v1 -cli "vcl.use vcl1"
varnish v1 -cli "vcl.show vcl1"
varnish v1 -cli "vcl.use vcl2"
varnish v1 -cli "vcl.discard vcl1"
varnish v1 -cli "vcl.list"
varnish v1 -vcl {
import ${vmod_file};
backend b { .host = "${bad_ip}"; }
sub vcl_recv {
return(synth(200));
}
sub vcl_synth {
set resp.http.x-version = file.version();
if (!resp.http.x-version) {
set resp.status = 500;
}
return(deliver);
}
}
client c1 {
txreq -url "/"
rxresp
expect resp.status == 200
expect resp.http.x-version ~ "^.+$"
} -run
# looks like -*- vcl -*-
varnishtest "reader constructor and .get()"
shell {echo "foo bar baz quux" > ${tmpdir}/file}
varnish v1 -vcl+backend {
import ${vmod_file};
backend b { .host = "${bad_ip}"; }
sub vcl_init {
new rdr = file.reader("${tmpdir}/file");
}
sub vcl_recv {
return (synth(200));
}
sub vcl_synth {
set resp.http.Get = rdr.get();
return (deliver);
}
} -start
client c1 {
txreq
rxresp
expect resp.status == 200
expect resp.http.Get == "foo bar baz quux"
} -run
/*-
* Copyright (c) 2018 UPLEX Nils Goroll Systemoptimierung
* All rights reserved
*
* Author: Geoffrey Simmons <geoffrey.simmons@uplex.de>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
/* for strdup() */
#define _POSIX_C_SOURCE 200809L
#include "config.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include "cache/cache.h"
#include "vcl.h"
#include "vcc_if.h"
#define VFAIL(ctx, fmt, ...) \
VRT_fail((ctx), "vmod file failure: " fmt, __VA_ARGS__)
struct file_info {
unsigned magic;
#define FILE_INFO_MAGIC 0x46ebec3d
struct timespec mtime;
dev_t dev;
ino_t ino;
};
struct VPFX(file_reader) {
unsigned magic;
#define FILE_READER_MAGIC 0x08d18e5b
struct file_info *info;
char *vcl_name;
char *path;
char *addr;
size_t len;
VCL_DURATION ttl;
VCL_TIME t_expire;
};
static inline int
do_stat(VRT_CTX, struct VPFX(file_reader) *rdr, struct stat *st,
const char *method)
{
CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
CHECK_OBJ_NOTNULL(rdr, FILE_READER_MAGIC);
AN(st);
errno = 0;
if (stat(rdr->path, st) != 0) {
VFAIL(ctx, "%s.%s(): cannot read info about %s: %s",
rdr->vcl_name, method, rdr->path, vstrerror(errno));
return (-1);
}
if (!S_ISREG(st->st_mode)) {
VFAIL(ctx, "%s.%s(): %s is not a regular file", rdr->vcl_name,
method, rdr->path);
return (-1);
}
return (0);
}
static int
update_map(VRT_CTX, struct VPFX(file_reader) *rdr, struct stat *st,
const char *method)
{
struct file_info *info;
int fd;
void *addr;
CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
CHECK_OBJ_NOTNULL(rdr, FILE_READER_MAGIC);
CHECK_OBJ_NOTNULL(rdr->info, FILE_INFO_MAGIC);
AN(st);
info = rdr->info;
errno = 0;
if ((fd = open(rdr->path, O_RDWR)) < 0) {
VFAIL(ctx, "%s.%s(): cannot open %s: %s", rdr->vcl_name, method,
rdr->path, vstrerror(errno));
return (-1);
}
errno = 0;
if ((addr = mmap(NULL, st->st_size + 1, PROT_READ|PROT_WRITE,
MAP_PRIVATE, fd, 0)) == MAP_FAILED) {
VFAIL(ctx, "%s.%s(): could not map %s: %s", rdr->vcl_name,
method, rdr->path, vstrerror(errno));
closefd(&fd);
return (-1);
}
closefd(&fd);
/*
* Add a terminating null byte, so that the mapped file can be
* used as a VCL_STRING or a C string.
*/
*((char *)(addr + st->st_size)) = '\0';
info->mtime.tv_sec = st->st_mtim.tv_sec;
info->mtime.tv_nsec = st->st_mtim.tv_nsec;
info->dev = st->st_dev;
info->ino = st->st_ino;
rdr->addr = addr;
rdr->len = st->st_size + 1;
return (0);
}
VCL_VOID
vmod_reader__init(VRT_CTX, struct VPFX(file_reader) **rdrp,
const char *vcl_name, struct vmod_priv *priv,
VCL_STRING name, VCL_DURATION ttl)
{
struct VPFX(file_reader) *rdr;
struct file_info *info;
struct stat st;