diff --git a/Makefile.in b/Makefile.in new file mode 100644 index 0000000..592406d --- /dev/null +++ b/Makefile.in @@ -0,0 +1,53 @@ +CC=@CC@ +CPPFLAGS=@CPPFLAGS@ +CFLAGS=@CFLAGS@ +LDFLAGS=@LDFLAGS@ +AR=@AR@ +RANLIB=@RANLIB@ +LIBS=@LIBS@ +INSTALL=@INSTALL@ + +prefix=@prefix@ +exec_prefix=@exec_prefix@ +bindir=@bindir@ +sbindir=@sbindir@ +libdir=@libdir@ +includedir=${prefix}/include +libexecdir=@libexecdir@ +mandir=@mandir@ +mansubdir=@mansubdir@ +docdir=${prefix}/@docdir@ +sysconfdir=@sysconfdir@ +srcdir=@srcdir@ +top_srcdir=@top_srcdir@ + +LIBOBJS= \ + jlog.o jlog_hash.o jlog_io.o + +all: libjlog.a jlogctl test + +test: jthreadtest + +jlogctl: libjlog.a jlogctl.o + $(CC) $(CFLAGS) -o jlogctl jlogctl.o libjlog.a $(LIBS) + +jthreadtest: libjlog.a jthreadtest.o + $(CC) $(CFLAGS) -o jthreadtest jthreadtest.o libjlog.a $(LIBS) + +libjlog.a: $(LIBOBJS) + $(AR) cq libjlog.a $(LIBOBJS) + $(RANLIB) libjlog.a + +.c.o: + $(CC) $(CPPFLAGS) $(CFLAGS) -c $< + +install: + $(srcdir)/mkinstalldirs $(DESTDIR)$(bindir) + $(srcdir)/mkinstalldirs $(DESTDIR)$(libdir) + $(srcdir)/mkinstalldirs $(DESTDIR)$(includedir) + $(INSTALL) -m 0755 jlogctl $(DESTDIR)$(bindir)/jlogctl + $(INSTALL) -m 0755 libjlog.a $(DESTDIR)$(libdir)/libjlog.a + $(INSTALL) -m 0644 jlog.h $(DESTDIR)$(includedir)/jlog.h + +clean: + rm -f *.o libjlog.a jthreadtest diff --git a/configure.in b/configure.in new file mode 100755 index 0000000..1203242 --- /dev/null +++ b/configure.in @@ -0,0 +1,201 @@ +AC_INIT(jlog.c) +AC_CONFIG_HEADER(jlog_config.h) + +AC_PROG_CC +AC_C_INLINE +AC_C_BIGENDIAN +AC_PROG_CPP +AC_PROG_RANLIB +AC_PROG_INSTALL +AC_PROG_LN_S +AC_PATH_PROG(AR, ar) +AC_PATH_PROGS(PERL, perl) +AC_SUBST(PERL) + +# Checks for data types +AC_CHECK_SIZEOF(char, 1) +AC_CHECK_SIZEOF(short int, 2) +AC_CHECK_SIZEOF(int, 4) +AC_CHECK_SIZEOF(long int, 4) +AC_CHECK_SIZEOF(long long int, 8) +AC_CHECK_SIZEOF(void *, 1) + +AC_CHECK_LIB(rt, sem_init, , ) +AC_CHECK_LIB(posix4, sem_wait, , ) + +AC_MSG_CHECKING([whether sem_init works]) +AC_TRY_RUN( + [ +#include +int main(void){sem_t s;return (0 != sem_init(&s,0,0));} + ], + [AC_MSG_RESULT(yes)], + [ + AC_MSG_RESULT(no) + AC_DEFINE(BROKEN_SEM_INIT) + AC_MSG_WARN([****** sem_init() is broken, I'll implement one myself.]) + ] +) + +AC_CHECK_LIB(rt, sem_init, , ) +AC_FUNC_STRFTIME + +# Checks for header files. +AC_CHECK_HEADERS(sys/file.h sys/types.h dirent.h sys/param.h fcntl.h errno.h limits.h \ + sys/resource.h pthread.h semaphore.h pwd.h stdio.h stdlib.h string.h \ + ctype.h unistd.h time.h sys/stat.h) + +AC_CACHE_CHECK([for u_int type], ac_cv_have_u_int, [ + AC_TRY_COMPILE( + [ #include ], + [ u_int a; a = 1;], + [ ac_cv_have_u_int="yes" ], + [ ac_cv_have_u_int="no" ] + ) +]) +if test "x$ac_cv_have_u_int" = "xyes" ; then + AC_DEFINE(HAVE_U_INT) + have_u_int=1 +fi + +AC_CACHE_CHECK([for intXX_t types], ac_cv_have_intxx_t, [ + AC_TRY_COMPILE( + [ #include ], + [ int8_t a; int16_t b; int32_t c; a = b = c = 1;], + [ ac_cv_have_intxx_t="yes" ], + [ ac_cv_have_intxx_t="no" ] + ) +]) +if test "x$ac_cv_have_intxx_t" = "xyes" ; then + AC_DEFINE(HAVE_INTXX_T) + have_intxx_t=1 +fi + +AC_CACHE_CHECK([for int64_t type], ac_cv_have_int64_t, [ + AC_TRY_COMPILE( + [ #include ], + [ int64_t a; a = 1;], + [ ac_cv_have_int64_t="yes" ], + [ ac_cv_have_int64_t="no" ] + ) +]) +if test "x$ac_cv_have_int64_t" = "xyes" ; then + AC_DEFINE(HAVE_INT64_T) + have_int64_t=1 +fi + +AC_CACHE_CHECK([for u_intXX_t types], ac_cv_have_u_intxx_t, [ + AC_TRY_COMPILE( + [ #include ], + [ u_int8_t a; u_int16_t b; u_int32_t c; a = b = c = 1;], + [ ac_cv_have_u_intxx_t="yes" ], + [ ac_cv_have_u_intxx_t="no" ] + ) +]) +if test "x$ac_cv_have_u_intxx_t" = "xyes" ; then + AC_DEFINE(HAVE_U_INTXX_T) + have_u_intxx_t=1 +fi + +AC_CACHE_CHECK([for u_int64_t types], ac_cv_have_u_int64_t, [ + AC_TRY_COMPILE( + [ #include ], + [ u_int64_t a; a = 1;], + [ ac_cv_have_u_int64_t="yes" ], + [ ac_cv_have_u_int64_t="no" ] + ) +]) +if test "x$ac_cv_have_u_int64_t" = "xyes" ; then + AC_DEFINE(HAVE_U_INT64_T) + have_u_int64_t=1 +fi + +if (test -z "$have_u_intxx_t" || test -z "$have_intxx_t" && \ + test "x$ac_cv_header_sys_bitypes_h" = "xyes") +then + AC_MSG_CHECKING([for intXX_t and u_intXX_t types in sys/bitypes.h]) + AC_TRY_COMPILE( + [ +#include + ], + [ + int8_t a; int16_t b; int32_t c; + u_int8_t e; u_int16_t f; u_int32_t g; + a = b = c = e = f = g = 1; + ], + [ + AC_DEFINE(HAVE_U_INTXX_T) + AC_DEFINE(HAVE_INTXX_T) + AC_MSG_RESULT(yes) + ], + [AC_MSG_RESULT(no)] + ) +fi + +if test -z "$have_u_intxx_t" ; then + AC_CACHE_CHECK([for uintXX_t types], ac_cv_have_uintxx_t, [ + AC_TRY_COMPILE( + [ +#include + ], + [ uint8_t a; uint16_t b; uint32_t c; a = b = c = 1; ], + [ ac_cv_have_uintxx_t="yes" ], + [ ac_cv_have_uintxx_t="no" ] + ) + ]) + if test "x$ac_cv_have_uintxx_t" = "xyes" ; then + AC_DEFINE(HAVE_UINTXX_T) + fi +fi + +AC_CACHE_CHECK([for socklen_t], ac_cv_have_socklen_t, [ + AC_TRY_COMPILE( + [ +#include +#include + ], + [socklen_t foo; foo = 1235;], + [ ac_cv_have_socklen_t="yes" ], + [ ac_cv_have_socklen_t="no" ] + ) +]) +if test "x$ac_cv_have_socklen_t" = "xyes" ; then + AC_DEFINE(HAVE_SOCKLEN_T) +fi + +AC_CACHE_CHECK([for size_t], ac_cv_have_size_t, [ + AC_TRY_COMPILE( + [ +#include + ], + [ size_t foo; foo = 1235; ], + [ ac_cv_have_size_t="yes" ], + [ ac_cv_have_size_t="no" ] + ) +]) +if test "x$ac_cv_have_size_t" = "xyes" ; then + AC_DEFINE(HAVE_SIZE_T) +fi + +AC_CACHE_CHECK([for ssize_t], ac_cv_have_ssize_t, [ + AC_TRY_COMPILE( + [ +#include + ], + [ ssize_t foo; foo = 1235; ], + [ ac_cv_have_ssize_t="yes" ], + [ ac_cv_have_ssize_t="no" ] + ) +]) +if test "x$ac_cv_have_ssize_t" = "xyes" ; then + AC_DEFINE(HAVE_SSIZE_T) +fi + +docdir="docs" +mansubdir="man" +AC_SUBST(docdir) +AC_SUBST(mansubdir) + +AC_OUTPUT([ +Makefile +]) diff --git a/getopt_long.c b/getopt_long.c new file mode 100644 index 0000000..135fa0e --- /dev/null +++ b/getopt_long.c @@ -0,0 +1,466 @@ +/*- + * Copyright (c) 2000 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Dieter Baron and Thomas Klausner. + * + * 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 THE FOUNDATION 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. + */ + +#include "jlog_config.h" +#include "util.h" +#include "getopt_long.h" + +/* We need out logging to take const char *'s */ + +#if HAVE_CONFIG_H && !HAVE_GETOPT_LONG && !HAVE_DECL_OPTIND +#define REPLACE_GETOPT +#endif + +#ifdef REPLACE_GETOPT +#ifdef __weak_alias +__weak_alias(getopt,_getopt) +#endif +int opterr = 1; /* if error message should be printed */ +int optind = 1; /* index into parent argv vector */ +int optopt = '?'; /* character checked for validity */ +int optreset; /* reset getopt */ +char *optarg; /* argument associated with option */ +#elif HAVE_CONFIG_H && !HAVE_DECL_OPTRESET +static int optreset; +#endif + +#ifdef __weak_alias +__weak_alias(getopt_long,_getopt_long) +#endif + +#if !HAVE_GETOPT_LONG +#define IGNORE_FIRST (*options == '-' || *options == '+') +#define PRINT_ERROR ((opterr) && ((*options != ':') \ + || (IGNORE_FIRST && options[1] != ':'))) +#define IS_POSIXLY_CORRECT (getenv("POSIXLY_CORRECT") != NULL) +#define PERMUTE (!IS_POSIXLY_CORRECT && !IGNORE_FIRST) +/* XXX: GNU ignores PC if *options == '-' */ +#define IN_ORDER (!IS_POSIXLY_CORRECT && *options == '-') + +/* return values */ +#define BADCH (int)'?' +#define BADARG ((IGNORE_FIRST && options[1] == ':') \ + || (*options == ':') ? (int)':' : (int)'?') +#define INORDER (int)1 + +#define EMSG "" + +static int getopt_internal __P((int, char * const *, const char *)); +static int gcd __P((int, int)); +static void permute_args __P((int, int, int, char * const *)); + +static char *place = EMSG; /* option letter processing */ + +/* XXX: set optreset to 1 rather than these two */ +static int nonopt_start = -1; /* first non option argument (for permute) */ +static int nonopt_end = -1; /* first option after non options (for permute) */ + +/* Error messages */ +static const char recargchar[] = "option requires an argument -- %c"; +static const char recargstring[] = "option requires an argument -- %s"; +static const char ambig[] = "ambiguous option -- %.*s"; +static const char noarg[] = "option doesn't take an argument -- %.*s"; +static const char illoptchar[] = "unknown option -- %c"; +static const char illoptstring[] = "unknown option -- %s"; + +/* + * Compute the greatest common divisor of a and b. + */ +static int +gcd(a, b) + int a; + int b; +{ + int c; + + c = a % b; + while (c != 0) { + a = b; + b = c; + c = a % b; + } + + return b; +} + +/* + * Exchange the block from nonopt_start to nonopt_end with the block + * from nonopt_end to opt_end (keeping the same order of arguments + * in each block). + */ +static void +permute_args(panonopt_start, panonopt_end, opt_end, nargv) + int panonopt_start; + int panonopt_end; + int opt_end; + char * const *nargv; +{ + int cstart, cyclelen, i, j, ncycle, nnonopts, nopts, pos; + char *swap; + + /* + * compute lengths of blocks and number and size of cycles + */ + nnonopts = panonopt_end - panonopt_start; + nopts = opt_end - panonopt_end; + ncycle = gcd(nnonopts, nopts); + cyclelen = (opt_end - panonopt_start) / ncycle; + + for (i = 0; i < ncycle; i++) { + cstart = panonopt_end+i; + pos = cstart; + for (j = 0; j < cyclelen; j++) { + if (pos >= panonopt_end) + pos -= nnonopts; + else + pos += nopts; + swap = nargv[pos]; + /* LINTED const cast */ + ((char **) nargv)[pos] = nargv[cstart]; + /* LINTED const cast */ + ((char **)nargv)[cstart] = swap; + } + } +} + +/* + * getopt_internal -- + * Parse argc/argv argument vector. Called by user level routines. + * Returns -2 if -- is found (can be long option or end of options marker). + */ +static int +getopt_internal(nargc, nargv, options) + int nargc; + char * const *nargv; + const char *options; +{ + char *oli; /* option letter list index */ + int optchar; + + optarg = NULL; + + /* + * XXX Some programs (like rsyncd) expect to be able to + * XXX re-initialize optind to 0 and have getopt_long(3) + * XXX properly function again. Work around this braindamage. + */ + if (optind == 0) + optind = 1; + + if (optreset) + nonopt_start = nonopt_end = -1; + start: + if (optreset || !*place) { /* update scanning pointer */ + optreset = 0; + if (optind >= nargc) { /* end of argument vector */ + place = EMSG; + if (nonopt_end != -1) { + /* do permutation, if we have to */ + permute_args(nonopt_start, nonopt_end, + optind, nargv); + optind -= nonopt_end - nonopt_start; + } + else if (nonopt_start != -1) { + /* + * If we skipped non-options, set optind + * to the first of them. + */ + optind = nonopt_start; + } + nonopt_start = nonopt_end = -1; + return -1; + } + if ((*(place = nargv[optind]) != '-') + || (place[1] == '\0')) { /* found non-option */ + place = EMSG; + if (IN_ORDER) { + /* + * GNU extension: + * return non-option as argument to option 1 + */ + optarg = nargv[optind++]; + return INORDER; + } + if (!PERMUTE) { + /* + * if no permutation wanted, stop parsing + * at first non-option + */ + return -1; + } + /* do permutation */ + if (nonopt_start == -1) + nonopt_start = optind; + else if (nonopt_end != -1) { + permute_args(nonopt_start, nonopt_end, + optind, nargv); + nonopt_start = optind - + (nonopt_end - nonopt_start); + nonopt_end = -1; + } + optind++; + /* process next argument */ + goto start; + } + if (nonopt_start != -1 && nonopt_end == -1) + nonopt_end = optind; + if (place[1] && *++place == '-') { /* found "--" */ + place++; + return -2; + } + } + if ((optchar = (int)*place++) == (int)':' || + (oli = strchr(options + (IGNORE_FIRST ? 1 : 0), optchar)) == NULL) { + /* option letter unknown or ':' */ + if (!*place) + ++optind; + if (PRINT_ERROR) + fprintf(stderr, (char *)illoptchar, optchar); + optopt = optchar; + return BADCH; + } + if (optchar == 'W' && oli[1] == ';') { /* -W long-option */ + /* XXX: what if no long options provided (called by getopt)? */ + if (*place) + return -2; + + if (++optind >= nargc) { /* no arg */ + place = EMSG; + if (PRINT_ERROR) + fprintf(stderr, recargchar, optchar); + optopt = optchar; + return BADARG; + } else /* white space */ + place = nargv[optind]; + /* + * Handle -W arg the same as --arg (which causes getopt to + * stop parsing). + */ + return -2; + } + if (*++oli != ':') { /* doesn't take argument */ + if (!*place) + ++optind; + } else { /* takes (optional) argument */ + optarg = NULL; + if (*place) /* no white space */ + optarg = place; + /* XXX: disable test for :: if PC? (GNU doesn't) */ + else if (oli[1] != ':') { /* arg not optional */ + if (++optind >= nargc) { /* no arg */ + place = EMSG; + if (PRINT_ERROR) + fprintf(stderr, recargchar, optchar); + optopt = optchar; + return BADARG; + } else + optarg = nargv[optind]; + } + place = EMSG; + ++optind; + } + /* dump back option letter */ + return optchar; +} + +#ifdef REPLACE_GETOPT +/* + * getopt -- + * Parse argc/argv argument vector. + * + * [eventually this will replace the real getopt] + */ +int +getopt(nargc, nargv, options) + int nargc; + char * const *nargv; + const char *options; +{ + int retval; + + if ((retval = getopt_internal(nargc, nargv, options)) == -2) { + ++optind; + /* + * We found an option (--), so if we skipped non-options, + * we have to permute. + */ + if (nonopt_end != -1) { + permute_args(nonopt_start, nonopt_end, optind, + nargv); + optind -= nonopt_end - nonopt_start; + } + nonopt_start = nonopt_end = -1; + retval = -1; + } + return retval; +} +#endif + +/* + * getopt_long -- + * Parse argc/argv argument vector. + */ +int +getopt_long(nargc, nargv, options, long_options, idx) + int nargc; + char * const *nargv; + const char *options; + const struct option *long_options; + int *idx; +{ + int retval; + + /* idx may be NULL */ + + if ((retval = getopt_internal(nargc, nargv, options)) == -2) { + char *current_argv, *has_equal; + size_t current_argv_len; + int i, match; + + current_argv = place; + match = -1; + + optind++; + place = EMSG; + + if (*current_argv == '\0') { /* found "--" */ + /* + * We found an option (--), so if we skipped + * non-options, we have to permute. + */ + if (nonopt_end != -1) { + permute_args(nonopt_start, nonopt_end, + optind, nargv); + optind -= nonopt_end - nonopt_start; + } + nonopt_start = nonopt_end = -1; + return -1; + } + if ((has_equal = strchr(current_argv, '=')) != NULL) { + /* argument found (--option=arg) */ + current_argv_len = has_equal - current_argv; + has_equal++; + } else + current_argv_len = strlen(current_argv); + + for (i = 0; long_options[i].name; i++) { + /* find matching long option */ + if (strncmp(current_argv, long_options[i].name, + current_argv_len)) + continue; + + if (strlen(long_options[i].name) == + (unsigned)current_argv_len) { + /* exact match */ + match = i; + break; + } + if (match == -1) /* partial match */ + match = i; + else { + /* ambiguous abbreviation */ + if (PRINT_ERROR) + fprintf(stderr, ambig, (int)current_argv_len, + current_argv); + optopt = 0; + return BADCH; + } + } + if (match != -1) { /* option found */ + if (long_options[match].has_arg == no_argument + && has_equal) { + if (PRINT_ERROR) + fprintf(stderr, noarg, (int)current_argv_len, + current_argv); + /* + * XXX: GNU sets optopt to val regardless of + * flag + */ + if (long_options[match].flag == NULL) + optopt = long_options[match].val; + else + optopt = 0; + return BADARG; + } + if (long_options[match].has_arg == required_argument || + long_options[match].has_arg == optional_argument) { + if (has_equal) + optarg = has_equal; + else if (long_options[match].has_arg == + required_argument) { + /* + * optional argument doesn't use + * next nargv + */ + optarg = nargv[optind++]; + } + } + if ((long_options[match].has_arg == required_argument) + && (optarg == NULL)) { + /* + * Missing argument; leading ':' + * indicates no erroc should be generated + */ + if (PRINT_ERROR) + fprintf(stderr, recargstring, current_argv); + /* + * XXX: GNU sets optopt to val regardless + * of flag + */ + if (long_options[match].flag == NULL) + optopt = long_options[match].val; + else + optopt = 0; + --optind; + return BADARG; + } + } else { /* unknown option */ + if (PRINT_ERROR) + fprintf(stderr, illoptstring, current_argv); + optopt = 0; + return BADCH; + } + if (long_options[match].flag) { + *long_options[match].flag = long_options[match].val; + retval = 0; + } else + retval = long_options[match].val; + if (idx) + *idx = match; + } + return retval; +} +#endif /* !GETOPT_LONG */ diff --git a/getopt_long.h b/getopt_long.h new file mode 100644 index 0000000..8765c76 --- /dev/null +++ b/getopt_long.h @@ -0,0 +1,35 @@ +#ifndef GETOPT_LONG_H +#define GETOPT_LONG_H + +#ifdef __cplusplus +extern "C" { +#endif + +extern char *optarg; +extern int optind; +extern int opterr; +extern int optopt; + +struct option { + const char *name; + int has_arg; + int *flag; + int val; +}; + +#define no_argument 0 +#define required_argument 1 +#define optional_argument 2 + +extern int getopt_long (int argc, char *const *argv, const char *shortopts, + const struct option *longopts, int *longind); + +#if defined(REPLACE_GETOPT) && defined(WIN32) +extern int getopt(int nargc, char * const *nargv, const char *options); +#endif + +#ifdef __cplusplus +} /* Close scope of 'extern "C"' declaration which encloses file. */ +#endif + +#endif diff --git a/install-sh b/install-sh new file mode 100755 index 0000000..e9de238 --- /dev/null +++ b/install-sh @@ -0,0 +1,251 @@ +#!/bin/sh +# +# install - install a program, script, or datafile +# This comes from X11R5 (mit/util/scripts/install.sh). +# +# Copyright 1991 by the Massachusetts Institute of Technology +# +# Permission to use, copy, modify, distribute, and sell this software and its +# documentation for any purpose is hereby granted without fee, provided that +# the above copyright notice appear in all copies and that both that +# copyright notice and this permission notice appear in supporting +# documentation, and that the name of M.I.T. not be used in advertising or +# publicity pertaining to distribution of the software without specific, +# written prior permission. M.I.T. makes no representations about the +# suitability of this software for any purpose. It is provided "as is" +# without express or implied warranty. +# +# Calling this script install-sh is preferred over install.sh, to prevent +# `make' implicit rules from creating a file called install from it +# when there is no Makefile. +# +# This script is compatible with the BSD install script, but was written +# from scratch. It can only install one file at a time, a restriction +# shared with many OS's install programs. + + +# set DOITPROG to echo to test this script + +# Don't use :- since 4.3BSD and earlier shells don't like it. +doit="${DOITPROG-}" + + +# put in absolute paths if you don't have them in your path; or use env. vars. + +mvprog="${MVPROG-mv}" +cpprog="${CPPROG-cp}" +chmodprog="${CHMODPROG-chmod}" +chownprog="${CHOWNPROG-chown}" +chgrpprog="${CHGRPPROG-chgrp}" +stripprog="${STRIPPROG-strip}" +rmprog="${RMPROG-rm}" +mkdirprog="${MKDIRPROG-mkdir}" + +transformbasename="" +transform_arg="" +instcmd="$mvprog" +chmodcmd="$chmodprog 0755" +chowncmd="" +chgrpcmd="" +stripcmd="" +rmcmd="$rmprog -f" +mvcmd="$mvprog" +src="" +dst="" +dir_arg="" + +while [ x"$1" != x ]; do + case $1 in + -c) instcmd="$cpprog" + shift + continue;; + + -d) dir_arg=true + shift + continue;; + + -m) chmodcmd="$chmodprog $2" + shift + shift + continue;; + + -o) chowncmd="$chownprog $2" + shift + shift + continue;; + + -g) chgrpcmd="$chgrpprog $2" + shift + shift + continue;; + + -s) stripcmd="$stripprog" + shift + continue;; + + -t=*) transformarg=`echo $1 | sed 's/-t=//'` + shift + continue;; + + -b=*) transformbasename=`echo $1 | sed 's/-b=//'` + shift + continue;; + + *) if [ x"$src" = x ] + then + src=$1 + else + # this colon is to work around a 386BSD /bin/sh bug + : + dst=$1 + fi + shift + continue;; + esac +done + +if [ x"$src" = x ] +then + echo "install: no input file specified" + exit 1 +else + true +fi + +if [ x"$dir_arg" != x ]; then + dst=$src + src="" + + if [ -d $dst ]; then + instcmd=: + chmodcmd="" + else + instcmd=mkdir + fi +else + +# Waiting for this to be detected by the "$instcmd $src $dsttmp" command +# might cause directories to be created, which would be especially bad +# if $src (and thus $dsttmp) contains '*'. + + if [ -f $src -o -d $src ] + then + true + else + echo "install: $src does not exist" + exit 1 + fi + + if [ x"$dst" = x ] + then + echo "install: no destination specified" + exit 1 + else + true + fi + +# If destination is a directory, append the input filename; if your system +# does not like double slashes in filenames, you may need to add some logic + + if [ -d $dst ] + then + dst="$dst"/`basename $src` + else + true + fi +fi + +## this sed command emulates the dirname command +dstdir=`echo $dst | sed -e 's,[^/]*$,,;s,/$,,;s,^$,.,'` + +# Make sure that the destination directory exists. +# this part is taken from Noah Friedman's mkinstalldirs script + +# Skip lots of stat calls in the usual case. +if [ ! -d "$dstdir" ]; then +defaultIFS=' +' +IFS="${IFS-${defaultIFS}}" + +oIFS="${IFS}" +# Some sh's can't handle IFS=/ for some reason. +IFS='%' +set - `echo ${dstdir} | sed -e 's@/@%@g' -e 's@^%@/@'` +IFS="${oIFS}" + +pathcomp='' + +while [ $# -ne 0 ] ; do + pathcomp="${pathcomp}${1}" + shift + + if [ ! -d "${pathcomp}" ] ; + then + $mkdirprog "${pathcomp}" + else + true + fi + + pathcomp="${pathcomp}/" +done +fi + +if [ x"$dir_arg" != x ] +then + $doit $instcmd $dst && + + if [ x"$chowncmd" != x ]; then $doit $chowncmd $dst; else true ; fi && + if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dst; else true ; fi && + if [ x"$stripcmd" != x ]; then $doit $stripcmd $dst; else true ; fi && + if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dst; else true ; fi +else + +# If we're going to rename the final executable, determine the name now. + + if [ x"$transformarg" = x ] + then + dstfile=`basename $dst` + else + dstfile=`basename $dst $transformbasename | + sed $transformarg`$transformbasename + fi + +# don't allow the sed command to completely eliminate the filename + + if [ x"$dstfile" = x ] + then + dstfile=`basename $dst` + else + true + fi + +# Make a temp file name in the proper directory. + + dsttmp=$dstdir/#inst.$$# + +# Move or copy the file name to the temp name + + $doit $instcmd $src $dsttmp && + + trap "rm -f ${dsttmp}" 0 && + +# and set any options; do chmod last to preserve setuid bits + +# If any of these fail, we abort the whole thing. If we want to +# ignore errors from any of these, just make sure not to ignore +# errors from the above "$doit $instcmd $src $dsttmp" command. + + if [ x"$chowncmd" != x ]; then $doit $chowncmd $dsttmp; else true;fi && + if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dsttmp; else true;fi && + if [ x"$stripcmd" != x ]; then $doit $stripcmd $dsttmp; else true;fi && + if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dsttmp; else true;fi && + +# Now rename the file to the real destination. + + $doit $rmcmd -f $dstdir/$dstfile && + $doit $mvcmd $dsttmp $dstdir/$dstfile + +fi && + + +exit 0 diff --git a/jlog.c b/jlog.c new file mode 100644 index 0000000..2d0eadb --- /dev/null +++ b/jlog.c @@ -0,0 +1,1412 @@ +/* + * Copyright (c) 2001-2006 OmniTI, Inc. All rights reserved + * + * THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF OMNITI + * The copyright notice above does not evidence any + * actual or intended publication of such source code. + * + * Redistribution of this material is strictly prohibited. + * + */ + +/***************************************************************** + + Journaled logging... append only. + + (1) find current file, or allocate a file, extendible and mark + it current. + + (2) Write records to it, records include their size, so + a simple inspection can detect and incomplete trailing + record. + + (3) Write append until the file reaches a certain size. + + (4) Allocate a file, extensible. + + (5) RESYNC INDEX on 'finished' file (see reading:3) and postpend + an offset '0' to the index. + + (2) goto (1) + + Reading journals... + + (1) find oldest checkpoint of all subscribers, remove all older files. + + (2) (file, last_read) = find_checkpoint for this subscriber + + (3) RESYNC INDEX: + open record index for file, seek to the end - off_t. + this is the offset of the last noticed record in this file. + open file, seek to this point, roll forward writing the index file + _do not_ write an offset for the last record unless it is found + complete. + + (4) read entries from last_read+1 -> index of record index + +*/ +#include + +#include "jlog_config.h" +#include "jlog_private.h" +#if HAVE_DIRENT_H +#include +#endif +#if HAVE_FCNTL_H +#include +#endif +#if HAVE_ERRNO_H +#include +#endif +#if HAVE_TIME_H +#include +#endif + +#define BUFFERED_INDICES 1024 + +static jlog_file *__jlog_open_writer(jlog_ctx *ctx); +static int __jlog_close_writer(jlog_ctx *ctx); +static jlog_file *__jlog_open_reader(jlog_ctx *ctx, u_int32_t log); +static int __jlog_close_reader(jlog_ctx *ctx); +static int __jlog_close_checkpoint(jlog_ctx *ctx); +static jlog_file *__jlog_open_indexer(jlog_ctx *ctx, u_int32_t log); +static int __jlog_close_indexer(jlog_ctx *ctx); +static int __jlog_resync_index(jlog_ctx *ctx, u_int32_t log, jlog_id *last, int *c); +static jlog_file *__jlog_open_named_checkpoint(jlog_ctx *ctx, const char *cpname, int flags); +static int __jlog_mmap_reader(jlog_ctx *ctx, u_int32_t log); +static int __jlog_munmap_reader(jlog_ctx *ctx); + +int jlog_snprint_logid(char *b, int n, const jlog_id *id) { + return snprintf(b, n, "%08x:%08x", id->log, id->marker); +} + +int jlog_repair_datafile(jlog_ctx *ctx, u_int32_t log) +{ + jlog_message_header hdr; + char *this, *next, *afternext, *mmap_end; + int i, invalid_count = 0; + struct { + off_t start, end; + } *invalid = NULL; + off_t orig_len, src, dst, len; + +#define TAG_INVALID(s, e) do { \ + if (invalid_count) \ + invalid = realloc(invalid, (invalid_count + 1) * sizeof(*invalid)); \ + else \ + invalid = malloc(sizeof(*invalid)); \ + invalid[invalid_count].start = s - (char *)ctx->mmap_base; \ + invalid[invalid_count].end = e - (char *)ctx->mmap_base; \ + invalid_count++; \ +} while (0) + + ctx->last_error = JLOG_ERR_SUCCESS; + + /* we want the reader's open logic because this runs in the read path + * the underlying fds are always RDWR anyway */ + __jlog_open_reader(ctx, log); + if (!ctx->data) { + ctx->last_error = JLOG_ERR_FILE_OPEN; + ctx->last_errno = errno; + return -1; + } + if (!jlog_file_lock(ctx->data)) { + ctx->last_error = JLOG_ERR_LOCK; + ctx->last_errno = errno; + return -1; + } + if (__jlog_mmap_reader(ctx, log) != 0) + SYS_FAIL(JLOG_ERR_FILE_READ); + + orig_len = ctx->mmap_len; + mmap_end = ctx->mmap_base + ctx->mmap_len; + /* these values will cause us to fall right into the error clause and + * start searching for a valid header from offset 0 */ + this = ctx->mmap_base - sizeof(hdr); + hdr.reserved = 0; + hdr.mlen = 0; + + while (this + sizeof(hdr) <= mmap_end) { + next = this + sizeof(hdr) + hdr.mlen; + if (next <= (char *)ctx->mmap_base) goto error; + if (next == mmap_end) { + this = next; + break; + } + if (next + sizeof(hdr) > mmap_end) goto error; + memcpy(&hdr, next, sizeof(hdr)); + if (hdr.reserved != 0) goto error; + this = next; + continue; + error: + for (next = this + sizeof(hdr); next + sizeof(hdr) <= mmap_end; next++) { + if (!next[0] && !next[1] && !next[2] && !next[3]) { + memcpy(&hdr, next, sizeof(hdr)); + afternext = next + sizeof(hdr) + hdr.mlen; + if (afternext <= (char *)ctx->mmap_base) continue; + if (afternext == mmap_end) break; + if (afternext + sizeof(hdr) > mmap_end) continue; + memcpy(&hdr, afternext, sizeof(hdr)); + if (hdr.reserved == 0) break; + } + } + /* correct for while loop entry condition */ + if (this < (char *)ctx->mmap_base) this = ctx->mmap_base; + if (next + sizeof(hdr) > mmap_end) break; + if (next > this) TAG_INVALID(this, next); + this = afternext; + } + if (this != mmap_end) TAG_INVALID(this, mmap_end); + +#undef TAG_INVALID + +#define MOVE_SEGMENT do { \ + char cpbuff[4096]; \ + off_t chunk; \ + while(len > 0) { \ + chunk = len; \ + if (chunk > sizeof(cpbuff)) chunk = sizeof(cpbuff); \ + if (!jlog_file_pread(ctx->data, &cpbuff, chunk, src)) \ + SYS_FAIL(JLOG_ERR_FILE_READ); \ + if (!jlog_file_pwrite(ctx->data, &cpbuff, chunk, dst)) \ + SYS_FAIL(JLOG_ERR_FILE_WRITE); \ + src += chunk; \ + dst += chunk; \ + len -= chunk; \ + } \ +} while (0) + + if (invalid_count > 0) { + __jlog_munmap_reader(ctx); + src = 0; + dst = invalid[0].start; + for (i = 0; i < invalid_count - 1; ) { + src = invalid[i].end; + len = invalid[++i].start - src; + MOVE_SEGMENT; + } + src = invalid[invalid_count - 1].end; + len = orig_len - src; + if (len > 0) MOVE_SEGMENT; + if (!jlog_file_truncate(ctx->data, dst)) + SYS_FAIL(JLOG_ERR_FILE_WRITE); + } + +#undef MOVE_SEGMENT + +finish: + jlog_file_unlock(ctx->data); + if (invalid) free(invalid); + if (ctx->last_error != JLOG_ERR_SUCCESS) return -1; + return invalid_count; +} + +int jlog_inspect_datafile(jlog_ctx *ctx, u_int32_t log) +{ + jlog_message_header hdr; + char *this, *next, *mmap_end; + int i; + time_t timet; + struct tm tm; + char tbuff[128]; + + ctx->last_error = JLOG_ERR_SUCCESS; + + __jlog_open_reader(ctx, log); + if (!ctx->data) + SYS_FAIL(JLOG_ERR_FILE_OPEN); + if (__jlog_mmap_reader(ctx, log) != 0) + SYS_FAIL(JLOG_ERR_FILE_READ); + + mmap_end = ctx->mmap_base + ctx->mmap_len; + this = ctx->mmap_base; + i = 0; + while (this + sizeof(hdr) <= mmap_end) { + memcpy(&hdr, this, sizeof(hdr)); + i++; + if (hdr.reserved != 0) { + fprintf(stderr, "Message %d at [%ld] has invalid reserved value %u\n", + i, this - (char *)ctx->mmap_base, hdr.reserved); + return 1; + } + + fprintf(stderr, "Message %d at [%ld] of (%lu+%u)", i, + this - (char *)ctx->mmap_base, sizeof(hdr), hdr.mlen); + + next = this + sizeof(hdr) + hdr.mlen; + if (next <= (char *)ctx->mmap_base) { + fprintf(stderr, " WRAPPED TO NEGATIVE OFFSET!\n"); + return 1; + } + if (next > mmap_end) { + fprintf(stderr, " OFF THE END!\n"); + return 1; + } + + timet = hdr.tv_sec; + localtime_r(&timet, &tm); + strftime(tbuff, sizeof(tbuff), "%c", &tm); + fprintf(stderr, "\n\ttime: %s\n\tmlen: %u\n", tbuff, hdr.mlen); + this = next; + } + if (this < mmap_end) { + fprintf(stderr, "%ld bytes of junk at the end\n", mmap_end - this); + return 1; + } + + return 0; +finish: + return -1; +} + +int jlog_idx_details(jlog_ctx *ctx, u_int32_t log, + u_int32_t *marker, int *closed) +{ + off_t index_len; + u_int64_t index; + + __jlog_open_indexer(ctx, log); + if (!ctx->index) + SYS_FAIL(JLOG_ERR_IDX_OPEN); + if ((index_len = jlog_file_size(ctx->index)) == -1) + SYS_FAIL(JLOG_ERR_IDX_SEEK); + if (index_len % sizeof(u_int64_t)) + SYS_FAIL(JLOG_ERR_IDX_CORRUPT); + if (index_len > sizeof(u_int64_t)) { + if (!jlog_file_pread(ctx->index, &index, sizeof(u_int64_t), + index_len - sizeof(u_int64_t))) + { + SYS_FAIL(JLOG_ERR_IDX_READ); + } + if (index) { + *marker = index_len / sizeof(u_int64_t); + *closed = 0; + } else { + *marker = (index_len / sizeof(u_int64_t)) - 1; + *closed = 1; + } + } else { + *marker = index_len / sizeof(u_int64_t); + *closed = 0; + } + + return 0; +finish: + return -1; +} + +static int __jlog_unlink_datafile(jlog_ctx *ctx, u_int32_t log) { + char file[MAXPATHLEN]; + + if(ctx->current_log == log) { + __jlog_close_reader(ctx); + __jlog_close_indexer(ctx); + } + + STRSETDATAFILE(ctx, file, log); +#ifdef DEBUG + fprintf(stderr, "unlinking %s\n", file); +#endif + unlink(file); + + strcat(file, INDEX_EXT); +#ifdef DEBUG + fprintf(stderr, "unlinking %s\n", file); +#endif + unlink(file); + return 0; +} + +static int __jlog_open_metastore(jlog_ctx *ctx) +{ + char file[MAXPATHLEN]; + int len; + +#ifdef DEBUG + fprintf(stderr, "__jlog_open_metastore\n"); +#endif + strcpy(file, ctx->path); + len = strlen(ctx->path); + file[len++] = IFS_CH; + strcpy(&file[len], "metastore"); + + ctx->metastore = jlog_file_open(file, O_CREAT, ctx->file_mode); + + if (!ctx->metastore) { + ctx->last_errno = errno; + ctx->last_error = JLOG_ERR_CREATE_META; + return -1; + } + + return 0; +} + +/* exported */ +int __jlog_pending_readers(jlog_ctx *ctx, u_int32_t log) { + int readers; + DIR *dir; + struct dirent *ent; + char file[MAXPATHLEN]; + int len; + jlog_id id; + + readers = 0; + + dir = opendir(ctx->path); + if (!dir) return -1; + + strcpy(file, ctx->path); + len = strlen(ctx->path); + file[len++] = IFS_CH; + file[len] = '\0'; + + while ((ent = readdir(dir))) { + if (ent->d_name[0] == 'c' && ent->d_name[1] == 'p' && ent->d_name[2] == '.') { + jlog_file *cp; + + strcpy(file + len, ent->d_name); +#ifdef DEBUG + fprintf(stderr, "Checking if %s needs %s...\n", ent->d_name, ctx->path); +#endif + if ((cp = jlog_file_open(file, 0, ctx->file_mode))) { + if (jlog_file_lock(cp)) { + jlog_file_pread(cp, &id, sizeof(id), 0); +#ifdef DEBUG + fprintf(stderr, "\t%u <= %u (pending reader)\n", id.log, log); +#endif + if (id.log <= log) { + readers++; + } + jlog_file_unlock(cp); + } + jlog_file_close(cp); + } + } + } + closedir(dir); + return readers; +} +struct _jlog_subs { + char **subs; + int used; + int allocd; +}; + +int jlog_ctx_list_subscribers_dispose(jlog_ctx *ctx, char **subs) { + char *s; + int i = 0; + if(subs) { + while((s = subs[i++]) != NULL) free(s); + free(subs); + } + return 0; +} + +int jlog_ctx_list_subscribers(jlog_ctx *ctx, char ***subs) { + struct _jlog_subs js = { NULL, 0, 0 }; + DIR *dir; + struct dirent *ent; + unsigned char file[MAXPATHLEN]; + char *p; + int len; + + js.subs = calloc(16, sizeof(char *)); + js.allocd = 16; + + dir = opendir(ctx->path); + if (!dir) return -1; + while ((ent = readdir(dir))) { + if (ent->d_name[0] == 'c' && ent->d_name[1] == 'p' && ent->d_name[2] == '.') { + + for (len = 0, p = ent->d_name + 3; *p;) { + unsigned char c; + int i; + + for (c = 0, i = 0; i < 16; i++) { + if (__jlog_hexchars[i] == *p) { + c = i << 4; + break; + } + } + p++; + for (i = 0; i < 16; i++) { + if (__jlog_hexchars[i] == *p) { + c |= i; + break; + } + } + p++; + file[len++] = c; + } + file[len] = '\0'; + + js.subs[js.used++] = strdup((char *)file); + if(js.used == js.allocd) { + js.allocd *= 2; + js.subs = realloc(js.subs, js.allocd*sizeof(char *)); + } + js.subs[js.used] = NULL; + } + } + closedir(dir); + *subs = js.subs; + return js.used; +} + +static int __jlog_save_metastore(jlog_ctx *ctx, int ilocked) +{ + struct _jlog_meta_info info; +#ifdef DEBUG + fprintf(stderr, "__jlog_save_metastore\n"); +#endif + + if (!ilocked && !jlog_file_lock(ctx->metastore)) { + return -1; + } + + info.storage_log = ctx->storage.log; + info.unit_limit = ctx->unit_limit; + info.safety = ctx->safety; + + if (!jlog_file_pwrite(ctx->metastore, &info, sizeof(info), 0)) { + if (!ilocked) jlog_file_unlock(ctx->metastore); + return -1; + } + if (ctx->safety == JLOG_SAFE) { + jlog_file_sync(ctx->metastore); + } + + if (!ilocked) jlog_file_unlock(ctx->metastore); + return 0; +} + +static int __jlog_restore_metastore(jlog_ctx *ctx, int ilocked) +{ + struct _jlog_meta_info info; +#ifdef DEBUG + fprintf(stderr, "__jlog_restore_metastore\n"); +#endif + + if (!ilocked && !jlog_file_lock(ctx->metastore)) { + return -1; + } + + if (!jlog_file_pread(ctx->metastore, &info, sizeof(info), 0)) { + if (!ilocked) jlog_file_unlock(ctx->metastore); + return -1; + } + + if (!ilocked) jlog_file_unlock(ctx->metastore); + + ctx->storage.log = info.storage_log; + ctx->unit_limit = info.unit_limit; + ctx->safety = info.safety; + + return 0; +} + +int jlog_get_checkpoint(jlog_ctx *ctx, const char *s, jlog_id *id) { + jlog_file *f; + int rv = -1; + + if(ctx->subscriber_name && !strcmp(ctx->subscriber_name, s)) { + if(!ctx->checkpoint) { + ctx->checkpoint = __jlog_open_named_checkpoint(ctx, s, 0); + } + f = ctx->checkpoint; + } else + f = __jlog_open_named_checkpoint(ctx, s, 0); + + if (f) { + if (jlog_file_lock(f)) { + if (jlog_file_pread(f, id, sizeof(*id), 0)) rv = 0; + jlog_file_unlock(f); + } + } + if (f && f != ctx->checkpoint) jlog_file_close(f); + return rv; +} + +static int __jlog_set_checkpoint(jlog_ctx *ctx, const char *s, const jlog_id *id) +{ + jlog_file *f; + int rv = -1; + jlog_id old_id; + u_int32_t log; + + if(ctx->subscriber_name && !strcmp(ctx->subscriber_name, s)) { + if(!ctx->checkpoint) { + ctx->checkpoint = __jlog_open_named_checkpoint(ctx, s, 0); + } + f = ctx->checkpoint; + } else + f = __jlog_open_named_checkpoint(ctx, s, 0); + + if(!f) return -1; + if (!jlog_file_lock(f)) + goto failset; + + if (jlog_file_size(f) == 0) { + /* we're setting it for the first time, no segments were pending on it */ + old_id.log = id->log; + } else { + if (!jlog_file_pread(f, &old_id, sizeof(old_id), 0)) + goto failset; + } + if (!jlog_file_pwrite(f, id, sizeof(*id), 0)) + goto failset; + if (ctx->safety == JLOG_SAFE) { + jlog_file_sync(f); + } + jlog_file_unlock(f); + rv = 0; + + for (log = old_id.log; log < id->log; log++) { + if (__jlog_pending_readers(ctx, log) == 0) { + __jlog_unlink_datafile(ctx, log); + } + } + + failset: + if (f && f != ctx->checkpoint) jlog_file_close(f); + return rv; +} + +static int __jlog_close_metastore(jlog_ctx *ctx) { + if (ctx->metastore) { + jlog_file_close(ctx->metastore); + ctx->metastore = NULL; + } + return 0; +} + +/* path is assumed to be MAXPATHLEN */ +static char *compute_checkpoint_filename(jlog_ctx *ctx, const char *subscriber, char *name) +{ + const char *sub; + int len; + + /* build checkpoint filename */ + len = strlen(ctx->path); + strcpy(name, ctx->path); + name[len++] = IFS_CH; + name[len++] = 'c'; + name[len++] = 'p'; + name[len++] = '.'; + for (sub = subscriber; *sub; ) { + name[len++] = __jlog_hexchars[((*sub & 0xf0) >> 4)]; + name[len++] = __jlog_hexchars[(*sub & 0x0f)]; + sub++; + } + name[len++] = '\0'; + +#ifdef DEBUG + fprintf(stderr, "checkpoint %s filename is %s\n", subscriber, name); +#endif + return name; +} + +static jlog_file *__jlog_open_named_checkpoint(jlog_ctx *ctx, const char *cpname, int flags) +{ + char name[MAXPATHLEN]; + compute_checkpoint_filename(ctx, cpname, name); + return jlog_file_open(name, flags, ctx->file_mode); +} + +static jlog_file *__jlog_open_reader(jlog_ctx *ctx, u_int32_t log) { + char file[MAXPATHLEN]; + + if(ctx->current_log != log) { + __jlog_close_reader(ctx); + __jlog_close_indexer(ctx); + } + if(ctx->data) { + return ctx->data; + } + STRSETDATAFILE(ctx, file, log); +#ifdef DEBUG + fprintf(stderr, "opening log file[ro]: '%s'\n", file); +#endif + ctx->data = jlog_file_open(file, 0, ctx->file_mode); + ctx->current_log = log; + return ctx->data; +} + +static int __jlog_munmap_reader(jlog_ctx *ctx) { + if(ctx->mmap_base) { + munmap(ctx->mmap_base, ctx->mmap_len); + ctx->mmap_base = NULL; + ctx->mmap_len = 0; + } + return 0; +} + +static int __jlog_mmap_reader(jlog_ctx *ctx, u_int32_t log) { + if(ctx->current_log == log && ctx->mmap_base) return 0; + __jlog_open_reader(ctx, log); + if(!ctx->data) + return -1; + if (!jlog_file_map_read(ctx->data, &ctx->mmap_base, &ctx->mmap_len)) { + ctx->mmap_base = NULL; + ctx->last_error = JLOG_ERR_FILE_READ; + ctx->last_errno = errno; + return -1; + } + return 0; +} + +static jlog_file *__jlog_open_writer(jlog_ctx *ctx) { + char file[MAXPATHLEN]; + + if(ctx->data) { + /* Still open */ + return ctx->data; + } + + if(!jlog_file_lock(ctx->metastore)) + SYS_FAIL(JLOG_ERR_LOCK); + if(__jlog_restore_metastore(ctx, 1)) + SYS_FAIL(JLOG_ERR_META_OPEN); + STRSETDATAFILE(ctx, file, ctx->storage.log); +#ifdef DEBUG + fprintf(stderr, "opening log file[rw]: '%s'\n", file); +#endif + ctx->data = jlog_file_open(file, O_CREAT, ctx->file_mode); + finish: + jlog_file_unlock(ctx->metastore); + return ctx->data; +} + +static int __jlog_close_writer(jlog_ctx *ctx) { + if (ctx->data) { + jlog_file_close(ctx->data); + ctx->data = NULL; + } + return 0; +} + +static int __jlog_close_reader(jlog_ctx *ctx) { + __jlog_munmap_reader(ctx); + if (ctx->data) { + jlog_file_close(ctx->data); + ctx->data = NULL; + } + return 0; +} + +static int __jlog_close_checkpoint(jlog_ctx *ctx) { + if (ctx->checkpoint) { + jlog_file_close(ctx->checkpoint); + ctx->checkpoint = NULL; + } + return 0; +} + +static jlog_file *__jlog_open_indexer(jlog_ctx *ctx, u_int32_t log) { + char file[MAXPATHLEN]; + + if(ctx->current_log != log) { + __jlog_close_reader(ctx); + __jlog_close_indexer(ctx); + } + if(ctx->index) { + return ctx->index; + } + STRSETDATAFILE(ctx, file, log); + strcat(file, INDEX_EXT); +#ifdef DEBUG + fprintf(stderr, "opening index file: '%s'\n", idx); +#endif + ctx->index = jlog_file_open(file, O_CREAT, ctx->file_mode); + ctx->current_log = log; + return ctx->index; +} + +static int __jlog_close_indexer(jlog_ctx *ctx) { + if (ctx->index) { + jlog_file_close(ctx->index); + ctx->index = NULL; + } + return 0; +} + +static int +___jlog_resync_index(jlog_ctx *ctx, u_int32_t log, jlog_id *last, + int *closed) { + jlog_message_header logmhdr; + int i, second_try = 0; + off_t index_off, data_off, data_len; + u_int64_t index; + u_int64_t indices[BUFFERED_INDICES]; + + ctx->last_error = JLOG_ERR_SUCCESS; + if(closed) *closed = 0; + + __jlog_open_reader(ctx, log); + if (!ctx->data) { + ctx->last_error = JLOG_ERR_FILE_OPEN; + ctx->last_errno = errno; + return -1; + } + +#define RESTART do { \ + if (second_try == 0) { \ + jlog_file_truncate(ctx->index, 0); \ + jlog_file_unlock(ctx->index); \ + second_try = 1; \ + ctx->last_error = JLOG_ERR_SUCCESS; \ + goto restart; \ + } \ + SYS_FAIL(JLOG_ERR_IDX_CORRUPT); \ +} while (0) + +restart: + __jlog_open_indexer(ctx, log); + if (!ctx->index) { + ctx->last_error = JLOG_ERR_IDX_OPEN; + ctx->last_errno = errno; + return -1; + } + if (!jlog_file_lock(ctx->index)) { + ctx->last_error = JLOG_ERR_LOCK; + ctx->last_errno = errno; + return -1; + } + + data_off = 0; + if ((data_len = jlog_file_size(ctx->data)) == -1) + SYS_FAIL(JLOG_ERR_FILE_SEEK); + if ((index_off = jlog_file_size(ctx->index)) == -1) + SYS_FAIL(JLOG_ERR_IDX_SEEK); + + if (index_off % sizeof(u_int64_t)) { +#ifdef DEBUG + fprintf(stderr, "corrupt index [%llu]\n", index_off); +#endif + RESTART; + } + + if (index_off > sizeof(u_int64_t)) { + if (!jlog_file_pread(ctx->index, &index, sizeof(index), + index_off - sizeof(u_int64_t))) + { + SYS_FAIL(JLOG_ERR_IDX_READ); + } + if (index == 0) { + /* This log file has been "closed" */ +#ifdef DEBUG + fprintf(stderr, "index closed\n"); +#endif + if(last) { + last->log = log; + last->marker = (index_off / sizeof(u_int64_t)) - 1; + } + if(closed) *closed = 1; + goto finish; + } else { + if (index > data_len) { +#ifdef DEBUG + fprintf(stderr, "index told me to seek somehwere I can't\n"); +#endif + RESTART; + } + data_off = index; + } + } + + if (index_off > 0) { + /* We are adding onto a partial index so we must advance a record */ + if (!jlog_file_pread(ctx->data, &logmhdr, sizeof(logmhdr), data_off)) + SYS_FAIL(JLOG_ERR_FILE_READ); + if ((data_off += sizeof(logmhdr) + logmhdr.mlen) > data_len) + RESTART; + } + + i = 0; + while (data_off + sizeof(logmhdr) <= data_len) { + off_t next_off = data_off; + + if (!jlog_file_pread(ctx->data, &logmhdr, sizeof(logmhdr), data_off)) + SYS_FAIL(JLOG_ERR_FILE_READ); + if ((next_off += sizeof(logmhdr) + logmhdr.mlen) > data_len) + break; + + /* Write our new index offset */ + indices[i++] = data_off; + if(i >= BUFFERED_INDICES) { +#ifdef DEBUG + fprintf(stderr, "writing %i offsets\n", i); +#endif + if (!jlog_file_pwrite(ctx->index, indices, i * sizeof(u_int64_t), index_off)) + RESTART; + index_off += i * sizeof(u_int64_t); + i = 0; + } + data_off = next_off; + } + if(i > 0) { +#ifdef DEBUG + fprintf(stderr, "writing %i offsets\n", i); +#endif + if (!jlog_file_pwrite(ctx->index, indices, i * sizeof(u_int64_t), index_off)) + RESTART; + index_off += i * sizeof(u_int64_t); + } + if(last) { + last->log = log; + last->marker = index_off / sizeof(u_int64_t); + } + if(log < ctx->storage.log) { + if (data_off != data_len) { +#ifdef DEBUG + fprintf(stderr, "closing index, but %llu != %llu\n", data_off, data_len); +#endif + SYS_FAIL(JLOG_ERR_FILE_CORRUPT); + } + /* Special case: if we are closing, we next write a '0' + * we can't write the closing marker if the data segment had no records + * in it, since it will be confused with an index to offset 0 by the + * next reader; this only happens when segments are repaired */ + if (index_off) { + index = 0; + if (!jlog_file_pwrite(ctx->index, &index, sizeof(u_int64_t), index_off)) + RESTART; + } + if(closed) *closed = 1; + } +#undef RESTART + +finish: + jlog_file_unlock(ctx->index); +#ifdef DEBUG + fprintf(stderr, "index is %s\n", closed?(*closed?"closed":"open"):"unknown"); +#endif + if(ctx->last_error == JLOG_ERR_SUCCESS) return 0; + return -1; +} + +static int __jlog_resync_index(jlog_ctx *ctx, u_int32_t log, jlog_id *last, int *closed) { + int attempts, rv = -1; + for(attempts=0; attempts<4; attempts++) { + rv = ___jlog_resync_index(ctx, log, last, closed); + if(ctx->last_error == JLOG_ERR_SUCCESS) break; + if(ctx->last_error == JLOG_ERR_FILE_OPEN || + ctx->last_error == JLOG_ERR_IDX_OPEN) break; + + /* We can't fix the file if someone may write to it again */ + if(log >= ctx->storage.log) break; + + jlog_file_lock(ctx->index); + /* it doesn't really matter what jlog_repair_datafile returns + * we'll keep retrying anyway */ + jlog_repair_datafile(ctx, log); + jlog_file_truncate(ctx->index, 0); + jlog_file_unlock(ctx->index); + } + return rv; +} + +jlog_ctx *jlog_new(const char *path) { + jlog_ctx *ctx; + ctx = calloc(1, sizeof(*ctx)); + ctx->unit_limit = DEFAULT_UNIT_LIMIT; + ctx->file_mode = DEFAULT_FILE_MODE; + ctx->safety = DEFAULT_SAFETY; + ctx->context_mode = JLOG_NEW; + ctx->path = strdup(path); + return ctx; +} + +void jlog_set_error_func(jlog_ctx *ctx, jlog_error_func Func, void *ptr) { + ctx->error_func = Func; + ctx->error_ctx = ptr; +} + +size_t jlog_raw_size(jlog_ctx *ctx) { + DIR *d; + struct dirent *de; + size_t totalsize = 0; + int ferr, len; + char filename[MAXPATHLEN]; + + d = opendir(ctx->path); + if(!d) return 0; + len = strlen(ctx->path); + memcpy(filename, ctx->path, len); + filename[len++] = IFS_CH; + while((de = readdir(d)) != NULL) { + struct stat sb; + strcpy(filename+len, de->d_name); + while((ferr = stat(filename, &sb)) == -1 && errno == EINTR); + if(ferr == 0 && S_ISREG(sb.st_mode)) totalsize += sb.st_size; + } + closedir(d); + return totalsize; +} + +const char *jlog_ctx_err_string(jlog_ctx *ctx) { + switch (ctx->last_error) { +#define MSG_O_MATIC(x) case x: return #x; + MSG_O_MATIC( JLOG_ERR_SUCCESS); + MSG_O_MATIC( JLOG_ERR_ILLEGAL_INIT); + MSG_O_MATIC( JLOG_ERR_ILLEGAL_OPEN); + MSG_O_MATIC( JLOG_ERR_OPEN); + MSG_O_MATIC( JLOG_ERR_NOTDIR); + MSG_O_MATIC( JLOG_ERR_CREATE_PATHLEN); + MSG_O_MATIC( JLOG_ERR_CREATE_EXISTS); + MSG_O_MATIC( JLOG_ERR_CREATE_MKDIR); + MSG_O_MATIC( JLOG_ERR_CREATE_META); + MSG_O_MATIC( JLOG_ERR_LOCK); + MSG_O_MATIC( JLOG_ERR_IDX_OPEN); + MSG_O_MATIC( JLOG_ERR_IDX_SEEK); + MSG_O_MATIC( JLOG_ERR_IDX_CORRUPT); + MSG_O_MATIC( JLOG_ERR_IDX_WRITE); + MSG_O_MATIC( JLOG_ERR_IDX_READ); + MSG_O_MATIC( JLOG_ERR_FILE_OPEN); + MSG_O_MATIC( JLOG_ERR_FILE_SEEK); + MSG_O_MATIC( JLOG_ERR_FILE_CORRUPT); + MSG_O_MATIC( JLOG_ERR_FILE_READ); + MSG_O_MATIC( JLOG_ERR_FILE_WRITE); + MSG_O_MATIC( JLOG_ERR_META_OPEN); + MSG_O_MATIC( JLOG_ERR_ILLEGAL_WRITE); + MSG_O_MATIC( JLOG_ERR_ILLEGAL_CHECKPOINT); + MSG_O_MATIC( JLOG_ERR_INVALID_SUBSCRIBER); + MSG_O_MATIC( JLOG_ERR_ILLEGAL_LOGID); + MSG_O_MATIC( JLOG_ERR_SUBSCRIBER_EXISTS); + MSG_O_MATIC( JLOG_ERR_CHECKPOINT); + MSG_O_MATIC( JLOG_ERR_NOT_SUPPORTED); + default: return "Unknown"; + } +} + +int jlog_ctx_err(jlog_ctx *ctx) { + return ctx->last_error; +} + +int jlog_ctx_errno(jlog_ctx *ctx) { + return ctx->last_errno; +} + +int jlog_ctx_alter_safety(jlog_ctx *ctx, jlog_safety safety) { + if(ctx->context_mode != JLOG_NEW) return -1; + ctx->safety = safety; + return 0; +} +int jlog_ctx_alter_journal_size(jlog_ctx *ctx, size_t size) { + if(ctx->context_mode != JLOG_NEW) return -1; + ctx->unit_limit = size; + return 0; +} +int jlog_ctx_alter_mode(jlog_ctx *ctx, int mode) { + ctx->file_mode = mode; + return 0; +} +int jlog_ctx_open_writer(jlog_ctx *ctx) { + int rv; + struct stat sb; + + ctx->last_error = JLOG_ERR_SUCCESS; + if(ctx->context_mode != JLOG_NEW) { + ctx->last_error = JLOG_ERR_ILLEGAL_OPEN; + return -1; + } + ctx->context_mode = JLOG_APPEND; + while((rv = stat(ctx->path, &sb)) == -1 && errno == EINTR); + if(rv == -1) SYS_FAIL(JLOG_ERR_OPEN); + if(!S_ISDIR(sb.st_mode)) SYS_FAIL(JLOG_ERR_NOTDIR); + if(__jlog_open_metastore(ctx) != 0) SYS_FAIL(JLOG_ERR_META_OPEN); + if(__jlog_restore_metastore(ctx, 0)) SYS_FAIL(JLOG_ERR_META_OPEN); + finish: + if(ctx->last_error == JLOG_ERR_SUCCESS) return 0; + ctx->context_mode = JLOG_INVALID; + return -1; +} +int jlog_ctx_open_reader(jlog_ctx *ctx, const char *subscriber) { + int rv; + struct stat sb; + jlog_id dummy; + + ctx->last_error = JLOG_ERR_SUCCESS; + if(ctx->context_mode != JLOG_NEW) { + ctx->last_error = JLOG_ERR_ILLEGAL_OPEN; + return -1; + } + ctx->context_mode = JLOG_READ; + ctx->subscriber_name = strdup(subscriber); + while((rv = stat(ctx->path, &sb)) == -1 && errno == EINTR); + if(rv == -1) SYS_FAIL(JLOG_ERR_OPEN); + if(!S_ISDIR(sb.st_mode)) SYS_FAIL(JLOG_ERR_NOTDIR); + if(__jlog_open_metastore(ctx) != 0) SYS_FAIL(JLOG_ERR_META_OPEN); + if(jlog_get_checkpoint(ctx, ctx->subscriber_name, &dummy)) + SYS_FAIL(JLOG_ERR_INVALID_SUBSCRIBER); + if(__jlog_restore_metastore(ctx, 0)) SYS_FAIL(JLOG_ERR_META_OPEN); + finish: + if(ctx->last_error == JLOG_ERR_SUCCESS) return 0; + ctx->context_mode = JLOG_INVALID; + return -1; +} +int jlog_ctx_init(jlog_ctx *ctx) { + int rv; + struct stat sb; + int dirmode; + + ctx->last_error = JLOG_ERR_SUCCESS; + if(strlen(ctx->path) > MAXLOGPATHLEN-1) { + ctx->last_error = JLOG_ERR_CREATE_PATHLEN; + return -1; + } + if(ctx->context_mode != JLOG_NEW) { + ctx->last_error = JLOG_ERR_ILLEGAL_INIT; + return -1; + } + ctx->context_mode = JLOG_INIT; + while((rv = stat(ctx->path, &sb)) == -1 && errno == EINTR); + if(rv == 0 || errno != ENOENT) { + SYS_FAIL_EX(JLOG_ERR_CREATE_EXISTS, 0); + } + dirmode = ctx->file_mode; + if(dirmode & 0400) dirmode |= 0100; + if(dirmode & 040) dirmode |= 010; + if(dirmode & 04) dirmode |= 01; + if(mkdir(ctx->path, dirmode) == -1) + SYS_FAIL(JLOG_ERR_CREATE_MKDIR); + chmod(ctx->path, dirmode); + /* Setup our initial state and store our instance metadata */ + if(__jlog_open_metastore(ctx) != 0) + SYS_FAIL(JLOG_ERR_CREATE_META); + if(__jlog_save_metastore(ctx, 0) != 0) + SYS_FAIL(JLOG_ERR_CREATE_META); + finish: + if(ctx->last_error == JLOG_ERR_SUCCESS) return 0; + return -1; +} +int jlog_ctx_close(jlog_ctx *ctx) { + __jlog_close_indexer(ctx); + __jlog_close_reader(ctx); + __jlog_close_metastore(ctx); + __jlog_close_checkpoint(ctx); + if(ctx->subscriber_name) free(ctx->subscriber_name); + if(ctx->path) free(ctx->path); + free(ctx); + return 0; +} + +static int __jlog_metastore_atomic_increment(jlog_ctx *ctx) { + u_int32_t saved_storage_log = ctx->storage.log; +#ifdef DEBUG + fprintf(stderr, "atomic increment on %u\n", saved_storage_log); +#endif + if (!jlog_file_lock(ctx->metastore)) + SYS_FAIL(JLOG_ERR_LOCK); + if(__jlog_restore_metastore(ctx, 1)) + SYS_FAIL(JLOG_ERR_META_OPEN); + if(ctx->storage.log == saved_storage_log) { + /* We're the first ones to it, so we get to increment it */ + ctx->storage.log++; + if(__jlog_save_metastore(ctx, 1)) + SYS_FAIL(JLOG_ERR_META_OPEN); + } + finish: + jlog_file_unlock(ctx->metastore); + if(ctx->last_error == JLOG_ERR_SUCCESS) return 0; + return -1; +} +int jlog_ctx_write_message(jlog_ctx *ctx, jlog_message *mess, struct timeval *when) { + struct timeval now; + jlog_message_header hdr; + off_t current_offset; + + ctx->last_error = JLOG_ERR_SUCCESS; + if(ctx->context_mode != JLOG_APPEND) { + ctx->last_error = JLOG_ERR_ILLEGAL_WRITE; + ctx->last_errno = EPERM; + return -1; + } + begin: + __jlog_open_writer(ctx); + if(!ctx->data) { + ctx->last_error = JLOG_ERR_FILE_OPEN; + ctx->last_errno = errno; + return -1; + } + if (!jlog_file_lock(ctx->data)) { + ctx->last_error = JLOG_ERR_LOCK; + ctx->last_errno = errno; + return -1; + } + + if ((current_offset = jlog_file_size(ctx->data)) == -1) + SYS_FAIL(JLOG_ERR_FILE_SEEK); + if(ctx->unit_limit <= current_offset) { + jlog_file_unlock(ctx->data); + __jlog_close_writer(ctx); + __jlog_metastore_atomic_increment(ctx); + goto begin; + } + + hdr.reserved = 0; + if (when) { + hdr.tv_sec = when->tv_sec; + hdr.tv_usec = when->tv_usec; + } else { + gettimeofday(&now, NULL); + hdr.tv_sec = now.tv_sec; + hdr.tv_usec = now.tv_usec; + } + hdr.mlen = mess->mess_len; + if (!jlog_file_pwrite(ctx->data, &hdr, sizeof(hdr), current_offset)) + SYS_FAIL(JLOG_ERR_FILE_WRITE); + current_offset += sizeof(hdr); + if (!jlog_file_pwrite(ctx->data, mess->mess, mess->mess_len, current_offset)) + SYS_FAIL(JLOG_ERR_FILE_WRITE); + current_offset += mess->mess_len; + + if(ctx->unit_limit <= current_offset) { + jlog_file_unlock(ctx->data); + __jlog_close_writer(ctx); + __jlog_metastore_atomic_increment(ctx); + return 0; + } + finish: + jlog_file_unlock(ctx->data); + if(ctx->last_error == JLOG_ERR_SUCCESS) return 0; + return -1; +} +int jlog_ctx_read_checkpoint(jlog_ctx *ctx, const jlog_id *chkpt) { + ctx->last_error = JLOG_ERR_SUCCESS; + + if(ctx->context_mode != JLOG_READ) { + ctx->last_error = JLOG_ERR_ILLEGAL_CHECKPOINT; + ctx->last_errno = EPERM; + return -1; + } + if(__jlog_set_checkpoint(ctx, ctx->subscriber_name, chkpt) != 0) { + ctx->last_error = JLOG_ERR_CHECKPOINT; + ctx->last_errno = 0; + return -1; + } + return 0; +} + +int jlog_ctx_remove_subscriber(jlog_ctx *ctx, const char *s) { + char name[MAXPATHLEN]; + int rv; + + compute_checkpoint_filename(ctx, s, name); + rv = unlink(name); + + if (rv == 0) { + ctx->last_error = JLOG_ERR_SUCCESS; + return 1; + } + if (errno == ENOENT) { + ctx->last_error = JLOG_ERR_INVALID_SUBSCRIBER; + return 0; + } + return -1; +} + +int jlog_ctx_add_subscriber(jlog_ctx *ctx, const char *s, jlog_position whence) { + jlog_id chkpt; + jlog_file *jchkpt; + ctx->last_error = JLOG_ERR_SUCCESS; + + jchkpt = __jlog_open_named_checkpoint(ctx, s, O_CREAT|O_EXCL); + if(!jchkpt) { + ctx->last_error = JLOG_ERR_SUBSCRIBER_EXISTS; + ctx->last_errno = EEXIST; + return -1; + } + jlog_file_close(jchkpt); + + if(whence == JLOG_BEGIN) { + memset(&chkpt, 0, sizeof(chkpt)); + if(__jlog_set_checkpoint(ctx, s, &chkpt) != 0) { + ctx->last_error = JLOG_ERR_CHECKPOINT; + ctx->last_errno = 0; + return -1; + } + return 0; + } + ctx->last_error = JLOG_ERR_NOT_SUPPORTED; + return -1; +} + +int jlog_ctx_write(jlog_ctx *ctx, const void *data, size_t len) { + jlog_message m; + m.mess = (void *)data; + m.mess_len = len; + return jlog_ctx_write_message(ctx, &m, NULL); +} + +static int __jlog_find_first_log_after(jlog_ctx *ctx, jlog_id *chkpt, + jlog_id *start, jlog_id *finish) { + jlog_id last; + int closed; + + memcpy(start, chkpt, sizeof(*chkpt)); + attempt: + if(__jlog_resync_index(ctx, start->log, &last, &closed) != 0) { + if(ctx->last_error == JLOG_ERR_FILE_OPEN && + ctx->last_errno == ENOENT) { + /* That file doesn't exist... bad, but we can fake a recovery by + advancing the next file that does exist */ + ctx->last_error = JLOG_ERR_SUCCESS; + if(start->log >= ctx->storage.log) { + /* We don't advance past where people are writing */ + memcpy(finish, start, sizeof(*start)); + return 0; + } + start->marker = 0; + start->log++; /* BE SMARTER! */ + goto attempt; + } + return -1; /* Just persist resync's error state */ + } + + /* If someone checkpoints off the end, be nice */ + if(last.log == start->log && last.marker < start->marker) + memcpy(start, &last, sizeof(*start)); + + if(!memcmp(start, &last, sizeof(last)) && closed) { + /* the chkpt is the last of the index and the index is closed... + next please */ +#ifdef DEBUG + fprintf(stderr, "checkpoint at end of file... advancing [%08x]\n", + start->log+1); +#endif + if(start->log >= ctx->storage.log) { + /* We don't advance past where people are writing */ + memcpy(finish, start, sizeof(*start)); + return 0; + } + start->marker = 0; + start->log++; + goto attempt; + } + memcpy(finish, &last, sizeof(last)); + return 0; +} +int jlog_ctx_read_message(jlog_ctx *ctx, const jlog_id *id, jlog_message *m) { + off_t index_len; + u_int64_t data_off; + + ctx->last_error = JLOG_ERR_SUCCESS; + if (ctx->context_mode != JLOG_READ) + SYS_FAIL(JLOG_ERR_ILLEGAL_WRITE); + if (id->marker < 1) + SYS_FAIL(JLOG_ERR_ILLEGAL_LOGID); + + __jlog_open_reader(ctx, id->log); + if(!ctx->data) + SYS_FAIL(JLOG_ERR_FILE_OPEN); + __jlog_open_indexer(ctx, id->log); + if(!ctx->index) + SYS_FAIL(JLOG_ERR_IDX_OPEN); + + if ((index_len = jlog_file_size(ctx->index)) == -1) + SYS_FAIL(JLOG_ERR_IDX_SEEK); + if (index_len % sizeof(u_int64_t)) + SYS_FAIL(JLOG_ERR_IDX_CORRUPT); + if (id->marker * sizeof(u_int64_t) > index_len) + SYS_FAIL(JLOG_ERR_ILLEGAL_LOGID); + + if (!jlog_file_pread(ctx->index, &data_off, sizeof(u_int64_t), + (id->marker - 1) * sizeof(u_int64_t))) + { + SYS_FAIL(JLOG_ERR_IDX_READ); + } + if (data_off == 0 && id->marker != 1) { + if (id->marker * sizeof(u_int64_t) == index_len) { + /* close tag; not a real offset */ + SYS_FAIL(JLOG_ERR_ILLEGAL_LOGID); + } else { + /* an offset of 0 in the middle of an index means curruption */ + SYS_FAIL(JLOG_ERR_IDX_CORRUPT); + } + } + + if(__jlog_mmap_reader(ctx, id->log) != 0) + SYS_FAIL(JLOG_ERR_FILE_READ); + + if(data_off > ctx->mmap_len - sizeof(jlog_message_header)) { +#ifdef DEBUG + fprintf(stderr, "read idx off end: %llu\n", data_off); +#endif + SYS_FAIL(JLOG_ERR_IDX_CORRUPT); + } + + memcpy(&m->aligned_header, ((u_int8_t *)ctx->mmap_base) + data_off, + sizeof(jlog_message_header)); + + if(data_off + sizeof(jlog_message_header) + m->aligned_header.mlen > ctx->mmap_len) { +#ifdef DEBUG + fprintf(stderr, "read idx off end: %llu %llu\n", data_off, ctx->mmap_len); +#endif + SYS_FAIL(JLOG_ERR_IDX_CORRUPT); + } + + m->header = &m->aligned_header; + m->mess_len = m->header->mlen; + m->mess = (((u_int8_t *)ctx->mmap_base) + data_off + sizeof(jlog_message_header)); + + finish: + if(ctx->last_error == JLOG_ERR_SUCCESS) return 0; + if (ctx->last_error == JLOG_ERR_IDX_CORRUPT) { + if (jlog_file_lock(ctx->index)) { + jlog_file_truncate(ctx->index, 0); + jlog_file_unlock(ctx->index); + } + } + return -1; +} +int jlog_ctx_read_interval(jlog_ctx *ctx, jlog_id *start, jlog_id *finish) { + jlog_id chkpt; + int count = 0; + + ctx->last_error = JLOG_ERR_SUCCESS; + if(ctx->context_mode != JLOG_READ) { + ctx->last_error = JLOG_ERR_ILLEGAL_WRITE; + ctx->last_errno = EPERM; + return -1; + } + + __jlog_restore_metastore(ctx, 0); + if(jlog_get_checkpoint(ctx, ctx->subscriber_name, &chkpt)) + SYS_FAIL(JLOG_ERR_INVALID_SUBSCRIBER); + if(__jlog_find_first_log_after(ctx, &chkpt, start, finish) != 0) + goto finish; /* Leave whatever error was set in find_first_log_after */ + if(start->log != chkpt.log) start->marker = 0; + else start->marker = chkpt.marker; + if(start->log != chkpt.log) { + /* We've advanced our checkpoint, let's not do this work again */ + if(__jlog_set_checkpoint(ctx, ctx->subscriber_name, start) != 0) + SYS_FAIL(JLOG_ERR_CHECKPOINT); + } + /* Here 'start' is actually the checkpoint, so we must advance it one. + However, that may not be possible, if there are no messages, so first + make sure finish is bigger */ + count = finish->marker - start->marker; + if(finish->marker > start->marker) start->marker++; + + /* We need to munmap it, so that we can remap it with more data if needed */ + __jlog_munmap_reader(ctx); + finish: + if(ctx->last_error == JLOG_ERR_SUCCESS) return count; + return -1; +} + +int jlog_ctx_last_log_id(jlog_ctx *ctx, jlog_id *id) { + ctx->last_error = JLOG_ERR_SUCCESS; + if(ctx->context_mode != JLOG_READ) { + ctx->last_error = JLOG_ERR_ILLEGAL_WRITE; + ctx->last_errno = EPERM; + return -1; + } + if (__jlog_restore_metastore(ctx, 0) != 0) return -1; + ___jlog_resync_index(ctx, ctx->storage.log, id, NULL); + if(ctx->last_error == JLOG_ERR_SUCCESS) return 0; + return -1; +} + +/* vim:se ts=2 sw=2 et: */ diff --git a/jlog.h b/jlog.h new file mode 100644 index 0000000..ab39fe5 --- /dev/null +++ b/jlog.h @@ -0,0 +1,122 @@ +#ifndef _JLOG_H +#define _JLOG_H + +#include "jlog_config.h" + +#ifndef JLOG_API +# ifdef _WIN32 +# ifdef JLOG_EXPORTS +# define JLOG_API(x) __declspec(dllexport) x +# else +# define JLOG_API(x) __declspec(dllimport) x +# endif +# else +# define JLOG_API(x) x +# endif +#endif + +struct _jlog_ctx; +struct _jlog_message_header; +struct _jlog_id; + +typedef struct _jlog_ctx jlog_ctx; + +typedef struct _jlog_message_header { + u_int32_t reserved; + u_int32_t tv_sec; + u_int32_t tv_usec; + u_int32_t mlen; +} jlog_message_header; + +typedef struct _jlog_id { + u_int32_t log; + u_int32_t marker; +} jlog_id; + +#define JLOG_ID_ADVANCE(id) (id)->marker++ + +typedef struct _jlog_message { + jlog_message_header *header; + u_int32_t mess_len; + void *mess; + jlog_message_header aligned_header; +} jlog_message; + +typedef enum { + JLOG_BEGIN, + JLOG_END +} jlog_position; + +typedef enum { + JLOG_UNSAFE, + JLOG_ALMOST_SAFE, + JLOG_SAFE +} jlog_safety; + +typedef enum { + JLOG_ERR_SUCCESS = 0, + JLOG_ERR_ILLEGAL_INIT, + JLOG_ERR_ILLEGAL_OPEN, + JLOG_ERR_OPEN, + JLOG_ERR_NOTDIR, + JLOG_ERR_CREATE_PATHLEN, + JLOG_ERR_CREATE_EXISTS, + JLOG_ERR_CREATE_MKDIR, + JLOG_ERR_CREATE_META, + JLOG_ERR_LOCK, + JLOG_ERR_IDX_OPEN, + JLOG_ERR_IDX_SEEK, + JLOG_ERR_IDX_CORRUPT, + JLOG_ERR_IDX_WRITE, + JLOG_ERR_IDX_READ, + JLOG_ERR_FILE_OPEN, + JLOG_ERR_FILE_SEEK, + JLOG_ERR_FILE_CORRUPT, + JLOG_ERR_FILE_READ, + JLOG_ERR_FILE_WRITE, + JLOG_ERR_META_OPEN, + JLOG_ERR_ILLEGAL_WRITE, + JLOG_ERR_ILLEGAL_CHECKPOINT, + JLOG_ERR_INVALID_SUBSCRIBER, + JLOG_ERR_ILLEGAL_LOGID, + JLOG_ERR_SUBSCRIBER_EXISTS, + JLOG_ERR_CHECKPOINT, + JLOG_ERR_NOT_SUPPORTED, +} jlog_err; + +typedef void (*jlog_error_func) (void *ctx, const char *msg, ...); + +JLOG_API(jlog_ctx *) jlog_new(const char *path); +JLOG_API(void) jlog_set_error_func(jlog_ctx *ctx, jlog_error_func Func, void *ptr); +JLOG_API(size_t) jlog_raw_size(jlog_ctx *ctx); +JLOG_API(int) jlog_ctx_init(jlog_ctx *ctx); +JLOG_API(int) jlog_get_checkpoint(jlog_ctx *ctx, const char *s, jlog_id *id); +JLOG_API(int) jlog_ctx_list_subscribers_dispose(jlog_ctx *ctx, char **subs); +JLOG_API(int) jlog_ctx_list_subscribers(jlog_ctx *ctx, char ***subs); + +JLOG_API(int) jlog_ctx_err(jlog_ctx *ctx); +JLOG_API(const char *) jlog_ctx_err_string(jlog_ctx *ctx); +JLOG_API(int) jlog_ctx_errno(jlog_ctx *ctx); +JLOG_API(int) jlog_ctx_open_writer(jlog_ctx *ctx); +JLOG_API(int) jlog_ctx_open_reader(jlog_ctx *ctx, const char *subscriber); +JLOG_API(int) jlog_ctx_close(jlog_ctx *ctx); + +JLOG_API(int) jlog_ctx_alter_mode(jlog_ctx *ctx, int mode); +JLOG_API(int) jlog_ctx_alter_journal_size(jlog_ctx *ctx, size_t size); +JLOG_API(int) jlog_ctx_alter_safety(jlog_ctx *ctx, jlog_safety safety); +JLOG_API(int) jlog_ctx_add_subscriber(jlog_ctx *ctx, const char *subscriber, + jlog_position whence); +JLOG_API(int) jlog_ctx_remove_subscriber(jlog_ctx *ctx, const char *subscriber); + +JLOG_API(int) jlog_ctx_write(jlog_ctx *ctx, const void *message, size_t mess_len); +JLOG_API(int) jlog_ctx_write_message(jlog_ctx *ctx, jlog_message *msg, struct timeval *when); +JLOG_API(int) jlog_ctx_read_interval(jlog_ctx *ctx, + jlog_id *first_mess, jlog_id *last_mess); +JLOG_API(int) jlog_ctx_read_message(jlog_ctx *ctx, const jlog_id *, jlog_message *); +JLOG_API(int) jlog_ctx_read_checkpoint(jlog_ctx *ctx, const jlog_id *checkpoint); +JLOG_API(int) jlog_snprint_logid(char *buff, int n, const jlog_id *checkpoint); + +JLOG_API(int) __jlog_pending_readers(jlog_ctx *ctx, u_int32_t log); +JLOG_API(int) jlog_ctx_last_log_id(jlog_ctx *ctx, jlog_id *id); + +#endif diff --git a/jlog_config.h b/jlog_config.h new file mode 100644 index 0000000..240c036 --- /dev/null +++ b/jlog_config.h @@ -0,0 +1,147 @@ +/* jlog_config.h. Generated by configure. */ +#ifndef __JLOG_CONFIG_H +#define __JLOG_CONFIG_H + +/* define inline unless that is what the compiler already calls it. */ +/* #undef inline */ + +#define HAVE_FCNTL_H 1 +#define HAVE_SYS_TYPES_H 1 +#define HAVE_DIRENT_H 1 +#define HAVE_ERRNO_H 1 +#define HAVE_STRING_H 1 +#define HAVE_STDLIB_H 1 +#define HAVE_SYS_PARAM_H 1 +#define HAVE_TIME_H 1 +#define HAVE_SYS_STAT_H 1 +#define IFS_CH '/' + +#ifdef HAVE_STRING_H +#include +#endif +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_SYS_PARAM_H +#include +#endif +#ifdef HAVE_SYS_TYPES_H +#include +#endif +#ifdef HAVE_SYS_STAT_H +#include +#endif + +/* The number of bytes in a char. */ +#define SIZEOF_CHAR 1 + +/* The number of bytes in a int. */ +#define SIZEOF_INT 4 + +/* The number of bytes in a size_t. */ +/* #undef SIZEOF_SIZE_T */ + + +/* The number of bytes in a long int. */ +#define SIZEOF_LONG_INT 4 + +/* The number of bytes in a long long int. */ +#define SIZEOF_LONG_LONG_INT 8 + +/* The number of bytes in a short int. */ +#define SIZEOF_SHORT_INT 2 + +/* The number of bytes in a void *. */ +#define SIZEOF_VOID_P 4 + +#ifndef HAVE_U_INT +typedef unsigned int u_int; +#endif + +#define HAVE_INTXX_T 1 +#ifndef HAVE_INTXX_T +#if (SIZEOF_CHAR == 1) +typedef char int8_t; +#else +#error "8 bit int type not found." +#endif +#if (SIZEOF_SHORT_INT == 2) +typedef short int int16_t; +#else +#ifdef _CRAY +typedef long int16_t; +#else +#warning "16 bit int type not found." +#endif /* _CRAY */ +#endif +#if (SIZEOF_INT == 4) +typedef int int32_t; +#else +#ifdef _CRAY +typedef long int32_t; +#else +#error "32 bit int type not found." +#endif /* _CRAY */ +#endif +#endif + +/* If sys/types.h does not supply u_intXX_t, supply them ourselves */ +#ifndef HAVE_U_INTXX_T +#ifdef HAVE_UINTXX_T +typedef uint8_t u_int8_t; +typedef uint16_t u_int16_t; +typedef uint32_t u_int32_t; +#define HAVE_U_INTXX_T 1 +#else +#if (SIZEOF_CHAR == 1) +typedef unsigned char u_int8_t; +#else +#error "8 bit int type not found." +#endif +#if (SIZEOF_SHORT_INT == 2) +typedef unsigned short int u_int16_t; +#else +#ifdef _CRAY +typedef unsigned long u_int16_t; +#else +#warning "16 bit int type not found." +#endif +#endif +#if (SIZEOF_INT == 4) +typedef unsigned int u_int32_t; +#else +#ifdef _CRAY +typedef unsigned long u_int32_t; +#else +#error "32 bit int type not found." +#endif +#endif +#endif +#endif + +/* 64-bit types */ +#ifndef HAVE_INT64_T +#if (SIZEOF_LONG_INT == 8) +typedef long int int64_t; +#define HAVE_INT64_T 1 +#else +#if (SIZEOF_LONG_LONG_INT == 8) +typedef long long int int64_t; +#define HAVE_INT64_T 1 +#define HAVE_LONG_LONG_INT +#endif +#endif +#endif +#ifndef HAVE_U_INT64_T +#if (SIZEOF_LONG_INT == 8) +typedef unsigned long int u_int64_t; +#define HAVE_U_INT64_T 1 +#else +#if (SIZEOF_LONG_LONG_INT == 8) +typedef unsigned long long int u_int64_t; +#define HAVE_U_INT64_T 1 +#endif +#endif +#endif + +#endif diff --git a/jlog_config.h.in b/jlog_config.h.in new file mode 100644 index 0000000..543ebce --- /dev/null +++ b/jlog_config.h.in @@ -0,0 +1,146 @@ +#ifndef __JLOG_CONFIG_H +#define __JLOG_CONFIG_H + +/* define inline unless that is what the compiler already calls it. */ +#undef inline + +#undef HAVE_FCNTL_H +#undef HAVE_SYS_TYPES_H +#undef HAVE_DIRENT_H +#undef HAVE_ERRNO_H +#undef HAVE_STRING_H +#undef HAVE_STDLIB_H +#undef HAVE_SYS_PARAM_H +#undef HAVE_TIME_H +#undef HAVE_SYS_STAT_H +#define IFS_CH '/' + +#ifdef HAVE_STRING_H +#include +#endif +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_SYS_PARAM_H +#include +#endif +#ifdef HAVE_SYS_TYPES_H +#include +#endif +#ifdef HAVE_SYS_STAT_H +#include +#endif + +/* The number of bytes in a char. */ +#undef SIZEOF_CHAR + +/* The number of bytes in a int. */ +#undef SIZEOF_INT + +/* The number of bytes in a size_t. */ +#undef SIZEOF_SIZE_T + + +/* The number of bytes in a long int. */ +#undef SIZEOF_LONG_INT + +/* The number of bytes in a long long int. */ +#undef SIZEOF_LONG_LONG_INT + +/* The number of bytes in a short int. */ +#undef SIZEOF_SHORT_INT + +/* The number of bytes in a void *. */ +#undef SIZEOF_VOID_P + +#ifndef HAVE_U_INT +typedef unsigned int u_int; +#endif + +#undef HAVE_INTXX_T +#ifndef HAVE_INTXX_T +#if (SIZEOF_CHAR == 1) +typedef char int8_t; +#else +#error "8 bit int type not found." +#endif +#if (SIZEOF_SHORT_INT == 2) +typedef short int int16_t; +#else +#ifdef _CRAY +typedef long int16_t; +#else +#warning "16 bit int type not found." +#endif /* _CRAY */ +#endif +#if (SIZEOF_INT == 4) +typedef int int32_t; +#else +#ifdef _CRAY +typedef long int32_t; +#else +#error "32 bit int type not found." +#endif /* _CRAY */ +#endif +#endif + +/* If sys/types.h does not supply u_intXX_t, supply them ourselves */ +#ifndef HAVE_U_INTXX_T +#ifdef HAVE_UINTXX_T +typedef uint8_t u_int8_t; +typedef uint16_t u_int16_t; +typedef uint32_t u_int32_t; +#define HAVE_U_INTXX_T 1 +#else +#if (SIZEOF_CHAR == 1) +typedef unsigned char u_int8_t; +#else +#error "8 bit int type not found." +#endif +#if (SIZEOF_SHORT_INT == 2) +typedef unsigned short int u_int16_t; +#else +#ifdef _CRAY +typedef unsigned long u_int16_t; +#else +#warning "16 bit int type not found." +#endif +#endif +#if (SIZEOF_INT == 4) +typedef unsigned int u_int32_t; +#else +#ifdef _CRAY +typedef unsigned long u_int32_t; +#else +#error "32 bit int type not found." +#endif +#endif +#endif +#endif + +/* 64-bit types */ +#ifndef HAVE_INT64_T +#if (SIZEOF_LONG_INT == 8) +typedef long int int64_t; +#define HAVE_INT64_T 1 +#else +#if (SIZEOF_LONG_LONG_INT == 8) +typedef long long int int64_t; +#define HAVE_INT64_T 1 +#define HAVE_LONG_LONG_INT +#endif +#endif +#endif +#ifndef HAVE_U_INT64_T +#if (SIZEOF_LONG_INT == 8) +typedef unsigned long int u_int64_t; +#define HAVE_U_INT64_T 1 +#else +#if (SIZEOF_LONG_LONG_INT == 8) +typedef unsigned long long int u_int64_t; +#define HAVE_U_INT64_T 1 +#endif +#endif +#endif + +#endif diff --git a/jlog_hash.c b/jlog_hash.c new file mode 100644 index 0000000..c0de721 --- /dev/null +++ b/jlog_hash.c @@ -0,0 +1,310 @@ +/* + * Copyright (c) 2001-2006 OmniTI, Inc. All rights reserved + * + * THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF OMNITI + * The copyright notice above does not evidence any + * actual or intended publication of such source code. + * + * Redistribution of this material is strictly prohibited. + * + */ + +#include "jlog_config.h" +#include "jlog_hash.h" + +/* This is from http://burtleburtle.net/bob/hash/doobs.html */ + +#define JLogHASH_INITIAL_SIZE (1<<7) + +#define mix(a,b,c) \ +{ \ + a -= b; a -= c; a ^= (c>>13); \ + b -= c; b -= a; b ^= (a<<8); \ + c -= a; c -= b; c ^= (b>>13); \ + a -= b; a -= c; a ^= (c>>12); \ + b -= c; b -= a; b ^= (a<<16); \ + c -= a; c -= b; c ^= (b>>5); \ + a -= b; a -= c; a ^= (c>>3); \ + b -= c; b -= a; b ^= (a<<10); \ + c -= a; c -= b; c ^= (b>>15); \ +} + +static inline +u_int32_t __hash(const char *k, u_int32_t length, u_int32_t initval) +{ + register u_int32_t a,b,c,len; + + /* Set up the internal state */ + len = length; + a = b = 0x9e3779b9; /* the golden ratio; an arbitrary value */ + c = initval; /* the previous hash value */ + + /*---------------------------------------- handle most of the key */ + while (len >= 12) + { + a += (k[0] +((u_int32_t)k[1]<<8) +((u_int32_t)k[2]<<16) +((u_int32_t)k[3]<<24)); + b += (k[4] +((u_int32_t)k[5]<<8) +((u_int32_t)k[6]<<16) +((u_int32_t)k[7]<<24)); + c += (k[8] +((u_int32_t)k[9]<<8) +((u_int32_t)k[10]<<16)+((u_int32_t)k[11]<<24)); + mix(a,b,c); + k += 12; len -= 12; + } + + /*------------------------------------- handle the last 11 bytes */ + c += length; + switch(len) /* all the case statements fall through */ + { + case 11: c+=((u_int32_t)k[10]<<24); + case 10: c+=((u_int32_t)k[9]<<16); + case 9 : c+=((u_int32_t)k[8]<<8); + /* the first byte of c is reserved for the length */ + case 8 : b+=((u_int32_t)k[7]<<24); + case 7 : b+=((u_int32_t)k[6]<<16); + case 6 : b+=((u_int32_t)k[5]<<8); + case 5 : b+=k[4]; + case 4 : a+=((u_int32_t)k[3]<<24); + case 3 : a+=((u_int32_t)k[2]<<16); + case 2 : a+=((u_int32_t)k[1]<<8); + case 1 : a+=k[0]; + /* case 0: nothing left to add */ + } + mix(a,b,c); + /*-------------------------------------------- report the result */ + return c; +} + +u_int32_t jlog_hash__hash(const char *k, u_int32_t length, u_int32_t initval) { + return __hash(k,length,initval); +} + +void jlog_hash_init(jlog_hash_table *h) { + memset(h, 0, sizeof(jlog_hash_table)); + h->initval = lrand48(); + h->table_size = JLogHASH_INITIAL_SIZE; + h->buckets = calloc(h->table_size, sizeof(jlog_hash_bucket *)); +} + +jlog_hash_bucket *jlog_hash__new_bucket(const char *k, int klen, void *data) { + jlog_hash_bucket *b; + b = calloc(1, sizeof(jlog_hash_bucket)); + b->k = k; + b->klen = klen; + b->data = data; + return b; +} +void jlog_hash__rebucket(jlog_hash_table *h, int newsize) { + int i, newoff; + jlog_hash_bucket **newbuckets, *b, *n; + + if (h->dont_rebucket) return; + + i = newsize; + while(i) { + if(i & 1) break; + i >>= 1; + } + if(i & ~1) { + return; + } + newbuckets = calloc(newsize, sizeof(jlog_hash_bucket *)); + h->num_used_buckets = 0; + for(i = 0; i < h->table_size; i++) { + b = h->buckets[i]; + while(b) { + n = b->next; + newoff = __hash(b->k, b->klen, h->initval) & (newsize-1); + if(newbuckets[newoff] == NULL) h->num_used_buckets++; + b->next = newbuckets[newoff]; + newbuckets[newoff] = b; + b = n; + } + } + free(h->buckets); + h->table_size = newsize; + h->buckets = newbuckets; +} +int jlog_hash_replace(jlog_hash_table *h, const char *k, int klen, void *data, + JLogHashFreeFunc keyfree, JLogHashFreeFunc datafree) { + int off; + int replaced = 0; + jlog_hash_bucket __b, *tr, *b = &__b; + + if(h->table_size == 0) jlog_hash_init(h); + off = __hash(k, klen, h->initval) & (h->table_size-1); + __b.next = h->buckets[off]; + if(!b->next) h->num_used_buckets++; + while(b->next) { + if(b->next->klen == klen && !memcmp(b->next->k, k, klen)) { + tr = b->next; + if(keyfree) keyfree((void *)tr->k); + if(datafree && tr->data) datafree((void *)tr->data); + b->next = tr->next; + if(tr == h->buckets[off]) h->buckets[off] = tr->next; + free(tr); + replaced = 1; + break; + } else { + b = b->next; + } + } + b = jlog_hash__new_bucket(k, klen, data); + b->next = h->buckets[off]; + h->buckets[off] = b; + if(!replaced) h->size++; + + if(h->size > h->table_size - (h->table_size >> 3)) { + jlog_hash__rebucket(h, h->table_size << 1); + } + return 1; +} +int jlog_hash_store(jlog_hash_table *h, const char *k, int klen, void *data) { + int off; + jlog_hash_bucket *b; + + if(h->table_size == 0) jlog_hash_init(h); + off = __hash(k, klen, h->initval) & (h->table_size-1); + b = h->buckets[off]; + if(!b) h->num_used_buckets++; + while(b) { + if(b->klen == klen && !memcmp(b->k, k, klen)) return 0; + b = b->next; + } + b = jlog_hash__new_bucket(k, klen, data); + b->next = h->buckets[off]; + h->buckets[off] = b; + h->size++; + + if(h->size > h->table_size - (h->table_size >> 3)) { + jlog_hash__rebucket(h, h->table_size << 1); + } + return 1; +} +int jlog_hash_retrieve(jlog_hash_table *h, const char *k, int klen, void **data) { + int off; + jlog_hash_bucket *b; + + if(h->table_size == 0) jlog_hash_init(h); + off = __hash(k, klen, h->initval) & (h->table_size-1); + b = h->buckets[off]; + while(b) { + if(b->klen == klen && !memcmp(b->k, k, klen)) break; + b = b->next; + } + if(b) { + if(data) *data = b->data; + return 1; + } + return 0; +} +int jlog_hash_delete(jlog_hash_table *h, const char *k, int klen, + JLogHashFreeFunc keyfree, JLogHashFreeFunc datafree) { + int off; + void *data; + jlog_hash_bucket *b, *prev = NULL; + + if(h->table_size == 0) jlog_hash_init(h); + off = __hash(k, klen, h->initval) & (h->table_size-1); + b = h->buckets[off]; + while(b) { + if(b->klen == klen && !memcmp(b->k, k, klen)) break; + prev = b; + b = b->next; + } + if(!b) return 0; /* No match */ + data = b->data; + if(!prev) h->buckets[off] = h->buckets[off]->next; + else prev->next = b->next; + if(keyfree) keyfree((void *)b->k); + if(datafree && b->data) datafree(b->data); + free(b); + h->size--; + if(h->buckets[off] == NULL) h->num_used_buckets--; + if(h->table_size > JLogHASH_INITIAL_SIZE && + h->size < h->table_size >> 2) + jlog_hash__rebucket(h, h->table_size >> 1); + return 1; +} + +void jlog_hash_delete_all(jlog_hash_table *h, JLogHashFreeFunc keyfree, JLogHashFreeFunc datafree) { + int i; + jlog_hash_bucket *b, *tofree; + for(i=0; itable_size; i++) { + b = h->buckets[i]; + while(b) { + tofree = b; + b = b->next; + if(keyfree) keyfree((void *)tofree->k); + if(datafree && tofree->data) datafree(tofree->data); + free(tofree); + } + h->buckets[i] = NULL; + } + h->num_used_buckets = 0; + h->size = 0; + jlog_hash__rebucket(h, JLogHASH_INITIAL_SIZE); +} + +void jlog_hash_destroy(jlog_hash_table *h, JLogHashFreeFunc keyfree, JLogHashFreeFunc datafree) { + jlog_hash_delete_all(h, keyfree, datafree); + if(h->buckets) free(h->buckets); +} + +int jlog_hash_next(jlog_hash_table *h, jlog_hash_iter *iter, + const char **k, int *klen, void **data) { + jlog_hash_bucket *b; + next_row: + if(iter->p1 < 0 || iter->p1 >= h->table_size) return 0; + if(iter->p2 == NULL) iter->p2 = (void *)h->buckets[iter->p1]; + if(iter->p2 == NULL) { + iter->p1++; + goto next_row; + } + b = (jlog_hash_bucket *)(iter->p2); + *k = b->k; *klen = b->klen; + if(data) *data = b->data; + b = b->next; + if(!b) iter->p1++; + iter->p2 = b; + return 1; +} + +int jlog_hash_firstkey(jlog_hash_table *h, const char **k, int *klen) { + int i; + for(i=0;itable_size;i++) { + if(h->buckets[i]) { + *k = h->buckets[i]->k; + *klen = h->buckets[i]->klen; + return 1; + } + } + return 0; +} +int jlog_hash_nextkey(jlog_hash_table *h, const char **k, int *klen, const char *lk, int lklen) { + int off; + jlog_hash_bucket *b; + + if(h->table_size == 0) return 0; + off = __hash(lk, lklen, h->initval) & (h->table_size-1); + b = h->buckets[off]; + while(b) { + if(b->klen == lklen && !memcmp(b->k, lk, lklen)) break; + b = b->next; + } + if(b) { + if(b->next) { + *k = b->next->k; + *klen = b->next->klen; + return 1; + } else { + off++; + for(;off < h->table_size; off++) { + if(h->buckets[off]) { + *k = h->buckets[off]->k; + *klen = h->buckets[off]->klen; + return 1; + } + } + } + } + return 0; +} +/* vim: se sw=2 ts=2 et: */ diff --git a/jlog_hash.h b/jlog_hash.h new file mode 100644 index 0000000..4a8bb80 --- /dev/null +++ b/jlog_hash.h @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2001-2007 OmniTI, Inc. All rights reserved + * + * THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF OMNITI + * The copyright notice above does not evidence any + * actual or intended publication of such source code. + * + * Redistribution of this material is strictly prohibited. + * + */ + +#ifndef _JLOG_HASH_H +#define _JLOG_HASH_H + +#include "jlog_config.h" + +typedef void (*JLogHashFreeFunc)(void *); + +typedef struct _jlog_hash_bucket { + const char *k; + int klen; + void *data; + struct _jlog_hash_bucket *next; +} jlog_hash_bucket; + +typedef struct { + jlog_hash_bucket **buckets; + u_int32_t table_size; + u_int32_t initval; + u_int32_t num_used_buckets; + u_int32_t size; + unsigned dont_rebucket:1; + unsigned _spare:31; +} jlog_hash_table; + +#define JLOG_HASH_EMPTY { NULL, 0, 0, 0, 0, 0, 0 } + +typedef struct { + void *p2; + int p1; +} jlog_hash_iter; + +#define JLOG_HASH_ITER_ZERO { NULL, 0 } + +void jlog_hash_init(jlog_hash_table *h); +/* NOTE! "k" and "data" MUST NOT be transient buffers, as the hash table + * implementation does not duplicate them. You provide a pair of + * JLogHashFreeFunc functions to free up their storage when you call + * jlog_hash_delete(), jlog_hash_delete_all() or jlog_hash_destroy(). + * */ +int jlog_hash_store(jlog_hash_table *h, const char *k, int klen, void *data); +int jlog_hash_replace(jlog_hash_table *h, const char *k, int klen, void *data, + JLogHashFreeFunc keyfree, JLogHashFreeFunc datafree); +int jlog_hash_retrieve(jlog_hash_table *h, const char *k, int klen, void **data); +int jlog_hash_delete(jlog_hash_table *h, const char *k, int klen, + JLogHashFreeFunc keyfree, JLogHashFreeFunc datafree); +void jlog_hash_delete_all(jlog_hash_table *h, JLogHashFreeFunc keyfree, + JLogHashFreeFunc datafree); +void jlog_hash_destroy(jlog_hash_table *h, JLogHashFreeFunc keyfree, + JLogHashFreeFunc datafree); + +/* This is an iterator and requires the hash to not be written to during the + iteration process. + To use: + jlog_hash_iter iter = JLOG_HASH_ITER_ZERO; + + const char *k; + int klen; + void *data; + + while(jlog_hash_next(h, &iter, &k, &klen, &data)) { + .... use k, klen and data .... + } +*/ +int jlog_hash_next(jlog_hash_table *h, jlog_hash_iter *iter, + const char **k, int *klen, void **data); +int jlog_hash_firstkey(jlog_hash_table *h, const char **k, int *klen); +int jlog_hash_nextkey(jlog_hash_table *h, const char **k, int *klen, const char *lk, int lklen); + +/* This function serves no real API use sans calculating expected buckets + for keys (or extending the hash... which is unsupported) */ +u_int32_t jlog_hash__hash(const char *k, u_int32_t length, u_int32_t initval); +jlog_hash_bucket *jlog_hash__new_bucket(const char *k, int klen, void *data); +void jlog_hash__rebucket(jlog_hash_table *h, int newsize); +#endif diff --git a/jlog_io.c b/jlog_io.c new file mode 100644 index 0000000..939aed2 --- /dev/null +++ b/jlog_io.c @@ -0,0 +1,212 @@ +#include "jlog_config.h" +#include "jlog_hash.h" +#include "jlog_io.h" +#include +#include +#include +#include +#include +#include +#include + +static pthread_mutex_t jlog_files_lock = PTHREAD_MUTEX_INITIALIZER; +static jlog_hash_table jlog_files = JLOG_HASH_EMPTY; + +typedef struct { + dev_t st_dev; + ino_t st_ino; +} jlog_file_id; + +struct _jlog_file { + jlog_file_id id; + int fd; + int refcnt; + int locked; + pthread_mutex_t lock; +}; + +jlog_file *jlog_file_open(const char *path, int flags, int mode) +{ + struct stat sb; + jlog_file_id id; + jlog_file *f = NULL; + union { + jlog_file *f; + void *vptr; + } pun; + int fd, realflags = O_RDWR; + + if (flags & O_CREAT) realflags |= O_CREAT; + if (flags & O_EXCL) realflags |= O_EXCL; + + if (pthread_mutex_lock(&jlog_files_lock) != 0) return NULL; + + if (stat(path, &sb) == 0) { + if (!S_ISREG(sb.st_mode)) goto out; + id.st_dev = sb.st_dev; + id.st_ino = sb.st_ino; + if (jlog_hash_retrieve(&jlog_files, (void *)&id, sizeof(jlog_file_id), + &pun.vptr)) + { + if (!(flags & O_EXCL)) { + f = pun.f; + f->refcnt++; + } + goto out; + } + } + + if ((fd = open(path, realflags, mode)) == -1) goto out; + if (fstat(fd, &sb) != 0) { + while (close(fd) == -1 && errno == EINTR) ; + goto out; + } + id.st_dev = sb.st_dev; + id.st_ino = sb.st_ino; + if (!(f = malloc(sizeof(jlog_file)))) { + while (close(fd) == -1 && errno == EINTR) ; + goto out; + } + memset(f, 0, sizeof(jlog_file)); + f->id = id; + f->fd = fd; + f->refcnt = 1; + f->locked = 0; + pthread_mutex_init(&(f->lock), NULL); + if (!jlog_hash_store(&jlog_files, (void *)&f->id, sizeof(jlog_file_id), f)) { + while (close(f->fd) == -1 && errno == EINTR) ; + free(f); + f = NULL; + } +out: + pthread_mutex_unlock(&jlog_files_lock); + return f; +} + +int jlog_file_close(jlog_file *f) +{ + if (pthread_mutex_lock(&jlog_files_lock) != 0) return 0; + if (--f->refcnt == 0) { + assert(jlog_hash_delete(&jlog_files, (void *)&f->id, sizeof(jlog_file_id), + NULL, NULL)); + while (close(f->fd) == -1 && errno == EINTR) ; + free(f); + } + pthread_mutex_unlock(&jlog_files_lock); + return 1; +} + +int jlog_file_lock(jlog_file *f) +{ + struct flock fl; + int frv; + + memset(&fl, 0, sizeof(fl)); + fl.l_type = F_WRLCK; + fl.l_whence = SEEK_SET; + fl.l_start = 0; + fl.l_len = 0; + + if (pthread_mutex_lock(&(f->lock)) != 0) return 0; + while ((frv = fcntl(f->fd, F_SETLKW, &fl)) == -1 && errno == EINTR) ; + if (frv != 0) { + int save = errno; + pthread_mutex_unlock(&(f->lock)); + errno = save; + return 0; + } + f->locked = 1; + return 1; +} + +int jlog_file_unlock(jlog_file *f) +{ + struct flock fl; + int frv; + + if (!f->locked) return 0; + + memset(&fl, 0, sizeof(fl)); + fl.l_type = F_UNLCK; + fl.l_whence = SEEK_SET; + fl.l_start = 0; + fl.l_len = 0; + + while ((frv = fcntl(f->fd, F_SETLKW, &fl)) == -1 && errno == EINTR) ; + if (frv != 0) return 0; + f->locked = 0; + pthread_mutex_unlock(&(f->lock)); + return 1; +} + +int jlog_file_pread(jlog_file *f, void *buf, size_t nbyte, off_t offset) +{ + while (nbyte > 0) { + ssize_t rv = pread(f->fd, buf, nbyte, offset); + if (rv == -1 && errno == EINTR) continue; + if (rv <= 0) return 0; + nbyte -= rv; + offset += rv; + } + return 1; +} + +int jlog_file_pwrite(jlog_file *f, const void *buf, size_t nbyte, off_t offset) +{ + while (nbyte > 0) { + ssize_t rv = pwrite(f->fd, buf, nbyte, offset); + if (rv == -1 && errno == EINTR) continue; + if (rv <= 0) return 0; + nbyte -= rv; + offset += rv; + } + return 1; +} + +int jlog_file_sync(jlog_file *f) +{ + int rv; + +#ifdef HAVE_FDATASYNC + while((rv = fdatasync(f->fd)) == -1 && errno == EINTR) ; +#else + while((rv = fsync(f->fd)) == -1 && errno == EINTR) ; +#endif + if (rv == 0) return 1; + return 0; +} + +int jlog_file_map_read(jlog_file *f, void **base, size_t *len) +{ + struct stat sb; + void *my_map; + int flags = 0; + +#ifdef MAP_SHARED + flags = MAP_SHARED; +#endif + if (fstat(f->fd, &sb) != 0) return 0; + my_map = mmap(NULL, sb.st_size, PROT_READ, flags, f->fd, 0); + if (my_map == MAP_FAILED) return 0; + *base = my_map; + *len = sb.st_size; + return 1; +} + +off_t jlog_file_size(jlog_file *f) +{ + struct stat sb; + if (fstat(f->fd, &sb) != 0) + return -1; + return sb.st_size; +} + +int jlog_file_truncate(jlog_file *f, off_t len) +{ + int rv; + while ((rv = ftruncate(f->fd, len)) == -1 && errno == EINTR) ; + if (rv == 0) return 1; + return 0; +} + +/* vim:se ts=2 sw=2 et: */ diff --git a/jlog_io.h b/jlog_io.h new file mode 100644 index 0000000..a76ceb6 --- /dev/null +++ b/jlog_io.h @@ -0,0 +1,91 @@ +#ifndef _JLOG_IO_H_ +#define _JLOG_IO_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct _jlog_file jlog_file; + +/** + * opens a jlog_file + * + * since a jlog_file is a shared handle potentially used by many threads, + * the underlying open mode is always O_RDWR; only the O_CREAT and O_EXCL + * flags are honored + * @return pointer to jlog_file on success, NULL on failure + * @internal + */ +jlog_file *jlog_file_open(const char *path, int flags, int mode); + +/** + * closes a jlog_file + * @return 1 on success, 0 on failure + * @internal + */ +int jlog_file_close(jlog_file *f); + +/** + * exclusively locks a jlog_file against other processes and threads + * @return 1 on success, 0 on failure + * @internal + */ +int jlog_file_lock(jlog_file *f); + +/** + * unlocks a jlog_file + * @return 1 on success, 0 on failure + * @internal + */ +int jlog_file_unlock(jlog_file *f); + +/** + * preads from a jlog_file, retries EINTR + * @return 1 if the read was fully satisfied, 0 otherwise + * @internal + */ +int jlog_file_pread(jlog_file *f, void *buf, size_t nbyte, off_t offset); + +/** + * pwrites to a jlog_file, retries EINTR + * @return 1 if the write was fully satisfied, 0 otherwise + * @internal + */ +int jlog_file_pwrite(jlog_file *f, const void *buf, size_t nbyte, off_t offset); + +/** + * calls fdatasync (if avalable) or fsync on the underlying filehandle + * @return 1 on success, 0 on failure + * @internal + */ +int jlog_file_sync(jlog_file *f); + +/** + * maps the entirety of a jlog_file into memory for reading + * @param[out] map is set to the base of the mapped region + * @param[out] len is set to the length of the mapped region + * @return 1 on success, 0 on failure + * @internal + */ +int jlog_file_map_read(jlog_file *f, void **base, size_t *len); + +/** + * gives the size of a jlog_file + * @return size of file on success, -1 on failure + * @internal + */ +off_t jlog_file_size(jlog_file *f); + +/** + * truncates a jlog_file, retries EINTR + * @return 1 on success, 0 on failure + * @internal + */ +int jlog_file_truncate(jlog_file *f, off_t len); + +#ifdef __cplusplus +} /* Close scope of 'extern "C"' declaration which encloses file. */ +#endif + +#endif /* _JLOG_IO_H_ */ +/* vim:se ts=2 sw=2 et: */ diff --git a/jlog_private.h b/jlog_private.h new file mode 100644 index 0000000..08ee93a --- /dev/null +++ b/jlog_private.h @@ -0,0 +1,111 @@ +#ifndef _JLOG_PRIVATE_H +#define _JLOG_PRIVATE_H +/* vim:se ts=2 sw=2 et: */ + +#include "jlog_config.h" +#include "jlog.h" +#include "jlog_io.h" + +#define DEFAULT_FILE_MODE 0640 +#define DEFAULT_UNIT_LIMIT (4*1024*1024) + /* 4 Megabytes */ +#define DEFAULT_SAFETY JLOG_ALMOST_SAFE +#define INDEX_EXT ".idx" +#define MAXLOGPATHLEN (MAXPATHLEN - (8+sizeof(INDEX_EXT))) + +static const char __jlog_hexchars[] = "0123456789abcdef"; + +typedef enum { + JLOG_NEW = 0, + JLOG_INIT, + JLOG_READ, + JLOG_APPEND, + JLOG_INVALID +} jlog_mode; + +struct _jlog_ctx { + jlog_safety safety; + jlog_mode context_mode; + size_t unit_limit; + char *path; + int file_mode; + u_int32_t current_log; + jlog_file *data; + jlog_file *index; + jlog_file *checkpoint; + jlog_file *metastore; + void *mmap_base; + size_t mmap_len; + jlog_id storage; + char *subscriber_name; + int last_error; + int last_errno; + jlog_error_func error_func; + void *error_ctx; +}; + +struct _jlog_meta_info { + u_int32_t storage_log; + u_int32_t unit_limit; + u_int32_t safety; +}; + +/* macros */ + +#define STRLOGID(s, logid) do { \ + int __i; \ + for(__i=0;__i<8;__i++) \ + (s)[__i] = __jlog_hexchars[(logid >> (32 - ((__i+1)*4))) & 0xf]; \ + (s)[__i] = '\0'; \ +} while(0) + +#define STRSETDATAFILE(ctx, file, log) do { \ + int __len; \ + __len = strlen((ctx)->path); \ + strcpy((file), (ctx)->path); \ + (file)[__len] = IFS_CH; \ + STRLOGID((file)+(__len+1), log); \ +} while(0) + +#define SYS_FAIL_EX(a, dowarn) do { \ + if (ctx) { \ + ctx->last_error = (a); \ + ctx->last_errno = errno; \ + if(ctx->error_func && dowarn) { \ + ctx->error_func(ctx->error_ctx, \ + "JLOG-%d error: %d (%s) errno: %d (%s)\n", __LINE__, \ + ctx->last_error, jlog_ctx_err_string(ctx), \ + ctx->last_errno, strerror(ctx->last_errno)); \ + } \ + } \ + goto finish; \ +} while(0) + +#define SYS_FAIL(a) SYS_FAIL_EX(a, 1) + +/** + * repairs a damaged datafile + * @return 0 OK, >0 number of damaged segments removed, -1 repair failed + * @internal + */ +JLOG_API(int) jlog_repair_datafile(jlog_ctx *ctx, u_int32_t log); +/** + * prints detailed info about the log segment to stderr + * @return 0 OK, 1 segment damaged, -1 other error + * @internal + */ +JLOG_API(int) jlog_inspect_datafile(jlog_ctx *ctx, u_int32_t log); +/** + * fetches the last marker in the index and the closedness thereof + * @return 0 OK, -1 error + * @internal + */ +JLOG_API(int) jlog_idx_details(jlog_ctx *ctx, u_int32_t log, + u_int32_t *marker, int *closed); + + +#ifdef _WIN32 +#include "ec_win32.h" +#endif + +#endif diff --git a/jlogctl.c b/jlogctl.c new file mode 100644 index 0000000..509f714 --- /dev/null +++ b/jlogctl.c @@ -0,0 +1,256 @@ +/* + * Copyright (c) 2001-2006 OmniTI, Inc. All rights reserved + * + * THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF OMNITI + * The copyright notice above does not evidence any + * actual or intended publication of such source code. + * + * Redistribution of this material is strictly prohibited. + * + */ + +#include "jlog_config.h" +#include "jlog_private.h" +#include "getopt_long.h" +#include +#if HAVE_ERRNO_H +#include +#endif +#if HAVE_DIRENT_H +#include +#endif + +static int verbose = 0; +static int show_progress = 0; +static int show_subscribers = 0; +static int show_files = 0; +static int show_index_info = 0; +static int analyze_datafiles = 0; +static int repair_datafiles = 0; +static int cleanup = 0; +static int quiet = 0; +static char *add_subscriber = NULL; +static char *remove_subscriber = NULL; + +static void usage(const char *prog) { + printf("Usage:\n %s logpath1 [logpath2 [...]]\n", + prog); + printf("\t-a :\tAdd as a log subscriber\n"); + printf("\t-e :\tErase as a log subscriber\n"); + printf("\t-p :\tShow the perspective of the subscriber \n"); + printf("\t -l:\tList all log segments with sizes and readers\n"); + printf("\t -i:\tList index information\n"); + printf("\t -c:\tClean all log segments with no pending readers\n"); + printf("\t -s:\tShow all subscribers\n"); + printf("\t -d:\tAnalyze datafiles\n"); + printf("\t -r:\tAnalyze datafiles and repair if needed\n"); + printf("\t -v:\tVerbose output\n"); + printf("\nWARNING: the -r option can't be used on jlogs that are " + "open by another process\n"); +} +static int is_datafile(const char *f, u_int32_t *logid) { + int i; + u_int32_t l = 0; + for(i=0; i<8; i++) { + if((f[i] >= '0' && f[i] <= '9') || + (f[i] >= 'a' && f[i] <= 'f')) { + l <<= 4; + l |= (f[i] < 'a') ? (f[i] - '0') : (f[i] - 'a' + 10); + } + else + return 0; + } + if(f[i] != '\0') return 0; + if(logid) *logid = l; + return 1; +} +static void analyze_datafile(jlog_ctx *ctx, u_int32_t logid) { + char idxfile[MAXPATHLEN]; + + if (jlog_inspect_datafile(ctx, logid) > 0) { + fprintf(stderr, "One or more errors were found.\n"); + if(repair_datafiles) { + jlog_repair_datafile(ctx, logid); + fprintf(stderr, + "Log file reconstructed, deleting the corresponding idx file.\n"); + STRSETDATAFILE(ctx, idxfile, logid); + strcat(idxfile, INDEX_EXT); + unlink(idxfile); + } + } +} +static void process_jlog(const char *file, const char *sub) { + jlog_ctx *log; + log = jlog_new(file); + + if(add_subscriber) { + if(jlog_ctx_add_subscriber(log, add_subscriber, JLOG_BEGIN)) { + fprintf(stderr, "Could not add subscriber '%s': %s\n", add_subscriber, + jlog_ctx_err_string(log)); + } else { + if(!quiet) printf("Added subscriber '%s'\n", add_subscriber); + } + } + if(remove_subscriber) { + if(jlog_ctx_remove_subscriber(log, remove_subscriber) <= 0) { + fprintf(stderr, "Could not erase subscriber '%s': %s\n", + remove_subscriber, jlog_ctx_err_string(log)); + } else { + if(!quiet) printf("Erased subscriber '%s'\n", remove_subscriber); + } + } + if(!sub) { + if(jlog_ctx_open_writer(log)) { + fprintf(stderr, "error opening '%s'\n", file); + return; + } + } else { + if(jlog_ctx_open_reader(log, sub)) { + fprintf(stderr, "error opening '%s'\n", file); + return; + } + } + if(show_progress) { + jlog_id id, id2, id3; + char buff[20], buff2[20], buff3[20]; + jlog_get_checkpoint(log, sub, &id); + if(jlog_ctx_last_log_id(log, &id3)) { + fprintf(stderr, "jlog_error: %s\n", jlog_ctx_err_string(log)); + fprintf(stderr, "error callign jlog_ctx_last_log_id\n"); + } + jlog_snprint_logid(buff, sizeof(buff), &id); + jlog_snprint_logid(buff3, sizeof(buff3), &id3); + if(!quiet) printf("--------------------\n"); + if(!quiet) printf(" Perspective of the '%s' subscriber\n", sub); + if(!quiet) printf(" current checkpoint: %s\n", buff); + if(!quiet) printf("Last write: %s\n", buff3); + if(jlog_ctx_read_interval(log, &id, &id2) < 0) { + fprintf(stderr, "jlog_error: %s\n", jlog_ctx_err_string(log)); + } + jlog_snprint_logid(buff, sizeof(buff), &id); + jlog_snprint_logid(buff2, sizeof(buff2), &id2); + if(!quiet) printf("\t next interval: [%s, %s]\n", buff, buff2); + if(!quiet) printf("--------------------\n\n"); + } + if(show_subscribers) { + char **list; + int i; + jlog_ctx_list_subscribers(log, &list); + for(i=0; list[i]; i++) { + jlog_id id; + char buff[20]; + jlog_get_checkpoint(log, list[i], &id); + jlog_snprint_logid(buff, sizeof(buff), &id); + if(!quiet) printf("\t%32s @ %s\n", list[i], buff); + } + jlog_ctx_list_subscribers_dispose(log, list); + } + if(show_files) { + DIR *dir; + struct dirent *de; + dir = opendir(file); + if(!dir) { + fprintf(stderr, "error opening '%s'\n", file); + return; + } + while((de = readdir(dir)) != NULL) { + u_int32_t logid; + if(is_datafile(de->d_name, &logid)) { + char fullfile[MAXPATHLEN]; + char fullidx[MAXPATHLEN]; + struct stat st; + int readers; + snprintf(fullfile, sizeof(fullfile), "%s/%s", file, de->d_name); + snprintf(fullidx, sizeof(fullidx), "%s/%s" INDEX_EXT, file, de->d_name); + if(stat(fullfile, &st)) { + if(!quiet) printf("\t%8s [error statting file: %s\n", de->d_name, strerror(errno)); + } else { + readers = __jlog_pending_readers(log, logid); + if(!quiet) printf("\t%8s [%9llu bytes] %d pending readers\n", + de->d_name, st.st_size, readers); + if(show_index_info && !quiet) { + struct stat sb; + if (stat(fullidx, &sb)) { + printf("\t\t idx: none\n"); + } else { + u_int32_t marker; + int closed; + if (jlog_idx_details(log, logid, &marker, &closed)) { + printf("\t\t idx: error\n"); + } else { + printf("\t\t idx: %u messages (%08x), %s\n", + marker, marker, closed?"closed":"open"); + } + } + } + if (analyze_datafiles) analyze_datafile(log, logid); + if((readers == 0) && cleanup) { + unlink(fullfile); + unlink(fullidx); + } + } + } + } + closedir(dir); + } + jlog_ctx_close(log); +} +int main(int argc, char **argv) { + int i, c; + int option_index = 0; + char *subscriber = NULL; + while((c = getopt_long(argc, argv, "a:e:dsilrcp:v", + NULL, &option_index)) != EOF) { + switch(c) { + case 'v': + verbose = 1; + break; + case 'i': + show_files = 1; + show_index_info = 1; + break; + case 'r': + show_files = 1; + analyze_datafiles = 1; + repair_datafiles = 1; + break; + case 'd': + show_files = 1; + analyze_datafiles = 1; + break; + case 'a': + add_subscriber = optarg; + break; + case 'e': + remove_subscriber = optarg; + break; + case 'p': + show_progress = 1; + subscriber = optarg; + break; + case 's': + show_subscribers = 1; + break; + case 'c': + show_files = 1; + quiet = 1; + cleanup = 1; + break; + case 'l': + show_files = 1; + break; + default: + usage(argv[0]); + exit(-1); + } + } + if(optind == argc) { + usage(argv[0]); + exit(-1); + } + for(i=optind; i\n\twrite \n"); +} +void jcreate() { + ctx = jlog_new(LOGNAME); + jlog_ctx_alter_journal_size(ctx, 1024); + if(jlog_ctx_init(ctx) != 0) { + fprintf(stderr, "jlog_ctx_init failed: %d %s\n", jlog_ctx_err(ctx), jlog_ctx_err_string(ctx)); + } else { + jlog_ctx_add_subscriber(ctx, SUBSCRIBER, JLOG_BEGIN); + } + jlog_ctx_close(ctx); +} + +void jopenw(char *foo, int count) { + int i; + ctx = jlog_new(LOGNAME); + if(jlog_ctx_open_writer(ctx) != 0) { + fprintf(stderr, "jlog_ctx_open_writer failed: %d %s\n", jlog_ctx_err(ctx), jlog_ctx_err_string(ctx)); + exit(-1); + } + for(i=0; i 0) { + if((count = jlog_ctx_read_interval(ctx, &begin, &end)) == -1) { + fprintf(stderr, "jlog_ctx_read_interval failed: %d %s\n", jlog_ctx_err(ctx), jlog_ctx_err_string(ctx)); + exit(-1); + } + jlog_snprint_logid(begins, sizeof(begins), &begin); + jlog_snprint_logid(ends, sizeof(ends), &end); + /* printf("reader [%s] (%s, %s] count: %d\n", s, begins, ends, count); */ + if(count > 0) { + int i; + count = MIN(count, expect); + for(i=0; i= argc) { usage(); exit(-1); } + i++; + len = atoi(argv[i]); + message = malloc(len+1); + memset(message, 'X', len-1); + message[len-1] = '\n'; + message[len] = '\0'; + i++; + jopenw(message, atoi(argv[i])); + exit(0); + } else if(!strcmp(argv[i], "read")) { + if(i+1 >= argc) { usage(); exit(-1); } + i++; + jopenr(SUBSCRIBER, atoi(argv[i])); + exit(0); + } else { + fprintf(stderr, "command '%s' not understood\n", argv[i]); + usage(); + } + } + return 0; +} diff --git a/jthreadtest.c b/jthreadtest.c new file mode 100644 index 0000000..72b4436 --- /dev/null +++ b/jthreadtest.c @@ -0,0 +1,218 @@ +#include + +#include "jlog_config.h" +#include "jlog.h" + +#define MASTER "master" +#define LOGNAME "/tmp/jtest.foo" + +int writer_done = 0; +int only_read = 0; +int only_write = 0; + +static void _croak(int lineno) +{ + fprintf(stderr, "croaked at line %d\n", lineno); + exit(2); +} +#define croak() _croak(__LINE__) + +void jcreate(jlog_safety s) { + jlog_ctx *ctx; + const char *label = NULL; + + switch (s) { + case JLOG_ALMOST_SAFE: label = "almost safe"; break; + case JLOG_UNSAFE: label = "unsafe"; break; + case JLOG_SAFE: label = "safe"; break; + } + fprintf(stderr, "jcreate %s in %s mode\n", LOGNAME, label); + + ctx = jlog_new(LOGNAME); + jlog_ctx_alter_journal_size(ctx, 102400); + jlog_ctx_alter_safety(ctx, s); + if(jlog_ctx_init(ctx) != 0) { + fprintf(stderr, "jlog_ctx_init failed: %d %s\n", jlog_ctx_err(ctx), jlog_ctx_err_string(ctx)); + if(jlog_ctx_err(ctx) != JLOG_ERR_CREATE_EXISTS) exit(0); + } else { + jlog_ctx_add_subscriber(ctx, MASTER, JLOG_BEGIN); + } + jlog_ctx_close(ctx); +} + +void *writer(void *unused) { + jlog_ctx *ctx; + int i; + char foo[1523]; + ctx = jlog_new(LOGNAME); + memset(foo, 'X', sizeof(foo)-1); + foo[sizeof(foo)-1] = '\0'; + if(jlog_ctx_open_writer(ctx) != 0) { + fprintf(stderr, "jlog_ctx_open_writer failed: %d %s\n", jlog_ctx_err(ctx), jlog_ctx_err_string(ctx)); + croak(); + } + for(i=0;i<1000;i++) { + int rv; + fprintf(stderr, "writer...\n"); + rv = jlog_ctx_write(ctx, foo, strlen(foo)); + if(rv != 0) { + fprintf(stderr, "jlog_ctx_write_message failed: %d %s\n", jlog_ctx_err(ctx), jlog_ctx_err_string(ctx)); + /* abort(); */ + } + } + jlog_ctx_close(ctx); + writer_done = 1; + return 0; +} + +void *reader(void *unused) { + jlog_ctx *ctx; + char subname[32]; + int tcount = 0; + int prev_err = 0; + int subno = (int)unused; + snprintf(subname, sizeof(subname), "sub-%02d", subno); +reader_retry: + ctx = jlog_new(LOGNAME); + if(jlog_ctx_open_reader(ctx, subname) != 0) { + if(prev_err == 0) { + prev_err = jlog_ctx_err(ctx); + jlog_ctx_close(ctx); + ctx = jlog_new(LOGNAME); + if(prev_err == JLOG_ERR_INVALID_SUBSCRIBER) { + fprintf(stderr, "[%02d] invalid subscriber, init...\n", subno); + if(jlog_ctx_open_writer(ctx) != 0) { + fprintf(stderr, "[%02d] jlog_ctx_open_writer failed: %d %s\n", subno, jlog_ctx_err(ctx), jlog_ctx_err_string(ctx)); + } else { + if(jlog_ctx_add_subscriber(ctx, subname, JLOG_BEGIN) != 0) { + fprintf(stderr, "[%02d] jlog_ctx_add_subscriber failed: %d %s\n", subno, jlog_ctx_err(ctx), jlog_ctx_err_string(ctx)); + } else { + jlog_ctx_close(ctx); + goto reader_retry; + } + } + } + } + fprintf(stderr, "[%02d] jlog_ctx_open_reader failed: %d %s\n", subno, jlog_ctx_err(ctx), jlog_ctx_err_string(ctx)); + croak(); + } + fprintf(stderr, "[%02d] reader started\n", subno); + while(1) { + char begins[20], ends[20]; + jlog_id begin, end; + int count; + jlog_message message; + if((count = jlog_ctx_read_interval(ctx, &begin, &end)) == -1) { + fprintf(stderr, "jlog_ctx_read_interval failed: %d %s\n", jlog_ctx_err(ctx), jlog_ctx_err_string(ctx)); + croak(); + } + jlog_snprint_logid(begins, sizeof(begins), &begin); + jlog_snprint_logid(ends, sizeof(ends), &end); + if(count > 0) { + int i; + fprintf(stderr, "[%02d] reader (%s, %s] count: %d\n", subno, begins, ends, count); + for(i=0; i 3) { + usage(); + } + + jcreate(safety); + + if(toremove) { + jlog_ctx *ctx; + ctx = jlog_new(LOGNAME); + if(jlog_ctx_open_writer(ctx) != 0) { + fprintf(stderr, "jlog_ctx_open_writer failed: %d %s\n", jlog_ctx_err(ctx), jlog_ctx_err_string(ctx)); + croak(); + } + jlog_ctx_remove_subscriber(ctx, argv[2]); + jlog_ctx_close(ctx); + exit(0); + } + if(!only_write) { + for(i=0; ictx) { \ + jlog_ctx_close(my_obj->ctx); \ + } \ + if(my_obj->path){ \ + free(my_obj->path); \ + } \ + free(my_obj); \ +} while(0) + +#define SYS_CROAK(message) do { \ + croak(message "; error: %d (%s) errno: %d (%s)", \ + jlog_ctx_err(my_obj->ctx), jlog_ctx_err_string(my_obj->ctx), \ + jlog_ctx_errno(my_obj->ctx), strerror(jlog_ctx_errno(my_obj->ctx))); \ +} while (0) + +MODULE = JLog PACKAGE = JLog PREFIX=JLOG_ + +SV *JLOG_new(classname, path, ...) + char *classname; + char *path; + CODE: + { + jlog_obj *my_obj; + int options = O_CREAT; + size_t size = 0; + my_obj = calloc(1, sizeof(*my_obj)); + my_obj->ctx = jlog_new(path); + my_obj->path = strdup(path); + if(items > 2) { + options = SvIV(ST(2)); + if(items > 3) { + size = SvIV(ST(3)); + } + } + + if(!my_obj->ctx) { + FREE_JLOG_OBJ(my_obj); + croak("jlog_new(%s) failed", path); + } + if(options & O_CREAT) { + if(size) { + jlog_ctx_alter_journal_size(my_obj->ctx, size); + } + if(jlog_ctx_init(my_obj->ctx) != 0) { + if(jlog_ctx_err(my_obj->ctx) == JLOG_ERR_CREATE_EXISTS) { + if(options & O_EXCL) { + FREE_JLOG_OBJ(my_obj); + croak("file already exists: %s", path); + } + } else { + int err = jlog_ctx_err(my_obj->ctx); + const char *err_string = jlog_ctx_err_string(my_obj->ctx); + FREE_JLOG_OBJ(my_obj); + croak("error initializing jlog: %d %s", err, err_string); + } + } + jlog_ctx_close(my_obj->ctx); + my_obj->ctx = jlog_new(path); + if(!my_obj->ctx) { + FREE_JLOG_OBJ(my_obj); + croak("jlog_new(%s) failed after successful init", path); + } + } + RETVAL = newSV(0); + sv_setref_pv(RETVAL, classname, (void *)my_obj); + } + OUTPUT: + RETVAL + +SV *JLOG_JLOG_BEGIN() + CODE: + { + RETVAL = newSViv(JLOG_BEGIN); + } + OUTPUT: + RETVAL + +SV *JLOG_JLOG_END() + CODE: + { + RETVAL = newSViv(JLOG_END); + } + OUTPUT: + RETVAL + + +SV *JLOG_add_subscriber(my_obj, subscriber, ...) + JLog my_obj; + char *subscriber; + CODE: + { + int whence = JLOG_BEGIN; + if(items > 2) { + whence = SvIV(ST(2)); + } + if(!my_obj || !my_obj->ctx || + jlog_ctx_add_subscriber(my_obj->ctx, subscriber, whence) != 0) + { + RETVAL = &PL_sv_no; + } else { + RETVAL = &PL_sv_yes; + } + } + OUTPUT: + RETVAL + +SV *JLOG_remove_subscriber(my_obj, subscriber) + JLog my_obj; + char *subscriber; + CODE: + { + if(!my_obj || !my_obj->ctx || + jlog_ctx_remove_subscriber(my_obj->ctx, subscriber) != 0) + { + RETVAL = &PL_sv_no; + } else { + RETVAL = &PL_sv_yes; + } + } + OUTPUT: + RETVAL + +void JLOG_list_subscribers(my_obj) + JLog my_obj; + PPCODE: + { + char **list; + int i; + if(!my_obj || !my_obj->ctx) { + croak("invalid jlog context"); + } + jlog_ctx_list_subscribers(my_obj->ctx, &list); + for(i=0; list[i]; i++) { + XPUSHs(sv_2mortal(newSVpv(list[i], 0))); + } + jlog_ctx_list_subscribers_dispose(my_obj->ctx, list); + } + +SV *JLOG_alter_journal_size(my_obj, size) + JLog my_obj; + size_t size; + CODE: + { + if(!my_obj || !my_obj->ctx) { + croak("invalid jlog context"); + } + /* calling jlog_ctx_alter_journal_size here will never have any + * effect, it's either too late or too early. Make this return + * failure and deprecate it */ + RETVAL = &PL_sv_no; + } + OUTPUT: + RETVAL + +SV *JLOG_raw_size(my_obj) + JLog my_obj; + CODE: + { + size_t size; + if(!my_obj || !my_obj->ctx) { + croak("invalid jlog context"); + } + size = jlog_raw_size(my_obj->ctx); + RETVAL = newSViv(size); + } + OUTPUT: + RETVAL + +void JLOG_close(my_obj) + JLog my_obj; + CODE: + { + if(!my_obj || !my_obj->ctx) { return; } + jlog_ctx_close(my_obj->ctx); + my_obj->ctx = NULL; + } + +void JLOG_DESTROY(my_obj) + JLog my_obj; + CODE: + { + if(!my_obj) return; + FREE_JLOG_OBJ(my_obj); + } + + +MODULE = JLog PACKAGE = JLog::Writer PREFIX=JLOG_W_ + + +SV *JLOG_W_open(my_obj) + JLog_Writer my_obj; + CODE: + { + if(!my_obj || !my_obj->ctx) { + croak("invalid jlog context"); + } + if(jlog_ctx_open_writer(my_obj->ctx) != 0) { + SYS_CROAK("jlog_ctx_open_writer failed"); + } else { + RETVAL = newSVsv(ST(0)); + } + } + OUTPUT: + RETVAL + +SV *JLOG_W_write(my_obj, buffer_sv) + JLog_Writer my_obj; + SV *buffer_sv; + CODE: + { + char *buffer; + STRLEN buffer_len; + if(!my_obj || !my_obj->ctx) { + croak("invalid jlog context"); + } + buffer = SvPVx(buffer_sv, buffer_len); + if(jlog_ctx_write(my_obj->ctx, buffer, buffer_len) < 0) { + RETVAL = &PL_sv_no; + } else { + RETVAL = &PL_sv_yes; + } + } + OUTPUT: + RETVAL + + +MODULE = JLog PACKAGE = JLog::Reader PREFIX=JLOG_R_ + + +SV *JLOG_R_open(my_obj, subscriber) + JLog_Reader my_obj; + char *subscriber; + CODE: + { + if(!my_obj || !my_obj->ctx) { + croak("invalid jlog context"); + } + if(jlog_ctx_open_reader(my_obj->ctx, subscriber) != 0) { + SYS_CROAK("jlog_ctx_open_reader failed"); + } else { + RETVAL = newSVsv(ST(0)); + } + } + OUTPUT: + RETVAL + +SV * JLOG_R_read(my_obj) + JLog_Reader my_obj; + CODE: + { + const jlog_id epoch = { 0, 0 }; + jlog_id cur; + jlog_message message; + int cnt; + if(!my_obj || !my_obj->ctx) { + croak("invalid jlog context"); + } + /* if start is unset, we need to read the interval (again) */ + if(!memcmp(&my_obj->start, &epoch, sizeof(jlog_id))) + { + cnt = jlog_ctx_read_interval(my_obj->ctx, &my_obj->start, &my_obj->end); + if(cnt == -1) SYS_CROAK("jlog_ctx_read_interval failed"); + if(cnt == 0) { + my_obj->start = epoch; + my_obj->end = epoch; + RETVAL = &PL_sv_undef; + goto end; + } + } + /* if last is unset, start at the beginning */ + if(!memcmp(&my_obj->last, &epoch, sizeof(jlog_id))) { + cur = my_obj->start; + } else { + /* if we've already read the end, return; otherwise advance */ + if (!memcmp(&my_obj->last, &my_obj->end, sizeof(jlog_id))) { + my_obj->start = epoch; + my_obj->end = epoch; + RETVAL = &PL_sv_undef; + goto end; + } else { + cur = my_obj->last; + JLOG_ID_ADVANCE(&cur); + } + } + + if(jlog_ctx_read_message(my_obj->ctx, &cur, &message) != 0) { + /* read failed; croak, but recover if the read is retried */ + my_obj->start = epoch; + my_obj->last = epoch; + my_obj->end = epoch; + SYS_CROAK("read failed"); + } + if(my_obj->auto_checkpoint) { + if(jlog_ctx_read_checkpoint(my_obj->ctx, &cur) != 0) + SYS_CROAK("checkpoint failed"); + /* we have to re-read the interval after a checkpoint */ + my_obj->last = epoch; + my_obj->start = epoch; + my_obj->end = epoch; + } else { + /* update last */ + my_obj->last = cur; + /* if we've reaached the end, clear interval so we'll re-read it */ + if(!memcmp(&my_obj->last, &my_obj->end, sizeof(jlog_id))) { + my_obj->start = epoch; + my_obj->end = epoch; + } + } + RETVAL = newSVpv(message.mess, message.mess_len); +end: + ; + } + OUTPUT: + RETVAL + +SV *JLOG_R_checkpoint(my_obj) + JLog_Reader my_obj; + CODE: + { + jlog_id epoch = { 0, 0 }; + if(!my_obj || !my_obj->ctx) { + croak("invalid jlog context"); + } + if(memcmp(&my_obj->last, &epoch, sizeof(jlog_id))) + { + jlog_ctx_read_checkpoint(my_obj->ctx, &my_obj->last); + /* we have to re-read the interval after a checkpoint */ + my_obj->last = epoch; + my_obj->start = epoch; + my_obj->end = epoch; + } + RETVAL = newSVsv(ST(0)); + } + OUTPUT: + RETVAL + +SV *JLOG_R_auto_checkpoint(my_obj, ...) + JLog_Reader my_obj; + CODE: + { + if(!my_obj || !my_obj->ctx) { + croak("invalid jlog context"); + } + if(items > 1) { + int ac = SvIV(ST(1)); + my_obj->auto_checkpoint = ac; + } + RETVAL = newSViv(my_obj->auto_checkpoint); + } + OUTPUT: + RETVAL diff --git a/perl/MANIFEST b/perl/MANIFEST new file mode 100644 index 0000000..fd74aec --- /dev/null +++ b/perl/MANIFEST @@ -0,0 +1,12 @@ +Changes +JLog.xs +Makefile.PL +MANIFEST +ppport.h +README +t/1.t +lib/JLog.pm +lib/JLog/Writer.pm +lib/JLog/Reader.pm +typemap +META.yml Module meta-data (added by MakeMaker) diff --git a/perl/Makefile.PL.in b/perl/Makefile.PL.in new file mode 100644 index 0000000..977f605 --- /dev/null +++ b/perl/Makefile.PL.in @@ -0,0 +1,22 @@ +use 5.008005; +use ExtUtils::MakeMaker; +use Config; +use Cwd qw/abs_path/; + +# See lib/ExtUtils/MakeMaker.pm for details of how to influence +# the contents of the Makefile that is written. + +WriteMakefile( + NAME => 'JLog', + VERSION_FROM => 'lib/JLog.pm', + CCFLAGS => "$ENV{'CFLAGS'}", + PREREQ_PM => {}, + ($] >= 5.005 ? + (ABSTRACT_FROM => 'lib/JLog.pm', + AUTHOR => 'George Schlossnagle ') : ()), + LDDLFLAGS => "$Config{lddlflags} @RLDFLAG@@libdir@", + LIBS => ["-L@libdir@ -L.. -ljlog"], + INC => '-I.. -I. -I../..', + # Un-comment this if you add C files to link with later: + # OBJECT => '$(O_FILES)', # link all the C files too +); diff --git a/perl/README b/perl/README new file mode 100644 index 0000000..fe7a3d0 --- /dev/null +++ b/perl/README @@ -0,0 +1,38 @@ +JLog version 0.01 +================= + +The README is used to introduce the module and provide instructions on +how to install the module, any machine dependencies it may have (for +example C compilers and installed libraries) and any other information +that should be provided before the module is installed. + +A README file is required for CPAN modules since CPAN extracts the +README file from a module distribution so that people browsing the +archive can use it get an idea of the modules uses. It is usually a +good idea to provide version information here so that people can +decide whether fixes for the module are worth downloading. + +INSTALLATION + +To install this module type the following: + + perl Makefile.PL + make + make test + make install + +DEPENDENCIES + +This module requires these other modules and libraries: + + blah blah blah + +COPYRIGHT AND LICENCE + +Copyright (C) 2006 by George Schlossnagle + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself, either Perl version 5.8.5 or, +at your option, any later version of Perl 5 you may have available. + + diff --git a/perl/lib/JLog.pm b/perl/lib/JLog.pm new file mode 100644 index 0000000..132f98c --- /dev/null +++ b/perl/lib/JLog.pm @@ -0,0 +1,128 @@ +package JLog; + +use 5.008005; +use strict; +use warnings; + +require Exporter; + +our @ISA = qw(Exporter); + +# Items to export into callers namespace by default. Note: do not export +# names by default without a very good reason. Use EXPORT_OK instead. +# Do not simply export all your public functions/methods/constants. + +# This allows declaration use JLog ':all'; +# If you do not need this, moving things directly into @EXPORT or @EXPORT_OK +# will save memory. +our %EXPORT_TAGS = ( 'all' => [ qw( + +) ] ); + +our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); + +our @EXPORT = qw( + +); + +our $VERSION = '1.0'; + +require XSLoader; +XSLoader::load('JLog', $VERSION); + +# Preloaded methods go here. + +1; +__END__ + +=head1 NAME + +JLog - Perl extension for the jlog journaled queueing system + +=head1 DESCRIPTION + +Parent class for JLog::Reader and JLog::Writer. You probably want to +be looking at those instead. JLog is a durable, reliable, +publish-and-subscribe queueing system. + +=head1 INTERFACE + +=head2 Subscriber Management + +A JLog must have subscribers to be functional. Without a subscriber, +a queue may be purged, as there are no interested readers. For this +reason it is highly recommended that you add a subscriber before +writing to a log. + +=head3 add_subscriber + + $w->add_subscriber( $name, [ $flag ] ); + +Add a subscriber to the JLog queue. + +=over 4 + +=item $name + +The name of the subscriber. + +=item $flag + +An optional flag dictating where the subscriber should be marked interested +from. The default is JLog::JLOG_BEGIN. The other available option is +JLog::JLOG_END. + +=back + +=head3 remove_subscriber + + $w->remove_subscriber ( $name ); + +Remove a subscriber to the JLog queue. + +=over 4 + +=item $name + +The name of the subscriber. + +=back + +=head3 list_subscribers + + @subscribers = $w->list_subscribers; + +Return a list of all the subscribers to a JLog queue. + +=head2 Internals + +=head3 alter_journal_size + +This function is a stub provided for backwards compatibility. It will always +return false. + +=over 4 + +=item $size + +The desired size in bytes. + +=back + +=head3 raw_size + + $size = $w->raw_size; + +The size of the existing journal (including checkpointed but unpurged messages +in the current journal file), in bytes. + +=head1 SEE ALSO + +JLog::Reader +JLog::Writer + +=head1 COPYRIGHT AND LICENSE + +Copyright (C) 2006 by OmniTI, Inc. + +=cut diff --git a/perl/lib/JLog/Reader.pm b/perl/lib/JLog/Reader.pm new file mode 100644 index 0000000..92b3288 --- /dev/null +++ b/perl/lib/JLog/Reader.pm @@ -0,0 +1,190 @@ +package JLog::Reader; + +require DynaLoader; + +use JLog; +use strict; + +use vars qw/@ISA/; + +@ISA = qw(Exporter DynaLoader JLog); + +1; +__END__ + +=head1 NAME + +JLog::Reader - Perl extension for reading to a jlog journal. + +=head1 SUMMARY + + use JLog::Reader; + # create a new reader off the log directory + $r = JLog::Reader->new($log); + # open the log as the indicated subscriber + $r->open($subscriber); + while(my $line = $r->read) { + # work with $line + } + # mark the seen records as read + $r->checkpoint; + +or + + use JLog::Reader; + $r = JLog::Reader->new($log); + $r->open($subscriber); + # mark lines read as they are pulled off the queue + $r->auto_checkpoint(1); + while(my $line = $r->read) { + # work with $line + } + + +=head1 DESCRIPTION + +JLog::Reader allows you to access a jlog queue for reader. + +=head1 INTERFACE + +=head2 Constructor + +=head3 new + + $w = JLog::Reader->new( $path_to_jlog, [ $flags [, $size ] ] ); + +Instantiates a JLog writer object associated with the JLog directory. + +=over 4 + +=item $path_to_jlog + +The directory for the JLog queue. + +=item $flags + +Optional flags, from 'Fcntl'. The default is O_CREAT. + +=item $size + +Optional size of the individual journal files. + +=back + +=head2 Subscriber Management + +These functions are inherited from JLog + +=head3 add_subscriber + + $w->add_subscriber( $name, [ $flag ] ); + +Add a subscriber to the JLog queue. + +=over 4 + +=item $name + +The name of the subscriber. + +=item $flag + +An optional flag dictating where the subscriber should be marked interested +from. The default is JLog::JLOG_BEGIN. The other available option is +JLog::JLOG_END. + +=back + +=head3 remove_subscriber + + $w->remove_subscriber ( $name ); + +Remove a subscriber to the JLog queue. + +=over 4 + +=item $name + +The name of the subscriber. + +=back + +=head2 Reading From The Queue + +=head3 open + + $w->open( $subscriber_name ); + +Opens the JLog for reading. + +=over 4 + +=item $subscriber_name + +The name we want to subscribe under. This must previously have been +registered as a log subscriber via add_subscriber(). + +=back + +=head3 read + + $message = $w->read; + +Read the next message from the JLog queue. + +=head3 checkpoint + + $r->checkpoint; + +Checkpoint your read. This will notify the JLog that you have successfully +read logs up to this point. If all registered subscribers have read to +a certain point, the JLog system can remove the underlying data for the +read messages. + +=head2 auto_checkpoint( [ $val ] ) + +Returns (and optionally sets) the auto_checkpoint property. With +auto-checkpointing enabled, JLog::Reader will automatically +checkpoint whenever you call read(). + +=over 4 + +=item $val + +The value you wish to set auto_checkpointing to. + +=back + +=head2 Internals + +=head3 alter_journal_size + + $r->alter_journal_size( $size ); + +Set the size of the individual journal files. + +=over 4 + +=item $size + +The desired size in bytes. + +=back + +=head3 raw_size + + $size = $r->raw_size; + +The size of the existing journal (including checkpointed but unpurged messages +in the current journal file), in bytes. + +=head1 SEE ALSO + +JLog +JLog::Writer + +=head1 COPYRIGHT + +Copyright (C) 2006 by OmniTI, Inc. + +=cut diff --git a/perl/lib/JLog/Writer.pm b/perl/lib/JLog/Writer.pm new file mode 100644 index 0000000..54ee46a --- /dev/null +++ b/perl/lib/JLog/Writer.pm @@ -0,0 +1,168 @@ +package JLog::Writer; + +require DynaLoader; + +use JLog; +use strict; + +use vars qw/@ISA/; + +@ISA = qw(Exporter DynaLoader JLog); + +1; +__END__ + +=head1 NAME + +JLog::Writer - Perl extension for writing to a jlog journal. + +=head1 SUMMARY + + use JLog::Writer; + use Fcntl qw/:DEFAULT/; + + my $sub = "testsubscriber"; + my $log = "foo.jlog"; + + # open a log - this respects stander Fcntl flags + my $w = JLog::Writer->new($log, O_CREAT); + + # add a subscriber - without this there is danger that + # a log may be expired without the unnamed subscriber + # stating its intention to read. + $w->add_subscriber($sub); + + # open for writing + $w->open; + + foreach (1 ... 3) { + # write to the queue + $w->write("foo $_"); + } + # close the queue + $w->close; + +=head1 DESCRIPTION + +JLog::Writer allows you to access a jlog queue for writing. + +=head1 INTERFACE + +=head2 Constructor + +=head3 new + + $w = JLog::Writer->new( $path_to_jlog, [ $flags [, $size ] ] ); + +Instantiates a JLog writer object associated with the JLog directory. + +=over 4 + +=item $path_to_jlog + +The directory for the JLog queue. + +=item $flags + +Optional flags, from 'Fcntl'. The default is O_CREAT. + +=item $size + +Optional size of the individual journal files. + +=back + +=head2 Subscriber Management + +These functions are inherited from JLog + +=head3 add_subscriber + + $w->add_subscriber( $name, [ $flag ] ); + +Add a subscriber to the JLog queue. + +=over 4 + +=item $name + +The name of the subscriber. + +=item $flag + +An optional flag dictating where the subscriber should be marked interested +from. The default is JLog::JLOG_BEGIN. The other available option is +JLog::JLOG_END. + +=back + +=head3 remove_subscriber + + $w->remove_subscriber ( $name ); + +Remove a subscriber to the JLog queue. + +=over 4 + +=item $name + +The name of the subscriber. + +=back + +=head2 Writing to the Queue + +=head3 open + + $w->open(); + +Opens the JLog for writing. + +=head3 write + + $w->write( $message ); + +Write a message to the JLog. + +=over 4 + +=item $message + +The message to write. + +=back + +=head2 Internals + +=head3 alter_journal_size + + $w->alter_journal_size( $size ); + +Set the size of the individual journal files. + +=over 4 + +=item $size + +The desired size in bytes. + +=back + +=head3 raw_size + + $size = $w->raw_size; + +The size of the existing journal (including checkpointed but unpurged messages +in the current journal file), in bytes. + +=head1 SEE ALSO + +JLog +JLog::Reader + +=head1 COPYRIGHT AND LICENSE + +Copyright (C) 2006 by OmniTI, Inc. + +=cut + diff --git a/perl/ppport.h b/perl/ppport.h new file mode 100644 index 0000000..71921b7 --- /dev/null +++ b/perl/ppport.h @@ -0,0 +1,1096 @@ + +/* ppport.h -- Perl/Pollution/Portability Version 2.011 + * + * Automatically Created by Devel::PPPort on Sun Jul 2 15:54:08 2006 + * + * Do NOT edit this file directly! -- Edit PPPort.pm instead. + * + * Version 2.x, Copyright (C) 2001, Paul Marquess. + * Version 1.x, Copyright (C) 1999, Kenneth Albanowski. + * This code may be used and distributed under the same license as any + * version of Perl. + * + * This version of ppport.h is designed to support operation with Perl + * installations back to 5.004, and has been tested up to 5.8.1. + * + * If this version of ppport.h is failing during the compilation of this + * module, please check if a newer version of Devel::PPPort is available + * on CPAN before sending a bug report. + * + * If you are using the latest version of Devel::PPPort and it is failing + * during compilation of this module, please send a report to perlbug@perl.com + * + * Include all following information: + * + * 1. The complete output from running "perl -V" + * + * 2. This file. + * + * 3. The name & version of the module you were trying to build. + * + * 4. A full log of the build that failed. + * + * 5. Any other information that you think could be relevant. + * + * + * For the latest version of this code, please retreive the Devel::PPPort + * module from CPAN. + * + */ + +/* + * In order for a Perl extension module to be as portable as possible + * across differing versions of Perl itself, certain steps need to be taken. + * Including this header is the first major one, then using dTHR is all the + * appropriate places and using a PL_ prefix to refer to global Perl + * variables is the second. + * + */ + + +/* If you use one of a few functions that were not present in earlier + * versions of Perl, please add a define before the inclusion of ppport.h + * for a static include, or use the GLOBAL request in a single module to + * produce a global definition that can be referenced from the other + * modules. + * + * Function: Static define: Extern define: + * newCONSTSUB() NEED_newCONSTSUB NEED_newCONSTSUB_GLOBAL + * + */ + + +/* To verify whether ppport.h is needed for your module, and whether any + * special defines should be used, ppport.h can be run through Perl to check + * your source code. Simply say: + * + * perl -x ppport.h *.c *.h *.xs foo/bar*.c [etc] + * + * The result will be a list of patches suggesting changes that should at + * least be acceptable, if not necessarily the most efficient solution, or a + * fix for all possible problems. It won't catch where dTHR is needed, and + * doesn't attempt to account for global macro or function definitions, + * nested includes, typemaps, etc. + * + * In order to test for the need of dTHR, please try your module under a + * recent version of Perl that has threading compiled-in. + * + */ + + +/* +#!/usr/bin/perl +@ARGV = ("*.xs") if !@ARGV; +%badmacros = %funcs = %macros = (); $replace = 0; +foreach () { + $funcs{$1} = 1 if /Provide:\s+(\S+)/; + $macros{$1} = 1 if /^#\s*define\s+([a-zA-Z0-9_]+)/; + $replace = $1 if /Replace:\s+(\d+)/; + $badmacros{$2}=$1 if $replace and /^#\s*define\s+([a-zA-Z0-9_]+).*?\s+([a-zA-Z0-9_]+)/; + $badmacros{$1}=$2 if /Replace (\S+) with (\S+)/; +} +foreach $filename (map(glob($_),@ARGV)) { + unless (open(IN, "<$filename")) { + warn "Unable to read from $file: $!\n"; + next; + } + print "Scanning $filename...\n"; + $c = ""; while () { $c .= $_; } close(IN); + $need_include = 0; %add_func = (); $changes = 0; + $has_include = ($c =~ /#.*include.*ppport/m); + + foreach $func (keys %funcs) { + if ($c =~ /#.*define.*\bNEED_$func(_GLOBAL)?\b/m) { + if ($c !~ /\b$func\b/m) { + print "If $func isn't needed, you don't need to request it.\n" if + $changes += ($c =~ s/^.*#.*define.*\bNEED_$func\b.*\n//m); + } else { + print "Uses $func\n"; + $need_include = 1; + } + } else { + if ($c =~ /\b$func\b/m) { + $add_func{$func} =1 ; + print "Uses $func\n"; + $need_include = 1; + } + } + } + + if (not $need_include) { + foreach $macro (keys %macros) { + if ($c =~ /\b$macro\b/m) { + print "Uses $macro\n"; + $need_include = 1; + } + } + } + + foreach $badmacro (keys %badmacros) { + if ($c =~ /\b$badmacro\b/m) { + $changes += ($c =~ s/\b$badmacro\b/$badmacros{$badmacro}/gm); + print "Uses $badmacros{$badmacro} (instead of $badmacro)\n"; + $need_include = 1; + } + } + + if (scalar(keys %add_func) or $need_include != $has_include) { + if (!$has_include) { + $inc = join('',map("#define NEED_$_\n", sort keys %add_func)). + "#include \"ppport.h\"\n"; + $c = "$inc$c" unless $c =~ s/#.*include.*XSUB.*\n/$&$inc/m; + } elsif (keys %add_func) { + $inc = join('',map("#define NEED_$_\n", sort keys %add_func)); + $c = "$inc$c" unless $c =~ s/^.*#.*include.*ppport.*$/$inc$&/m; + } + if (!$need_include) { + print "Doesn't seem to need ppport.h.\n"; + $c =~ s/^.*#.*include.*ppport.*\n//m; + } + $changes++; + } + + if ($changes) { + open(OUT,">/tmp/ppport.h.$$"); + print OUT $c; + close(OUT); + open(DIFF, "diff -u $filename /tmp/ppport.h.$$|"); + while () { s!/tmp/ppport\.h\.$$!$filename.patched!; print STDOUT; } + close(DIFF); + unlink("/tmp/ppport.h.$$"); + } else { + print "Looks OK\n"; + } +} +__DATA__ +*/ + +#ifndef _P_P_PORTABILITY_H_ +#define _P_P_PORTABILITY_H_ + +#ifndef PERL_REVISION +# ifndef __PATCHLEVEL_H_INCLUDED__ +# define PERL_PATCHLEVEL_H_IMPLICIT +# include +# endif +# if !(defined(PERL_VERSION) || (defined(SUBVERSION) && defined(PATCHLEVEL))) +# include +# endif +# ifndef PERL_REVISION +# define PERL_REVISION (5) + /* Replace: 1 */ +# define PERL_VERSION PATCHLEVEL +# define PERL_SUBVERSION SUBVERSION + /* Replace PERL_PATCHLEVEL with PERL_VERSION */ + /* Replace: 0 */ +# endif +#endif + +#define PERL_BCDVERSION ((PERL_REVISION * 0x1000000L) + (PERL_VERSION * 0x1000L) + PERL_SUBVERSION) + +/* It is very unlikely that anyone will try to use this with Perl 6 + (or greater), but who knows. + */ +#if PERL_REVISION != 5 +# error ppport.h only works with Perl version 5 +#endif /* PERL_REVISION != 5 */ + +#ifndef ERRSV +# define ERRSV perl_get_sv("@",FALSE) +#endif + +#if (PERL_VERSION < 4) || ((PERL_VERSION == 4) && (PERL_SUBVERSION <= 5)) +/* Replace: 1 */ +# define PL_Sv Sv +# define PL_compiling compiling +# define PL_copline copline +# define PL_curcop curcop +# define PL_curstash curstash +# define PL_defgv defgv +# define PL_dirty dirty +# define PL_dowarn dowarn +# define PL_hints hints +# define PL_na na +# define PL_perldb perldb +# define PL_rsfp_filters rsfp_filters +# define PL_rsfpv rsfp +# define PL_stdingv stdingv +# define PL_sv_no sv_no +# define PL_sv_undef sv_undef +# define PL_sv_yes sv_yes +/* Replace: 0 */ +#endif + +#ifdef HASATTRIBUTE +# if (defined(__GNUC__) && defined(__cplusplus)) || defined(__INTEL_COMPILER) +# define PERL_UNUSED_DECL +# else +# define PERL_UNUSED_DECL __attribute__((unused)) +# endif +#else +# define PERL_UNUSED_DECL +#endif + +#ifndef dNOOP +# define NOOP (void)0 +# define dNOOP extern int Perl___notused PERL_UNUSED_DECL +#endif + +#ifndef dTHR +# define dTHR dNOOP +#endif + +#ifndef dTHX +# define dTHX dNOOP +# define dTHXa(x) dNOOP +# define dTHXoa(x) dNOOP +#endif + +#ifndef pTHX +# define pTHX void +# define pTHX_ +# define aTHX +# define aTHX_ +#endif + +#ifndef dAX +# define dAX I32 ax = MARK - PL_stack_base + 1 +#endif +#ifndef dITEMS +# define dITEMS I32 items = SP - MARK +#endif + +/* IV could also be a quad (say, a long long), but Perls + * capable of those should have IVSIZE already. */ +#if !defined(IVSIZE) && defined(LONGSIZE) +# define IVSIZE LONGSIZE +#endif +#ifndef IVSIZE +# define IVSIZE 4 /* A bold guess, but the best we can make. */ +#endif + +#ifndef UVSIZE +# define UVSIZE IVSIZE +#endif + +#ifndef NVTYPE +# if defined(USE_LONG_DOUBLE) && defined(HAS_LONG_DOUBLE) +# define NVTYPE long double +# else +# define NVTYPE double +# endif +typedef NVTYPE NV; +#endif + +#ifndef INT2PTR + +#if (IVSIZE == PTRSIZE) && (UVSIZE == PTRSIZE) +# define PTRV UV +# define INT2PTR(any,d) (any)(d) +#else +# if PTRSIZE == LONGSIZE +# define PTRV unsigned long +# else +# define PTRV unsigned +# endif +# define INT2PTR(any,d) (any)(PTRV)(d) +#endif +#define NUM2PTR(any,d) (any)(PTRV)(d) +#define PTR2IV(p) INT2PTR(IV,p) +#define PTR2UV(p) INT2PTR(UV,p) +#define PTR2NV(p) NUM2PTR(NV,p) +#if PTRSIZE == LONGSIZE +# define PTR2ul(p) (unsigned long)(p) +#else +# define PTR2ul(p) INT2PTR(unsigned long,p) +#endif + +#endif /* !INT2PTR */ + +#ifndef boolSV +# define boolSV(b) ((b) ? &PL_sv_yes : &PL_sv_no) +#endif + +#ifndef gv_stashpvn +# define gv_stashpvn(str,len,flags) gv_stashpv(str,flags) +#endif + +#ifndef newSVpvn +# define newSVpvn(data,len) ((len) ? newSVpv ((data), (len)) : newSVpv ("", 0)) +#endif + +#ifndef newRV_inc +/* Replace: 1 */ +# define newRV_inc(sv) newRV(sv) +/* Replace: 0 */ +#endif + +/* DEFSV appears first in 5.004_56 */ +#ifndef DEFSV +# define DEFSV GvSV(PL_defgv) +#endif + +#ifndef SAVE_DEFSV +# define SAVE_DEFSV SAVESPTR(GvSV(PL_defgv)) +#endif + +#ifndef newRV_noinc +# ifdef __GNUC__ +# define newRV_noinc(sv) \ + ({ \ + SV *nsv = (SV*)newRV(sv); \ + SvREFCNT_dec(sv); \ + nsv; \ + }) +# else +# if defined(USE_THREADS) +static SV * newRV_noinc (SV * sv) +{ + SV *nsv = (SV*)newRV(sv); + SvREFCNT_dec(sv); + return nsv; +} +# else +# define newRV_noinc(sv) \ + (PL_Sv=(SV*)newRV(sv), SvREFCNT_dec(sv), (SV*)PL_Sv) +# endif +# endif +#endif + +/* Provide: newCONSTSUB */ + +/* newCONSTSUB from IO.xs is in the core starting with 5.004_63 */ +#if (PERL_VERSION < 4) || ((PERL_VERSION == 4) && (PERL_SUBVERSION < 63)) + +#if defined(NEED_newCONSTSUB) +static +#else +extern void newCONSTSUB(HV * stash, char * name, SV *sv); +#endif + +#if defined(NEED_newCONSTSUB) || defined(NEED_newCONSTSUB_GLOBAL) +void +newCONSTSUB(stash,name,sv) +HV *stash; +char *name; +SV *sv; +{ + U32 oldhints = PL_hints; + HV *old_cop_stash = PL_curcop->cop_stash; + HV *old_curstash = PL_curstash; + line_t oldline = PL_curcop->cop_line; + PL_curcop->cop_line = PL_copline; + + PL_hints &= ~HINT_BLOCK_SCOPE; + if (stash) + PL_curstash = PL_curcop->cop_stash = stash; + + newSUB( + +#if (PERL_VERSION < 3) || ((PERL_VERSION == 3) && (PERL_SUBVERSION < 22)) + /* before 5.003_22 */ + start_subparse(), +#else +# if (PERL_VERSION == 3) && (PERL_SUBVERSION == 22) + /* 5.003_22 */ + start_subparse(0), +# else + /* 5.003_23 onwards */ + start_subparse(FALSE, 0), +# endif +#endif + + newSVOP(OP_CONST, 0, newSVpv(name,0)), + newSVOP(OP_CONST, 0, &PL_sv_no), /* SvPV(&PL_sv_no) == "" -- GMB */ + newSTATEOP(0, Nullch, newSVOP(OP_CONST, 0, sv)) + ); + + PL_hints = oldhints; + PL_curcop->cop_stash = old_cop_stash; + PL_curstash = old_curstash; + PL_curcop->cop_line = oldline; +} +#endif + +#endif /* newCONSTSUB */ + +#ifndef START_MY_CXT + +/* + * Boilerplate macros for initializing and accessing interpreter-local + * data from C. All statics in extensions should be reworked to use + * this, if you want to make the extension thread-safe. See ext/re/re.xs + * for an example of the use of these macros. + * + * Code that uses these macros is responsible for the following: + * 1. #define MY_CXT_KEY to a unique string, e.g. "DynaLoader_guts" + * 2. Declare a typedef named my_cxt_t that is a structure that contains + * all the data that needs to be interpreter-local. + * 3. Use the START_MY_CXT macro after the declaration of my_cxt_t. + * 4. Use the MY_CXT_INIT macro such that it is called exactly once + * (typically put in the BOOT: section). + * 5. Use the members of the my_cxt_t structure everywhere as + * MY_CXT.member. + * 6. Use the dMY_CXT macro (a declaration) in all the functions that + * access MY_CXT. + */ + +#if defined(MULTIPLICITY) || defined(PERL_OBJECT) || \ + defined(PERL_CAPI) || defined(PERL_IMPLICIT_CONTEXT) + +/* This must appear in all extensions that define a my_cxt_t structure, + * right after the definition (i.e. at file scope). The non-threads + * case below uses it to declare the data as static. */ +#define START_MY_CXT + +#if (PERL_VERSION < 4 || (PERL_VERSION == 4 && PERL_SUBVERSION < 68 )) +/* Fetches the SV that keeps the per-interpreter data. */ +#define dMY_CXT_SV \ + SV *my_cxt_sv = perl_get_sv(MY_CXT_KEY, FALSE) +#else /* >= perl5.004_68 */ +#define dMY_CXT_SV \ + SV *my_cxt_sv = *hv_fetch(PL_modglobal, MY_CXT_KEY, \ + sizeof(MY_CXT_KEY)-1, TRUE) +#endif /* < perl5.004_68 */ + +/* This declaration should be used within all functions that use the + * interpreter-local data. */ +#define dMY_CXT \ + dMY_CXT_SV; \ + my_cxt_t *my_cxtp = INT2PTR(my_cxt_t*,SvUV(my_cxt_sv)) + +/* Creates and zeroes the per-interpreter data. + * (We allocate my_cxtp in a Perl SV so that it will be released when + * the interpreter goes away.) */ +#define MY_CXT_INIT \ + dMY_CXT_SV; \ + /* newSV() allocates one more than needed */ \ + my_cxt_t *my_cxtp = (my_cxt_t*)SvPVX(newSV(sizeof(my_cxt_t)-1));\ + Zero(my_cxtp, 1, my_cxt_t); \ + sv_setuv(my_cxt_sv, PTR2UV(my_cxtp)) + +/* This macro must be used to access members of the my_cxt_t structure. + * e.g. MYCXT.some_data */ +#define MY_CXT (*my_cxtp) + +/* Judicious use of these macros can reduce the number of times dMY_CXT + * is used. Use is similar to pTHX, aTHX etc. */ +#define pMY_CXT my_cxt_t *my_cxtp +#define pMY_CXT_ pMY_CXT, +#define _pMY_CXT ,pMY_CXT +#define aMY_CXT my_cxtp +#define aMY_CXT_ aMY_CXT, +#define _aMY_CXT ,aMY_CXT + +#else /* single interpreter */ + +#define START_MY_CXT static my_cxt_t my_cxt; +#define dMY_CXT_SV dNOOP +#define dMY_CXT dNOOP +#define MY_CXT_INIT NOOP +#define MY_CXT my_cxt + +#define pMY_CXT void +#define pMY_CXT_ +#define _pMY_CXT +#define aMY_CXT +#define aMY_CXT_ +#define _aMY_CXT + +#endif + +#endif /* START_MY_CXT */ + +#ifndef IVdf +# if IVSIZE == LONGSIZE +# define IVdf "ld" +# define UVuf "lu" +# define UVof "lo" +# define UVxf "lx" +# define UVXf "lX" +# else +# if IVSIZE == INTSIZE +# define IVdf "d" +# define UVuf "u" +# define UVof "o" +# define UVxf "x" +# define UVXf "X" +# endif +# endif +#endif + +#ifndef NVef +# if defined(USE_LONG_DOUBLE) && defined(HAS_LONG_DOUBLE) && \ + defined(PERL_PRIfldbl) /* Not very likely, but let's try anyway. */ +# define NVef PERL_PRIeldbl +# define NVff PERL_PRIfldbl +# define NVgf PERL_PRIgldbl +# else +# define NVef "e" +# define NVff "f" +# define NVgf "g" +# endif +#endif + +#ifndef AvFILLp /* Older perls (<=5.003) lack AvFILLp */ +# define AvFILLp AvFILL +#endif + +#ifdef SvPVbyte +# if PERL_REVISION == 5 && PERL_VERSION < 7 + /* SvPVbyte does not work in perl-5.6.1, borrowed version for 5.7.3 */ +# undef SvPVbyte +# define SvPVbyte(sv, lp) \ + ((SvFLAGS(sv) & (SVf_POK|SVf_UTF8)) == (SVf_POK) \ + ? ((lp = SvCUR(sv)), SvPVX(sv)) : my_sv_2pvbyte(aTHX_ sv, &lp)) + static char * + my_sv_2pvbyte(pTHX_ register SV *sv, STRLEN *lp) + { + sv_utf8_downgrade(sv,0); + return SvPV(sv,*lp); + } +# endif +#else +# define SvPVbyte SvPV +#endif + +#ifndef SvPV_nolen +# define SvPV_nolen(sv) \ + ((SvFLAGS(sv) & (SVf_POK)) == SVf_POK \ + ? SvPVX(sv) : sv_2pv_nolen(sv)) + static char * + sv_2pv_nolen(pTHX_ register SV *sv) + { + STRLEN n_a; + return sv_2pv(sv, &n_a); + } +#endif + +#ifndef get_cv +# define get_cv(name,create) perl_get_cv(name,create) +#endif + +#ifndef get_sv +# define get_sv(name,create) perl_get_sv(name,create) +#endif + +#ifndef get_av +# define get_av(name,create) perl_get_av(name,create) +#endif + +#ifndef get_hv +# define get_hv(name,create) perl_get_hv(name,create) +#endif + +#ifndef call_argv +# define call_argv perl_call_argv +#endif + +#ifndef call_method +# define call_method perl_call_method +#endif + +#ifndef call_pv +# define call_pv perl_call_pv +#endif + +#ifndef call_sv +# define call_sv perl_call_sv +#endif + +#ifndef eval_pv +# define eval_pv perl_eval_pv +#endif + +#ifndef eval_sv +# define eval_sv perl_eval_sv +#endif + +#ifndef PERL_SCAN_GREATER_THAN_UV_MAX +# define PERL_SCAN_GREATER_THAN_UV_MAX 0x02 +#endif + +#ifndef PERL_SCAN_SILENT_ILLDIGIT +# define PERL_SCAN_SILENT_ILLDIGIT 0x04 +#endif + +#ifndef PERL_SCAN_ALLOW_UNDERSCORES +# define PERL_SCAN_ALLOW_UNDERSCORES 0x01 +#endif + +#ifndef PERL_SCAN_DISALLOW_PREFIX +# define PERL_SCAN_DISALLOW_PREFIX 0x02 +#endif + +#if (PERL_VERSION > 6) || ((PERL_VERSION == 6) && (PERL_SUBVERSION >= 1)) +#define I32_CAST +#else +#define I32_CAST (I32*) +#endif + +#ifndef grok_hex +static UV _grok_hex (char *string, STRLEN *len, I32 *flags, NV *result) { + NV r = scan_hex(string, *len, I32_CAST len); + if (r > UV_MAX) { + *flags |= PERL_SCAN_GREATER_THAN_UV_MAX; + if (result) *result = r; + return UV_MAX; + } + return (UV)r; +} + +# define grok_hex(string, len, flags, result) \ + _grok_hex((string), (len), (flags), (result)) +#endif + +#ifndef grok_oct +static UV _grok_oct (char *string, STRLEN *len, I32 *flags, NV *result) { + NV r = scan_oct(string, *len, I32_CAST len); + if (r > UV_MAX) { + *flags |= PERL_SCAN_GREATER_THAN_UV_MAX; + if (result) *result = r; + return UV_MAX; + } + return (UV)r; +} + +# define grok_oct(string, len, flags, result) \ + _grok_oct((string), (len), (flags), (result)) +#endif + +#if !defined(grok_bin) && defined(scan_bin) +static UV _grok_bin (char *string, STRLEN *len, I32 *flags, NV *result) { + NV r = scan_bin(string, *len, I32_CAST len); + if (r > UV_MAX) { + *flags |= PERL_SCAN_GREATER_THAN_UV_MAX; + if (result) *result = r; + return UV_MAX; + } + return (UV)r; +} + +# define grok_bin(string, len, flags, result) \ + _grok_bin((string), (len), (flags), (result)) +#endif + +#ifndef IN_LOCALE +# define IN_LOCALE \ + (PL_curcop == &PL_compiling ? IN_LOCALE_COMPILETIME : IN_LOCALE_RUNTIME) +#endif + +#ifndef IN_LOCALE_RUNTIME +# define IN_LOCALE_RUNTIME (PL_curcop->op_private & HINT_LOCALE) +#endif + +#ifndef IN_LOCALE_COMPILETIME +# define IN_LOCALE_COMPILETIME (PL_hints & HINT_LOCALE) +#endif + + +#ifndef IS_NUMBER_IN_UV +# define IS_NUMBER_IN_UV 0x01 +# define IS_NUMBER_GREATER_THAN_UV_MAX 0x02 +# define IS_NUMBER_NOT_INT 0x04 +# define IS_NUMBER_NEG 0x08 +# define IS_NUMBER_INFINITY 0x10 +# define IS_NUMBER_NAN 0x20 +#endif + +#ifndef grok_numeric_radix +# define GROK_NUMERIC_RADIX(sp, send) grok_numeric_radix(aTHX_ sp, send) + +#define grok_numeric_radix Perl_grok_numeric_radix + +bool +Perl_grok_numeric_radix(pTHX_ const char **sp, const char *send) +{ +#ifdef USE_LOCALE_NUMERIC +#if (PERL_VERSION > 6) || ((PERL_VERSION == 6) && (PERL_SUBVERSION >= 1)) + if (PL_numeric_radix_sv && IN_LOCALE) { + STRLEN len; + char* radix = SvPV(PL_numeric_radix_sv, len); + if (*sp + len <= send && memEQ(*sp, radix, len)) { + *sp += len; + return TRUE; + } + } +#else + /* pre5.6.0 perls don't have PL_numeric_radix_sv so the radix + * must manually be requested from locale.h */ +#include + struct lconv *lc = localeconv(); + char *radix = lc->decimal_point; + if (radix && IN_LOCALE) { + STRLEN len = strlen(radix); + if (*sp + len <= send && memEQ(*sp, radix, len)) { + *sp += len; + return TRUE; + } + } +#endif /* PERL_VERSION */ +#endif /* USE_LOCALE_NUMERIC */ + /* always try "." if numeric radix didn't match because + * we may have data from different locales mixed */ + if (*sp < send && **sp == '.') { + ++*sp; + return TRUE; + } + return FALSE; +} +#endif /* grok_numeric_radix */ + +#ifndef grok_number + +#define grok_number Perl_grok_number + +int +Perl_grok_number(pTHX_ const char *pv, STRLEN len, UV *valuep) +{ + const char *s = pv; + const char *send = pv + len; + const UV max_div_10 = UV_MAX / 10; + const char max_mod_10 = UV_MAX % 10; + int numtype = 0; + int sawinf = 0; + int sawnan = 0; + + while (s < send && isSPACE(*s)) + s++; + if (s == send) { + return 0; + } else if (*s == '-') { + s++; + numtype = IS_NUMBER_NEG; + } + else if (*s == '+') + s++; + + if (s == send) + return 0; + + /* next must be digit or the radix separator or beginning of infinity */ + if (isDIGIT(*s)) { + /* UVs are at least 32 bits, so the first 9 decimal digits cannot + overflow. */ + UV value = *s - '0'; + /* This construction seems to be more optimiser friendly. + (without it gcc does the isDIGIT test and the *s - '0' separately) + With it gcc on arm is managing 6 instructions (6 cycles) per digit. + In theory the optimiser could deduce how far to unroll the loop + before checking for overflow. */ + if (++s < send) { + int digit = *s - '0'; + if (digit >= 0 && digit <= 9) { + value = value * 10 + digit; + if (++s < send) { + digit = *s - '0'; + if (digit >= 0 && digit <= 9) { + value = value * 10 + digit; + if (++s < send) { + digit = *s - '0'; + if (digit >= 0 && digit <= 9) { + value = value * 10 + digit; + if (++s < send) { + digit = *s - '0'; + if (digit >= 0 && digit <= 9) { + value = value * 10 + digit; + if (++s < send) { + digit = *s - '0'; + if (digit >= 0 && digit <= 9) { + value = value * 10 + digit; + if (++s < send) { + digit = *s - '0'; + if (digit >= 0 && digit <= 9) { + value = value * 10 + digit; + if (++s < send) { + digit = *s - '0'; + if (digit >= 0 && digit <= 9) { + value = value * 10 + digit; + if (++s < send) { + digit = *s - '0'; + if (digit >= 0 && digit <= 9) { + value = value * 10 + digit; + if (++s < send) { + /* Now got 9 digits, so need to check + each time for overflow. */ + digit = *s - '0'; + while (digit >= 0 && digit <= 9 + && (value < max_div_10 + || (value == max_div_10 + && digit <= max_mod_10))) { + value = value * 10 + digit; + if (++s < send) + digit = *s - '0'; + else + break; + } + if (digit >= 0 && digit <= 9 + && (s < send)) { + /* value overflowed. + skip the remaining digits, don't + worry about setting *valuep. */ + do { + s++; + } while (s < send && isDIGIT(*s)); + numtype |= + IS_NUMBER_GREATER_THAN_UV_MAX; + goto skip_value; + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + numtype |= IS_NUMBER_IN_UV; + if (valuep) + *valuep = value; + + skip_value: + if (GROK_NUMERIC_RADIX(&s, send)) { + numtype |= IS_NUMBER_NOT_INT; + while (s < send && isDIGIT(*s)) /* optional digits after the radix */ + s++; + } + } + else if (GROK_NUMERIC_RADIX(&s, send)) { + numtype |= IS_NUMBER_NOT_INT | IS_NUMBER_IN_UV; /* valuep assigned below */ + /* no digits before the radix means we need digits after it */ + if (s < send && isDIGIT(*s)) { + do { + s++; + } while (s < send && isDIGIT(*s)); + if (valuep) { + /* integer approximation is valid - it's 0. */ + *valuep = 0; + } + } + else + return 0; + } else if (*s == 'I' || *s == 'i') { + s++; if (s == send || (*s != 'N' && *s != 'n')) return 0; + s++; if (s == send || (*s != 'F' && *s != 'f')) return 0; + s++; if (s < send && (*s == 'I' || *s == 'i')) { + s++; if (s == send || (*s != 'N' && *s != 'n')) return 0; + s++; if (s == send || (*s != 'I' && *s != 'i')) return 0; + s++; if (s == send || (*s != 'T' && *s != 't')) return 0; + s++; if (s == send || (*s != 'Y' && *s != 'y')) return 0; + s++; + } + sawinf = 1; + } else if (*s == 'N' || *s == 'n') { + /* XXX TODO: There are signaling NaNs and quiet NaNs. */ + s++; if (s == send || (*s != 'A' && *s != 'a')) return 0; + s++; if (s == send || (*s != 'N' && *s != 'n')) return 0; + s++; + sawnan = 1; + } else + return 0; + + if (sawinf) { + numtype &= IS_NUMBER_NEG; /* Keep track of sign */ + numtype |= IS_NUMBER_INFINITY | IS_NUMBER_NOT_INT; + } else if (sawnan) { + numtype &= IS_NUMBER_NEG; /* Keep track of sign */ + numtype |= IS_NUMBER_NAN | IS_NUMBER_NOT_INT; + } else if (s < send) { + /* we can have an optional exponent part */ + if (*s == 'e' || *s == 'E') { + /* The only flag we keep is sign. Blow away any "it's UV" */ + numtype &= IS_NUMBER_NEG; + numtype |= IS_NUMBER_NOT_INT; + s++; + if (s < send && (*s == '-' || *s == '+')) + s++; + if (s < send && isDIGIT(*s)) { + do { + s++; + } while (s < send && isDIGIT(*s)); + } + else + return 0; + } + } + while (s < send && isSPACE(*s)) + s++; + if (s >= send) + return numtype; + if (len == 10 && memEQ(pv, "0 but true", 10)) { + if (valuep) + *valuep = 0; + return IS_NUMBER_IN_UV; + } + return 0; +} +#endif /* grok_number */ + +#ifndef PERL_MAGIC_sv +# define PERL_MAGIC_sv '\0' +#endif + +#ifndef PERL_MAGIC_overload +# define PERL_MAGIC_overload 'A' +#endif + +#ifndef PERL_MAGIC_overload_elem +# define PERL_MAGIC_overload_elem 'a' +#endif + +#ifndef PERL_MAGIC_overload_table +# define PERL_MAGIC_overload_table 'c' +#endif + +#ifndef PERL_MAGIC_bm +# define PERL_MAGIC_bm 'B' +#endif + +#ifndef PERL_MAGIC_regdata +# define PERL_MAGIC_regdata 'D' +#endif + +#ifndef PERL_MAGIC_regdatum +# define PERL_MAGIC_regdatum 'd' +#endif + +#ifndef PERL_MAGIC_env +# define PERL_MAGIC_env 'E' +#endif + +#ifndef PERL_MAGIC_envelem +# define PERL_MAGIC_envelem 'e' +#endif + +#ifndef PERL_MAGIC_fm +# define PERL_MAGIC_fm 'f' +#endif + +#ifndef PERL_MAGIC_regex_global +# define PERL_MAGIC_regex_global 'g' +#endif + +#ifndef PERL_MAGIC_isa +# define PERL_MAGIC_isa 'I' +#endif + +#ifndef PERL_MAGIC_isaelem +# define PERL_MAGIC_isaelem 'i' +#endif + +#ifndef PERL_MAGIC_nkeys +# define PERL_MAGIC_nkeys 'k' +#endif + +#ifndef PERL_MAGIC_dbfile +# define PERL_MAGIC_dbfile 'L' +#endif + +#ifndef PERL_MAGIC_dbline +# define PERL_MAGIC_dbline 'l' +#endif + +#ifndef PERL_MAGIC_mutex +# define PERL_MAGIC_mutex 'm' +#endif + +#ifndef PERL_MAGIC_shared +# define PERL_MAGIC_shared 'N' +#endif + +#ifndef PERL_MAGIC_shared_scalar +# define PERL_MAGIC_shared_scalar 'n' +#endif + +#ifndef PERL_MAGIC_collxfrm +# define PERL_MAGIC_collxfrm 'o' +#endif + +#ifndef PERL_MAGIC_tied +# define PERL_MAGIC_tied 'P' +#endif + +#ifndef PERL_MAGIC_tiedelem +# define PERL_MAGIC_tiedelem 'p' +#endif + +#ifndef PERL_MAGIC_tiedscalar +# define PERL_MAGIC_tiedscalar 'q' +#endif + +#ifndef PERL_MAGIC_qr +# define PERL_MAGIC_qr 'r' +#endif + +#ifndef PERL_MAGIC_sig +# define PERL_MAGIC_sig 'S' +#endif + +#ifndef PERL_MAGIC_sigelem +# define PERL_MAGIC_sigelem 's' +#endif + +#ifndef PERL_MAGIC_taint +# define PERL_MAGIC_taint 't' +#endif + +#ifndef PERL_MAGIC_uvar +# define PERL_MAGIC_uvar 'U' +#endif + +#ifndef PERL_MAGIC_uvar_elem +# define PERL_MAGIC_uvar_elem 'u' +#endif + +#ifndef PERL_MAGIC_vstring +# define PERL_MAGIC_vstring 'V' +#endif + +#ifndef PERL_MAGIC_vec +# define PERL_MAGIC_vec 'v' +#endif + +#ifndef PERL_MAGIC_utf8 +# define PERL_MAGIC_utf8 'w' +#endif + +#ifndef PERL_MAGIC_substr +# define PERL_MAGIC_substr 'x' +#endif + +#ifndef PERL_MAGIC_defelem +# define PERL_MAGIC_defelem 'y' +#endif + +#ifndef PERL_MAGIC_glob +# define PERL_MAGIC_glob '*' +#endif + +#ifndef PERL_MAGIC_arylen +# define PERL_MAGIC_arylen '#' +#endif + +#ifndef PERL_MAGIC_pos +# define PERL_MAGIC_pos '.' +#endif + +#ifndef PERL_MAGIC_backref +# define PERL_MAGIC_backref '<' +#endif + +#ifndef PERL_MAGIC_ext +# define PERL_MAGIC_ext '~' +#endif + +#endif /* _P_P_PORTABILITY_H_ */ + +/* End of File ppport.h */ diff --git a/perl/t/1.t b/perl/t/1.t new file mode 100644 index 0000000..04fa200 --- /dev/null +++ b/perl/t/1.t @@ -0,0 +1,60 @@ +# Before `make install' is performed this script should be runnable with +# `make test'. After `make install' it should work as `perl JLog.t' + +######################### + +use Fcntl; +use Test::More tests => 7; +BEGIN { + use_ok('JLog::Writer'); + use_ok('JLog::Reader'); +}; + +my $sub = "testsubscriber"; +my $log = "foo.jlog"; + +system("rm -rf $log"); +my $w = JLog::Writer->new($log, O_CREAT|O_EXCL, 1024); +ok !$w->alter_journal_size(1024), "set journal size should fail"; + +$w->add_subscriber($sub); +$w->open; +foreach (1 ... 3) { + $w->write("foo $_"); +} +$w->close; + +my $r = JLog::Reader->new($log); + +my @subs = $r->list_subscribers; +is_deeply(\@subs, [ $sub ], 'list_subscribers'); + +$r->open($sub); + +my @lines; +my $i; + +while(my $line = $r->read) { + push @lines, $line; + $i++; +} +is_deeply(\@lines, [ 'foo 1', 'foo 2', 'foo 3' ], "in == out"); + +# checkpoint +undef $r; + +$r = JLog::Reader->new($log); +$r->open($sub); +@lines = (); +$i = 0; +while(my $line = $r->read) { + push @lines, $line; + $i++; +} +is_deeply(\@lines, [ 'foo 1', 'foo 2', 'foo 3' ], "in == out"); + +$r->checkpoint; + +$r = JLog::Reader->new($log); +$r->open($sub); +is $r->read, undef, "our checkpoint cleared things"; diff --git a/perl/typemap b/perl/typemap new file mode 100644 index 0000000..e2d7c26 --- /dev/null +++ b/perl/typemap @@ -0,0 +1,19 @@ +JLog T_PTROBJ_SPECIAL +JLog_Writer T_PTROBJ_SPECIAL +JLog_Reader T_PTROBJ_SPECIAL + +INPUT +T_PTROBJ_SPECIAL + if (sv_derived_from($arg, \"${(my $ntt=$ntype)=~s/_/::/g;\$ntt}\")) { + IV tmp = SvIV((SV*)SvRV($arg)); + $var = ($type) tmp; + } + else + croak(\"$var is not of type ${(my $ntt=$ntype)=~s/_/::/g;\$ntt}\") + + +OUTPUT +T_PTROBJ_SPECIAL + sv_setref_pv($arg, \"${(my $ntt=$ntype)=~s/_/::/g;\$ntt}\", + (void*)$var); + diff --git a/php/config.m4 b/php/config.m4 new file mode 100644 index 0000000..1efb87a --- /dev/null +++ b/php/config.m4 @@ -0,0 +1,31 @@ +dnl +dnl $ Id: $ +dnl + +PHP_ARG_WITH(jlog, jlog,[ --with-jlog[=DIR] With jlog support]) + + +if test "$PHP_JLOG" != "no"; then + if test "$PHP_JLOG" == "yes"; then + PHP_JLOG="/opt/ecelerity" + fi + export CPPFLAGS="$CPPFLAGS $INCLUDES -DHAVE_JLOG" + + PHP_ADD_INCLUDE(..) + PHP_ADD_INCLUDE(../..) + PHP_ADD_INCLUDE(.) + export OLD_CPPFLAGS="$CPPFLAGS" + export CPPFLAGS="$CPPFLAGS $INCLUDES -DHAVE_JLOG" + AC_CHECK_HEADER([jlog.h], [], AC_MSG_ERROR('jlog.h' header not found)) + export CPPFLAGS="$OLD_CPPFLAGS" + PHP_SUBST(JLOG_SHARED_LIBADD) + + PHP_ADD_LIBRARY_WITH_PATH(jlog, $PHP_JLOG/lib64, JLOG_SHARED_LIBADD) + + + PHP_SUBST(JLOG_SHARED_LIBADD) + AC_DEFINE(HAVE_JLOG, 1, [ ]) + PHP_NEW_EXTENSION(jlog, jlog.c , $ext_shared) + +fi + diff --git a/php/jlog.c b/php/jlog.c new file mode 100644 index 0000000..f755134 --- /dev/null +++ b/php/jlog.c @@ -0,0 +1,659 @@ +/* + +----------------------------------------------------------------------+ + | unknown license: | + +----------------------------------------------------------------------+ + +----------------------------------------------------------------------+ +*/ + +/* $ Id: $ */ + +#include "php.h" +#include "php_ini.h" +#include "php_jlog.h" + +#if HAVE_JLOG + +typedef struct { + zend_object zo; + jlog_ctx *ctx; + char *path; + jlog_id start; + jlog_id last; + jlog_id end; + int auto_checkpoint; +} jlog_obj; + +static zend_class_entry * Jlog_ce_ptr = NULL; +static zend_class_entry * Jlog_Writer_ce_ptr = NULL; +static zend_class_entry * Jlog_Reader_ce_ptr = NULL; +static zend_object_handlers jlog_object_handlers; + + +static void FREE_JLOG_OBJ(jlog_obj *intern) +{ + if(intern) { + if(intern->ctx) { + jlog_ctx_close(intern->ctx); + intern->ctx = NULL; + } + if(intern->path) { + free(intern->path); + } + } +} + +static void jlog_obj_dtor(void *object TSRMLS_DC) +{ + zend_object *zo = (zend_object *) object; + jlog_obj *intern = (jlog_obj *) zo; + FREE_JLOG_OBJ(intern); + zend_object_std_dtor(&intern->zo TSRMLS_CC); + efree(intern); +} + +zend_object_value jlog_objects_new(zend_class_entry *class_type TSRMLS_DC) +{ + zend_object_value retval; + jlog_obj *intern; + + intern = emalloc(sizeof(*intern)); + memset(intern, 0, sizeof(*intern)); + + zend_object_std_init(&intern->zo, class_type TSRMLS_CC); + retval.handle = zend_objects_store_put(intern, (zend_objects_store_dtor_t) zend_objects_destroy_object, jlog_obj_dtor, NULL TSRMLS_CC); + retval.handlers = &jlog_object_handlers; + return retval; +} + +/* {{{ Class definitions */ + +/* {{{ Class Jlog */ + + +/* {{{ Methods */ + + +/* {{{ proto object Jlog __construct(string path [, array options]) + */ +PHP_METHOD(Jlog, __construct) +{ + zend_class_entry * _this_ce; + zval * _this_zval; + const char * path = NULL; + int path_len = 0; + int options = 0; + int size = 0; + jlog_obj *jo; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|ll", &path, &path_len, &options, &size) == FAILURE) { + return; + } + + _this_zval = getThis(); + _this_ce = Z_OBJCE_P(_this_zval); + jo = (jlog_obj *) zend_object_store_get_object(_this_zval TSRMLS_CC); + + jo->ctx = jlog_new(path); + jo->path = strdup(path); + if(options & O_CREAT) { + if(size) { + jlog_ctx_alter_journal_size(jo->ctx, size); + } + if(jlog_ctx_init(jo->ctx) != 0) { + if(jlog_ctx_err(jo->ctx) == JLOG_ERR_CREATE_EXISTS) { + if(options & O_EXCL) { + FREE_JLOG_OBJ(jo); + efree(jo); + php_error(E_WARNING, "file already exists: %s", path); + } + } else { + int err = jlog_ctx_err(jo->ctx); + const char *err_string = jlog_ctx_err_string(jo->ctx); + FREE_JLOG_OBJ(jo); + efree(jo); + php_error(E_WARNING, "error initializing jlog: %d %s", err, err_string); + RETURN_FALSE; + } + } + jlog_ctx_close(jo->ctx); + jo->ctx = jlog_new(path); + if(!jo->ctx) { + FREE_JLOG_OBJ(jo); + efree(jo); + php_error(E_WARNING, "jlog_new(%s) failed after successful init", path); + RETURN_FALSE; + } + } +} +/* }}} __construct */ + + + +/* {{{ proto bool add_subscriber(string subscriber [, int whence]) + */ +PHP_METHOD(Jlog, add_subscriber) +{ + zend_class_entry * _this_ce; + zval * _this_zval = NULL; + const char * subscriber = NULL; + int subscriber_len = 0; + long whence = 0; + jlog_obj *jo; + + if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os|l", &_this_zval, Jlog_ce_ptr, &subscriber, &subscriber_len, &whence) == FAILURE) { + return; + } + + _this_ce = Z_OBJCE_P(_this_zval); + jo = (jlog_obj *) zend_object_store_get_object(_this_zval TSRMLS_CC); + if(!jo || !jo->ctx || jlog_ctx_add_subscriber(jo->ctx, subscriber, whence) != 0) + { + RETURN_FALSE; + } + RETURN_TRUE; +} +/* }}} add_subscriber */ + + + +/* {{{ proto bool remove_subscriber(string subscriber) + */ +PHP_METHOD(Jlog, remove_subscriber) +{ + zend_class_entry * _this_ce; + zval * _this_zval = NULL; + const char * subscriber = NULL; + int subscriber_len = 0; + jlog_obj *jo; + + if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os", &_this_zval, Jlog_ce_ptr, &subscriber, &subscriber_len) == FAILURE) { + return; + } + + _this_ce = Z_OBJCE_P(_this_zval); + jo = (jlog_obj *) zend_object_store_get_object(_this_zval TSRMLS_CC); + if(!jo || !jo->ctx || jlog_ctx_remove_subscriber(jo->ctx, subscriber) != 0) + { + RETURN_FALSE; + } + RETURN_TRUE; +} +/* }}} remove_subscriber */ + + + +/* {{{ proto array list_subscribers() + */ +PHP_METHOD(Jlog, list_subscribers) +{ + zend_class_entry * _this_ce; + zval * _this_zval = NULL; + jlog_obj *jo; + char **list; + int i; + + if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &_this_zval, Jlog_ce_ptr) == FAILURE) { + return; + } + + _this_ce = Z_OBJCE_P(_this_zval); + jo = (jlog_obj *) zend_object_store_get_object(_this_zval TSRMLS_CC); + if(!jo || !jo->ctx) { + RETURN_NULL(); + } + array_init(return_value); + jlog_ctx_list_subscribers(jo->ctx, &list); + for(i=0; list[i]; i++) { + add_index_string(return_value, i, list[i], 1); + } + jlog_ctx_list_subscribers_dispose(jo->ctx, list); +} +/* }}} list_subscribers */ + + + +/* {{{ proto int raw_size() + */ +PHP_METHOD(Jlog, raw_size) +{ + long size; + zend_class_entry * _this_ce; + zval * _this_zval = NULL; + jlog_obj *jo; + + if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &_this_zval, Jlog_ce_ptr) == FAILURE) { + return; + } + + _this_ce = Z_OBJCE_P(_this_zval); + jo = (jlog_obj *) zend_object_store_get_object(_this_zval TSRMLS_CC); + if(!jo || !jo->ctx) { + php_error(E_WARNING, "no valid context"); + RETURN_LONG(0); + } + size = jlog_raw_size(jo->ctx); + RETURN_LONG(size); +} +/* }}} raw_size */ + + + +/* {{{ proto void close() + */ +PHP_METHOD(Jlog, close) +{ + zend_class_entry * _this_ce; + zval * _this_zval = NULL; + jlog_obj *jo; + + if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &_this_zval, Jlog_ce_ptr) == FAILURE) { + return; + } + + _this_ce = Z_OBJCE_P(_this_zval); + jo = (jlog_obj *) zend_object_store_get_object(_this_zval TSRMLS_CC); + if(!jo || !jo->ctx) { return; } + jlog_ctx_close(jo->ctx); + jo->ctx = NULL; +} +/* }}} close */ + + +static zend_function_entry Jlog_methods[] = { + PHP_ME(Jlog, __construct, Jlog____construct_args, /**/ZEND_ACC_PUBLIC) + PHP_ME(Jlog, add_subscriber, Jlog__add_subscriber_args, /**/ZEND_ACC_PUBLIC) + PHP_ME(Jlog, remove_subscriber, Jlog__remove_subscriber_args, /**/ZEND_ACC_PUBLIC) + PHP_ME(Jlog, list_subscribers, NULL, /**/ZEND_ACC_PUBLIC) + PHP_ME(Jlog, raw_size, NULL, /**/ZEND_ACC_PUBLIC) + PHP_ME(Jlog, close, NULL, /**/ZEND_ACC_PUBLIC) + { NULL, NULL, NULL } +}; + +/* }}} Methods */ + +static void class_init_Jlog(TSRMLS_D) +{ + zend_class_entry ce; + + INIT_CLASS_ENTRY(ce, "Jlog", Jlog_methods); + ce.create_object = jlog_objects_new; + Jlog_ce_ptr = zend_register_internal_class(&ce TSRMLS_CC); + Jlog_ce_ptr->ce_flags |= ZEND_ACC_EXPLICIT_ABSTRACT_CLASS; +} + +/* }}} Class Jlog */ + +/* {{{ Class Jlog_Writer */ + + +/* {{{ Methods */ + + +/* {{{ proto object Jlog_Writer open() + */ +PHP_METHOD(Jlog_Writer, open) +{ + zend_class_entry * _this_ce; + zval * _this_zval = NULL; + jlog_obj *jo; + + if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &_this_zval, Jlog_Writer_ce_ptr) == FAILURE) { + return; + } + + _this_ce = Z_OBJCE_P(_this_zval); + jo = (jlog_obj *) zend_object_store_get_object(_this_zval TSRMLS_CC); + if(!jo || !jo->ctx) { + RETURN_NULL(); + } + if(jlog_ctx_open_writer(jo->ctx) != 0) { + php_error(E_WARNING, "jlog_ctx_open_writer failed"); + RETURN_NULL(); + } + ZVAL_ADDREF(_this_zval); + return_value = _this_zval; +} +/* }}} open */ + + + +/* {{{ proto bool write(string buffer) + */ +PHP_METHOD(Jlog_Writer, write) +{ + zend_class_entry * _this_ce; + zval * _this_zval = NULL; + const char * buffer = NULL; + int buffer_len = 0; + jlog_obj *jo; + + if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os", &_this_zval, Jlog_Writer_ce_ptr, &buffer, &buffer_len) == FAILURE) { + return; + } + + _this_ce = Z_OBJCE_P(_this_zval); + jo = (jlog_obj *) zend_object_store_get_object(_this_zval TSRMLS_CC); + if(!jo || !jo->ctx) { + RETURN_FALSE; + } + if(jlog_ctx_write(jo->ctx, buffer, buffer_len) < 0) { + RETURN_FALSE; + } + RETURN_TRUE; +} +/* }}} write */ + + +static zend_function_entry Jlog_Writer_methods[] = { + PHP_ME(Jlog_Writer, open, NULL, /**/ZEND_ACC_PUBLIC) + PHP_ME(Jlog_Writer, write, Jlog_Writer__write_args, /**/ZEND_ACC_PUBLIC) + { NULL, NULL, NULL } +}; + +/* }}} Methods */ + +static void class_init_Jlog_Writer(TSRMLS_D) +{ + zend_class_entry ce; + + INIT_CLASS_ENTRY(ce, "Jlog_Writer", Jlog_Writer_methods); + ce.create_object = jlog_objects_new; + Jlog_Reader_ce_ptr = zend_register_internal_class_ex(&ce, Jlog_ce_ptr, NULL TSRMLS_CC); +} + +/* }}} Class Jlog_Writer */ + +/* {{{ Class Jlog_Reader */ + +/* {{{ Methods */ + + +/* {{{ proto object Jlog_Reader open(string subscriber) + */ +PHP_METHOD(Jlog_Reader, open) +{ + zend_class_entry * _this_ce; + zval * _this_zval = NULL; + const char * subscriber = NULL; + int subscriber_len = 0; + jlog_obj *jo; + + if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os", &_this_zval, Jlog_Reader_ce_ptr, &subscriber, &subscriber_len) == FAILURE) { + return; + } + + _this_ce = Z_OBJCE_P(_this_zval); + jo = (jlog_obj *) zend_object_store_get_object(_this_zval TSRMLS_CC); + if(!jo || !jo->ctx) { + RETURN_NULL(); + } + if(jlog_ctx_open_reader(jo->ctx, subscriber) != 0) { + RETURN_NULL(); + } + ZVAL_ADDREF(_this_zval); + return_value = _this_zval; +} +/* }}} open */ + + + +/* {{{ proto string read() + */ +PHP_METHOD(Jlog_Reader, read) +{ + zend_class_entry * _this_ce; + zval * _this_zval = NULL; + jlog_obj *jo; + const jlog_id epoch = { 0, 0 }; + jlog_id cur; + jlog_message message; + int cnt; + + if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &_this_zval, Jlog_Reader_ce_ptr) == FAILURE) { + return; + } + + _this_ce = Z_OBJCE_P(_this_zval); + jo = (jlog_obj *) zend_object_store_get_object(_this_zval TSRMLS_CC); + if(!jo || !jo->ctx) { + RETURN_FALSE; + } + /* if start is unset, we need to read the interval (again) */ + if(!memcmp(&jo->start, &epoch, sizeof(jlog_id))) + { + cnt = jlog_ctx_read_interval(jo->ctx, &jo->start, &jo->end); + if(cnt == -1) { + php_error(E_WARNING, "jlog_ctx_read_interval failed"); + } + if(cnt == 0) { + jo->start = epoch; + jo->end = epoch; + RETURN_FALSE; + } + } + /* if last is unset, start at the beginning */ + if(!memcmp(&jo->last, &epoch, sizeof(jlog_id))) { + cur = jo->start; + } else { + /* if we've already read the end, return; otherwise advance */ + if (!memcmp(&jo->last, &jo->end, sizeof(jlog_id))) { + jo->start = epoch; + jo->end = epoch; + RETURN_FALSE; + } else { + cur = jo->last; + JLOG_ID_ADVANCE(&cur); + } + } + + if(jlog_ctx_read_message(jo->ctx, &cur, &message) != 0) { + php_error(E_WARNING, "read failed"); + RETURN_FALSE; + } + if(jo->auto_checkpoint) { + if(jlog_ctx_read_checkpoint(jo->ctx, &cur) != 0) { + php_error(E_WARNING, "checkpoint failed"); + RETURN_FALSE; + } + /* we have to re-read the interval after a checkpoint */ + jo->last = epoch; + jo->start = epoch; + jo->end = epoch; + } else { + /* update last */ + jo->last = cur; + /* if we've reaached the end, clear interval so we'll re-read it */ + if(!memcmp(&jo->last, &jo->end, sizeof(jlog_id))) { + jo->start = epoch; + jo->end = epoch; + } + } + RETURN_STRINGL(message.mess, message.mess_len, 1); +end: + ; +} + +/* }}} read */ + + + +/* {{{ proto object Jlog_Reader checkpoint() + */ +PHP_METHOD(Jlog_Reader, checkpoint) +{ + jlog_id epoch = { 0, 0 }; + zend_class_entry * _this_ce; + zval * _this_zval = NULL; + jlog_obj *jo; + + if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &_this_zval, Jlog_Reader_ce_ptr) == FAILURE) { + return; + } + + _this_ce = Z_OBJCE_P(_this_zval); + jo = (jlog_obj *) zend_object_store_get_object(_this_zval TSRMLS_CC); + if(!jo || !jo->ctx) { RETURN_NULL(); } + if(memcmp(&jo->last, &epoch, sizeof(jlog_id))) + { + jlog_ctx_read_checkpoint(jo->ctx, &jo->last); + /* we have to re-read the interval after a checkpoint */ + jo->last = epoch; + jo->start = epoch; + jo->end = epoch; + } + ZVAL_ADDREF(_this_zval); + return_value = _this_zval; +} +/* }}} checkpoint */ + + + +/* {{{ proto bool auto_checkpoint([bool state]) + */ +PHP_METHOD(Jlog_Reader, auto_checkpoint) +{ + zend_class_entry * _this_ce; + zval * _this_zval = NULL; + zend_bool state = 0; + jlog_obj *jo; + + if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O|b", &_this_zval, Jlog_Reader_ce_ptr, &state) == FAILURE) { + return; + } + + fprintf(stderr, "num_args = %d\n", ZEND_NUM_ARGS()); + _this_ce = Z_OBJCE_P(_this_zval); + jo = (jlog_obj *) zend_object_store_get_object(_this_zval TSRMLS_CC); + if(!jo || !jo->ctx) { RETURN_NULL(); } + if(ZEND_NUM_ARGS() == 1) { + jo->auto_checkpoint = state; + } + RETURN_LONG(jo->auto_checkpoint); +} +/* }}} auto_checkpoint */ + + +static zend_function_entry Jlog_Reader_methods[] = { + PHP_ME(Jlog_Reader, open, Jlog_Reader__open_args, /**/ZEND_ACC_PUBLIC) + PHP_ME(Jlog_Reader, read, NULL, /**/ZEND_ACC_PUBLIC) + PHP_ME(Jlog_Reader, checkpoint, NULL, /**/ZEND_ACC_PUBLIC) + PHP_ME(Jlog_Reader, auto_checkpoint, Jlog_Reader__auto_checkpoint_args, /**/ZEND_ACC_PUBLIC) + { NULL, NULL, NULL } +}; + +/* }}} Methods */ + +static void class_init_Jlog_Reader(TSRMLS_D) +{ + zend_class_entry ce; + + INIT_CLASS_ENTRY(ce, "Jlog_Reader", Jlog_Reader_methods); + ce.create_object = jlog_objects_new; + Jlog_Reader_ce_ptr = zend_register_internal_class_ex(&ce, Jlog_ce_ptr, NULL TSRMLS_CC); +} + +/* }}} Class Jlog_Reader */ + +/* }}} Class definitions*/ + +/* {{{ jlog_functions[] */ +function_entry jlog_functions[] = { + { NULL, NULL, NULL } +}; +/* }}} */ + + +/* {{{ jlog_module_entry + */ +zend_module_entry jlog_module_entry = { + STANDARD_MODULE_HEADER, + "jlog", + jlog_functions, + PHP_MINIT(jlog), /* Replace with NULL if there is nothing to do at php startup */ + PHP_MSHUTDOWN(jlog), /* Replace with NULL if there is nothing to do at php shutdown */ + PHP_RINIT(jlog), /* Replace with NULL if there is nothing to do at request start */ + PHP_RSHUTDOWN(jlog), /* Replace with NULL if there is nothing to do at request end */ + PHP_MINFO(jlog), + "0.0.1", + STANDARD_MODULE_PROPERTIES +}; +/* }}} */ + +#ifdef COMPILE_DL_JLOG +ZEND_GET_MODULE(jlog) +#endif + + +/* {{{ PHP_MINIT_FUNCTION */ +PHP_MINIT_FUNCTION(jlog) +{ + zend_object_handlers *std_hnd = zend_get_std_object_handlers(); + + memcpy(&jlog_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers)); + jlog_object_handlers.clone_obj = NULL; + + class_init_Jlog(TSRMLS_C); + class_init_Jlog_Writer(TSRMLS_C); + class_init_Jlog_Reader(TSRMLS_C); + + + /* add your stuff here */ + + return SUCCESS; +} +/* }}} */ + + +/* {{{ PHP_MSHUTDOWN_FUNCTION */ +PHP_MSHUTDOWN_FUNCTION(jlog) +{ + + /* add your stuff here */ + + return SUCCESS; +} +/* }}} */ + + +/* {{{ PHP_RINIT_FUNCTION */ +PHP_RINIT_FUNCTION(jlog) +{ + /* add your stuff here */ + + return SUCCESS; +} +/* }}} */ + + +/* {{{ PHP_RSHUTDOWN_FUNCTION */ +PHP_RSHUTDOWN_FUNCTION(jlog) +{ + /* add your stuff here */ + + return SUCCESS; +} +/* }}} */ + + +/* {{{ PHP_MINFO_FUNCTION */ +PHP_MINFO_FUNCTION(jlog) +{ + php_info_print_box_start(0); + php_printf("

A sample PHP extension

\n"); + php_printf("

Version 0.0.1devel (2007-07-02)

\n"); + php_info_print_box_end(); + /* add your stuff here */ + +} +/* }}} */ + +#endif /* HAVE_JLOG */ + + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: et sw=2 ts=2 sts=2 ai bs=2 fdm=marker + */ diff --git a/php/package.xml b/php/package.xml new file mode 100644 index 0000000..37fdf94 --- /dev/null +++ b/php/package.xml @@ -0,0 +1,53 @@ + + + + + jlog + A sample PHP extension + + This is a sample extension specification + showing how to use CodeGen_PECL for + extension generation. + + + + + + + + 0.0.1 + devel + 2007-07-02 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/php/package2.xml b/php/package2.xml new file mode 100644 index 0000000..7504255 --- /dev/null +++ b/php/package2.xml @@ -0,0 +1,80 @@ + + + + jlog + pecl.php.net + + A sample PHP extension + + + This is a sample extension specification + showing how to use CodeGen_PECL for + extension generation. + + + + 2007-07-02 + + 0.0.1 + 0.0.1 + + + devel + devel + + + unknown + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5.0.0 + + + 1.4.0a1 + + unix + + + + jlog + + + + + + diff --git a/php/php_jlog.h b/php/php_jlog.h new file mode 100644 index 0000000..494fd88 --- /dev/null +++ b/php/php_jlog.h @@ -0,0 +1,133 @@ +/* + +----------------------------------------------------------------------+ + | unknown license: | + +----------------------------------------------------------------------+ + +----------------------------------------------------------------------+ +*/ + +/* $ Id: $ */ + +#ifndef PHP_JLOG_H +#define PHP_JLOG_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#ifdef HAVE_JLOG + +#include +#include +#include +#include +#ifdef __cplusplus +} // extern "C" +#endif +#include +#ifdef __cplusplus +extern "C" { +#endif + +extern zend_module_entry jlog_module_entry; +#define phpext_jlog_ptr &jlog_module_entry + +#ifdef PHP_WIN32 +#define PHP_JLOG_API __declspec(dllexport) +#else +#define PHP_JLOG_API +#endif + +PHP_MINIT_FUNCTION(jlog); +PHP_MSHUTDOWN_FUNCTION(jlog); +PHP_RINIT_FUNCTION(jlog); +PHP_RSHUTDOWN_FUNCTION(jlog); +PHP_MINFO_FUNCTION(jlog); + +#ifdef ZTS +#include "TSRM.h" +#endif + +#define FREE_RESOURCE(resource) zend_list_delete(Z_LVAL_P(resource)) + +#define PROP_GET_LONG(name) Z_LVAL_P(zend_read_property(_this_ce, _this_zval, #name, strlen(#name), 1 TSRMLS_CC)) +#define PROP_SET_LONG(name, l) zend_update_property_long(_this_ce, _this_zval, #name, strlen(#name), l TSRMLS_CC) + +#define PROP_GET_DOUBLE(name) Z_DVAL_P(zend_read_property(_this_ce, _this_zval, #name, strlen(#name), 1 TSRMLS_CC)) +#define PROP_SET_DOUBLE(name, d) zend_update_property_double(_this_ce, _this_zval, #name, strlen(#name), d TSRMLS_CC) + +#define PROP_GET_STRING(name) Z_STRVAL_P(zend_read_property(_this_ce, _this_zval, #name, strlen(#name), 1 TSRMLS_CC)) +#define PROP_GET_STRLEN(name) Z_STRLEN_P(zend_read_property(_this_ce, _this_zval, #name, strlen(#name), 1 TSRMLS_CC)) +#define PROP_SET_STRING(name, s) zend_update_property_string(_this_ce, _this_zval, #name, strlen(#name), s TSRMLS_CC) +#define PROP_SET_STRINGL(name, s, l) zend_update_property_string(_this_ce, _this_zval, #name, strlen(#name), s, l TSRMLS_CC) + + +PHP_METHOD(Jlog, __construct); +ZEND_BEGIN_ARG_INFO(Jlog____construct_args, 0) + ZEND_ARG_INFO(0, options) +ZEND_END_ARG_INFO() +PHP_METHOD(Jlog, add_subscriber); +ZEND_BEGIN_ARG_INFO(Jlog__add_subscriber_args, 0) + ZEND_ARG_INFO(0, subscriber) + ZEND_ARG_INFO(0, whence) +ZEND_END_ARG_INFO() +PHP_METHOD(Jlog, remove_subscriber); +ZEND_BEGIN_ARG_INFO(Jlog__remove_subscriber_args, 0) + ZEND_ARG_INFO(0, subscriber) +ZEND_END_ARG_INFO() +PHP_METHOD(Jlog, list_subscribers); +ZEND_BEGIN_ARG_INFO(Jlog__list_subscribers_args, 0) +ZEND_END_ARG_INFO() +PHP_METHOD(Jlog, alter_journal_size); +ZEND_BEGIN_ARG_INFO(Jlog__alter_journal_size_args, 0) + ZEND_ARG_INFO(0, size) +ZEND_END_ARG_INFO() +PHP_METHOD(Jlog, raw_size); +ZEND_BEGIN_ARG_INFO(Jlog__raw_size_args, 0) +ZEND_END_ARG_INFO() +PHP_METHOD(Jlog, close); +ZEND_BEGIN_ARG_INFO(Jlog__close_args, 0) +ZEND_END_ARG_INFO() +PHP_METHOD(Jlog_Writer, open); +ZEND_BEGIN_ARG_INFO(Jlog_Writer__open_args, 0) +ZEND_END_ARG_INFO() +PHP_METHOD(Jlog_Writer, write); +ZEND_BEGIN_ARG_INFO(Jlog_Writer__write_args, 0) + ZEND_ARG_INFO(0, buffer) +ZEND_END_ARG_INFO() +PHP_METHOD(Jlog_Reader, open); +ZEND_BEGIN_ARG_INFO(Jlog_Reader__open_args, 0) + ZEND_ARG_INFO(0, subscriber) +ZEND_END_ARG_INFO() +PHP_METHOD(Jlog_Reader, read); +ZEND_BEGIN_ARG_INFO(Jlog_Reader__read_args, 0) +ZEND_END_ARG_INFO() +PHP_METHOD(Jlog_Reader, checkpoint); +ZEND_BEGIN_ARG_INFO(Jlog_Reader__checkpoint_args, 0) +ZEND_END_ARG_INFO() +PHP_METHOD(Jlog_Reader, auto_checkpoint); +ZEND_BEGIN_ARG_INFO(Jlog_Reader__auto_checkpoint_args, 0) + ZEND_ARG_INFO(0, state) +ZEND_END_ARG_INFO() +#ifdef __cplusplus +} // extern "C" +#endif + +#endif /* PHP_HAVE_JLOG */ + +#endif /* PHP_JLOG_H */ + + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */