Index: ChangeLog =================================================================== RCS file: /cvsroot/gnats/gnats/gnats/ChangeLog,v retrieving revision 1.311 diff -u -p -r1.311 ChangeLog --- ChangeLog 24 Feb 2005 21:21:22 -0000 1.311 +++ ChangeLog 19 Apr 2005 00:49:19 -0000 @@ -1,5 +1,194 @@ 2005-02-19 Mel Hatzis + * configure.in: Added the beginnings for supporting multiple + backend datastores. A lot of the funtionality dealing directly + with the flat-file datastore has been moved into it's own + library (libds-file.a) which is pulled in unconditionally for now + - since there are no alternative backend datastores to configure. + + + * Makefile.in: Moved most of logic dealing with the flat-file + backend into a ds-file sub-directory for which we generate a + libds-file.a library. Modified all binary targets to link + against this library - the name of the datastore library to link + with is actually supplied by configure. Note that the index + file is now part of the flat-file datastore library; GNATS_LIBS + now includes a $(DS_LIB) macro defined by configure; a ds-libs + target has been added for building the datastore library; fconfig.y + is now generated (see the note about fconfig.y below). + + * ds.mk: New file. Used to generate the Makefile in all datastore + implementations. Contains all the common elements of a datastore + Makefile. + + * aclocal.m4: Regenerated. + + * autoconf.h.in: Regenerated. + + * configure: Regenerated. + + * cmds.c (do_server_query, GNATS_lock, GNATS_unlk, GNATS_edit): + Function renames for the datastore API - iterate_prs, prExists and + readPRWithNum renamed to db_query, pr_exists and pr_load_by_id + respectively. The renames are to conform to a standard naming + convention used by the datastore API to assist with datastore + implementation. + (GNATS_rset): Removed references to the index, which is now only + relevant when GNATS if configured with the flat-file datastore. + This change involves a modification to the message accompanying + the return code when the index is reloaded - since the index is + no longer always relevant, it makes no sense for the GNATS server + to refer to it in the client/server protocol. + + * database.c (struct databaseInfo, newDatabaseInfo): Replaced + indexDesc with a void ds_private pointer which is intended to cache + any datastore specific information (such as the indexDesc). The + indexDesc is now defined and exclusively used in the flat-file + datastore library and is pulled in via ds_private when needed. + (getIndexDesc, setIndexDesc, clearIndexDesc): Moved to index.c in + the flat-file datastore library since this is no longer generically + relevant. + (getDatastorePrivate, setDatastorePrivate): New functions. Used for + managing the datastore specific database elements such as the index + in the flat-file datastore implementation. + (loadDatabase): Replaced all flat-file datastore specific + functionality with a new "db_init" function which is part of the new + GNATS datastore API. + (freeDatabaseInfo): Replaced all references to functions which free + the index with a call to a new "db_destroy" function which is part of + the new GNATS datastore API. + + * database.h (setIndexDesc, setDatastorePrivate): Replaced + setIndexDesc with setDatastorePrivate - setIndexDesc now declared + in ds-file/index.h. Any datastore specific configuration elements + defined in the dbconfig (via ds-file/t-fconfig) are cached in the + DatabaseInfo struct via setDatastorePrivate. + (getIndexDesc, getDatastorePrivate): Replaced getIndexDesc with + getDatastorePrivate - getIndexDesc now declared in ds-file/index.h. + + * edit.c (processPRChanges): No need to check if the PR has been + completely read into memory...all callers of this function do so. + Furthermore, the idea of reading a PR partially is not desirable + for a generalized backend datastore. + (rewrite_pr): Moved all the logic associated with reading/writing + to a PR file and dealing with the index into a new 'pr_update' + function in ds-file/pr.c. + (replace_pr): Renamed prExists to pr_exists, readPRWithNum to + pr_load_by_id and fillInPR with pr_load. These functions are + all part of the datastore API and have been renamed for clarity. + + * ds-file/t-fconfig, fconfig.y, fconfig.y.in: fconfig.y renamed to + fconfig.y.in which no longer contains any datastore specific + grammar. The datastore specific items are now pulled in from + a t-fconfig file located in the ds-file directory. The fconfig.y + file is generated by concatenating fconfig.y.in with t-fconfig. + To accomodate this, fconfig.y.in now contains a dsDescription + section which all datastore's must define. + + * file-pr.c (createNewPRFile): Renamed to createNewPR since we'll + eventually be using alternatives to files for the backend. Also, + moved all the logic associated with reading/writing to a PR file + dealing with the index into a new 'pr_create' function in + ds-file/pr.c. + (run_atpr): Pass in an int for the PR number since we + no longer refer to a string representation of it in createNewPR. + (checkIfReply, append_report): Function renames for datastore API. + (getBugNumber): Moved to ds-file/pr.c - only relevant to flat-file + datastore. + (submit_pr): Function rename for multiple backend datastore support. + + * gnats.h: No longer pull in index.h...this is only used for the + flat-file datastore. + + * index.c, index.h, ds-file/index.c, ds-file/index.h: Moved the + index.[ch] files into the new ds-file sub-directory which implements + the flat-file backend datastore. The index is now only applicable + if GNATS is configured with the flat-file backend datastore. All + references to the index in the DatabaseInfo and PR data structures + have been replaced with an opaque 'ds_private' field, which is used + to cache all datastore specific data. The getIndexDesc/setIndexDesc + function definitions were moved out of database.c into index.c since + these functions are specific to the flat-file datastore. + + * pr.c (allocPR): Replaced allocIndex with a call to the new pr_init + function in the datastore API. The flat-file datastore implementation + of pr_init is where allocIndex is now called. Also handle the case + where there are no fields in the dbconfig without corrupting memory. + (get_pr, get_pr_from_index, get_pr_path, pr_file_readable): These + functions have been relocated to ds-file/pr.c since they are + specific to the flat-file datastore. + (fillInPR): Renamed to pr_load and moved to ds-file/pr.c since the + implementation of this function is datastore specific. + (field_value): Replaced the call to indexValue which retrieves the + field value from the flat-file index with a datastore API function + call named field_cache_value. This allows for datastore + implementations to utilize a cache (such as the flat-file index), + for keeping some field values in memory. + (free_pr): Replaced calls related to freeing the index with a new + datastore API function named pr_destroy. The flat-file datastore + implementation of pr_destroy is where the index is now free'd. + (prExists): Renamed to pr_exists and moved to ds-file/pr.c since + the implementation of this function is datastore specific. + (readPRWithNum): Renamed to pr_load_by_id and moved to ds-file/pr.c + since the implementation of this function is datastore specific. + (pr_delete): Moved to ds-file/pr.c since the implementation of this + function is datastore specific. + + * pr.h (PR_struct): Replaced the index pointer with an opaque + pointer to a struct containing datastore specific data. The index + is now defined within this opaque datastore data structure only when + GNATS is linked with the flat-file datastore library. + (fillInPR): Renamed to pr_load and moved to ds.h. + + * query.h (index.h): Removed index.h since the index is no longer a + generic part of the GNATS datastore; index.h is now only pulled in + when GNATS is configured with the flat-file backend. + (*QueryItem, SearchItem, *QueryTree, struct queryExpr): Imported + from query.c since these is required by the datastore library. + (*QueryFunc): Moved to ds.h since this is an integral part of the + datastore query functionality from which it is referenced. + (iterate_prs): Renamed to db_query and moved to ds.h since this is + now a part of the GNATS backend datastore API. The function was + renamed to conform to a standard naming convention used by the + datastore API to assist with datastore implementation. + + * query.c (*QueryItem, SearchItem, *QueryTree, struct queryExpr): + Moved to query.h since these are now referenced from the datastore + library. + (fieldCompare, process_format): The index is now only relevant when + GNATS is configured with the flat-file backend. The check for + whether a PR is only partially in memory or whether we're only + referencing an indexed field is no longer relevant in all cases. + The datastore is now responsible for implementing a partial PR + cache (such as the index). + (pr_matches_tree): No longer a static - referenced by the datastore + backend. + (iterate_prs): Renamed to db_query and moved into the datastore + library - this is a member of the datastore API. + + * ds.h: New File. Contains function declarations for all functions + implementing the GNATS datastore API. All datastore implementations + are required to define these functions. + + * ds-file/Makefile.in, ds-file/ds-file.h, ds-file/db.c, + ds-file/pr.c, ds-file/fld.c: New files. Collectively implement the + GNATS flat-file datastore. + + * gen-index.c: We will eventually only build this as part of the + flat-file datastore. For now, pull in the index include file from + the ds-file directory. + + * getclose.c (do_prlist): Reworked so as not to use the flat-file + index. Use a query to get the relevant PRs for processing. + + * pr-age.c (main): Function rename for multiple backend datastore. + + * pr-edit.c (main): Function renames for multiple backend datastore. + + * query-pr.c (main): Function renames for multiple backend datastore. + +2005-02-19 Mel Hatzis + * adm.c (copy_adm_entry): Handle blank metadata fields by correctly assigning the relevant field in the adm struct to NULL - and thereby avoid an attempt to xstrdup a NULL value. Index: Makefile.in =================================================================== RCS file: /cvsroot/gnats/gnats/gnats/Makefile.in,v retrieving revision 1.71 diff -u -p -r1.71 Makefile.in --- Makefile.in 24 Feb 2005 21:21:22 -0000 1.71 +++ Makefile.in 19 Apr 2005 00:49:19 -0000 @@ -129,11 +129,16 @@ SOURCES = btime.c cmds.c file-pr.c gen-c EXTRA_OBJS = @EXTRA_OBJS@ -LIBSRC = edit.c getdate.c gnugetopt.c gnugetopt1.c internal.c misc.c pr.c index.c lists.c query.c version.c regex.c adm.c client.c pr-init.c database.c fconfig.c fconfigl.c mail.c field.c mk_auth.c -LIBOBJS = edit.o getdate.o gnugetopt.o gnugetopt1.o internal.o misc.o pr.o index.o lists.o query.o version.o regex.o adm.o client.o pr-init.o database.o fconfig.o fconfigl.o mail.o field.o $(EXTRA_OBJS) +LIBSRC = edit.c getdate.c gnugetopt.c gnugetopt1.c internal.c misc.c pr.c lists.c query.c version.c regex.c adm.c client.c pr-init.c database.c fconfig.c fconfigl.c mail.c field.c mk_auth.c +LIBOBJS = edit.o getdate.o gnugetopt.o gnugetopt1.o internal.o misc.o pr.o lists.o query.o version.o regex.o adm.o client.o pr-init.o database.o fconfig.o fconfigl.o mail.o field.o $(EXTRA_OBJS) INCLUDEDIR = -I. -I$(srcdir) $(KRBINCLUDE) +DSLIB_DIR = @DSLIB_DIR@ +DS_LIB = $(DSLIB_DIR)/lib$(DSLIB_DIR).a +GNATS_LIB = libgnats.a +GNATS_LIBS = $(GNATS_LIB) $(DS_LIB) + # Variables that the send-pr Makefile will want to have changed. GNATS_VARS = \ "GNATS_DEFAULT_DB_DIR=$(GNATS_DEFAULT_DB_DIR)" \ @@ -161,14 +166,20 @@ all-gnats: all-tools gnatsd queue-pr mai at-pr mkcat mkdb rmcat gen-closed-date \ check-db delete-pr dbconfig gnats-databases gnats-pwconv $(EXTRA_STUFF) -all-tools: libgnats.a query-pr pr-age pr-edit edit-pr file-pr getclose \ - config-send-pr mail-agent dbconfig diff-prs +all-tools: $(GNATS_LIB) ds-libs query-pr pr-age pr-edit edit-pr file-pr \ + getclose config-send-pr mail-agent dbconfig diff-prs -libgnats.a: $(LIBOBJS) - -rm -f tmplibgnats.a libgnats.a - $(AR) $(AR_FLAGS) tmplibgnats.a $(LIBOBJS) - $(RANLIB) tmplibgnats.a - mv tmplibgnats.a libgnats.a +$(GNATS_LIB): $(LIBOBJS) + -rm -f tmp$@ $@ + $(AR) $(AR_FLAGS) tmp$@ $(LIBOBJS) + $(RANLIB) tmp$@ + mv tmp$@ $@ + +ds-libs: + @if [ -d $(DSLIB_DIR) ]; then \ + echo " "; echo " cd $(DSLIB_DIR); $(MAKE) $(GNATS_VARS) all"; \ + ( cd $(DSLIB_DIR); $(MAKE) $(GNATS_VARS) all ); \ + fi .c.o: $(CC) -c $(INCLUDEDIR) $(CFLAGS) $(GCC_CFLAGS) $(CPPFLAGS) $(DEFS) $< @@ -177,40 +188,40 @@ version.c: Makefile echo 'const char *version_string = "$(VERSION)";' > $@-t mv $@-t $@ -query-pr: query-pr.o regex.o libgnats.a +query-pr: query-pr.o regex.o $(GNATS_LIBS) $(CC) $(LDFLAGS) -o $@ query-pr.o regex.o \ - libgnats.a $(LIBS) + $(GNATS_LIBS) $(LIBS) -gnatsd: gnatsd.o cmds.o regex.o file-pr.o btime.o libgnats.a +gnatsd: gnatsd.o cmds.o regex.o file-pr.o btime.o $(GNATS_LIBS) $(CC) $(LDFLAGS) -o $@ gnatsd.o cmds.o regex.o file-pr.o btime.o \ - libgnats.a $(LIBRX) $(LIBS) + $(GNATS_LIBS) $(LIBRX) $(LIBS) -queue-pr: queue-pr.o libgnats.a +queue-pr: queue-pr.o $(GNATS_LIBS) $(CC) $(LDFLAGS) -o $@ queue-pr.o \ - libgnats.a $(LIBS) + $(GNATS_LIBS) $(LIBS) -pr-age: pr-age.o regex.o libgnats.a +pr-age: pr-age.o regex.o $(GNATS_LIBS) $(CC) $(LDFLAGS) -o $@ pr-age.o regex.o \ - libgnats.a $(LIBS) + $(GNATS_LIBS) $(LIBS) -getclose: getclose.o regex.o libgnats.a +getclose: getclose.o regex.o $(GNATS_LIBS) $(CC) $(LDFLAGS) -o $@ getclose.o regex.o \ - libgnats.a $(LIBS) + $(GNATS_LIBS) $(LIBS) -pr-edit: pr-edit.o file-pr.o btime.o libgnats.a +pr-edit: pr-edit.o file-pr.o btime.o $(GNATS_LIBS) $(CC) $(LDFLAGS) -o $@ pr-edit.o file-pr.o btime.o \ - libgnats.a $(LIBS) + $(GNATS_LIBS) $(LIBS) -pr-stat: pr-stat.o regex.o libgnats.a +pr-stat: pr-stat.o regex.o $(GNATS_LIBS) $(CC) $(LDFLAGS) -o $@ pr-stat.o regex.o \ - libgnats.a $(LIBS) + $(GNATS_LIBS) $(LIBS) -gen-index: gen-index.o libgnats.a - $(CC) $(LDFLAGS) -o $@ gen-index.o libgnats.a \ +gen-index: gen-index.o $(GNATS_LIBS) + $(CC) $(LDFLAGS) -o $@ gen-index.o $(GNATS_LIBS) \ $(LIBS) -gen-closed-date: gen-closed-date.o libgnats.a - $(CC) $(LDFLAGS) -o $@ gen-closed-date.o libgnats.a \ +gen-closed-date: gen-closed-date.o $(GNATS_LIBS) + $(CC) $(LDFLAGS) -o $@ gen-closed-date.o $(GNATS_LIBS) \ $(LIBS) at-pr: at-pr.sh Makefile @@ -318,9 +329,9 @@ gnats-databases: gnats-databases.in Make $(srcdir)/gnats-databases.in > $@-t @mv $@-t $@ -gnats-pwconv: gnats-pwconv.o libgnats.a +gnats-pwconv: gnats-pwconv.o $(GNATS_LIBS) $(CC) $(LDFLAGS) -o $@ gnats-pwconv.o \ - libgnats.a $(LIBS) + $(GNATS_LIBS) $(LIBS) getdate.c: getdate.y @echo expect 10 shift/reduce conflicts @@ -335,7 +346,7 @@ fconfig.o: fconfig.c # fconfig.c: fconfig.y @rm -f fconfig.c fconfig.h fconfig.tab.c fconfig.tab.h - $(YACC) -p fconf -b fconfig -d $(srcdir)/fconfig.y + $(YACC) -p fconf -b fconfig -d fconfig.y @if [ -f y.tab.c -a ! -f fconfig.c ] ; then \ mv y.tab.c fconfig.c ; \ mv y.tab.h fconfig.h ; \ @@ -345,6 +356,10 @@ fconfig.c: fconfig.y mv fconfig.tab.h fconfig.h ; \ fi +fconfig.y: fconfig.y.in $(srcdir)/$(DSLIB_DIR)/t-fconfig + @echo Creating fconfig.y... + cat $(srcdir)/fconfig.y.in $(srcdir)/$(DSLIB_DIR)/t-fconfig > $@ + fconfigl.c: fconfigl.l $(LEX) -t $(srcdir)/fconfigl.l >fconfigl.c @@ -555,7 +570,7 @@ version.texi: clean: mostlyclean -rm -f *.o core queue-pr gnats at-pr mkcat mkdb gnats-pwconv -rm -f rmcat file-pr mail-query check-db delete-pr diff-prs - -rm -f libgnats.a + -rm -f $(GNATS_LIB) -rm -f pr-age pr-stat query-pr pr-edit gnats-edit-pr edit-pr gen-index -rm -f gnatsd getclose gen-closed-date -rm -f gnats-file-pr file-pr nfile-pr *.dvi version.c @@ -573,7 +588,7 @@ distclean: clean -rm -f TAGS maintainer-clean realclean: distclean - -rm -f getdate.c fconfig.c fconfig.h fconfigl.c + -rm -f fconfig.y getdate.c fconfig.c fconfig.h fconfigl.c # FIXME dist: @@ -617,9 +632,6 @@ getclose.o: $(srcdir)/query.h getdate.o: autoconf.h gnugetopt.o: autoconf.h $(srcdir)/gnugetopt.h gnugetopt1.o: autoconf.h $(srcdir)/gnugetopt.h -index.o: $(srcdir)/pr.h -index.o: autoconf.h -index.o: $(srcdir)/gnats.h $(srcdir)/ansidecl.h internal.o: $(srcdir)/pr.h internal.o: autoconf.h misc.o: $(srcdir)/ansidecl.h Index: aclocal.m4 =================================================================== RCS file: /cvsroot/gnats/gnats/gnats/aclocal.m4,v retrieving revision 1.3 diff -u -p -r1.3 aclocal.m4 --- aclocal.m4 4 Aug 2001 21:07:18 -0000 1.3 +++ aclocal.m4 19 Apr 2005 00:49:19 -0000 @@ -1,64 +1,72 @@ -dnl aclocal.m4 generated automatically by aclocal 1.4-p4 +# aclocal.m4t generated automatically by aclocal 1.6.3 -*- Autoconf -*- -dnl Copyright (C) 1994, 1995-8, 1999 Free Software Foundation, Inc. -dnl This file is free software; the Free Software Foundation -dnl gives unlimited permission to copy and/or distribute it, -dnl with or without modifications, as long as this notice is preserved. - -dnl This program is distributed in the hope that it will be useful, -dnl but WITHOUT ANY WARRANTY, to the extent permitted by law; without -dnl even the implied warranty of MERCHANTABILITY or FITNESS FOR A -dnl PARTICULAR PURPOSE. - -#serial 1 -# This test replaces the one in autoconf. -# Currently this macro should have the same name as the autoconf macro -# because gettext's gettext.m4 (distributed in the automake package) -# still uses it. Otherwise, the use in gettext.m4 makes autoheader -# give these diagnostics: -# configure.in:556: AC_TRY_COMPILE was called before AC_ISC_POSIX -# configure.in:556: AC_TRY_RUN was called before AC_ISC_POSIX - -undefine([AC_ISC_POSIX]) - -AC_DEFUN([AC_ISC_POSIX], - [ - dnl This test replaces the obsolescent AC_ISC_POSIX kludge. - AC_CHECK_LIB(cposix, strerror, [LIBS="$LIBS -lcposix"]) - ] -) - - -# serial 1 - -AC_DEFUN(AM_PATH_LISPDIR, - [# If set to t, that means we are running in a shell under Emacs. - # If you have an Emacs named "t", then use the full path. - test "$EMACS" = t && EMACS= - AC_PATH_PROGS(EMACS, emacs xemacs, no) - if test $EMACS != "no"; then - AC_MSG_CHECKING([where .elc files should go]) - dnl Set default value - lispdir="\$(datadir)/emacs/site-lisp" - emacs_flavor=`echo "$EMACS" | sed -e 's,^.*/,,'` - if test "x$prefix" = "xNONE"; then - if test -d $ac_default_prefix/share/$emacs_flavor/site-lisp; then - lispdir="\$(prefix)/share/$emacs_flavor/site-lisp" - else - if test -d $ac_default_prefix/lib/$emacs_flavor/site-lisp; then - lispdir="\$(prefix)/lib/$emacs_flavor/site-lisp" - fi - fi - else - if test -d $prefix/share/$emacs_flavor/site-lisp; then - lispdir="\$(prefix)/share/$emacs_flavor/site-lisp" - else - if test -d $prefix/lib/$emacs_flavor/site-lisp; then - lispdir="\$(prefix)/lib/$emacs_flavor/site-lisp" - fi - fi - fi - AC_MSG_RESULT($lispdir) - fi - AC_SUBST(lispdir)]) +# Copyright 1996, 1997, 1998, 1999, 2000, 2001, 2002 +# Free Software Foundation, Inc. +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + + +# Copyright 1996, 1997, 1998, 1999, 2000, 2001 Free Software Foundation, Inc. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +# 02111-1307, USA. + +# serial 5 + +# AM_PATH_LISPDIR +# --------------- +AC_DEFUN([AM_PATH_LISPDIR], +[AC_ARG_WITH(lispdir, + [ --with-lispdir Override the default lisp directory ], + [ lispdir="$withval" + AC_MSG_CHECKING([where .elc files should go]) + AC_MSG_RESULT([$lispdir])], + [ + # If set to t, that means we are running in a shell under Emacs. + # If you have an Emacs named "t", then use the full path. + test x"$EMACS" = xt && EMACS= + AC_CHECK_PROGS(EMACS, emacs xemacs, no) + if test $EMACS != "no"; then + if test x${lispdir+set} != xset; then + AC_CACHE_CHECK([where .elc files should go], [am_cv_lispdir], + [# If $EMACS isn't GNU Emacs or XEmacs, this can blow up pretty badly + # Some emacsen will start up in interactive mode, requiring C-x C-c to exit, + # which is non-obvious for non-emacs users. + # Redirecting /dev/null should help a bit; pity we can't detect "broken" + # emacsen earlier and avoid running this altogether. + AC_RUN_LOG([$EMACS -batch -q -eval '(while load-path (princ (concat (car load-path) "\n")) (setq load-path (cdr load-path)))' conftest.out]) + am_cv_lispdir=`sed -n \ + -e 's,/$,,' \ + -e '/.*\/lib\/\(x\?emacs\/site-lisp\)$/{s,,${libdir}/\1,;p;q;}' \ + -e '/.*\/share\/\(x\?emacs\/site-lisp\)$/{s,,${datadir}/\1,;p;q;}' \ + conftest.out` + rm conftest.out + if test -z "$am_cv_lispdir"; then + am_cv_lispdir='${datadir}/emacs/site-lisp' + fi + ]) + lispdir="$am_cv_lispdir" + fi + fi +]) +AC_SUBST(lispdir) +])# AM_PATH_LISPDIR Index: autoconf.h.in =================================================================== RCS file: /cvsroot/gnats/gnats/gnats/autoconf.h.in,v retrieving revision 1.13 diff -u -p -r1.13 autoconf.h.in --- autoconf.h.in 24 Feb 2005 21:21:22 -0000 1.13 +++ autoconf.h.in 19 Apr 2005 00:49:19 -0000 @@ -37,6 +37,9 @@ Software Foundation, 59 Temple Place - S /* Define to 1 if using `alloca.c'. */ #undef C_ALLOCA +/* Use flat-file datastore. */ +#undef DS_FILE + /* Define to 1 if you have `alloca', as a function or macro. */ #undef HAVE_ALLOCA Index: cmds.c =================================================================== RCS file: /cvsroot/gnats/gnats/gnats/cmds.c,v retrieving revision 1.71 diff -u -p -r1.71 cmds.c --- cmds.c 30 Aug 2003 07:58:14 -0000 1.71 +++ cmds.c 19 Apr 2005 00:49:19 -0000 @@ -23,6 +23,7 @@ Software Foundation, 59 Temple Place - S #include "gnatsd.h" #include "query.h" #include "mail.h" +#include "ds.h" #ifdef HAVE_KERBEROS #include @@ -381,8 +382,8 @@ do_server_query (int ac, char **av) return; } set_confidential_access (&query); - if (iterate_prs (currentDatabase, ac, av, query, queryFormat, - server_print_query, &err) < 0) + if (db_query (currentDatabase, ac, av, query, + server_print_query, queryFormat, &err) < 0) { print_server_errors (err); } @@ -415,7 +416,7 @@ GNATS_lock (int ac, char **av) return; } - if (! prExists (currentDatabase, av[0], &err)) + if (! pr_exists (currentDatabase, av[0], &err)) { print_server_errors (err); return; @@ -440,7 +441,7 @@ GNATS_lock (int ac, char **av) if ((result = lock_pr (currentDatabase, av[0], av[1], l, &err)) != 0) { - PR *pr = readPRWithNum (currentDatabase, av[0], 0, &err); + PR *pr = pr_load_by_id (currentDatabase, av[0], 0, &err); if (pr != NULL) { @@ -539,7 +540,7 @@ GNATS_unlk (int ac, char **av) if (! chk_nargs (ac, "PR to be unlocked")) return; - if (! prExists (currentDatabase, av[0], &err)) + if (! pr_exists (currentDatabase, av[0], &err)) { print_server_errors (err); return; @@ -775,7 +776,7 @@ GNATS_edit (int ac, char **av) if (! chk_nargs (ac, "PR to be edited")) return; - if (! prExists (currentDatabase, av[0], &err)) + if (! pr_exists (currentDatabase, av[0], &err)) { print_server_errors (err); return; @@ -1095,7 +1096,6 @@ GNATS_expr (int ac, char **av) void GNATS_rset (int ac, char **av ATTRIBUTE_UNUSED) { - int new_index = 0; ErrorDesc err = NULL; if (ac != 0) @@ -1107,28 +1107,15 @@ GNATS_rset (int ac, char **av ATTRIBUTE_ freeQueryExpr (query); query = NULL; - if (checkPRChain (currentDatabase, &err)) - { - new_index = 1; - } - + db_reset (currentDatabase, &err); if (err != NULL) { print_server_errors (err); freeDatabaseInfo (currentDatabase); currentDatabase = NULL; + return; } - else - { - if (new_index) - { - printf ("%d Reset state...reloaded the index.\r\n", CODE_OK); - } - else - { - printf ("%d Reset state.\r\n", CODE_OK); - } - } + printf ("%d Reset state.\r\n", CODE_OK); } static char * Index: configure =================================================================== RCS file: /cvsroot/gnats/gnats/gnats/configure,v retrieving revision 1.36 diff -u -p -r1.36 configure --- configure 24 Feb 2005 21:21:22 -0000 1.36 +++ configure 19 Apr 2005 00:49:19 -0000 @@ -310,7 +310,7 @@ ac_includes_default="\ # include #endif" -ac_subst_vars='SHELL PATH_SEPARATOR PACKAGE_NAME PACKAGE_TARNAME PACKAGE_VERSION PACKAGE_STRING PACKAGE_BUGREPORT exec_prefix prefix program_transform_name bindir sbindir libexecdir datadir sysconfdir sharedstatedir localstatedir libdir includedir oldincludedir infodir mandir build_alias host_alias target_alias DEFS ECHO_C ECHO_N ECHO_T LIBS PROGS MAN CC CFLAGS LDFLAGS CPPFLAGS ac_ct_CC EXEEXT OBJEXT CPP INSTALL_PROGRAM INSTALL_SCRIPT INSTALL_DATA RANLIB ac_ct_RANLIB YACC LEX LEXLIB LEX_OUTPUT_ROOT M4 AWK EGREP ALLOCA EMACS lispdir GNATS_ALL GNATS_INSTALL KRB4 HAVE_KERBEROS KRBINCLUDE EXTRA_OBJS GNATS_SERVICE GNATS_USER DEFAULT_RELEASE DEFAULT_ORGANIZATION SUBMITTER DEFAULT_MAIL_AGENT BWEEK_START BWEEK_END BDAY_START BDAY_END GNATSD_USER_ACCESS_FILE GNATSD_HOST_ACCESS_FILE GLOBAL_DB_LIST_FILE GNATS_DEFAULT_DB_DIR GCC_CFLAGS SENDMAIL LIBOBJS LTLIBOBJS' +ac_subst_vars='SHELL PATH_SEPARATOR PACKAGE_NAME PACKAGE_TARNAME PACKAGE_VERSION PACKAGE_STRING PACKAGE_BUGREPORT exec_prefix prefix program_transform_name bindir sbindir libexecdir datadir sysconfdir sharedstatedir localstatedir libdir includedir oldincludedir infodir mandir build_alias host_alias target_alias DEFS ECHO_C ECHO_N ECHO_T LIBS PROGS MAN CC CFLAGS LDFLAGS CPPFLAGS ac_ct_CC EXEEXT OBJEXT CPP INSTALL_PROGRAM INSTALL_SCRIPT INSTALL_DATA RANLIB ac_ct_RANLIB YACC LEX LEXLIB LEX_OUTPUT_ROOT M4 AWK EGREP ALLOCA EMACS lispdir GNATS_ALL GNATS_INSTALL KRB4 HAVE_KERBEROS KRBINCLUDE EXTRA_OBJS GNATS_SERVICE GNATS_USER DEFAULT_RELEASE DEFAULT_ORGANIZATION SUBMITTER DEFAULT_MAIL_AGENT BWEEK_START BWEEK_END BDAY_START BDAY_END GNATSD_USER_ACCESS_FILE GNATSD_HOST_ACCESS_FILE GLOBAL_DB_LIST_FILE GNATS_DEFAULT_DB_DIR GCC_CFLAGS SENDMAIL BUILD_FILE DSLIB_DIR LIBOBJS LTLIBOBJS' ac_subst_files='' # Initialize some variables set by options. @@ -853,6 +853,7 @@ Optional Packages: --with-gnats-dblist-file=PATH specify file containing list of databases --with-gnats-default-db=PATH specify the default database directory to use --with-lispdir=PATH specify the default lisp directory to use + --with-lispdir Override the default lisp directory --with-krb4 support Kerberos 4 Some influential environment variables: @@ -1317,6 +1318,9 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_config_headers="$ac_config_headers autoconf.h" +# default to using the file data store +DSLIB_DIR="ds-file" + # Check whether --with-kerberos or --without-kerberos was given. if test "${with_kerberos+set}" = set; then @@ -3562,14 +3566,13 @@ _ACEOF fi - - echo "$as_me:$LINENO: checking for strerror in -lcposix" >&5 -echo $ECHO_N "checking for strerror in -lcposix... $ECHO_C" >&6 -if test "${ac_cv_lib_cposix_strerror+set}" = set; then +echo "$as_me:$LINENO: checking for library containing strerror" >&5 +echo $ECHO_N "checking for library containing strerror... $ECHO_C" >&6 +if test "${ac_cv_search_strerror+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else - ac_check_lib_save_LIBS=$LIBS -LIBS="-lcposix $LIBS" + ac_func_search_save_LIBS=$LIBS +ac_cv_search_strerror=no cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF @@ -3614,24 +3617,80 @@ if { (eval echo "$as_me:$LINENO: \"$ac_l ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); }; }; then - ac_cv_lib_cposix_strerror=yes + ac_cv_search_strerror="none required" else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 -ac_cv_lib_cposix_strerror=no fi rm -f conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext -LIBS=$ac_check_lib_save_LIBS +if test "$ac_cv_search_strerror" = no; then + for ac_lib in cposix; do + LIBS="-l$ac_lib $ac_func_search_save_LIBS" + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ + +/* Override any gcc2 internal prototype to avoid an error. */ +#ifdef __cplusplus +extern "C" +#endif +/* We use char because int might match the return type of a gcc2 + builtin and then its argument prototype would still apply. */ +char strerror (); +int +main () +{ +strerror (); + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext conftest$ac_exeext +if { (eval echo "$as_me:$LINENO: \"$ac_link\"") >&5 + (eval $ac_link) 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && + { ac_try='test -z "$ac_c_werror_flag" + || test ! -s conftest.err' + { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5 + (eval $ac_try) 2>&5 + ac_status=$? + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); }; } && + { ac_try='test -s conftest$ac_exeext' + { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5 + (eval $ac_try) 2>&5 + ac_status=$? + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); }; }; then + ac_cv_search_strerror="-l$ac_lib" +break +else + echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + fi -echo "$as_me:$LINENO: result: $ac_cv_lib_cposix_strerror" >&5 -echo "${ECHO_T}$ac_cv_lib_cposix_strerror" >&6 -if test $ac_cv_lib_cposix_strerror = yes; then - LIBS="$LIBS -lcposix" +rm -f conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + done fi +LIBS=$ac_func_search_save_LIBS +fi +echo "$as_me:$LINENO: result: $ac_cv_search_strerror" >&5 +echo "${ECHO_T}$ac_cv_search_strerror" >&6 +if test "$ac_cv_search_strerror" != no; then + test "$ac_cv_search_strerror" = "none required" || LIBS="$ac_cv_search_strerror $LIBS" - +fi # The Ultrix 4.2 mips builtin alloca declared by alloca.h only works # for constant arguments. Useless! @@ -5509,42 +5568,49 @@ if test -n "$with_lispdir" ; then echo "${ECHO_T}$with_lispdir" >&6 lispdir=$with_lispdir else - # If set to t, that means we are running in a shell under Emacs. - # If you have an Emacs named "t", then use the full path. - test "$EMACS" = t && EMACS= - for ac_prog in emacs xemacs + +# Check whether --with-lispdir or --without-lispdir was given. +if test "${with_lispdir+set}" = set; then + withval="$with_lispdir" + lispdir="$withval" + echo "$as_me:$LINENO: checking where .elc files should go" >&5 +echo $ECHO_N "checking where .elc files should go... $ECHO_C" >&6 + echo "$as_me:$LINENO: result: $lispdir" >&5 +echo "${ECHO_T}$lispdir" >&6 +else + + # If set to t, that means we are running in a shell under Emacs. + # If you have an Emacs named "t", then use the full path. + test x"$EMACS" = xt && EMACS= + for ac_prog in emacs xemacs do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 echo "$as_me:$LINENO: checking for $ac_word" >&5 echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6 -if test "${ac_cv_path_EMACS+set}" = set; then +if test "${ac_cv_prog_EMACS+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else - case $EMACS in - [\\/]* | ?:[\\/]*) - ac_cv_path_EMACS="$EMACS" # Let the user override the test with a path. - ;; - *) - as_save_IFS=$IFS; IFS=$PATH_SEPARATOR + if test -n "$EMACS"; then + ac_cv_prog_EMACS="$EMACS" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if $as_executable_p "$as_dir/$ac_word$ac_exec_ext"; then - ac_cv_path_EMACS="$as_dir/$ac_word$ac_exec_ext" + ac_cv_prog_EMACS="$ac_prog" echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done - ;; -esac fi -EMACS=$ac_cv_path_EMACS - +fi +EMACS=$ac_cv_prog_EMACS if test -n "$EMACS"; then echo "$as_me:$LINENO: result: $EMACS" >&5 echo "${ECHO_T}$EMACS" >&6 @@ -5557,31 +5623,42 @@ fi done test -n "$EMACS" || EMACS="no" - if test $EMACS != "no"; then - echo "$as_me:$LINENO: checking where .elc files should go" >&5 + if test $EMACS != "no"; then + if test x${lispdir+set} != xset; then + echo "$as_me:$LINENO: checking where .elc files should go" >&5 echo $ECHO_N "checking where .elc files should go... $ECHO_C" >&6 - lispdir="\$(datadir)/emacs/site-lisp" - emacs_flavor=`echo "$EMACS" | sed -e 's,^.*/,,'` - if test "x$prefix" = "xNONE"; then - if test -d $ac_default_prefix/share/$emacs_flavor/site-lisp; then - lispdir="\$(prefix)/share/$emacs_flavor/site-lisp" - else - if test -d $ac_default_prefix/lib/$emacs_flavor/site-lisp; then - lispdir="\$(prefix)/lib/$emacs_flavor/site-lisp" - fi - fi - else - if test -d $prefix/share/$emacs_flavor/site-lisp; then - lispdir="\$(prefix)/share/$emacs_flavor/site-lisp" - else - if test -d $prefix/lib/$emacs_flavor/site-lisp; then - lispdir="\$(prefix)/lib/$emacs_flavor/site-lisp" - fi - fi - fi - echo "$as_me:$LINENO: result: $lispdir" >&5 -echo "${ECHO_T}$lispdir" >&6 - fi +if test "${am_cv_lispdir+set}" = set; then + echo $ECHO_N "(cached) $ECHO_C" >&6 +else + # If $EMACS isn't GNU Emacs or XEmacs, this can blow up pretty badly + # Some emacsen will start up in interactive mode, requiring C-x C-c to exit, + # which is non-obvious for non-emacs users. + # Redirecting /dev/null should help a bit; pity we can't detect "broken" + # emacsen earlier and avoid running this altogether. + { (echo "$as_me:$LINENO: \$EMACS -batch -q -eval '(while load-path (princ (concat (car load-path) \"\\n\")) (setq load-path (cdr load-path)))' conftest.out") >&5 + ($EMACS -batch -q -eval '(while load-path (princ (concat (car load-path) "\n")) (setq load-path (cdr load-path)))' conftest.out) 2>&5 + ac_status=$? + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } + am_cv_lispdir=`sed -n \ + -e 's,/$,,' \ + -e '/.*\/lib\/\(x\?emacs\/site-lisp\)$/{s,,${libdir}/\1,;p;q;}' \ + -e '/.*\/share\/\(x\?emacs\/site-lisp\)$/{s,,${datadir}/\1,;p;q;}' \ + conftest.out` + rm conftest.out + if test -z "$am_cv_lispdir"; then + am_cv_lispdir='${datadir}/emacs/site-lisp' + fi + +fi +echo "$as_me:$LINENO: result: $am_cv_lispdir" >&5 +echo "${ECHO_T}$am_cv_lispdir" >&6 + lispdir="$am_cv_lispdir" + fi + fi + +fi; + echo "$as_me:$LINENO: result: $lispdir" >&5 echo "${ECHO_T}$lispdir" >&6 @@ -6626,9 +6703,21 @@ if test -n "$verbose"; then echo " setting DEFAULT_MAIL_AGENT to $DEFAULT_MAIL_AGENT" fi +if test "$DSLIB_DIR" = ds-file ; then + BUILD_FILE=yes + +cat >>confdefs.h <<\_ACEOF +#define DS_FILE 1 +_ACEOF + +fi + + + + # ***** End of configuration section ***** - ac_config_files="$ac_config_files Makefile" + ac_config_files="$ac_config_files Makefile ds-file/Makefile:ds.mk:ds-file/Makefile.in" cat >confcache <<\_ACEOF # This file is a shell script that caches the results of configure @@ -7156,6 +7245,7 @@ do case "$ac_config_target" in # Handling of arguments. "Makefile" ) CONFIG_FILES="$CONFIG_FILES Makefile" ;; + "ds-file/Makefile" ) CONFIG_FILES="$CONFIG_FILES ds-file/Makefile:ds.mk:ds-file/Makefile.in" ;; "autoconf.h" ) CONFIG_HEADERS="$CONFIG_HEADERS autoconf.h" ;; *) { { echo "$as_me:$LINENO: error: invalid argument: $ac_config_target" >&5 echo "$as_me: error: invalid argument: $ac_config_target" >&2;} @@ -7288,6 +7378,8 @@ s,@GLOBAL_DB_LIST_FILE@,$GLOBAL_DB_LIST_ s,@GNATS_DEFAULT_DB_DIR@,$GNATS_DEFAULT_DB_DIR,;t t s,@GCC_CFLAGS@,$GCC_CFLAGS,;t t s,@SENDMAIL@,$SENDMAIL,;t t +s,@BUILD_FILE@,$BUILD_FILE,;t t +s,@DSLIB_DIR@,$DSLIB_DIR,;t t s,@LIBOBJS@,$LIBOBJS,;t t s,@LTLIBOBJS@,$LTLIBOBJS,;t t CEOF Index: configure.in =================================================================== RCS file: /cvsroot/gnats/gnats/gnats/configure.in,v retrieving revision 1.36 diff -u -p -r1.36 configure.in --- configure.in 24 Feb 2005 21:21:22 -0000 1.36 +++ configure.in 19 Apr 2005 00:49:19 -0000 @@ -25,6 +25,9 @@ AC_INIT(gnats,4.1.0,bug-gnats@gnu.org) AC_CONFIG_HEADER(autoconf.h) +# default to using the file data store +DSLIB_DIR="ds-file" + AC_ARG_WITH(kerberos, [ --with-kerberos include code for Kerberos authentication]) AC_ARG_WITH(gnats-service, @@ -338,7 +341,15 @@ if test -n "$verbose"; then echo " setting DEFAULT_MAIL_AGENT to $DEFAULT_MAIL_AGENT" fi +if test "$DSLIB_DIR" = ds-file ; then + BUILD_FILE=yes + AC_DEFINE(DS_FILE,1,[Use flat-file datastore.]) +fi + +AC_SUBST(BUILD_FILE) +AC_SUBST(DSLIB_DIR) + # ***** End of configuration section ***** -AC_CONFIG_FILES([Makefile]) -AC_OUTPUT +AC_CONFIG_FILES([Makefile] [ds-file/Makefile:ds.mk:ds-file/Makefile.in]) +AC_OUTPUT() Index: database.c =================================================================== RCS file: /cvsroot/gnats/gnats/gnats/database.c,v retrieving revision 1.25 diff -u -p -r1.25 database.c --- database.c 25 Nov 2002 13:58:33 -0000 1.25 +++ database.c 19 Apr 2005 00:49:19 -0000 @@ -22,6 +22,7 @@ Software Foundation, 59 Temple Place - S #include "mail.h" #include "pcodes.h" #include "field.h" +#include "ds.h" struct databaseInfo { @@ -42,10 +43,10 @@ struct databaseInfo FieldList requiredInputFields; DatabaseFieldInfo fieldInfo; AdmEntry *hostList; - IndexDesc indexDesc; MailMessageFormat mailFormatList; InputTemplate *inputTemplate; FieldIndex builtinFields[NUM_BUILTIN_FIELDS]; + void *ds_private; /* all the datastore specific stuff */ /* Next and previous entries in the chain. */ struct databaseInfo *next, *prev; }; @@ -87,9 +88,9 @@ newDatabaseInfo (void) res->requiredInputFields = NULL; res->fieldInfo = NULL; res->hostList = NULL; - res->indexDesc = NULL; res->mailFormatList = NULL; res->inputTemplate = NULL; + res->ds_private = NULL; for (x = 0; x < NUM_BUILTIN_FIELDS; x++) { @@ -275,16 +276,6 @@ setRequiredInputFields (DatabaseInfo dat } void -setIndexDesc (DatabaseInfo database, IndexDesc new) -{ - if (databaseValid (database)) - { - finishIndexDesc (database, new); - database->indexDesc = new; - } -} - -void setInputTemplate (DatabaseInfo database, InputTemplate *list) { database->inputTemplate = list; @@ -316,6 +307,12 @@ setMailFormatList (DatabaseInfo database database->mailFormatList = formatList; } +void +setDatastorePrivate(DatabaseInfo database, void *data) +{ + database->ds_private = data; +} + int keepReceivedHeaders (const DatabaseInfo database) { @@ -583,6 +580,12 @@ getMailFormatList (const DatabaseInfo da return database->mailFormatList; } +void * +getDatastorePrivate(const DatabaseInfo database) +{ + return database->ds_private; +} + static AdmEntry *dbList = NULL; static int @@ -741,25 +744,17 @@ loadDatabase (const char *databaseName, res->databaseEnt->admFields[DatabaseListPath], databaseName); freeDatabaseInfo (res); - res = NULL; + return NULL; } - else - { - char *path; - initHostList (res); - path = gnats_adm_dir (res, "dbconfig"); - if (fconfigParse (res, path, NULL, err)) - { - freeDatabaseInfo (res); - res = NULL; - } - else - { - initIndex (res); - } - free (path); + initHostList (res); + + if (db_init (res, err) < 0) + { + freeDatabaseInfo (res); + return NULL; } + return res; } @@ -867,12 +862,6 @@ getRequiredInputFields (const DatabaseIn return database->requiredInputFields; } -IndexDesc -getIndexDesc (const DatabaseInfo database) -{ - return database->indexDesc; -} - InputTemplate * getInputTemplate (const DatabaseInfo database) { @@ -921,16 +910,6 @@ clearGlobalChangeActions (DatabaseInfo d } static void -clearIndexDesc (DatabaseInfo database) -{ - if (database->indexDesc != NULL) - { - freeIndexDesc (database->indexDesc); - database->indexDesc = NULL; - } -} - -static void clearQueryFormatList (DatabaseInfo database) { if (database->queryFormatList != NULL) @@ -965,11 +944,13 @@ freeDatabaseInfo (DatabaseInfo database) { if (database != NULL) { - clearPRChain (database); clearQueryFormatList (database); - clearFieldList (database); clearMessageFormatList (database); - clearIndexDesc (database); + if (database->ds_private != NULL) + { + db_destroy (database); /* free up the datastore specific stuff */ + } + clearFieldList (database); clearAuditTrailFormat (database); clearHostList (database); clearInputTemplate (database); Index: database.h =================================================================== RCS file: /cvsroot/gnats/gnats/gnats/database.h,v retrieving revision 1.13 diff -u -p -r1.13 database.h --- database.h 24 Oct 2002 12:45:09 -0000 1.13 +++ database.h 19 Apr 2005 00:49:19 -0000 @@ -102,8 +102,7 @@ extern void setBuiltinDBField (DatabaseI extern void setMailFormatList (DatabaseInfo database, MailMessageFormat mailFormatList); extern void setRequiredInputFields (DatabaseInfo database, FieldList list); - -extern void setIndexDesc (DatabaseInfo database, const IndexDesc desc); +extern void setDatastorePrivate(DatabaseInfo database, void *data); extern int keepReceivedHeaders (const DatabaseInfo database); extern int debugMode (DatabaseInfo database); @@ -124,12 +123,12 @@ extern int createCategoryDirs (const Dat QueryFormat *getAuditTrailFormat (const DatabaseInfo database); extern int categoryDirPerms (const DatabaseInfo database); extern FieldList getRequiredInputFields (const DatabaseInfo database); -extern IndexDesc getIndexDesc (const DatabaseInfo database); extern InputTemplate *getInputTemplate (const DatabaseInfo database); extern QueryFormat *getQueryFormatList (const DatabaseInfo database); extern MailMessageFormat getMailFormatList (const DatabaseInfo database); extern FieldIndex getBuiltinField (const DatabaseInfo database, int whichField); +extern void *getDatastorePrivate(const DatabaseInfo database); extern void freeDatabaseInfo (DatabaseInfo database); Index: ds.h =================================================================== RCS file: ds.h diff -N ds.h --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ ds.h 19 Apr 2005 00:49:19 -0000 @@ -0,0 +1,67 @@ +/* The GNATS datastore API. + Copyright (C) 2005 Free Software Foundation, Inc. + Contributed by Mel Hatzis . + +This file is part of GNU GNATS. + +GNU GNATS is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GNU GNATS is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along +with GNU GNATS; see the file COPYING. if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#ifndef _DS_H +#define _DS_H + +#include "gnats.h" +#include "database.h" +#include "query.h" +#include "pr.h" + +/* --------- ds-[db]/db.c ------------- */ + +/* The function that is invoked from db_query () is of this type. + First argument is the result # (1 for the first PR matched, 2 for + the second, etc). The second argument is the PR entry for the + PR, and the final argument is the QueryFormat supplied to db_query. + + The function is invoked with a NULL PR entry when we run out of PRs + to match; the result # is the total # of PRs that were matched. */ +typedef void (*QueryFunc)(int, PR *, QueryFormat *); + +extern int db_init (DatabaseInfo db, ErrorDesc *err); +extern void db_destroy (DatabaseInfo db); +extern int db_query (const DatabaseInfo database, int ac, char **av, + QueryExpr exp, QueryFunc func, QueryFormat *query_format, + ErrorDesc *err); +extern void db_reset (const DatabaseInfo database, ErrorDesc *err); + +/* --------- ds-[db]/pr.c ------------- */ + +extern void pr_init (PR *pr); +extern void pr_destroy (PR *pr); +extern int pr_create (PR *pr, ErrorDesc *err); +extern int pr_update (PR *curr_pr, PR *new_pr, ErrorDesc *err); +extern int pr_delete (const DatabaseInfo database, const char *prnum, + ErrorDesc *err); +extern int pr_load (PR *pr, ErrorDesc *err); +extern int pr_load_fields (PR *pr, FieldList flds); +extern PR *pr_load_by_id (const DatabaseInfo database, const char *prnum, + int prune, ErrorDesc *err); +extern int pr_load_fields (PR *pr, FieldList flds); +extern int pr_exists (const DatabaseInfo database, const char *prnum, + ErrorDesc *err); + +/* --------- ds-[db]/fld.c ------------- */ + +extern char *field_cache_value (PR *pr, FieldIndex field); + +#endif /* !_DS_H */ Index: ds.mk =================================================================== RCS file: ds.mk diff -N ds.mk --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ ds.mk 19 Apr 2005 00:49:19 -0000 @@ -0,0 +1,85 @@ +# Common macros and targets included by all datastore Makefiles. +# Copyright (C) 2005 Free Software Foundation, Inc. +# Adapted from Makefile.in by Mel Hatzis . +# +# This file is part of GNU GNATS. +# +# GNU GNATS is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# GNU GNATS is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU gnats; see the file COPYING. If not, write to +# the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + +srcdir = @srcdir@ +VPATH = @srcdir@ + +#**** Start of system configuration section. + +#**** End of GNATS-specific configuration variables. + +CC = @CC@ +AR = ar +AR_FLAGS = rc + +# Set RANLIB = echo if your system doesn't have or need ranlib. +RANLIB = @RANLIB@ + +DIFFOPT = @DIFFOPT@ + +# These are set by autoconf. +M4 = @M4@ + +CFLAGS = @CFLAGS@ +GCC_CFLAGS = @GCC_CFLAGS@ +LDFLAGS = @LDFLAGS@ + +SHELL = /bin/sh + +all: + @if [ "$(BUILD_DS)" = "yes" ]; then \ + $(MAKE) TARGETLIB=$(TARGETLIB) $(GNATS_VARS) all-ds; \ + else \ + echo "run configure with $(CONFIG_OPT) to build $(TARGETLIB)"; \ + fi +dist: +info: +dvi: +clean: doclean +mostlyclean: domostlyclean +distclean: dodistclean +maintainer-clean realclean: dodistclean + +.c.o: + $(CC) -c $(INCLUDEDIR) $(CFLAGS) $(GCC_CFLAGS) $(CPPFLAGS) $(DEFS) $< + +$(TARGETLIB): $(LIBOBJS) + -rm -f tmp_$(TARGETLIB) $(TARGETLIB) + $(AR) $(AR_FLAGS) tmp_$(TARGETLIB) $(LIBOBJS) + $(RANLIB) tmp_$(TARGETLIB) + mv tmp_$(TARGETLIB) $(TARGETLIB) + +doclean: domostlyclean + -rm -f *.o core + -rm -f $(TARGETLIB) + -rm -f TAGS + +domostlyclean: + -rm -f *.toc *.log *.vr *.fn *.cp *.tp *.ky *.pg *.i *.s *.aux *.cps + +dodistclean: doclean + -rm -f Makefile config.status config.cache autoconf.h config config.log + -rm -rf =* .\#* \#* *~* + -rm -f *.orig *.rej + +# Prevent GNU make v3 from overflowing arg limit on SysV. +.NOEXPORT: + +#### ------------------------------------------------------------- Index: edit.c =================================================================== RCS file: /cvsroot/gnats/gnats/gnats/edit.c,v retrieving revision 1.61 diff -u -p -r1.61 edit.c --- edit.c 24 Feb 2005 19:21:12 -0000 1.61 +++ edit.c 19 Apr 2005 00:49:19 -0000 @@ -23,6 +23,7 @@ Software Foundation, 59 Temple Place - S #include "regex.h" #include "query.h" #include "mail.h" +#include "ds.h" /* Euuugh. There's gotta be a better way to keep this around. */ static char *newAuditTrailEntries = NULL; @@ -276,14 +277,6 @@ processPRChanges (const char *editUserEm ChangeActions globalActions = globalChangeActions (database); int *fieldsChanged; - if (! PR_IS_FULL (old_pr)) - { - if (fillInPR (old_pr, err) != 0) - { - return 1; - } - } - fieldsChanged = (int *) xmalloc (sizeof (int) * num_fields); for (x = 0; x < num_fields; x++) @@ -401,13 +394,6 @@ static int rewrite_pr (PR *pr, PR *new_pr, const char *editUserEmailAddr, ErrorDesc *err) { const DatabaseInfo database = new_pr->database; - FILE *prfile; - - char *new_path = NULL; - char *old_path = NULL; - char *bkup_path = NULL; - - int res = 1; newAuditTrailEntries = NULL; @@ -430,123 +416,11 @@ rewrite_pr (PR *pr, PR *new_pr, const ch return 0; } - /* check to see if the category changes, and if so, write the - new file out, and unlink the old one. */ - if (strcmp (field_value (pr, CATEGORY (database)), - field_value (new_pr, CATEGORY (database))) != 0) - { - char *dirPath; - - asprintf (&dirPath, "%s/%s", databaseDir (database), - field_value (new_pr, CATEGORY (database))); - - if (! fileExists (dirPath)) - { - if (createCategoryDirs (database)) - { - mode_t mode = categoryDirPerms (database); - - if (mkdir (dirPath, mode) != 0) - { - setError (err, CODE_FILE_ERROR, - "Error creating directory for category %s: %s", - field_value (new_pr, CATEGORY (database)), - strerror (errno)); - free (dirPath); - return 0; - } - } - else - { - setError (err, CODE_FILE_ERROR, - "Directory for category %s does not exist", - field_value (new_pr, CATEGORY (database))); - free (dirPath); - return 0; - } - } - free (dirPath); - } - - /* backup the current PR file */ - old_path = gen_pr_path (pr); - asprintf (&bkup_path, "%s.old", old_path); - if (rename (old_path, bkup_path) < 0) - { - if (errno != EXDEV) - { - setError (err, CODE_FILE_ERROR, "Could not rename %s: %s", - old_path, strerror (errno)); - free (bkup_path); - free (old_path); - return 0; - } - if (copy_file (old_path, bkup_path) != 0) - { - setError (err, CODE_FILE_ERROR, "Could not copy %s to %s: %s", - old_path, bkup_path, strerror (errno)); - free (bkup_path); - free (old_path); - return 0; - } - - /* Don't complain if this fails, since trying to write to it will give - us the diagnostic if it's really serious. */ - unlink (old_path); - } - - /* Now build the file. */ - new_path = gen_pr_path (new_pr); - prfile = fopen (new_path, "w+"); - if (prfile == (FILE *) NULL) - { - setError (err, CODE_FILE_ERROR, "Cannot write the PR to %s: %s", - new_path, strerror (errno)); - free (bkup_path); - free (old_path); - free (new_path); - return 0; - } - - /* We had to wait until now because we wanted to wait and see - about changing the closed_date. XXX ??? !!! FIXME */ - - buildIndexEntry (new_pr); - - write_entire_header (prfile, new_pr, "\n"); - fprintf (prfile, "\n"); - write_entire_pr (prfile, new_pr, "\n"); - - if (fclose (prfile) == EOF) + if (!pr_update (pr, new_pr, err)) { - setError (err, CODE_FILE_ERROR, "Error writing out PR %s: %s", - new_path, strerror (errno)); - free (bkup_path); - free (old_path); - free (new_path); return 0; } - unlink (bkup_path); - - /* unlink the old file, if it is in another category dir. */ - if (strcmp (field_value (pr, CATEGORY (database)), - field_value (new_pr, CATEGORY (database))) != 0) - { - unlink (old_path); - } - - free (bkup_path); - free (old_path); - free (new_path); - - /* write out the new index. */ - replaceCurrentPRInIndex (pr, new_pr, err); - if (writeIndex (database, err) != 0) - { - res = 0; - } - /* Send mail about the edit. */ if (newAuditTrailEntries != NULL) { @@ -556,7 +430,7 @@ rewrite_pr (PR *pr, PR *new_pr, const ch newAuditTrailEntries = NULL; } - return res; + return 1; } @@ -586,12 +460,12 @@ replace_pr (const DatabaseInfo database, return 0; } - if (! prExists (database, prnum, err)) + if (! pr_exists (database, prnum, err)) { return 0; } - curr_pr = readPRWithNum (database, prnum, 0, err); + curr_pr = pr_load_by_id (database, prnum, 0, err); if (curr_pr == NULL) { setError (err, CODE_NO_INDEX, @@ -685,7 +559,7 @@ validateFieldValue (FieldIndex field, co char *prnum = get_next_field (&tptr, ' '); if (prnum[0] != '\0') { - if (! prExists (fieldDefForIndex (field)->database, prnum, + if (! pr_exists (fieldDefForIndex (field)->database, prnum, err)) { setError (err, CODE_INVALID_FIELD_CONTENTS, @@ -994,7 +868,7 @@ edit_field (const DatabaseInfo database, int res; PR *pr, *new_pr; - if (! prExists (database, prnum, err)) + if (! pr_exists (database, prnum, err)) { free (newcontents); if (changeReason != NULL) @@ -1034,7 +908,7 @@ edit_field (const DatabaseInfo database, return 0; } - pr = readPRWithNum (database, prnum, 0, err); + pr = pr_load_by_id (database, prnum, 0, err); if (pr == NULL) { unlock_pr (database, prnum, err); @@ -1071,7 +945,7 @@ edit_field (const DatabaseInfo database, set_field (new_pr, NUMBER (database), prnum, err); set_field (new_pr, CATEGORY (database), field_value (pr, CATEGORY (database)), err); - fillInPR (new_pr, err); + pr_load (new_pr, err); /* set_field () verifies that the value is valid before doing the set. */ Index: fconfig.y =================================================================== RCS file: fconfig.y diff -N fconfig.y --- fconfig.y 13 Jan 2003 13:22:10 -0000 1.37 +++ /dev/null 1 Jan 1970 00:00:00 -0000 @@ -1,852 +0,0 @@ -%{ -#include "gnats.h" -#include "index.h" -#include "field.h" - extern DatabaseInfo databaseBeingDefined; - static FieldDef currField; - static ChangeActions currChange; - static FieldEdit *currEdit; - static QueryFormat *qformat; - static FieldList requiredFlds; - static InputTemplate *inputTemplate; - static MailMessageFormat mailFormat; - IndexDesc indexEntry; - static int badFile; - struct qstring; - - extern char *takeQString (struct qstring *str); - extern char *qStrVal (struct qstring *str); -%} - -%union { - int intval; - char *sval; - struct qstring *qstr; - AdmFieldDesc *adm_field_des; - FieldList flist; - StringList *stringlist; - InputTemplate *inputlist; - MailAddress *mailaddr; - MailAddressList *mailaddrlist; -} - -%token FIELD STRINGTYPE QDEFAULT MATCHING ENUM MULTIENUMTOK VALUES DEFAULT -%token EXACT_REGEXP INEXACT_REGEXP ALL FORMAT ENUMSEPARATORSTOK -%token MULTITEXTTYPE DATETYPE ENUM_IN_FILE MULTI_ENUM_IN_FILE -%token PATHTOK FIELDSTOK KEYTOK -%token QSTRING INTVAL TEXTSEARCH QUERYTOK FORMATTOK INDEXTOK -%token SEPARATORTOK RESTRICTEDTOK NOSPACESTOK INTEGERTOK INPUTDEFAULTTOK -%token BUILTINTOK ALLOWANYVALUETOK REQUIRETOK APPENDFIELDTOK SETFIELDTOK -%token CHANGETOK DESCRIPTIONTOK INPUTTOK DATABASEINFOTOK -%token DEBUGMODETOK KEEPRECTOK NOTIFYEXPTOK LIBEXECDIRTOK SUBMITTERACKTOK -%token BUSINESSDAYTOK BUSINESSWEEKTOK CREATECATEGORYDIRSTOK FALSETOK TRUETOK -%token MAILFORMATTOK TOADDRESSESTOK FROMADDRESSTOK REPLYTOTOK FIXEDTOK -%token BODYTOK HEADERTOK AUDITTRAILFMTTOK ADDAUDITTRAILTOK -%token REQUIRECHANGEREASONTOK READONLYTOK BINARYINDEXTOK RAWTOK -%token BADTOK AUXFLAGSTOK PRLISTTOK MAXPRSTOK EDITONLYTOK VIRTUALFORMATTOK -%token CATPERMSTOK -%type optChangeExpr -%type QSTRING -%type INTVAL -%type enumFieldList enumFieldMember -%type queryFieldsList FieldListMember fieldEditFieldList -%type enumValueList regexpList auxFlagsList -%type inputFields inputFieldsList -%type booleanVal -%type mailAddressTries MailAddressMember -%type mailAddress -%type mailAddressList -%type requiredFieldsList -%% - -config : configEnts - | parseError - ; - -configEnts : databaseInfo fieldDecStmt optQueryList auditTrailFmt mailFormatList globalChangeEnts indexDescription inputDescription - ; - -databaseInfo : DATABASEINFOTOK '{' databaseInfoList '}' - | DATABASEINFOTOK '{' parseError '}' - | /* empty */ { - fconferror ("Missing/bad database info section"); - } - ; - -parseError : error { badFile = 1; } ; - -databaseInfoList: databaseInfoEnt - | databaseInfoList databaseInfoEnt - ; - -databaseInfoEnt : DEBUGMODETOK booleanVal { - setDebugMode (databaseBeingDefined, $2); - } - | KEEPRECTOK booleanVal { - setKeepReceivedHeaders (databaseBeingDefined, $2); - } - | NOTIFYEXPTOK booleanVal { - setNotifyExpire (databaseBeingDefined, $2); - } - | LIBEXECDIRTOK QSTRING { - setBinDir (databaseBeingDefined, qStrVal ($2)); - } - | SUBMITTERACKTOK booleanVal { - setSubmitterAck (databaseBeingDefined, $2); - } - | BUSINESSDAYTOK INTVAL '-' INTVAL { - setBusinessDay (databaseBeingDefined, $2, $4); - } - | BUSINESSWEEKTOK INTVAL '-' INTVAL { - setBusinessWeek(databaseBeingDefined,$2, $4); - } - | CREATECATEGORYDIRSTOK booleanVal { - setCreateCategoryDirs (databaseBeingDefined, $2); - } - | CATPERMSTOK QSTRING { - setCategoryDirPerms (databaseBeingDefined, qStrVal ($2)); - } - ; - -booleanVal : FALSETOK { $$ = 0; } - | TRUETOK { $$ = 1; } - ; - -fieldDecStmt : fieldDecList - | /* empty */ { - fconferror ("Missing/bad field declarations"); - } - ; - -fieldDecList : fieldDec - | fieldDecList fieldDec - ; - -fieldDec : startFieldDec '{' fieldDataList '}' { - currField = NULL; - } - | startFieldDec '{' parseError '}' { - currField = NULL; - } - ; - -startFieldDec : FIELD QSTRING { - char *fname = takeQString ($2); - currField = newFieldDef (databaseBeingDefined, fname); - if (currField == NULL) - { - char *msg; - asprintf (&msg, "Duplicate field definition for %s\n", - fname); - fconferror (msg); - free (msg); - } - currField->default_value = NULL; - } - ; - -fieldDataList : fieldData - | fieldDataList fieldData - ; - -fieldData : fieldDataType - | miscOptions - | queryDefault - | virtualFieldFormat - ; - -virtualFieldFormat: VIRTUALFORMATTOK plainFormat { - currField->virtualFormat = qformat; - qformat = NULL; - } - ; - -fieldDataType : stringType - | enumType - | MULTITEXTTYPE optSimple { - currField->datatype = MultiText; - currField->defaultSearchType = NilSearch; - } - | DATETYPE { - currField->datatype = Date; - currField->defaultSearchType = LessThan; - } - | INTEGERTOK optSimple { - currField->datatype = Integer; - currField->defaultSearchType = NilSearch; - } - | PRLISTTOK prListOpts { - currField->datatype = PRListType; - currField->defaultSearchType = RegCmp; - } - ; - -stringType : STRINGTYPE { - currField->datatype = Text; - } - | STRINGTYPE MATCHING '{' regexpList '}' { - currField->datatype = TextWithRegex; - } - | STRINGTYPE MATCHING '{' parseError '}' - ; - -regexpList : QSTRING { - $$ = new_string_list_ent (takeQString ($1), NULL); - currField->regex = $$; - } - | regexpList QSTRING { - $1->next = new_string_list_ent (takeQString ($2), NULL); - $$ = $1->next; - } - ; - -enumType : ENUM '{' enumcontents '}' { - currField->datatype = Enum; - currField->defaultSearchType = RegCmp; - } - | ENUM_IN_FILE '{' enumFileContents '}' { - currField->datatype = Enum; - currField->defaultSearchType = RegCmp; - initAdmField (currField); - } - | MULTI_ENUM_IN_FILE '{' multiEnumFileContents '}' { - currField->datatype = MultiEnum; - currField->defaultSearchType = RegCmp; - initAdmField (currField); - } - | MULTIENUMTOK '{' multienumcontents '}' { - currField->datatype = MultiEnum; - currField->defaultSearchType = RegCmp; - if (currField->multiEnumSeparator == NULL) - { - currField->multiEnumSeparator - = xstrdup (DEFAULT_MULTIENUM_SEPARATOR); - } - } - | ENUM '{' parseError '}' - | ENUM_IN_FILE '{' parseError '}' - ; - -globalChangeEnts: /* empty */ - | globalChangeEnts globalChangeEnt - ; - -globalChangeEnt : changeHeader changeOpts '}' { - addGlobalChangeActions (databaseBeingDefined, currChange); - currChange = NULL; - } - ; - -changeClause : changeHeader changeOpts '}' { - ChangeActions *p = &(currField->changeActions); - while (*p != NULL) - { - p = &((*p)->next); - } - *p = currChange; - currChange = NULL; - } - ; - -changeHeader : CHANGETOK optChangeExpr '{' { - currChange = newChangeAction (databaseBeingDefined, $2); - if ($2 != NULL) - { - free ($2); - } - } - ; - -optChangeExpr : QSTRING { - $$ = takeQString ($1); - } - | /* Empty */ { - $$ = NULL; - } - ; - -changeOpts : changeOpt - | changeOpts changeOpt - ; - -changeOpt : REQUIRETOK '{' reqFieldNameList '}' - | REQUIRETOK '{' parseError '}' - | SETFIELDTOK fieldEditOpts { - currChange->edits = currEdit; - currEdit = NULL; - } - | APPENDFIELDTOK fieldEditOpts { - currEdit->append = 1; - currChange->edits = currEdit; - currEdit = NULL; - } - | ADDAUDITTRAILTOK { - currChange->addAuditTrail = 1; - } - | AUDITTRAILFMTTOK plainFormat { - currChange->auditTrailFormat = qformat; - qformat = NULL; - } - | REQUIRECHANGEREASONTOK { - currChange->requireChangeReason = 1; - } - ; - -reqFieldNameList: reqFieldNameEnt - | reqFieldNameList reqFieldNameEnt - ; - -reqFieldNameEnt: QSTRING { - FieldList foo - = newFieldListEnt (databaseBeingDefined, qStrVal ($1), - currChange->requiredFields); - currChange->requiredFields = foo; - } - ; - -fieldEditOpts : fieldEditName '{' fieldEditFormat optFieldEditFieldList '}' - | fieldEditName '{' parseError '}' - ; - -fieldEditName : QSTRING { - currEdit = (FieldEdit *) xmalloc (sizeof (FieldEdit)); - currEdit->expr = NULL; - currEdit->fieldToEditName = takeQString ($1); - currEdit->append = 0; - currEdit->textFormat = NULL; - currEdit->fieldsForFormat = NULL; - currEdit->next = NULL; - } - ; - -fieldEditFormat : QSTRING { - currEdit->textFormat = takeQString ($1); - } - ; - -optFieldEditFieldList: /* empty */ { - currEdit->fieldsForFormat = NULL; - } - | fieldEditFieldList { - } - ; - -fieldEditFieldList: QSTRING { - $$ = newFieldListEnt (databaseBeingDefined, qStrVal ($1), - NULL); - currEdit->fieldsForFormat = $$; - } - | fieldEditFieldList QSTRING { - $$ = newFieldListEnt (databaseBeingDefined, qStrVal ($2), - NULL); - $1->next = $$; - } - ; - -optSimple : /* Empty */ - | '{' defaultFieldVal '}' - | '{' parseError '}' - ; - -prListOpts : /* Empty */ - | '{' prListOptList '}' - ; - -prListOptList : MAXPRSTOK INTVAL { - currField->maxPrsPerLine = $2; - } - ; - -enumcontents : enumItem - | enumcontents enumItem - ; - -multienumcontents: multiEnumItem - | multienumcontents multiEnumItem - ; - -multiEnumItem : enumItem - | ENUMSEPARATORSTOK QSTRING { - currField->multiEnumSeparator = takeQString ($2); - } - ; - -enumItem : VALUES '{' enumValueList '}' - | VALUES '{' parseError '}' - | defaultFieldVal - ; - -enumValueList : QSTRING { - $$ = new_string_list_ent (takeQString ($1), NULL); - currField->enumValues = $$; - } - | enumValueList QSTRING { - $1->next = new_string_list_ent (takeQString ($2), NULL); - $$ = $1->next; - } - ; - -enumFileContents: enumFileItem - | enumFileContents enumFileItem - ; - -enumFileItem : PATHTOK QSTRING { - currField->adm_db_name = takeQString ($2); - } - | FIELDSTOK '{' enumFieldList '}' KEYTOK QSTRING { - AdmFieldDesc *p; - int which = 0; - - for (p = currField->adm_field_des; p != NULL; p = p->next) - { - if (strcmp (p->name, qStrVal ($6)) == 0) - { - break; - } - which++; - } - - if (p != NULL) - { - currField->key_field = which; - } - else - { - char *msg; - - asprintf (&msg, "Invalid adm subfield %s\n", - qStrVal ($6)); - fconferror (msg); - free (msg); - } - } - | FIELDSTOK '{' parseError '}' - | defaultFieldVal - | ALLOWANYVALUETOK { - currField->allow_any_value = 1; - } - ; - -multiEnumFileContents: multiEnumFileItem - | enumFileContents multiEnumFileItem - ; - -multiEnumFileItem : enumFileItem - | ENUMSEPARATORSTOK QSTRING { - currField->multiEnumSeparator = takeQString ($2); - } - ; - -defaultFieldVal : DEFAULT QSTRING { - currField->default_value = takeQString ($2); - } - | INPUTDEFAULTTOK QSTRING { - currField->input_default_value = takeQString ($2); - } - ; - -enumFieldList : enumFieldMember { - currField->adm_db_fields = 1; - currField->adm_field_des = $1; - $$ = $1; - } - | enumFieldList enumFieldMember { - $1->next = $2; - $$ = $2; - currField->adm_db_fields++; - } - ; - -enumFieldMember : QSTRING { - $$ = (AdmFieldDesc *) - xmalloc (sizeof (AdmFieldDesc)); - $$->name = takeQString ($1); - $$->next = NULL; - } - ; - -queryDefault : QDEFAULT EXACT_REGEXP { - currField->defaultSearchType = RegCmp; - } - | QDEFAULT INEXACT_REGEXP { - currField->defaultSearchType = RegFind; - } - ; - -miscOptions : TEXTSEARCH { - currField->textsearch = 1; - } - | RESTRICTEDTOK { - currField->restricted = 1; - } - | NOSPACESTOK { - currField->nospaces = 1; - } - | BUILTINTOK QSTRING { - if (setBuiltinField (currField, qStrVal ($2)) != 0) - { - char *msg; - asprintf (&msg, "Invalid builtin fieldname %s", - qStrVal ($2)); - fconferror (msg); - free (msg); - } - } - | changeClause - | DESCRIPTIONTOK QSTRING { - currField->description = takeQString ($2); - } - | READONLYTOK { - currField->readonly = 1; - } - | AUXFLAGSTOK '{' auxFlagsList '}' { - currField->auxFlags = $3; - } - | EDITONLYTOK { - currField->editonly = 1; - } - ; - -auxFlagsList : QSTRING { - $$ = new_string_list_ent (takeQString ($1), NULL); - currField->auxFlags = $$; - } - | auxFlagsList QSTRING { - $1->next = new_string_list_ent (takeQString ($2), NULL); - $$ = $1->next; - } - ; - -optQueryList : /* empty */ - | queryList - ; - -queryList : query - | queryList query - ; - -query : queryBegin '{' queryFmt '}' { - addQueryFormat (databaseBeingDefined, qformat); - qformat = NULL; - } - | queryBegin '{' parseError '}' { - freeQueryFormat (qformat); - qformat = NULL; - } - ; - -queryBegin : QUERYTOK QSTRING { - qformat = (QueryFormat *) xmalloc (sizeof (QueryFormat)); - qformat->name = takeQString ($2); - qformat->printf = NULL; - qformat->separator = NULL; - qformat->fields = NULL; - qformat->next = NULL; - } - ; - -queryFmt : queryFieldDesc optQueryOpts - | queryOpts queryFieldDesc - ; - -queryFieldDesc : FIELDSTOK ALL - | queryfields - | queryPrintf queryfields - | queryfields queryPrintf - ; - -optQueryOpts :/* empty */ - | queryOpts - ; - -queryOpts : RAWTOK { - qformat->rawQuery = 1; - } - ; - -queryPrintf : FORMATTOK QSTRING { - qformat->printf = takeQString ($2); - } - ; - -queryfields : FIELDSTOK '{' queryFieldsList '}' - | FIELDSTOK '{' parseError '}' - ; - -queryFieldsList : FieldListMember { - qformat->fields = $1; - } - | queryFieldsList FieldListMember { - $1->next = $2; - $$ = $2; - } - ; - -FieldListMember: QSTRING { - $$ = newFieldListEnt (databaseBeingDefined, qStrVal ($1), - NULL); - if (parseComplexFieldIndex ($$->ent) != 0) - { - char *msg; - - asprintf (&msg, "Field %s is invalid\n", qStrVal ($1)); - fconferror (msg); - free (msg); - } - } - ; - -indexDescription: indexheader '{' indexlist '}' { - setIndexDesc (databaseBeingDefined, indexEntry); - indexEntry = NULL; - } - | indexheader '{' parseError '}' { - freeIndexDesc (indexEntry); - indexEntry = NULL; - } - | parseError - ; - -indexheader : INDEXTOK { - indexEntry = newIndexDesc (databaseBeingDefined); - } - ; - -indexlist : indexEnt - | indexlist indexEnt - ; - -indexEnt : PATHTOK QSTRING { - setIndexDescPath (indexEntry, qStrVal ($2)); - } - | FIELDSTOK '{' indexFieldList '}' - | FIELDSTOK '{' parseError '}' - | indexSep - ; - -indexFieldList : FieldListMember { - addFieldToIndex (indexEntry, $1); - } - | indexFieldList FieldListMember { - addFieldToIndex (indexEntry, $2); - } - ; - -indexSep : SEPARATORTOK QSTRING { - setIndexDescSeparator (indexEntry, takeQString ($2)); - } - | BINARYINDEXTOK booleanVal { - setIndexDescBinary (indexEntry, $2); - } - ; - -inputDescription: INPUTTOK '{' inputEnt '}' - | INPUTTOK '{' parseError '}' - ; - -inputEnt : inputFields requiredFields { - setInputTemplate (databaseBeingDefined, $1); - } - ; - -requiredFields :/* empty */ - | REQUIRETOK '{' requiredFieldsList '}' { - setRequiredInputFields (databaseBeingDefined, requiredFlds); - } - | REQUIRETOK '{' parseError '}' { - freeFieldList (requiredFlds); - requiredFlds = NULL; - } - ; - -requiredFieldsList: FieldListMember { - requiredFlds = $1; - } - | requiredFieldsList FieldListMember { - $1->next = $2; - $$ = $2; - } - ; - -inputFields : FIELDSTOK '{' inputFieldsList '}' { - $$ = inputTemplate; - inputTemplate = NULL; - } - | FIELDSTOK '{' parseError '}' { - $$ = NULL; - } - ; - -inputFieldsList : QSTRING { - $$ = (InputTemplate *) - xmalloc (sizeof (InputTemplate)); - $$->index = find_field_index (databaseBeingDefined, - qStrVal ($1)); - if ($$->index == InvalidFieldIndex) - { - char *msg; - - asprintf (&msg, "Field %s is invalid\n", - qStrVal ($1)); - fconferror (msg); - free ($$); - free (msg); - inputTemplate = NULL; - } - else - { - inputTemplate = $$; - $$->next = NULL; - } - } - | inputFieldsList QSTRING { - $$ = (InputTemplate *) - xmalloc (sizeof (InputTemplate)); - $$->index = find_field_index (databaseBeingDefined, - qStrVal ($2)); - if ($$->index == InvalidFieldIndex) - { - char *msg; - - asprintf (&msg, "Field %s is invalid\n", - qStrVal ($2)); - fconferror (msg); - free (msg); - free ($$); - } - else - { - $$->next = NULL; - $1->next = $$; - if (inputTemplate == NULL) - { - inputTemplate = $$; - } - } - } - ; - -mailFormatList : mailFormat - | mailFormatList mailFormat - ; - -mailFormat : mailFormatHeader '{' mailFormatBody '}' { - addMessageFormat (databaseBeingDefined, mailFormat); - mailFormat = NULL; - } - | mailFormatHeader '{' parseError '}' { - freeMessageFormat (mailFormat); - mailFormat = NULL; - } - ; - -mailFormatHeader: MAILFORMATTOK QSTRING { - mailFormat - = (MailMessageFormat) - xmalloc (sizeof (struct mail_message_format)); - mailFormat->name = takeQString ($2); - mailFormat->toAddresses = NULL; - mailFormat->fromAddress = NULL; - mailFormat->replyTo = NULL; - mailFormat->body = NULL; - mailFormat->header = NULL; - mailFormat->next = NULL; - } - ; - -mailFormatBody : mailFormatEnt - | mailFormatBody mailFormatEnt - ; - -mailFormatEnt : bodyFormat { - mailFormat->body = qformat; - qformat = NULL; - } - | headerFormat { - mailFormat->header = qformat; - qformat = NULL; - } - | TOADDRESSESTOK '{' mailAddressList '}' { - mailFormat->toAddresses = $3; - } - | FROMADDRESSTOK '{' mailAddress '}' { - mailFormat->fromAddress = $3; - } - | REPLYTOTOK '{' mailAddressList '}' { - mailFormat->replyTo = $3; - } - ; - -bodyFormat : BODYTOK plainFormat - ; - -headerFormat : HEADERTOK plainFormat - ; - -plainFormat : plainFormatHeader queryFmt '}' - | plainFormatHeader parseError '}' { - freeQueryFormat (qformat); - qformat = NULL; - } - ; - -plainFormatHeader: '{' { - qformat = (QueryFormat *) xmalloc (sizeof (QueryFormat)); - qformat->name = NULL; - qformat->printf = NULL; - qformat->separator = NULL; - qformat->fields = NULL; - qformat->next = NULL; - } - ; - -mailAddressList : mailAddress { - $$ = (MailAddressList *) xmalloc (sizeof (MailAddressList)); - $$->address = $1; - $$->next = NULL; - } - | mailAddressList mailAddress { - MailAddressList *lp = $$; - while (lp->next != NULL) { lp = lp->next; } - lp->next = - (MailAddressList *) xmalloc (sizeof (MailAddressList)); - lp->next->address = $2; - lp->next->next = NULL; - } - ; - -mailAddress : FIXEDTOK QSTRING { - $$ = (MailAddress *) xmalloc (sizeof (MailAddress)); - $$->fixedAddress = takeQString ($2); - $$->addresses = NULL; - } - | mailAddressTries { - $$ = (MailAddress *) xmalloc (sizeof (MailAddress)); - $$->fixedAddress = NULL; - $$->addresses = $1; - } - ; - -mailAddressTries: MailAddressMember { - $$ = $1; - } - | mailAddressTries '|' MailAddressMember { - FieldList p = $$; - while (p->next != NULL) - { - p = p->next; - } - p->next = $3; - } - ; - -MailAddressMember: QSTRING { - $$ = newFieldListEnt (databaseBeingDefined, qStrVal ($1), - NULL); - } - ; - -auditTrailFmt : AUDITTRAILFMTTOK plainFormat { - setAuditTrailFormat (databaseBeingDefined, qformat); - } - ; Index: fconfig.y.in =================================================================== RCS file: fconfig.y.in diff -N fconfig.y.in --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ fconfig.y.in 19 Apr 2005 00:49:19 -0000 @@ -0,0 +1,812 @@ +%{ +#include "gnats.h" +#include "field.h" +#ifdef DS_FILE +#include "ds-file/index.h" +#endif + extern DatabaseInfo databaseBeingDefined; + static FieldDef currField; + static ChangeActions currChange; + static FieldEdit *currEdit; + static QueryFormat *qformat; + static FieldList requiredFlds; + static InputTemplate *inputTemplate; + static MailMessageFormat mailFormat; + static int badFile; + struct qstring; +#ifdef DS_FILE + IndexDesc indexEntry; +#endif + + extern char *takeQString (struct qstring *str); + extern char *qStrVal (struct qstring *str); +%} + +%union { + int intval; + char *sval; + struct qstring *qstr; + AdmFieldDesc *adm_field_des; + FieldList flist; + StringList *stringlist; + InputTemplate *inputlist; + MailAddress *mailaddr; + MailAddressList *mailaddrlist; +} + +%token FIELD STRINGTYPE QDEFAULT MATCHING ENUM MULTIENUMTOK VALUES DEFAULT +%token EXACT_REGEXP INEXACT_REGEXP ALL FORMAT ENUMSEPARATORSTOK +%token MULTITEXTTYPE DATETYPE ENUM_IN_FILE MULTI_ENUM_IN_FILE +%token PATHTOK FIELDSTOK KEYTOK +%token QSTRING INTVAL TEXTSEARCH QUERYTOK FORMATTOK INDEXTOK +%token SEPARATORTOK RESTRICTEDTOK NOSPACESTOK INTEGERTOK INPUTDEFAULTTOK +%token BUILTINTOK ALLOWANYVALUETOK REQUIRETOK APPENDFIELDTOK SETFIELDTOK +%token CHANGETOK DESCRIPTIONTOK INPUTTOK DATABASEINFOTOK +%token DEBUGMODETOK KEEPRECTOK NOTIFYEXPTOK LIBEXECDIRTOK SUBMITTERACKTOK +%token BUSINESSDAYTOK BUSINESSWEEKTOK CREATECATEGORYDIRSTOK FALSETOK TRUETOK +%token MAILFORMATTOK TOADDRESSESTOK FROMADDRESSTOK REPLYTOTOK FIXEDTOK +%token BODYTOK HEADERTOK AUDITTRAILFMTTOK ADDAUDITTRAILTOK +%token REQUIRECHANGEREASONTOK READONLYTOK BINARYINDEXTOK RAWTOK +%token BADTOK AUXFLAGSTOK PRLISTTOK MAXPRSTOK EDITONLYTOK VIRTUALFORMATTOK +%token CATPERMSTOK +%type optChangeExpr +%type QSTRING +%type INTVAL +%type enumFieldList enumFieldMember +%type queryFieldsList FieldListMember fieldEditFieldList +%type enumValueList regexpList auxFlagsList +%type inputFields inputFieldsList +%type booleanVal +%type mailAddressTries MailAddressMember +%type mailAddress +%type mailAddressList +%type requiredFieldsList +%% + +config : configEnts + | parseError + ; + +configEnts : databaseInfo fieldDecStmt optQueryList auditTrailFmt mailFormatList globalChangeEnts dsDescription inputDescription + ; + +databaseInfo : DATABASEINFOTOK '{' databaseInfoList '}' + | DATABASEINFOTOK '{' parseError '}' + | /* empty */ { + fconferror ("Missing/bad database info section"); + } + ; + +parseError : error { badFile = 1; } ; + +databaseInfoList: databaseInfoEnt + | databaseInfoList databaseInfoEnt + ; + +databaseInfoEnt : DEBUGMODETOK booleanVal { + setDebugMode (databaseBeingDefined, $2); + } + | KEEPRECTOK booleanVal { + setKeepReceivedHeaders (databaseBeingDefined, $2); + } + | NOTIFYEXPTOK booleanVal { + setNotifyExpire (databaseBeingDefined, $2); + } + | LIBEXECDIRTOK QSTRING { + setBinDir (databaseBeingDefined, qStrVal ($2)); + } + | SUBMITTERACKTOK booleanVal { + setSubmitterAck (databaseBeingDefined, $2); + } + | BUSINESSDAYTOK INTVAL '-' INTVAL { + setBusinessDay (databaseBeingDefined, $2, $4); + } + | BUSINESSWEEKTOK INTVAL '-' INTVAL { + setBusinessWeek(databaseBeingDefined,$2, $4); + } + | CREATECATEGORYDIRSTOK booleanVal { + setCreateCategoryDirs (databaseBeingDefined, $2); + } + | CATPERMSTOK QSTRING { + setCategoryDirPerms (databaseBeingDefined, qStrVal ($2)); + } + ; + +booleanVal : FALSETOK { $$ = 0; } + | TRUETOK { $$ = 1; } + ; + +fieldDecStmt : fieldDecList + | /* empty */ { + fconferror ("Missing/bad field declarations"); + } + ; + +fieldDecList : fieldDec + | fieldDecList fieldDec + ; + +fieldDec : startFieldDec '{' fieldDataList '}' { + currField = NULL; + } + | startFieldDec '{' parseError '}' { + currField = NULL; + } + ; + +startFieldDec : FIELD QSTRING { + char *fname = takeQString ($2); + currField = newFieldDef (databaseBeingDefined, fname); + if (currField == NULL) + { + char *msg; + asprintf (&msg, "Duplicate field definition for %s\n", + fname); + fconferror (msg); + free (msg); + } + currField->default_value = NULL; + } + ; + +fieldDataList : fieldData + | fieldDataList fieldData + ; + +fieldData : fieldDataType + | miscOptions + | queryDefault + | virtualFieldFormat + ; + +virtualFieldFormat: VIRTUALFORMATTOK plainFormat { + currField->virtualFormat = qformat; + qformat = NULL; + } + ; + +fieldDataType : stringType + | enumType + | MULTITEXTTYPE optSimple { + currField->datatype = MultiText; + currField->defaultSearchType = NilSearch; + } + | DATETYPE { + currField->datatype = Date; + currField->defaultSearchType = LessThan; + } + | INTEGERTOK optSimple { + currField->datatype = Integer; + currField->defaultSearchType = NilSearch; + } + | PRLISTTOK prListOpts { + currField->datatype = PRListType; + currField->defaultSearchType = RegCmp; + } + ; + +stringType : STRINGTYPE { + currField->datatype = Text; + } + | STRINGTYPE MATCHING '{' regexpList '}' { + currField->datatype = TextWithRegex; + } + | STRINGTYPE MATCHING '{' parseError '}' + ; + +regexpList : QSTRING { + $$ = new_string_list_ent (takeQString ($1), NULL); + currField->regex = $$; + } + | regexpList QSTRING { + $1->next = new_string_list_ent (takeQString ($2), NULL); + $$ = $1->next; + } + ; + +enumType : ENUM '{' enumcontents '}' { + currField->datatype = Enum; + currField->defaultSearchType = RegCmp; + } + | ENUM_IN_FILE '{' enumFileContents '}' { + currField->datatype = Enum; + currField->defaultSearchType = RegCmp; + initAdmField (currField); + } + | MULTI_ENUM_IN_FILE '{' multiEnumFileContents '}' { + currField->datatype = MultiEnum; + currField->defaultSearchType = RegCmp; + initAdmField (currField); + } + | MULTIENUMTOK '{' multienumcontents '}' { + currField->datatype = MultiEnum; + currField->defaultSearchType = RegCmp; + if (currField->multiEnumSeparator == NULL) + { + currField->multiEnumSeparator + = xstrdup (DEFAULT_MULTIENUM_SEPARATOR); + } + } + | ENUM '{' parseError '}' + | ENUM_IN_FILE '{' parseError '}' + ; + +globalChangeEnts: /* empty */ + | globalChangeEnts globalChangeEnt + ; + +globalChangeEnt : changeHeader changeOpts '}' { + addGlobalChangeActions (databaseBeingDefined, currChange); + currChange = NULL; + } + ; + +changeClause : changeHeader changeOpts '}' { + ChangeActions *p = &(currField->changeActions); + while (*p != NULL) + { + p = &((*p)->next); + } + *p = currChange; + currChange = NULL; + } + ; + +changeHeader : CHANGETOK optChangeExpr '{' { + currChange = newChangeAction (databaseBeingDefined, $2); + if ($2 != NULL) + { + free ($2); + } + } + ; + +optChangeExpr : QSTRING { + $$ = takeQString ($1); + } + | /* Empty */ { + $$ = NULL; + } + ; + +changeOpts : changeOpt + | changeOpts changeOpt + ; + +changeOpt : REQUIRETOK '{' reqFieldNameList '}' + | REQUIRETOK '{' parseError '}' + | SETFIELDTOK fieldEditOpts { + currChange->edits = currEdit; + currEdit = NULL; + } + | APPENDFIELDTOK fieldEditOpts { + currEdit->append = 1; + currChange->edits = currEdit; + currEdit = NULL; + } + | ADDAUDITTRAILTOK { + currChange->addAuditTrail = 1; + } + | AUDITTRAILFMTTOK plainFormat { + currChange->auditTrailFormat = qformat; + qformat = NULL; + } + | REQUIRECHANGEREASONTOK { + currChange->requireChangeReason = 1; + } + ; + +reqFieldNameList: reqFieldNameEnt + | reqFieldNameList reqFieldNameEnt + ; + +reqFieldNameEnt: QSTRING { + FieldList foo + = newFieldListEnt (databaseBeingDefined, qStrVal ($1), + currChange->requiredFields); + currChange->requiredFields = foo; + } + ; + +fieldEditOpts : fieldEditName '{' fieldEditFormat optFieldEditFieldList '}' + | fieldEditName '{' parseError '}' + ; + +fieldEditName : QSTRING { + currEdit = (FieldEdit *) xmalloc (sizeof (FieldEdit)); + currEdit->expr = NULL; + currEdit->fieldToEditName = takeQString ($1); + currEdit->append = 0; + currEdit->textFormat = NULL; + currEdit->fieldsForFormat = NULL; + currEdit->next = NULL; + } + ; + +fieldEditFormat : QSTRING { + currEdit->textFormat = takeQString ($1); + } + ; + +optFieldEditFieldList: /* empty */ { + currEdit->fieldsForFormat = NULL; + } + | fieldEditFieldList { + } + ; + +fieldEditFieldList: QSTRING { + $$ = newFieldListEnt (databaseBeingDefined, qStrVal ($1), + NULL); + currEdit->fieldsForFormat = $$; + } + | fieldEditFieldList QSTRING { + $$ = newFieldListEnt (databaseBeingDefined, qStrVal ($2), + NULL); + $1->next = $$; + } + ; + +optSimple : /* Empty */ + | '{' defaultFieldVal '}' + | '{' parseError '}' + ; + +prListOpts : /* Empty */ + | '{' prListOptList '}' + ; + +prListOptList : MAXPRSTOK INTVAL { + currField->maxPrsPerLine = $2; + } + ; + +enumcontents : enumItem + | enumcontents enumItem + ; + +multienumcontents: multiEnumItem + | multienumcontents multiEnumItem + ; + +multiEnumItem : enumItem + | ENUMSEPARATORSTOK QSTRING { + currField->multiEnumSeparator = takeQString ($2); + } + ; + +enumItem : VALUES '{' enumValueList '}' + | VALUES '{' parseError '}' + | defaultFieldVal + ; + +enumValueList : QSTRING { + $$ = new_string_list_ent (takeQString ($1), NULL); + currField->enumValues = $$; + } + | enumValueList QSTRING { + $1->next = new_string_list_ent (takeQString ($2), NULL); + $$ = $1->next; + } + ; + +enumFileContents: enumFileItem + | enumFileContents enumFileItem + ; + +enumFileItem : PATHTOK QSTRING { + currField->adm_db_name = takeQString ($2); + } + | FIELDSTOK '{' enumFieldList '}' KEYTOK QSTRING { + AdmFieldDesc *p; + int which = 0; + + for (p = currField->adm_field_des; p != NULL; p = p->next) + { + if (strcmp (p->name, qStrVal ($6)) == 0) + { + break; + } + which++; + } + + if (p != NULL) + { + currField->key_field = which; + } + else + { + char *msg; + + asprintf (&msg, "Invalid adm subfield %s\n", + qStrVal ($6)); + fconferror (msg); + free (msg); + } + } + | FIELDSTOK '{' parseError '}' + | defaultFieldVal + | ALLOWANYVALUETOK { + currField->allow_any_value = 1; + } + ; + +multiEnumFileContents: multiEnumFileItem + | enumFileContents multiEnumFileItem + ; + +multiEnumFileItem : enumFileItem + | ENUMSEPARATORSTOK QSTRING { + currField->multiEnumSeparator = takeQString ($2); + } + ; + +defaultFieldVal : DEFAULT QSTRING { + currField->default_value = takeQString ($2); + } + | INPUTDEFAULTTOK QSTRING { + currField->input_default_value = takeQString ($2); + } + ; + +enumFieldList : enumFieldMember { + currField->adm_db_fields = 1; + currField->adm_field_des = $1; + $$ = $1; + } + | enumFieldList enumFieldMember { + $1->next = $2; + $$ = $2; + currField->adm_db_fields++; + } + ; + +enumFieldMember : QSTRING { + $$ = (AdmFieldDesc *) + xmalloc (sizeof (AdmFieldDesc)); + $$->name = takeQString ($1); + $$->next = NULL; + } + ; + +queryDefault : QDEFAULT EXACT_REGEXP { + currField->defaultSearchType = RegCmp; + } + | QDEFAULT INEXACT_REGEXP { + currField->defaultSearchType = RegFind; + } + ; + +miscOptions : TEXTSEARCH { + currField->textsearch = 1; + } + | RESTRICTEDTOK { + currField->restricted = 1; + } + | NOSPACESTOK { + currField->nospaces = 1; + } + | BUILTINTOK QSTRING { + if (setBuiltinField (currField, qStrVal ($2)) != 0) + { + char *msg; + asprintf (&msg, "Invalid builtin fieldname %s", + qStrVal ($2)); + fconferror (msg); + free (msg); + } + } + | changeClause + | DESCRIPTIONTOK QSTRING { + currField->description = takeQString ($2); + } + | READONLYTOK { + currField->readonly = 1; + } + | AUXFLAGSTOK '{' auxFlagsList '}' { + currField->auxFlags = $3; + } + | EDITONLYTOK { + currField->editonly = 1; + } + ; + +auxFlagsList : QSTRING { + $$ = new_string_list_ent (takeQString ($1), NULL); + currField->auxFlags = $$; + } + | auxFlagsList QSTRING { + $1->next = new_string_list_ent (takeQString ($2), NULL); + $$ = $1->next; + } + ; + +optQueryList : /* empty */ + | queryList + ; + +queryList : query + | queryList query + ; + +query : queryBegin '{' queryFmt '}' { + addQueryFormat (databaseBeingDefined, qformat); + qformat = NULL; + } + | queryBegin '{' parseError '}' { + freeQueryFormat (qformat); + qformat = NULL; + } + ; + +queryBegin : QUERYTOK QSTRING { + qformat = (QueryFormat *) xmalloc (sizeof (QueryFormat)); + qformat->name = takeQString ($2); + qformat->printf = NULL; + qformat->separator = NULL; + qformat->fields = NULL; + qformat->next = NULL; + } + ; + +queryFmt : queryFieldDesc optQueryOpts + | queryOpts queryFieldDesc + ; + +queryFieldDesc : FIELDSTOK ALL + | queryfields + | queryPrintf queryfields + | queryfields queryPrintf + ; + +optQueryOpts :/* empty */ + | queryOpts + ; + +queryOpts : RAWTOK { + qformat->rawQuery = 1; + } + ; + +queryPrintf : FORMATTOK QSTRING { + qformat->printf = takeQString ($2); + } + ; + +queryfields : FIELDSTOK '{' queryFieldsList '}' + | FIELDSTOK '{' parseError '}' + ; + +queryFieldsList : FieldListMember { + qformat->fields = $1; + } + | queryFieldsList FieldListMember { + $1->next = $2; + $$ = $2; + } + ; + +FieldListMember: QSTRING { + $$ = newFieldListEnt (databaseBeingDefined, qStrVal ($1), + NULL); + if (parseComplexFieldIndex ($$->ent) != 0) + { + char *msg; + + asprintf (&msg, "Field %s is invalid\n", qStrVal ($1)); + fconferror (msg); + free (msg); + } + } + ; + +inputDescription: INPUTTOK '{' inputEnt '}' + | INPUTTOK '{' parseError '}' + ; + +inputEnt : inputFields requiredFields { + setInputTemplate (databaseBeingDefined, $1); + } + ; + +requiredFields :/* empty */ + | REQUIRETOK '{' requiredFieldsList '}' { + setRequiredInputFields (databaseBeingDefined, requiredFlds); + } + | REQUIRETOK '{' parseError '}' { + freeFieldList (requiredFlds); + requiredFlds = NULL; + } + ; + +requiredFieldsList: FieldListMember { + requiredFlds = $1; + } + | requiredFieldsList FieldListMember { + $1->next = $2; + $$ = $2; + } + ; + +inputFields : FIELDSTOK '{' inputFieldsList '}' { + $$ = inputTemplate; + inputTemplate = NULL; + } + | FIELDSTOK '{' parseError '}' { + $$ = NULL; + } + ; + +inputFieldsList : QSTRING { + $$ = (InputTemplate *) + xmalloc (sizeof (InputTemplate)); + $$->index = find_field_index (databaseBeingDefined, + qStrVal ($1)); + if ($$->index == InvalidFieldIndex) + { + char *msg; + + asprintf (&msg, "Field %s is invalid\n", + qStrVal ($1)); + fconferror (msg); + free ($$); + free (msg); + inputTemplate = NULL; + } + else + { + inputTemplate = $$; + $$->next = NULL; + } + } + | inputFieldsList QSTRING { + $$ = (InputTemplate *) + xmalloc (sizeof (InputTemplate)); + $$->index = find_field_index (databaseBeingDefined, + qStrVal ($2)); + if ($$->index == InvalidFieldIndex) + { + char *msg; + + asprintf (&msg, "Field %s is invalid\n", + qStrVal ($2)); + fconferror (msg); + free (msg); + free ($$); + } + else + { + $$->next = NULL; + $1->next = $$; + if (inputTemplate == NULL) + { + inputTemplate = $$; + } + } + } + ; + +mailFormatList : mailFormat + | mailFormatList mailFormat + ; + +mailFormat : mailFormatHeader '{' mailFormatBody '}' { + addMessageFormat (databaseBeingDefined, mailFormat); + mailFormat = NULL; + } + | mailFormatHeader '{' parseError '}' { + freeMessageFormat (mailFormat); + mailFormat = NULL; + } + ; + +mailFormatHeader: MAILFORMATTOK QSTRING { + mailFormat + = (MailMessageFormat) + xmalloc (sizeof (struct mail_message_format)); + mailFormat->name = takeQString ($2); + mailFormat->toAddresses = NULL; + mailFormat->fromAddress = NULL; + mailFormat->replyTo = NULL; + mailFormat->body = NULL; + mailFormat->header = NULL; + mailFormat->next = NULL; + } + ; + +mailFormatBody : mailFormatEnt + | mailFormatBody mailFormatEnt + ; + +mailFormatEnt : bodyFormat { + mailFormat->body = qformat; + qformat = NULL; + } + | headerFormat { + mailFormat->header = qformat; + qformat = NULL; + } + | TOADDRESSESTOK '{' mailAddressList '}' { + mailFormat->toAddresses = $3; + } + | FROMADDRESSTOK '{' mailAddress '}' { + mailFormat->fromAddress = $3; + } + | REPLYTOTOK '{' mailAddressList '}' { + mailFormat->replyTo = $3; + } + ; + +bodyFormat : BODYTOK plainFormat + ; + +headerFormat : HEADERTOK plainFormat + ; + +plainFormat : plainFormatHeader queryFmt '}' + | plainFormatHeader parseError '}' { + freeQueryFormat (qformat); + qformat = NULL; + } + ; + +plainFormatHeader: '{' { + qformat = (QueryFormat *) xmalloc (sizeof (QueryFormat)); + qformat->name = NULL; + qformat->printf = NULL; + qformat->separator = NULL; + qformat->fields = NULL; + qformat->next = NULL; + } + ; + +mailAddressList : mailAddress { + $$ = (MailAddressList *) xmalloc (sizeof (MailAddressList)); + $$->address = $1; + $$->next = NULL; + } + | mailAddressList mailAddress { + MailAddressList *lp = $$; + while (lp->next != NULL) { lp = lp->next; } + lp->next = + (MailAddressList *) xmalloc (sizeof (MailAddressList)); + lp->next->address = $2; + lp->next->next = NULL; + } + ; + +mailAddress : FIXEDTOK QSTRING { + $$ = (MailAddress *) xmalloc (sizeof (MailAddress)); + $$->fixedAddress = takeQString ($2); + $$->addresses = NULL; + } + | mailAddressTries { + $$ = (MailAddress *) xmalloc (sizeof (MailAddress)); + $$->fixedAddress = NULL; + $$->addresses = $1; + } + ; + +mailAddressTries: MailAddressMember { + $$ = $1; + } + | mailAddressTries '|' MailAddressMember { + FieldList p = $$; + while (p->next != NULL) + { + p = p->next; + } + p->next = $3; + } + ; + +MailAddressMember: QSTRING { + $$ = newFieldListEnt (databaseBeingDefined, qStrVal ($1), + NULL); + } + ; + +auditTrailFmt : AUDITTRAILFMTTOK plainFormat { + setAuditTrailFormat (databaseBeingDefined, qformat); + } + ; Index: file-pr.c =================================================================== RCS file: /cvsroot/gnats/gnats/gnats/file-pr.c,v retrieving revision 1.55 diff -u -p -r1.55 file-pr.c --- file-pr.c 24 Feb 2005 19:21:12 -0000 1.55 +++ file-pr.c 19 Apr 2005 00:49:19 -0000 @@ -25,13 +25,13 @@ Software Foundation, 59 Temple Place - S #include "query.h" #include "pcodes.h" #include "mail.h" +#include "ds.h" static int needs_analysis (PR *pr); static int run_atpr(PR *pr, AdmEntry *submitter, int submitter_rtime, - struct tm *expired, char *bug_name, ErrorDesc *err); + struct tm *expired, int bug_number, ErrorDesc *err); -static int getBugNumber (const DatabaseInfo database, ErrorDesc *err); static char* derive_submitter (PR *pr); static int missing_required_fields (const DatabaseInfo database, PR *pr, @@ -45,18 +45,13 @@ static int missing_required_fields (cons should cause the category to be automatically created. */ static int -createNewPRFile (PR *pr, int flag_autocreate, ErrorDesc *err) +createNewPR (PR *pr, ErrorDesc *err) { const DatabaseInfo database = pr->database; int bug_number = 0; - int errnum = 0; AdmEntry *submitter = NULL; AdmEntry *category = NULL; AdmEntry *responsible = NULL; - char *path = NULL; - char *prPath = NULL; - char *bug_name = NULL; - char number[14]; /* No more than 13 digits in a PR #, ha ha. */ struct tm *expired = NULL; int submitter_rtime; @@ -248,15 +243,6 @@ createNewPRFile (PR *pr, int flag_autocr } } - /* Set arrival date and time of response. */ - { - time_t seconds = time ((time_t) 0); - char arrival_time[GNATS_TIME_LENGTH]; - gnats_strftime (arrival_time, GNATS_TIME_LENGTH, "%a %b %d %H:%M:%S %z %Y", - localtime (&seconds)); - set_field (pr, ARRIVAL_DATE (database), arrival_time, err); - } - /* If the submitter isn't supposed to have a response time, don't bother trying to get it. */ if (submitter_rtime >= 0) @@ -264,58 +250,6 @@ createNewPRFile (PR *pr, int flag_autocr expired = get_response_time (database, submitter_rtime); } - /* Put together the path to where the bug will be stored. If the dir - is not there, and the category is the default, auto-create that one, - if we want to. If not, make the bug pending, and store in there. */ - - asprintf (&path, "%s/%s", databaseDir (database), bug_group); - - errnum = fileExists (path) ? 0 : -1; - - if (errnum != 0 && ! flag_autocreate) - { - /* XXX ??? !!! Should append a message to the email being sent out. */ - bug_group = defaultCategory(database); - set_field (pr, CATEGORY (database), defaultCategory(database), err); - log_msg (LOG_INFO, 1, "directory does not exist, changing to default:", - path); - free (path); - asprintf (&path, "%s/%s", databaseDir (database), bug_group); - errnum = fileExists (path) ? 0 : -1; - } - - /* Check ERR again, to see if default category was there. */ - if (errnum != 0) - { - mode_t mode = categoryDirPerms (database); - - if (strcmp (bug_group, defaultCategory(database)) == 0) - { - log_msg (LOG_INFO, 1, defaultCategory(database), - " does not exist, creating..."); - if (mkdir (path, mode) != 0) - { - setError (err, CODE_FILE_ERROR, - "Can't create `%s' directory under %s.", - defaultCategory(database), databaseDir (database)); - return -1; - } - } - else if (flag_autocreate) - { - if (mkdir (path, mode) != 0) - { - setError (err, CODE_FILE_ERROR, - "Cannot create group: %s", path); - return -1; - } - else - { - log_msg (LOG_INFO, 1, "creating directory:", path); - } - } - } - { /* Ensure the PR has a valid STATE. We set the default state on any of these cases: @@ -348,39 +282,12 @@ createNewPRFile (PR *pr, int flag_autocr } } - /* Retrieve a unique bug number. */ - bug_number = getBugNumber (database, err); + bug_number = pr_create (pr, err); if (bug_number > 0) { - sprintf (number, "%d", bug_number); - set_field (pr, NUMBER (database), number, err); - bad_fields = checkEnumTypes (pr, bad_fields, 1); - /* Write the file out. */ - asprintf (&bug_name, "%s/%d", bug_group, bug_number); - asprintf (&prPath, "%s/%s", databaseDir (database), bug_name); - if (createPrFile (pr, prPath, 1, err) != 0) - { - bug_number = -1; - } - else - { - log_msg (LOG_INFO, 1, "PR written out:", prPath); - - /* Add a line into the index for use by query-pr. */ - /* This should be done regardless of whether the bug is default or - not. */ - if (addToIndex (pr, err) != 0) - { - bug_number = -1; - } - } - } - - if (bug_number > 0) - { /* If it isn't in the default category [pending], send all of the mail for it, create an index entry, and run at_pr. Otherwise, just notify gnats-admin. */ @@ -400,7 +307,7 @@ createNewPRFile (PR *pr, int flag_autocr if (submitter_rtime != -1 && notifyExpire (database) && needs_analysis (pr)) { - if (run_atpr (pr, submitter, submitter_rtime, expired, bug_name, + if (run_atpr (pr, submitter, submitter_rtime, expired, bug_number, err) != 0) { bug_number = -1; @@ -417,18 +324,6 @@ createNewPRFile (PR *pr, int flag_autocr free_adm_entry (category); free_adm_entry (submitter); free_adm_entry (responsible); - if (path != NULL) - { - free (path); - } - if (prPath != NULL) - { - free (prPath); - } - if (bug_name != NULL) - { - free (bug_name); - } if (def_subm != NULL) { free (def_subm); @@ -532,7 +427,7 @@ arg_quote (const char *s) static int run_atpr (PR *pr, AdmEntry *submitter, int submitter_rtime, - struct tm *expired, char *bug_name, ErrorDesc *err) + struct tm *expired, int bug_number, ErrorDesc *err) { char *at_pr; char *command = NULL; @@ -570,9 +465,9 @@ run_atpr (PR *pr, AdmEntry *submitter, i char *cont = arg_quote (submitter->admFields[SubmitterAdmContact]); char *pa = arg_quote (gnatsAdminMailAddr (pr->database)); - fprintf (p, "%s --database %s %d %s %s '%s' '%s' '%s'\n", at_pr, + fprintf (p, "%s --database %s %d %d %s '%s' '%s' '%s'\n", at_pr, databaseName (pr->database), - submitter_rtime, bug_name, + submitter_rtime, bug_number, submitter->admFields[SubmitterAdmKey], orig, cont, pa); @@ -659,7 +554,7 @@ checkIfReply (PR *pr, ErrorDesc *err) free (regs.start); free (regs.end); - if (! prExists (pr->database, prID, err)) + if (! pr_exists (pr->database, prID, err)) { free (prID); prID = NULL; @@ -787,7 +682,7 @@ append_report (FILE *infile, PR *new_pr, date = get_curr_date (); } - if (! prExists (new_pr->database, number, err)) + if (! pr_exists (new_pr->database, number, err)) { return 1; } @@ -827,7 +722,7 @@ Date: %s\n\ fclose (infile); - pr = readPRWithNum (new_pr->database, number, 0, err); + pr = pr_load_by_id (new_pr->database, number, 0, err); if (pr == NULL) { return 1; @@ -865,61 +760,6 @@ Date: %s\n\ return 0; } -/* Return the next available unique GNATS id. */ -static int -getBugNumber (const DatabaseInfo database, ErrorDesc *err) -{ - char *sbuf; - int bug_number; - FILE *bug_file; - - /* First try to find and lock the gnats lock file. We need this since - they want every bug to have a unique number. If lock doesn't exist, - make it, if possible. */ - sbuf = gnats_adm_dir (database, "current"); - - block_signals (); - - bug_file = fopen (sbuf, "r+"); - if (bug_file == (FILE *) NULL) - { - log_msg (LOG_INFO, 1, "file 'current' not found, creating", sbuf); - bug_file = fopen (sbuf, "w+"); - if (bug_file != (FILE *) NULL) - { - bug_number = 1; - } - else - { - setError (err, CODE_FILE_ERROR, - "Can't create the GNATS 'current' file (%s).", - sbuf); - return -1; - } - } - else - { - if (fscanf (bug_file, "%d", &bug_number) != 1) - { - setError (err, CODE_FILE_ERROR, - "Can't read from the global PR number counter (%s).", - sbuf); - return -1; - } - - bug_number++; - rewind (bug_file); - } - - fprintf (bug_file, "%d", bug_number); - - fclose (bug_file); - unblock_signals (); - free (sbuf); - - return bug_number; -} - /* Submit the PR whose contents are referred to by FP. * Return the new PR number if it's a new PR, or return the PR number of * the PR which was appended to if it's an existing PR. */ @@ -957,7 +797,7 @@ submit_pr (const DatabaseInfo database, else { read_pr (pr, fp, 0); - retval = createNewPRFile (pr, createCategoryDirs (database), err); + retval = createNewPR (pr, err); result = (retval < 0) ? 0 : retval; } } Index: gen-index.c =================================================================== RCS file: /cvsroot/gnats/gnats/gnats/gen-index.c,v retrieving revision 1.27 diff -u -p -r1.27 gen-index.c --- gen-index.c 28 Jul 2002 19:21:11 -0000 1.27 +++ gen-index.c 19 Apr 2005 00:49:19 -0000 @@ -21,6 +21,8 @@ Software Foundation, 59 Temple Place - S #include "gnats.h" #include "gnats-dirs.h" +/* temporary - gen-index will eventually only build for flat-file backend */ +#include "ds-file/ds-file.h" /* The name this program was run with. */ const char *program_name; Index: getclose.c =================================================================== RCS file: /cvsroot/gnats/gnats/gnats/getclose.c,v retrieving revision 1.26 diff -u -p -r1.26 getclose.c --- getclose.c 31 Oct 2002 23:43:28 -0000 1.26 +++ getclose.c 19 Apr 2005 00:49:19 -0000 @@ -37,6 +37,7 @@ Software Foundation, 59 Temple Place - S #include "gnats.h" #include "query.h" +#include "ds.h" /* The name this program was run with. */ const char *program_name; @@ -164,38 +165,42 @@ do_stat (PR *pr) } static void -do_prlist (const DatabaseInfo database) +process_pr (int num ATTRIBUTE_UNUSED, PR *pr, + QueryFormat *data ATTRIBUTE_UNUSED) { ErrorDesc err; - PR *pr = getFirstPR (database, &err); if (pr == NULL) { - client_print_errors (database, err); - exit (1); + return; } - while (pr != NULL) + if (pr_load (pr, &err) == 0) { - if (check_state_type (database, field_value (pr, STATE (pr->database)), - "closed") - && gnats_regcmp ("no", field_value (pr, CONFIDENTIAL (pr->database)), - NULL) == 0) - { - if (fillInPR (pr, &err) == 0) - { - do_stat (pr); - free_pr_contents (pr); - } - else - { - client_print_errors (database, err); - fprintf (stderr, "Unable to read PR %s\n", - field_value (pr, NUMBER (pr->database))); - } - } - pr = getNextPR (pr); + do_stat (pr); + free_pr_contents (pr); + } + else + { + client_print_errors (pr->database, err); + fprintf (stderr, "Unable to read PR %s\n", + field_value (pr, NUMBER (pr->database))); + } +} + +static void +do_prlist (const DatabaseInfo database) +{ + QueryExpr expr; + ErrorDesc err; + const char *query_expr = "(builtinfield:State[type] ~ \"closed\") & (builtinfield:Confidential ~ \"no\")"; + + expr = parseQueryExpression (database, query_expr, NULL); + if (db_query (database, 0, NULL, expr, process_pr, NULL, &err) < 0) + { + client_print_errors (database, err); } + freeQueryExpr (expr); } static void Index: gnats.h =================================================================== RCS file: /cvsroot/gnats/gnats/gnats/gnats.h,v retrieving revision 1.55 diff -u -p -r1.55 gnats.h --- gnats.h 24 Feb 2005 21:21:22 -0000 1.55 +++ gnats.h 19 Apr 2005 00:49:19 -0000 @@ -249,7 +249,6 @@ extern const char *program_name; #include "field.h" #include "database.h" -#include "index.h" #include "pr.h" #include "query.h" #include "regex.h" Index: index.c =================================================================== RCS file: index.c diff -N index.c --- index.c 24 Feb 2005 19:21:12 -0000 1.41 +++ /dev/null 1 Jan 1970 00:00:00 -0000 @@ -1,1271 +0,0 @@ -/* Miscellaneous index routines for use with GNATS. - Copyright (C) 1993, 1995, 1999, 2000, 2001 Free Software Foundation, Inc. - Contributed by Brendan Kehoe (brendan@cygnus.com) and - by Tim Wicinski (wicinski@barn.com). - - Heavily revised by Bob Manson (manson@juniper.net); also added - binary index support. - -This file is part of GNU GNATS. - -GNU GNATS is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2, or (at your option) -any later version. - -GNU GNATS is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with GNU GNATS; see the file COPYING. If not, write to the Free -Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111, USA. */ - -/* Binary format (all values are in MSB-first order): - * 1 byte: number of fields in each index entry - * For each record: - * 2 bytes: total length of record, including this 2 byte length - * For each field: - * 2 bytes: length of field, not including this 2 byte length or NUL - * terminator - * N bytes: data in field, NUL-terminated - */ - - -#include "gnats.h" -#include "gnatsd.h" - -struct index_desc -{ - DatabaseInfo database; - char *path; - FieldList fields; - char *separator; - int isBinary; - int numberOfFields; - int *fieldInIndex; - time_t mtime; - int indexRead; - PR *prChain; -}; - -typedef struct index_file_desc -{ - IndexDesc desc; - FILE *fileDes; -} *IndexFileDesc; - -struct indexEntry -{ - char *buf; - char **fields; - PR *nextPR; - PR *prevPR; -}; - -static PR *nextIndexEntryBinary (IndexFileDesc fp); - -static int -format_field (FieldIndex field, const char *value, const char *separator, - char **dest, size_t *length) -{ - char datebuf[30]; - size_t valLen; - size_t destLen; - size_t sepLen; - - if (value == NULL) - { - value = ""; - } - - if (fieldDefForIndex (field)->nospaces) - { - char *r = strchr (value, ' '); - if (r != NULL) - { - valLen = r - value; - } - } - - if (fieldDefForIndex (field)->datatype == Date) - { - time_t d = 0; - if (value != NULL && *value != '\0') - { - /* We used to fail here if the date was invalid, but that's - a mistake. */ - d = get_any_date (value); - } - sprintf (datebuf, "%d", (int) d); - value = datebuf; - } - - if (value == NULL) - { - value = ""; - } - destLen = *length; - valLen = strlen (value); - sepLen = strlen (separator); - - *dest = xrealloc (*dest, destLen + valLen + sepLen + 1); - memcpy (*dest + destLen, separator, sepLen); - memcpy (*dest + destLen + sepLen, value, valLen); - (*dest)[destLen + sepLen + valLen] = '\0'; - *length = destLen + sepLen + valLen; - return sepLen + valLen; -} - -char * -createIndexEntry (PR *pr, size_t *reclen) -{ - FieldList f; - char *res; - IndexDesc indexDesc = getIndexDesc (pr->database); - - if (indexIsBinary (pr->database)) - { - return createIndexEntryBinary (pr, reclen); - } - - /* XXX ??? !!! For now. We need a way to refer to "PRID". */ - asprintf (&res, "%s/%s", field_value (pr, CATEGORY (pr->database)), - field_value (pr, NUMBER (pr->database))); - *reclen = strlen (res); - - for (f = indexDesc->fields; f != NULL; f = f->next) - { - FieldIndex i = simpleFieldIndexValue (f->ent); - if (format_field (i, field_value (pr, i), indexDesc->separator, &res, - reclen) < 0) - { - free (res); - res = NULL; - break; - } - } - append_string (&res, "\n"); - (*reclen)++; - return res; -} - -static char * -find_sep (IndexDesc indexDesc, char *start) -{ - char *end; - int seplen = strlen (indexDesc->separator); - - do { - end = strchr (start, indexDesc->separator[0]); - if (end != NULL) - { - if (seplen == 1 || strncmp (start, indexDesc->separator, seplen) == 0) - { - return end; - } - end++; - } - } while (end != NULL); - - return end; -} - -static char * -readBinaryRecord (IndexFileDesc fp, size_t *length) -{ - char *res; - unsigned char entlen[2]; - - if (fread (entlen, 1, 2, fp->fileDes) != 2) - { - return NULL; - } - *length = entlen[0] * 256 + entlen[1] - 2; - res = xmalloc (*length); - if (fread (res, 1, *length, fp->fileDes) == *length) - { - return res; - } - else - { - free (res); - return NULL; - } -} - -static char * -extractBinaryField (char **record, size_t *lengthPtr) -{ - size_t length = *lengthPtr; - if (length > 0) - { - char *recLen = *record; - size_t fieldLen = (recLen[0] & 255) * 256 + (recLen[1] & 255); - if ((fieldLen + 3) <= length) - { - char *recptr = *record + 2; - *lengthPtr = length - (fieldLen + 3); - (*record) = recLen + fieldLen + 3; - return recptr; - } - } - return NULL; -} - -/* Return the next index entry from FP. */ -static PR * -nextIndexEntry (IndexFileDesc fp) -{ - char *start, *end; - char *buf; - PR *res; - int x; - int seplen; - FieldList f; - Index *p; - - if (fp->desc->isBinary) - { - return nextIndexEntryBinary (fp); - } - - res = allocPR (fp->desc->database); - p = res->index; - seplen = strlen (fp->desc->separator); - - p->prevPR = NULL; - p->nextPR = NULL; - p->fields = (char **) xmalloc (sizeof (char *) - * get_num_fields (fp->desc->database)); - memset (p->fields, 0, sizeof (char *) * get_num_fields (fp->desc->database)); - while ((buf = read_line (fp->fileDes, NULL)) != NULL) - { - if (buf[0] != '#') - { - break; - } - else - { - free (buf); - } - } - - if (buf == NULL) - { - goto no_entry; - } - - p->buf = buf; - x = strlen (buf) - 1; - if (buf[x] == '\n') - { - buf[x] = '\0'; - } - - start = buf; - end = strchr (start, '/'); - if (end == NULL) - { - goto no_entry; - } - *end = '\0'; - p->fields[fieldDefForIndex (CATEGORY (fp->desc->database))->number] = start; - - start = end + 1; - end = find_sep (fp->desc, start); - - if (end == NULL) - { - goto no_entry; - } - *end = '\0'; - p->fields[fieldDefForIndex (NUMBER (fp->desc->database))->number] = start; - - for (f = fp->desc->fields; f != NULL && end != NULL; f = f->next) - { - FieldIndex i = simpleFieldIndexValue (f->ent); - - if (i != InvalidFieldIndex) - { - start = end + seplen; - p->fields[fieldNumber (i)] = start; - } - if (f->next != NULL) - { - end = find_sep (fp->desc, start); - if (end != NULL) - { - *end = '\0'; - } - } - } - - if (f == NULL) - { - return res; - } - -no_entry: - - free_pr (res); - return NULL; -} - -static PR * -nextIndexEntryBinary (IndexFileDesc fp) -{ - char *record; - PR *res = NULL; - size_t length; - - record = readBinaryRecord (fp, &length); - if (record != NULL) - { - FieldList f; - Index *indexEnt; - - res = allocPR (fp->desc->database); - indexEnt = res->index; - indexEnt->prevPR = NULL; - indexEnt->nextPR = NULL; - indexEnt->fields - = (char **) xmalloc (sizeof (char *) - * get_num_fields (fp->desc->database)); - indexEnt->buf = record; - memset (indexEnt->fields, 0, sizeof (char *) - * get_num_fields (fp->desc->database)); - indexEnt->fields[fieldDefForIndex (NUMBER (fp->desc->database))->number] - = extractBinaryField (&record, &length); - indexEnt->fields[fieldDefForIndex (CATEGORY (fp->desc->database))->number] - = extractBinaryField (&record, &length); - for (f = fp->desc->fields; res != NULL && f != NULL; f = f->next) - { - FieldIndex i = simpleFieldIndexValue (f->ent); - - if (i != InvalidFieldIndex) - { - indexEnt->fields[fieldNumber (i)] - = extractBinaryField (&record, &length); - if (indexEnt->fields[fieldNumber (i)] == NULL) - { - free_pr (res); - res = NULL; - } - } - else - { - free_pr (res); - res = NULL; - } - } - } - return res; -} - -static void -appendBinaryFieldContents (char **dest, - const char *value, size_t *recLenPtr) -{ - size_t valLen; - size_t recLen = *recLenPtr; - - if (value == NULL) - { - value = ""; - valLen = 0; - } - else - { - valLen = strlen (value); - } - *dest = xrealloc (*dest, recLen + valLen + 3); - memcpy (*dest + recLen + 2, value, valLen + 1); - (*dest)[recLen + 0] = valLen / 256; - (*dest)[recLen + 1] = valLen % 256; - *recLenPtr += valLen + 3; -} - -char * -createIndexEntryBinary (PR *pr, size_t *recLen) -{ - const DatabaseInfo database = pr->database; - const IndexDesc indexDesc = getIndexDesc (database); - FieldList f; - char *res; - - *recLen = 2; - res = xmalloc (2); - appendBinaryFieldContents (&res, field_value (pr, NUMBER (pr->database)), - recLen); - appendBinaryFieldContents (&res, field_value (pr, CATEGORY (pr->database)), - recLen); - for (f = indexDesc->fields; f != NULL; f = f->next) - { - FieldIndex i = simpleFieldIndexValue (f->ent); - size_t fieldLenOffset = *recLen; - int valLen; - - /* Two bytes for the field length. */ - *recLen += 2; - valLen = format_field (i, field_value (pr, i), "", &res, recLen); - res[fieldLenOffset + 0] = valLen / 256; - res[fieldLenOffset + 1] = valLen & 255; - /* One for the NUL character; format_field() always appends one, - but doesn't include it in the length. */ - (*recLen)++; - } - res[0] = *recLen / 256; - res[1] = *recLen % 256; - return res; -} - -static char * -findPrCategoryBinary (IndexFileDesc fp, const char *number) -{ - unsigned char fieldcount; - char *record; - size_t recLen; - - if (fread (&fieldcount, 1, 1, fp->fileDes) != 1) - { - return NULL; - } - while ((record = readBinaryRecord (fp, &recLen)) != NULL) - { - char *recPos = record; - char *prNum = extractBinaryField (&recPos, &recLen); - if (prNum != NULL && strcmp (prNum, number) == 0) - { - char *category = extractBinaryField (&recPos, &recLen); - free (record); - return category; - } - free (record); - } - return NULL; -} - -/* Find problem report NUMBER in the index file, returning its category. */ -static char * -findPrCategory (IndexFileDesc fp, const char *number) -{ - char *buf; - char *res = NULL; - - while ((res == NULL) && (buf = read_line (fp->fileDes, NULL)) != NULL) - { - if (buf[0] != '#') - { - char *start = buf; - char *end = strchr (start, '/'); - if (end != NULL) - { - char *numStart = end + 1; - char *category = start; - - *end = '\0'; - end = find_sep (fp->desc, numStart); - if (end != NULL) - { - *end = '\0'; - - if (strcmp (numStart, number) == 0) - { - res = xstrdup (category); - } - } - } - } - free (buf); - } - - return res; -} - -static time_t -statIndex (char *name) -{ - struct stat buf; - int i; - - i = stat (name, &buf); - if (i < 0) - { - return (time_t)-1; - } - - return buf.st_mtime; -} - -static IndexFileDesc -openIndex (IndexDesc indexDesc, ErrorDesc *err) -{ - FILE *fp; - time_t t; - char *index_filename = gnats_adm_dir (indexDesc->database, indexDesc->path); - IndexFileDesc res - = (IndexFileDesc) xmalloc (sizeof (struct index_file_desc)); - - res->desc = indexDesc; - - fp = fopen (index_filename, "r"); - - if (fp != NULL) - { - t = statIndex (index_filename); - /* ??? XXX !!! Do we want to do something here? */ - if (t != (time_t)-1) - { - indexDesc->mtime = t; - } - res->fileDes = fp; - if (indexDesc->isBinary) - { - unsigned char fieldCount; - if ((fread (&fieldCount, 1, 1, res->fileDes) != 1) - || (fieldCount != indexDesc->numberOfFields)) - { - setError (err, CODE_INVALID_INDEX, - "Index file %s contains incorrect number of fields", - index_filename); - fclose (res->fileDes); - free (res); - res = NULL; - } - } - } - else - { - setError (err, CODE_FILE_ERROR, "Unable to open index file %s", - index_filename); - free (res); - res = NULL; - } - - free (index_filename); - return res; -} - -static void -closeIndex (IndexFileDesc desc) -{ - fclose (desc->fileDes); - free (desc); -} - -/* getIndex - reads in the entire set of PRs with index entries. */ -static PR * -getIndex (IndexDesc indexDesc, ErrorDesc *err) -{ - IndexFileDesc fp = openIndex (indexDesc, err); - PR *new_chain = NULL; - PR *end_chain = NULL; - - if (fp != NULL) - { - PR *i; - - while ((i = nextIndexEntry (fp))) - { - if (new_chain == NULL) - { - new_chain = i; - end_chain = i; - } - else - { - i->index->prevPR = end_chain; - end_chain->index->nextPR = i; - end_chain = i; - } - } - - closeIndex (fp); - } - indexDesc->indexRead = 1; - return new_chain; -} - -static PR * -getNewIndexIfChanged (DatabaseInfo database, ErrorDesc *err) -{ - int rereadIndex; - IndexDesc indexDesc = getIndexDesc (database); - - if (indexDesc->indexRead) - { - time_t t; - char *index_filename = gnats_adm_dir (database, indexDesc->path); - - t = statIndex (index_filename); - free (index_filename); - - if (t == (time_t)-1) - { - setError (err, CODE_FILE_ERROR, - "GNATS cannot stat the index: %s.", strerror (errno)); - return 0; - } - if (t > indexDesc->mtime) - { - rereadIndex = 1; - } - else - { - rereadIndex = 0; - } - } - else - { - rereadIndex = 1; - } - - if (rereadIndex) - { - return getIndex (indexDesc, err); - } - else - { - return NULL; - } -} - -void -freePRIndex (PR *pr) -{ - if (pr->index != NULL && pr->index->fields != NULL) - { - if (pr->index->buf != NULL) - { - free (pr->index->buf); - pr->index->buf = NULL; - } - else - { - int x; - - for (x = get_num_fields (pr->database) - 1; x >= 0; x--) - { - if (pr->index->fields[x] != NULL) - { - free (pr->index->fields[x]); - pr->index->fields[x] = NULL; - } - } - } - free (pr->index->fields); - pr->index->fields = NULL; - } -} - -void -allocIndex (PR *pr) -{ - pr->index = (Index *) xmalloc (sizeof (Index)); - pr->index->buf = NULL; - pr->index->fields = NULL; - pr->index->prevPR = NULL; - pr->index->nextPR = NULL; -} - -void -buildIndexEntry (PR *pr) -{ - int x; - int num_fields = get_num_fields (pr->database); - Index *newIndex; - - freePRIndex (pr); - newIndex = pr->index; - - newIndex->fields = (char **) xmalloc (sizeof (char *) * num_fields); - memset (newIndex->fields, 0, sizeof (char *) * num_fields); - newIndex->buf = NULL; - for (x = 0; x < num_fields; x++) - { - FieldIndex field = getNthField (pr->database, x); - if (isIndexedFieldIndex (field)) - { - const char *fc = field_value (pr, field); - - if (fieldDefForIndex (field)->datatype == Date) - { - if (fc == NULL - || fc[0] == '\0' - || (strcmp (fc, "-1") == 0)) - { - newIndex->fields[x] = xstrdup ("0"); - } - else - { - time_t t = get_any_date (fc); - asprintf (&(newIndex->fields[x]), "%d", (int) t); - } - } - else - { - char *fcCopy; - - if (fc == NULL) - { - fcCopy = xstrdup (""); - } - else - { - fcCopy = xstrdup (fc); - } - - /* ??? XXX Should we do this here? It's already being - done in the "write out the index" code. */ - if (fieldDefForIndex (field)->nospaces) - { - /* Try to minimize the leakage. */ - char *p = strchr (fcCopy, ' '); - if (p != NULL) - { - *p = '\0'; - } - } - newIndex->fields[x] = fcCopy; - } - } - } -} - -int -isIndexedFieldIndex (FieldIndex index) -{ - IndexDesc indexDesc; - if (index == InvalidFieldIndex) - { - return 0; - } - else - { - indexDesc = getIndexDesc (fieldDefForIndex (index)->database); - return indexDesc->fieldInIndex[fieldDefForIndex (index)->number]; - } -} - -void -finishIndexDesc (DatabaseInfo database, IndexDesc new) -{ - int numFields = get_num_fields (database); - - new->fieldInIndex = (int *) xmalloc (sizeof (int) * numFields); - - { - int x; - - for (x = 0; x < numFields; x++) - { - new->fieldInIndex[x] = 0; - } - } - - { - FieldList f; - - for (f = new->fields; f != NULL; f = f->next) - { - FieldIndex i = simpleFieldIndexValue (f->ent); - if (i != InvalidFieldIndex) - { - new->fieldInIndex[fieldNumber (i)] = 1; - } - } - } - new->fieldInIndex[fieldNumber (NUMBER (database))] = 1; - new->fieldInIndex[fieldNumber (CATEGORY (database))] = 1; -} - -int -isIndexedField (ComplexFieldIndex ent) -{ - if (parseComplexFieldIndex (ent) != 0) - { - return 0; - } - return isIndexedFieldIndex (simpleFieldIndexValue (ent)); -} - -/* Write out the index for DATABASE's PR chain. Returns 0 on success, - a non-zero value otherwise (and ERR will be filled in). */ -int -writeIndex (const DatabaseInfo database, ErrorDesc *err) -{ - FILE *fp; - char *path, *workfile; - PR *pr; - IndexDesc indexDesc = getIndexDesc (database); - struct stat buf; - - workfile = gnats_adm_dir (database, "indXXXXXX"); - fp = fopen_temporary_file (workfile, "w", 0644); - if (fp == NULL) - { - setError (err, CODE_FILE_ERROR, - "writeIndex () can't open the temporary file %s", - workfile); - free (workfile); - return -1; - } - - if (indexIsBinary (database)) - { - unsigned char fieldCount = indexFieldCount (database); - fwrite (&fieldCount, 1, 1, fp); - } - - for (pr = getFirstPR (database, err); pr != NULL ; pr = getNextPR (pr)) - { - size_t length; - char *line = createIndexEntry (pr, &length); - - if (line != NULL) - { - fwrite (line, 1, length, fp); - free (line); - } - } - - fclose (fp); - - block_signals (); - - path = gnats_adm_dir (database, indexDesc->path); - - /* sanity check -- make sure nobody has changed the index since we - last read it */ - if (stat (path, &buf) < 0) - { - /* if the index ain't there, no problem. if it's there, but - unstattable, we're going to declare it a serious problem. - not sure if this is the right thing to do... */ - if (errno != ENOENT) - { - setError (err, CODE_FILE_ERROR, - "GNATS cannot stat the index: %s.", strerror (errno)); - log_msg (LOG_ERR, 1, "error in writeIndex:", - getErrorMessage (*err)); - return -1; - } - } - - if (buf.st_mtime > indexDesc->mtime) - { - setError (err, CODE_INVALID_INDEX, - "Index has been modified since last read"); - log_msg (LOG_ERR, 1, "error in writeIndex:", - getErrorMessage (*err)); - /* the index is corrupt: alert the administrator */ - punt (database, 0, "Attempting to write index file %s,\n\ -but index has been modified since last read. A PR has been modified,\n\ -but the change has not been recorded in the index. You must manually\n\ -regenerate the index.", path); - return -1; - } - - if ((rename (workfile, path)) < 0) - { - if (errno != EXDEV) - { - free (path); - setError (err, CODE_FILE_ERROR, - "could not rename temporary index %s into gnats-adm: %s", - workfile, strerror (errno)); - return -1; - } - - if (copy_file (workfile, path)) - { - setError (err, CODE_FILE_ERROR, - "could not copy temporary index %s into %s: %s", - workfile, path, strerror (errno)); - return -1; - } - - unlink (workfile); - } - - free (path); - free (workfile); - unblock_signals (); - return 0; -} - -/* Add the PR to the GNATS index file. */ -int -addToIndex (PR *pr, ErrorDesc *err) -{ - FILE *fp; - char *path; - IndexDesc indexDesc = getIndexDesc (pr->database); - - buildIndexEntry (pr); - - block_signals (); - - path = gnats_adm_dir (pr->database, indexDesc->path); - - if (indexIsBinary (pr->database) && ! fileExists (path)) - { - unsigned char fieldCount = indexFieldCount (pr->database); - fp = fopen (path, "w"); - if (fp != NULL) - { - fwrite (&fieldCount, 1, 1, fp); - } - } - else - { - fp = fopen (path, "a+"); - } - if (fp == NULL) - { - setError (err, CODE_FILE_ERROR, - "Can't append to the GNATS index file (%s).", path); - unblock_signals (); - return -1; - } - - { - size_t reclen; - - char *line = createIndexEntry (pr, &reclen); - fwrite (line, 1, reclen, fp); - free (line); - } - - fclose (fp); - free (path); - unblock_signals (); - return 0; -} - -char * -indexValue (PR *pr, FieldIndex field) -{ - if (pr->index != NULL && pr->index->fields != NULL) - { - return pr->index->fields[field->number]; - } - else - { - return NULL; - } -} - -char * -getCategoryFromIndex (const DatabaseInfo database, const char *prnum, - ErrorDesc *err) -{ - IndexDesc indexDesc = getIndexDesc (database); - IndexFileDesc fp = openIndex (indexDesc, err); - if (fp != NULL) - { - char *category; - if (fp->desc->isBinary) - { - category = findPrCategoryBinary (fp, prnum); - } - else - { - category = findPrCategory (fp, prnum); - } - closeIndex (fp); - if (category == NULL) - { - setError (err, CODE_NONEXISTENT_PR, - "PR %s is not listed in the index.", prnum); - } - return category; - } - else - { - return NULL; - } -} - -void -freeIndexDesc (IndexDesc p) -{ - free (p->path); - freeFieldList (p->fields); - free (p->separator); - if (p->fieldInIndex != NULL) - { - free (p->fieldInIndex); - } - free (p); -} - -IndexDesc -newIndexDesc (const DatabaseInfo database) -{ - IndexDesc res = xmalloc (sizeof (struct index_desc)); - - res->database = database; - res->prChain = NULL; - res->path = NULL; - res->fields = NULL; - res->separator = xstrdup ("|"); - res->isBinary = 0; - res->numberOfFields = 0; - res->fieldInIndex = NULL; - res->mtime = 0; - res->indexRead = 0; - - return res; -} - -void -addFieldToIndex (IndexDesc desc, FieldList ent) -{ - if (desc->fields == NULL) - { - desc->fields = ent; - } - else - { - FieldList f = desc->fields; - while (f->next != NULL) - { - f = f->next; - } - f->next = ent; - } - desc->numberOfFields++; -} - -void -setIndexDescPath (IndexDesc desc, const char *path) -{ - desc->path = xstrdup (path); -} - -void -setIndexDescSeparator (IndexDesc desc, const char *separator) -{ - free (desc->separator); - desc->separator = xstrdup (separator); -} - -void -setIndexDescBinary (IndexDesc desc, int flag) -{ - desc->isBinary = flag; -} - -static void -freePRChain (PR *start) -{ - while (start != NULL) - { - PR *next = getNextPR (start); - free_pr (start); - start = next; - } -} - -int -checkPRChain (const DatabaseInfo database, ErrorDesc *err) -{ - IndexDesc desc = getIndexDesc (database); - PR *new_chain = getNewIndexIfChanged (database, err); - - if (new_chain != NULL) - { - freePRChain (desc->prChain); - desc->prChain = new_chain; - return 1; - } - else - { - return 0; - } -} - -void -initIndex (DatabaseInfo database) -{ - clearPRChain (database); -} - -PR * -getFirstPR (const DatabaseInfo database, ErrorDesc *err) -{ - IndexDesc desc = getIndexDesc (database); - - /* first check the PRChain to make sure that the index isn't stale */ - checkPRChain (database, err); - - return desc->prChain; -} - -void -clearPRChain (const DatabaseInfo database) -{ - IndexDesc desc = getIndexDesc (database); - if (desc != NULL) - { - if (desc->prChain != NULL) - { - freePRChain (desc->prChain); - desc->prChain = NULL; - } - desc->indexRead = 0; - } -} - -/* Replace a PR in the index with a copy of a new PR. - By generating a copy of the new PR, we allow for the new PR - to be free'd without harming the index pr chain. */ - -int -replaceCurrentPRInIndex (PR *curr_pr, PR *new_pr, ErrorDesc *err) -{ - PR *pr_replace, *pr_copy; - const DatabaseInfo database = curr_pr->database; - const char *new_pr_num = field_value (new_pr, NUMBER (database)); - const char *curr_pr_num = field_value (curr_pr, NUMBER (database)); - - if (strcmp (curr_pr_num, new_pr_num) != 0) - { - return 0; - } - - /* create a copy of the new pr to be put into the index pr chain */ - pr_copy = allocPR (database); - set_field (pr_copy, NUMBER (curr_pr->database), new_pr_num, err); - set_field (pr_copy, CATEGORY (database), - field_value (new_pr, CATEGORY (database)), err); - setPrevPR (pr_copy, curr_pr->index->prevPR); - setNextPR (pr_copy, curr_pr->index->nextPR); - fillInPR (pr_copy, err); - - /* put the copy of new_pr into place in the index pr chain... */ - - if (curr_pr->index->prevPR == NULL) - { - /* the pr to be replaced is at the head of the pr chain */ - IndexDesc indexDesc = getIndexDesc (database); - free_pr (indexDesc->prChain); - indexDesc->prChain = pr_copy; - return 1; - } - - /* save a pointer to the pr to be replaced so that it can later be free'd */ - pr_replace = getNextPR (curr_pr->index->prevPR); - - setNextPR (curr_pr->index->prevPR, pr_copy); - - if (curr_pr->index->nextPR != NULL) - { - /* the pr to be replaced is not at the end of the pr chain */ - setPrevPR (curr_pr->index->nextPR, pr_copy); - } - - free_pr (pr_replace); - - return 1; -} - -/* Remove PR PRNUM from the index for the current database, and rewrite the - database file. The database must have been locked before calling this - function. */ -int -removePRFromIndex (const DatabaseInfo database, const char *prNum, - ErrorDesc *err) -{ - PR *list; - PR *prev = NULL; - IndexDesc indexDesc = getIndexDesc (database); - - if (! is_gnats_locked (database)) - { - setError (err, CODE_GNATS_NOT_LOCKED, - "Must lock the database before removing a PR from the index"); - return -1; - } - - list = getFirstPR (database, err); - while (list != NULL - && strcmp (field_value (list, NUMBER (database)), prNum) != 0) - { - prev = list; - list = getNextPR (list); - } - - if (list == NULL) - { - setError (err, CODE_NONEXISTENT_PR, - "PR %s not in index", prNum); - return -2; - } - - if (prev != NULL) - { - prev->index->nextPR = list->index->nextPR; - } - else - { - indexDesc->prChain = list->index->nextPR; - } - - return writeIndex (database, err); -} - -PR * -getNextPR (PR *pr) -{ - return pr->index->nextPR; -} - -void -setNextPR (PR *pr, PR *next_pr) -{ - pr->index->nextPR = next_pr; -} - -PR * -getPrevPR (PR *pr) -{ - return pr->index->prevPR; -} - -void -setPrevPR (PR *pr, PR *prev_pr) -{ - pr->index->prevPR = prev_pr; -} - -int -indexIsBinary (const DatabaseInfo database) -{ - IndexDesc indexDesc = getIndexDesc (database); - if (indexDesc != NULL) - { - return indexDesc->isBinary; - } - else - { - return 0; - } -} - -int -indexFieldCount (const DatabaseInfo database) -{ - IndexDesc indexDesc = getIndexDesc (database); - if (indexDesc != NULL) - { - return indexDesc->numberOfFields; - } - else - { - return 0; - } -} Index: index.h =================================================================== RCS file: index.h diff -N index.h --- index.h 24 Feb 2005 19:21:12 -0000 1.18 +++ /dev/null 1 Jan 1970 00:00:00 -0000 @@ -1,72 +0,0 @@ -#ifndef _INDEX_H_ -#define _INDEX_H_ - -typedef struct indexEntry Index; -typedef struct index_desc *IndexDesc; - -#include "gnats.h" -#include "database.h" -#include "field.h" -#include "pr.h" - -extern void initIndex (DatabaseInfo database); - -extern IndexDesc newIndexDesc (const DatabaseInfo database); -extern void addFieldToIndex (IndexDesc desc, FieldList ent); -extern void setIndexDescPath (IndexDesc desc, const char *path); -extern void setIndexDescSeparator (IndexDesc desc, const char *separator); -extern void setIndexDescBinary (IndexDesc desc, int flag); - -extern void allocIndex (PR *pr); - -extern void clearPRChain (const DatabaseInfo database); -extern int checkPRChain (const DatabaseInfo database, ErrorDesc *err); - -extern PR *getFirstPR (const DatabaseInfo database, ErrorDesc *err); - -extern PR *getPrevPR (PR *pr); -extern PR *getNextPR (PR *pr); -extern void setPrevPR (PR *pr, PR *prev_pr); -extern void setNextPR (PR *pr, PR *next_pr); - -extern void freePRIndex (PR *pr); - -/* Construct an index entry for PR. */ -extern void buildIndexEntry (PR *pr); - -/* Return a non-zero value if FIELD is part of the index for its database. */ -extern int isIndexedFieldIndex (FieldIndex field); -extern int isIndexedField (ComplexFieldIndex field); - -/* Write out the database's PR index from the copy in memory. Returns 0 on - success, a non-zero value otherwise. */ -extern int writeIndex (const DatabaseInfo database, ErrorDesc *err); -/* Add the PR to the GNATS index file for its database. Returns - 0 on success, a non-zero value otherwise. */ -extern int addToIndex (PR *pr, ErrorDesc *err); - -/* Create a new string representing the index entry for PR in DEST. */ -extern char *createIndexEntry (PR *pr, size_t *entLen); -extern char *createIndexEntryBinary (PR *pr, size_t *entLen); - -extern int replaceCurrentPRInIndex (PR *curr_pr, PR *newPR, ErrorDesc *err); - -extern char *indexValue (PR *pr, FieldIndex field); - -extern char *getCategoryFromIndex (const DatabaseInfo database, - const char *prnum, ErrorDesc *err); - -extern int removePRFromIndex (const DatabaseInfo database, const char *prNum, - ErrorDesc *err); - -extern void finishIndexDesc (const DatabaseInfo database, IndexDesc new); - -extern void freeIndexDesc (IndexDesc desc); - -extern int indexIsBinary (const DatabaseInfo database); - -extern int indexFieldCount (const DatabaseInfo database); - -extern int verifyPRExists (const DatabaseInfo database, const char *prID); -#endif - Index: pr-age.c =================================================================== RCS file: /cvsroot/gnats/gnats/gnats/pr-age.c,v retrieving revision 1.17 diff -u -p -r1.17 pr-age.c --- pr-age.c 31 Oct 2002 23:43:28 -0000 1.17 +++ pr-age.c 19 Apr 2005 00:49:19 -0000 @@ -22,6 +22,7 @@ Software Foundation, 59 Temple Place - S #include "gnats.h" #include "query.h" +#include "ds.h" /* The name this program was run with. */ const char *program_name; @@ -177,7 +178,7 @@ main (int argc, char **argv) client_print_errors (NULL, err); exit (1); } - pr = readPRWithNum (database, argv[optind], 0, &err); + pr = pr_load_by_id (database, argv[optind], 0, &err); if (pr == NULL) { client_print_errors (database, err); Index: pr-edit.c =================================================================== RCS file: /cvsroot/gnats/gnats/gnats/pr-edit.c,v retrieving revision 1.38 diff -u -p -r1.38 pr-edit.c --- pr-edit.c 30 Aug 2003 07:58:14 -0000 1.38 +++ pr-edit.c 19 Apr 2005 00:49:20 -0000 @@ -23,6 +23,7 @@ Software Foundation, 59 Temple Place - S #include "gnats.h" #include "pcodes.h" +#include "ds.h" /* Name of this program. */ const char *program_name; @@ -439,12 +440,12 @@ main (int argc, char **argv) switch (edit_options) { case LOCK: - if (prExists (database, prnum, &err)) + if (pr_exists (database, prnum, &err)) { result = lock_pr (database, prnum, username, processid, &err); if (result != 0) { - PR *pr = readPRWithNum (database, prnum, 0, &err); + PR *pr = pr_load_by_id (database, prnum, 0, &err); if (pr != NULL) { print_named_format_pr (stdout, pr, "edit", "\n", &err); Index: pr.c =================================================================== RCS file: /cvsroot/gnats/gnats/gnats/pr.c,v retrieving revision 1.65 diff -u -p -r1.65 pr.c --- pr.c 24 Feb 2005 19:21:12 -0000 1.65 +++ pr.c 19 Apr 2005 00:49:20 -0000 @@ -24,6 +24,7 @@ Software Foundation, 59 Temple Place - S #include "gnats.h" #include "pcodes.h" +#include "ds.h" /* These will eventually be properly configurable. */ #define PR_FIELD_TAG_START '>' @@ -127,13 +128,20 @@ allocPR (const DatabaseInfo database) int field_num = get_num_fields (database); res->database = database; - allocIndex (res); + pr_init (res); /* initialize the private ds info */ res->private = (struct PR_private *) xmalloc (sizeof (struct PR_private)); - res->private->value - = (struct PR_private_value *) xmalloc (sizeof (struct PR_private_value) - * field_num); - memset (res->private->value, 0, - sizeof (struct PR_private_value) * field_num); + if (field_num == -1) + { + res->private->value = NULL; + } + else + { + res->private->value + = (struct PR_private_value *) xmalloc (sizeof (struct PR_private_value) + * field_num); + memset (res->private->value, 0, + sizeof (struct PR_private_value) * field_num); + } res->private->header = (char **) xmalloc (sizeof (char *) * NUM_HEADER_ITEMS); memset (res->private->header, 0, sizeof (char *) * NUM_HEADER_ITEMS); @@ -153,90 +161,6 @@ allocPR (const DatabaseInfo database) return res; } -/* Get the PR at PATH. PR is the PR entry to be filled in. - If PRUNE is non-zero, don't read any multitext fields. */ -static int -get_pr (PR *pr, const char *path, int prune) -{ - FILE *fp = fopen (path, "r"); - - if (fp == (FILE *)NULL) - { - return 0; - } - - if (read_header (pr, fp) < 0) - { - return 0; - } - - read_pr (pr, fp, prune); - - fclose (fp); - - return 1; -} - -int -fillInPR (PR *pr, ErrorDesc *err) -{ - char *path = gen_pr_path (pr); - const char *num = field_value (pr, NUMBER (pr->database)); - int val; - - if (num == NULL || path == NULL) - { - setError (err, CODE_ERROR, "Invalid PR in fillInPR()"); - if (path != NULL) - { - free (path); - } - return -1; - } - else - { - val = get_pr (pr, path, 0); - - free (path); - if (val == 0) - { - setError (err, CODE_FILE_ERROR, "Unable to read PR %s", num); - return -1; - } - else - { - return 0; - } - } -} - -static PR * -get_pr_from_index (const DatabaseInfo database, const char *prnum, - ErrorDesc *err) -{ - PR *pr = getFirstPR (database, err); - - /* If they gave it to us with the category, remove it. */ - if (( strrchr (prnum, '/')) != NULL) - { - prnum = strrchr (prnum, '/') + 1; - } - - while (pr != NULL && strcmp (prnum, field_value (pr, NUMBER (database))) != 0) - { - pr = getNextPR (pr); - } - - if (pr == NULL) - { - setError (err, CODE_NONEXISTENT_PR, - "No PR %s listed in the index.", prnum); - return NULL; - } - - return pr; -} - /* Initializes PR for reading. Each line of the PR should be passed in via addLineToPR (). */ @@ -906,8 +830,8 @@ setFieldChangeReason (PR *pr, FieldIndex /* Returns the value of field FIELD from PR. If the PR does not directly - have a value, check the PR's associated index entry; if that's also - empty, we return the field's default value. + have a value, check the field cache; if that's also empty, we return + the field's default value. The policy about returning a default value is a bit broken--we should only do that if the PR's actually been read in from a file. */ @@ -929,10 +853,7 @@ field_value (PR *pr, FieldIndex field) if (result == NULL) { - /* Houston, we have a problem. For dates, the value that's - stored in the index is an integer, not the actual value of - the field... \hbadness 10000. XXX ??? !!! FIXME */ - result = indexValue (pr, field); + result = field_cache_value (pr, field); if (result != NULL && fieldDefForIndex (field)->datatype == Date && strcmp (result, "0") == 0) @@ -1365,143 +1286,13 @@ free_pr (PR *pr) { free_pr_header (pr); free_pr_contents (pr); - freePRIndex (pr); - free (pr->index); + pr_destroy (pr); /* free up the private ds info */ free (pr->private->header); free (pr->private->value); free (pr->private); free (pr); } -static char * -get_pr_path (const DatabaseInfo database, PR *pr, const char *prnum, - ErrorDesc *err) -{ - char *path = NULL; - const char *category = NULL; - char *categoryFromIndex = NULL; - - if (pr != NULL) - { - category = field_value (pr, CATEGORY (database)); - } - else - { - categoryFromIndex = getCategoryFromIndex (database, prnum, err); - category = categoryFromIndex; - } - - if (category != NULL) - { - asprintf (&path, "%s/%s/%s", databaseDir (database), category, prnum); - if (categoryFromIndex != NULL) - { - free (categoryFromIndex); - } - } - - return path; -} - -int -prExists (const DatabaseInfo database, const char *prnum, ErrorDesc *err) -{ - PR *pr = get_pr_from_index (database, prnum, err); - char *path = get_pr_path (database, pr, prnum, err); - int res = 0; - if (path != NULL) - { - res = fileExists (path); - free (path); - if (res == 0) - { - setError (err, CODE_FILE_ERROR, "Can't open file `%s'", path); - } - } - return res; -} - -static bool -pr_file_readable (const char *path, ErrorDesc *err) -{ - FILE *fp; - - if (path == NULL) - { - return FALSE; - } - - if ((fp = fopen (path, "r")) == NULL) - { - setError (err, CODE_FILE_ERROR, "Can't open file `%s'", path); - return FALSE; - } - - fclose (fp); - return TRUE; -} - -PR * -readPRWithNum (const DatabaseInfo database, const char *prnum, - int prune, ErrorDesc *err) -{ - PR *pr = NULL; - PR *index_pr = get_pr_from_index (database, prnum, err); - char *path = get_pr_path (database, index_pr, prnum, err); - - if (path != NULL) - { - if (pr_file_readable (path, err)) - { - pr = allocPR (database); - setPrevPR (pr, getPrevPR (index_pr)); - setNextPR (pr, getNextPR (index_pr)); - if (get_pr (pr, path, prune) == 0) - { - free_pr (pr); - pr = NULL; - } - } - free (path); - } - return pr; -} - -int -pr_delete (const DatabaseInfo database, const char *prnum, ErrorDesc *err) -{ - PR *pr; - char *path = NULL; - - pr = get_pr_from_index (database, prnum, err); - path = get_pr_path (database, pr, prnum, err); - - if (path == NULL || !pr_file_readable (path, err)) - { - if (path != NULL) - { - free (path); - } - return -1; - } - - if (removePRFromIndex (database, prnum, err)) - { - free (path); - return -5; - } - - if (unlink (path)) - { - setError (err, CODE_FILE_ERROR, "Unable to unlink file %s\n", path); - free (path); - return -6; - } - - free (path); - return 1; -} - void freeInputTemplate (InputTemplate *template) { Index: pr.h =================================================================== RCS file: /cvsroot/gnats/gnats/gnats/pr.h,v retrieving revision 1.39 diff -u -p -r1.39 pr.h --- pr.h 24 Feb 2005 19:21:12 -0000 1.39 +++ pr.h 19 Apr 2005 00:49:20 -0000 @@ -52,15 +52,15 @@ typedef enum { #include #include "gnats.h" #include "field.h" -#include "index.h" +#include "database.h" /* Describes one entire PR. */ struct PR_struct { /* The database this PR is associated with. */ DatabaseInfo database; - /* Index entry for this PR. */ - Index *index; + /* all the datastore specific stuff */ + void *ds_private; /* Non-zero if the PR contents are pruned. */ int prune; @@ -88,7 +88,6 @@ extern int setBuiltinField (FieldIndex f extern FieldIndex findBuiltinField (const DatabaseInfo database, const char *name); -extern int fillInPR (PR *pr, ErrorDesc *err); extern void initReadPR (PR *pr); extern void addLineToPR (PR *pr, char *buffer, char *line, size_t linelen, int prune); @@ -131,17 +130,8 @@ extern int fconfigParse (DatabaseInfo da extern int createPrFile (PR *pr, const char *filename, int force, ErrorDesc *err); -extern int pr_delete (const DatabaseInfo database, const char *prnum, - ErrorDesc *err); - -extern int prExists (const DatabaseInfo database, const char *prID, - ErrorDesc *err); - extern void printValidValues (FILE *outfile, FieldIndex i, const char *eol); -extern PR *readPRWithNum (const DatabaseInfo database, const char *prID, - int prune, ErrorDesc *err); - /* Given the name of a header NAME, return its Header_Name value, or InvalidHeaderName if NAME is not a valid header name. */ extern Header_Name find_header_index (const char *name); Index: query-pr.c =================================================================== RCS file: /cvsroot/gnats/gnats/gnats/query-pr.c,v retrieving revision 1.45 diff -u -p -r1.45 query-pr.c --- query-pr.c 31 Oct 2002 23:43:28 -0000 1.45 +++ query-pr.c 19 Apr 2005 00:49:20 -0000 @@ -23,6 +23,7 @@ Software Foundation, 59 Temple Place - S #include "gnats.h" #include "query.h" #include "mail.h" +#include "ds.h" /* The name this program was run with. */ const char *program_name; @@ -960,8 +961,8 @@ main (int argc, char **argv) { ErrorDesc err; - int res = iterate_prs (database, argc - optind, argv + optind, expr, - query_format, client_print_query, &err); + int res = db_query (database, argc - optind, argv + optind, expr, + client_print_query, query_format, &err); if (res < 0) { client_print_errors (database, err); Index: query.c =================================================================== RCS file: /cvsroot/gnats/gnats/gnats/query.c,v retrieving revision 1.57 diff -u -p -r1.57 query.c --- query.c 25 Nov 2002 13:58:33 -0000 1.57 +++ query.c 19 Apr 2005 00:49:20 -0000 @@ -22,15 +22,10 @@ Software Foundation, 59 Temple Place - S #include "gnats.h" #include "query.h" #include "pcodes.h" +#include "ds.h" /* One item to search for. */ -typedef struct query_item -{ - ComplexFieldIndex fieldIndex; - struct re_pattern_buffer *compRegexp; -} *QueryItem; - static QueryItem freeQueryItemEnt = NULL; static QueryItem @@ -86,37 +81,6 @@ freeQueryItem (QueryItem i) } } -typedef struct search_item -{ - /* The type of search to perform, of course. */ - SearchType searchType; - - /* The items on the left and right-hand-sides. */ - QueryItem lhs; - QueryItem rhs; -} SearchItem; - -typedef struct queryTree -{ - /* The operation to perform on this node. */ - QueryOp op; - - /* The left and right sides of the expression; if this is a unary op, - the left side contains the expression. */ - struct queryTree *left, *right; - - /* If this is a QueryMatch expression, the actual query to perform. */ - SearchItem ent; -} *QueryTree; - -struct queryExpr -{ - /* Database that this query is associated with. */ - DatabaseInfo database; - /* The actual query tree. */ - QueryTree tree; -}; - struct SearchTypes { const char *name; const char *operator; @@ -618,15 +582,6 @@ fieldCompare (PR *pr, PR *oldPR, QueryIt searchType = RegCmp; } } - if ((! PR_IS_FULL (pr)) && (! isIndexedField (fields[0]->fieldIndex))) - { - ErrorDesc err; - - if (fillInPR (pr, &err) != 0) - { - return 0; - } - } for (x = 0; x < 2; x++) { @@ -721,7 +676,7 @@ pr_match_field (PR *pr, PR *oldPR, Searc /* Returns a non-zero value if the PR referenced by I matches the expression tree in QEXP. */ -static int +int pr_matches_tree (PR *pr, PR *oldPR, QueryTree qexp, FormatNamedParameter *params) { @@ -1202,41 +1157,12 @@ int process_format (FILE *fp, char **res, PR *pr, PR *oldPR, QueryFormat *fmt, const char *eolTerminator, FormatNamedParameter *parameters) { - int do_open_pr = 0; - if (pr != NULL) { - if (! PR_IS_FULL (pr)) - { - FieldList p = fmt->fields; - - if (p == NULL) - { - do_open_pr = 1; - } - else - { - while (p != NULL) - { - if (! isIndexedField (p->ent)) - { - do_open_pr = 1; - break; - } - p = p->next; - } - } - } - - if (do_open_pr) - { - ErrorDesc err; - - if (fillInPR (pr, &err)) - { - return 0; - } - } + if (pr_load_fields (pr, fmt->fields) == -1) + { + return 0; + } } if (fmt->printf != NULL) @@ -1322,119 +1248,6 @@ print_pr (FILE *dest, PR *pr, QueryForma } -/* Iterate through a set of PRs. For those PRs that match the - expression in EXP, invoke FUNC with the count of PRs that have - matched so far, the PR* entry for the PR, and the supplied - QUERY_FORMAT. - - FUNC is invoked once more after all of the queries have been - searched. The PR* pointer is NULL, and the count corresponds to - the total # of PRs that matched. - - If AC is 0 or AV is NULL, we iterate through all of the PRs in the - index for the current database. Otherwise, only those PRs that - match the PRIDs in AV[0], AV[1]. AV[2]...AV[AC-1] are tested. */ - -int -iterate_prs (const DatabaseInfo database, int ac, char **av, QueryExpr exp, - QueryFormat *query_format, QueryFunc func, ErrorDesc *err) -{ - int found = 0; - QueryTree tree = NULL; - - if (exp != NULL) - { - if (exp->database != database) - { - abort (); - } - tree = exp->tree; - } - - *err = NULL; - if (ac == 0 || av == NULL) - { - PR *pr = getFirstPR (database, err); - - if (*err != NULL) - { - return -1; - } - /* We weren't given a list of PRs to check, so we do the - whole shooting match. */ - while (pr != NULL) - { - if (pr_matches_tree (pr, NULL, tree, NULL)) - { - found++; - func (found, pr, query_format); - } - free_pr_header (pr); - free_pr_contents (pr); - pr = getNextPR (pr); - } - } - else - { - int cpr; - - for (cpr = 0; cpr < ac; cpr++) - { - char *pat, *n; - char *p = av[cpr]; - int plen; - PR *pr; - struct re_pattern_buffer buf; - - memset (&buf, 0, sizeof (buf)); - - /* Remove the category */ - if ((n = (char *) strrchr (p, '/')) != NULL) - { - p = n + 1; - } - - plen = strlen (p); - pat = (char *) xmalloc (plen + 3); - strcpy (pat, p); - strcpy (pat + plen, "\\'"); - - *err = NULL; - pr = getFirstPR (database, err); - if (*err != NULL) - { - return -1; - } - - while (pr != NULL) - { - if (gnats_regcmp (pat, field_value (pr, NUMBER (pr->database)), - &buf) == 0) - { - if (pr_matches_expr (pr, NULL, exp, NULL)) - { - found++; - func (found, pr, query_format); - } - /* Only one PR will match this PR number, because it's - not really a regexp */ - break; - } - free_pr_header (pr); - free_pr_contents (pr); - pr = getNextPR (pr); - } - buf.translate = NULL; - regfree (&buf); - free (pat); - } - } - - func (found, NULL, 0); - - return found; -} - static QueryTree newQueryTree (void) { Index: query.h =================================================================== RCS file: /cvsroot/gnats/gnats/gnats/query.h,v retrieving revision 1.37 diff -u -p -r1.37 query.h --- query.h 28 Oct 2002 22:57:50 -0000 1.37 +++ query.h 19 Apr 2005 00:49:20 -0000 @@ -37,11 +37,47 @@ typedef enum queryOp { InvalidQueryOp = -1, QueryMatch = 0, QueryAnd, QueryOr, QueryNot } QueryOp; -#include "index.h" #include "builtin-fields.h" #include "field.h" #include "mail.h" +typedef struct query_item +{ + ComplexFieldIndex fieldIndex; + struct re_pattern_buffer *compRegexp; +} *QueryItem; + +typedef struct search_item +{ + /* The type of search to perform, of course. */ + SearchType searchType; + + /* The items on the left and right-hand-sides. */ + QueryItem lhs; + QueryItem rhs; +} SearchItem; + +typedef struct queryTree +{ + /* The operation to perform on this node. */ + QueryOp op; + + /* The left and right sides of the expression; if this is a unary op, + the left side contains the expression. */ + struct queryTree *left, *right; + + /* If this is a QueryMatch expression, the actual query to perform. */ + SearchItem ent; +} *QueryTree; + +struct queryExpr +{ + /* Database that this query is associated with. */ + DatabaseInfo database; + /* The actual query tree. */ + QueryTree tree; +}; + struct queryFormat { /* The name of this query format. */ @@ -66,16 +102,6 @@ struct queryFormat struct queryFormat *next; }; -/* The function that is invoked from iterate_prs () is of this type. - First argument is the result # (1 for the first PR matched, 2 for - the second, etc). The second argument is the PR entry for the - PR, and the final argument is the QueryFormat supplied to - iterate_prs. - - The function is invoked with a NULL PR entry when we run out of PRs - to match; the result # is the total # of PRs that were matched. */ -typedef void (*QueryFunc)(int, PR *, QueryFormat *); - /* Return the numeric equivalent of enum entry TEXT for field FIELD; entries are numbered starting from 1. 0 is returned if TEXT is not a valid entry for FIELD. */ @@ -112,14 +138,12 @@ extern int process_format (FILE *fp, cha const char *eolTerminator, FormatNamedParameter *parameters); +extern int pr_matches_tree (PR *pr, PR *oldPR, QueryTree qexp, + FormatNamedParameter *params); extern int pr_matches_expr (PR *pr, PR *oldPR, QueryExpr expr, FormatNamedParameter *params); extern int set_query_opt (QueryExpr *, int, const char *, int); -extern int iterate_prs (const DatabaseInfo database, - int ac, char **av, QueryExpr exp, QueryFormat *format, - QueryFunc func, ErrorDesc *err); - extern void addQueryFormat (DatabaseInfo database, QueryFormat *format); extern QueryFormat *findQueryFormat (const DatabaseInfo database, const char *name, ErrorDesc *err); Index: ds-file/Makefile.in =================================================================== RCS file: ds-file/Makefile.in diff -N ds-file/Makefile.in --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ ds-file/Makefile.in 19 Apr 2005 00:49:20 -0000 @@ -0,0 +1,61 @@ +# Makefile for GNU GNATS flat-file datastore. +# Copyright (C) 2005 Free Software Foundation, Inc. +# Adapted from /gnats/Makefile.in by Mel Hatzis . +# +# This file is part of GNU GNATS. + +# GNU GNATS is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# GNU GNATS is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with GNU GNATS; see the file COPYING. if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +INSTALL = $(srcdir)/../../install-sh -c + +CONFIG_OPT = "--with-gnats-ds=file" + +LIBSRC = db.c pr.c fld.c index.c +LIBOBJS = db.o pr.o fld.o index.o + +TARGETLIB = libds-file.a + +LIBS = @LIBS@ + +INCLUDEDIR = -I. -I.. -I$(srcdir) -I$(srcdir)/.. -I$(srcdir)/../../include + +LIBGNATS = `if [ -f ../libgnats.a ] ; then echo ../libgnats.a ; else echo -lgnats ; fi` + +GNATS_LIBS = $(LIBGNATS) $(TARGETLIB) + +BUILD_DS = @BUILD_FILE@ + +all-ds: $(TARGETLIB) + +$(TARGETLIB): $(LIBOBJS) + +TAGS: $(LIBSRC) + etags -f TAGS $(LIBSRC) *.h ; + +# Clean things up. +# Don't use rm -rf in case there's other stuff in the directories +uninstall: + +# Dependencies + +index.o: ../pr.h +index.o: $(srcdir)/../gnats.h $(srcdir)/../../include/ansidecl.h +pr.o: $(srcdir)/../../include/ansidecl.h +pr.o: $(srcdir)/../gnats.h $(srcdir)/../pcodes.h +pr.o: $(srcdir)/../../include/getopt.h ../autoconf.h +db.o: $(srcdir)/../gnats.h $(srcdir)/../../include/ansidecl.h +db.o: $(srcdir)/../regex.h $(srcdir)/../query.h $(srcdir)/../gnatsd.h +fld.o: $(srcdir)/../ds.h $(srcdir)/../../include/ansidecl.h +fld.o: $(srcdir)/../gnats.h $(srcdir)/../pr.h Index: ds-file/db.c =================================================================== RCS file: ds-file/db.c diff -N ds-file/db.c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ ds-file/db.c 19 Apr 2005 00:49:20 -0000 @@ -0,0 +1,238 @@ +/* Flat-file datastore API - functions operating on a single database instance. + Copyright (C) 2005 Free Software Foundation, Inc. + Contributed by Mel Hatzis . + +This file is part of GNU GNATS. + +GNU GNATS is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GNU GNATS is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along +with GNU GNATS; see the file COPYING. if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "ds.h" +#include "gnatsd.h" +#include "ds-file.h" + +/* load the contents of the PR which are required to test the PR against + * the passed in query expression - ie. read's the PR from disk if the + * query expression refers to fields which are not in the index. + * Returns -1 on error, 0 if the PR wasn't loaded and 1 if it was loaded. */ +static int +get_pr_contents(PR *pr, QueryTree qexp) +{ + int pr_loaded = 0; + + if (qexp == NULL) + { + return 1; + } + + switch (qexp->op) + { + case QueryAnd: + case QueryOr: + { + pr_loaded = get_pr_contents (pr, qexp->left); + if (pr_loaded == 0) + { + pr_loaded = get_pr_contents (pr, qexp->right); + } + break; + } + case QueryNot: + { + pr_loaded = get_pr_contents (pr, qexp->left); + break; + } + case QueryMatch: + { + SearchItem *query_element = &(qexp->ent); + ComplexFieldIndex lhs_field = query_element->lhs->fieldIndex; + if ((! PR_IS_FULL (pr)) && (! isIndexedField (lhs_field))) + { + ErrorDesc err; + if (pr_load (pr, &err) != 0) + { + return -1; + } + pr_loaded = 1; + } + break; + } + default: + { + abort (); + break; + } + } + + return pr_loaded; +} + +int +db_init (DatabaseInfo database, ErrorDesc *err) +{ + int retval = 0; + char *path; + FileDBInfo dbi = (FileDBInfo) xmalloc (sizeof (struct file_db_info)); + setDatastorePrivate (database, (void *) dbi); + dbi->indexDesc = NULL; + path = gnats_adm_dir (database, "dbconfig"); + if (fconfigParse (database, path, NULL, err)) + { + retval = -1; + } + else + { + initIndex (database); + } + free (path); + return retval; +} + +void +db_destroy (DatabaseInfo database) +{ + FileDBInfo dbi = (FileDBInfo) getDatastorePrivate(database); + clearPRChain (database); + if (dbi != NULL) + { + if (dbi->indexDesc != NULL) + { + freeIndexDesc (dbi->indexDesc); + } + free (dbi); + setDatastorePrivate (database, (void *)NULL); + } +} + +/* Iterate through a set of PRs. For those PRs that match the + expression in EXP, invoke FUNC with the count of PRs that have + matched so far, the PR* entry for the PR, and the supplied + QUERY_FORMAT. + + FUNC is invoked once more after all of the queries have been + searched. The PR* pointer is NULL, and the count corresponds to + the total # of PRs that matched. + + If AC is 0 or AV is NULL, we iterate through all of the PRs in the + index for the current database. Otherwise, only those PRs that + match the PRIDs in AV[0], AV[1]. AV[2]...AV[AC-1] are tested. */ + +int +db_query (const DatabaseInfo database, int ac, char **av, QueryExpr exp, + QueryFunc func, QueryFormat *query_format, ErrorDesc *err) +{ + int found = 0; + QueryTree tree = NULL; + + if (exp != NULL) + { + if (exp->database != database) + { + abort (); + } + tree = exp->tree; + } + + *err = NULL; + if (ac == 0 || av == NULL) + { + PR *pr = getFirstPR (database, err); + + if (*err != NULL) + { + return -1; + } + /* We weren't given a list of PRs to check, so we do the + whole shooting match. */ + while (pr != NULL) + { + if (get_pr_contents (pr, tree) != -1 && + pr_matches_tree (pr, NULL, tree, NULL)) + { + found++; + func (found, pr, query_format); + } + free_pr_header (pr); + free_pr_contents (pr); + pr = getNextPR (pr); + } + } + else + { + int cpr; + + for (cpr = 0; cpr < ac; cpr++) + { + char *pat, *n; + char *p = av[cpr]; + int plen; + PR *pr; + struct re_pattern_buffer buf; + + memset (&buf, 0, sizeof (buf)); + + /* Remove the category */ + if ((n = (char *) strrchr (p, '/')) != NULL) + { + p = n + 1; + } + + plen = strlen (p); + pat = (char *) xmalloc (plen + 3); + strcpy (pat, p); + strcpy (pat + plen, "\\'"); + + *err = NULL; + pr = getFirstPR (database, err); + if (*err != NULL) + { + return -1; + } + + while (pr != NULL) + { + if (gnats_regcmp (pat, field_value (pr, NUMBER (pr->database)), + &buf) == 0) + { + if (get_pr_contents (pr, tree) != -1 && + pr_matches_expr (pr, NULL, exp, NULL)) + { + found++; + func (found, pr, query_format); + } + /* Only one PR will match this PR number, because it's + not really a regexp */ + break; + } + free_pr_header (pr); + free_pr_contents (pr); + pr = getNextPR (pr); + } + buf.translate = NULL; + regfree (&buf); + free (pat); + } + } + + func (found, NULL, query_format); + + return found; +} + +void +db_reset (const DatabaseInfo database, ErrorDesc *err) +{ + checkPRChain (database, err); +} + Index: ds-file/ds-file.h =================================================================== RCS file: ds-file/ds-file.h diff -N ds-file/ds-file.h --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ ds-file/ds-file.h 19 Apr 2005 00:49:20 -0000 @@ -0,0 +1,37 @@ +/* Functions and data types used throughout the flat-file datastore. + Copyright (C) 2005 Free Software Foundation, Inc. + Contributed by Mel Hatzis . + +This file is part of GNU GNATS. + +GNU GNATS is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GNU GNATS is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along +with GNU GNATS; see the file COPYING. if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#ifndef _DS_FILE_H +#define _DS_FILE_H + +typedef struct file_pr_info *FilePRInfo; +typedef struct file_db_info *FileDBInfo; + +#include "index.h" + +struct file_pr_info { + Index *index; /* Index entry for the PR. */ +}; + +struct file_db_info { + IndexDesc indexDesc; +}; + +#endif /* !_DS_FILE_H */ Index: ds-file/fld.c =================================================================== RCS file: ds-file/fld.c diff -N ds-file/fld.c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ ds-file/fld.c 19 Apr 2005 00:49:20 -0000 @@ -0,0 +1,32 @@ +/* Flat-file datastore API - functions operating on PR fields. + Copyright (C) 2005 Free Software Foundation, Inc. + Contributed by Mel Hatzis . + +This file is part of GNU GNATS. + +GNU GNATS is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GNU GNATS is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along +with GNU GNATS; see the file COPYING. if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "ds.h" +#include "ds-file.h" + +char * +field_cache_value (PR *pr, FieldIndex field) +{ + /* Houston, we have a problem. For dates, the value that's + stored in the index is an integer, not the actual value of + the field... \hbadness 10000. XXX ??? !!! FIXME */ + return indexValue (pr, field); +} + Index: ds-file/index.c =================================================================== RCS file: ds-file/index.c diff -N ds-file/index.c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ ds-file/index.c 19 Apr 2005 00:49:20 -0000 @@ -0,0 +1,1321 @@ +/* Miscellaneous index routines for use with GNATS. + Copyright (C) 1993, 1995, 1999, 2000, 2001 Free Software Foundation, Inc. + Contributed by Brendan Kehoe (brendan@cygnus.com) and + by Tim Wicinski (wicinski@barn.com). + + Heavily revised by Bob Manson (manson@juniper.net); also added + binary index support. + +This file is part of GNU GNATS. + +GNU GNATS is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU GNATS is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU GNATS; see the file COPYING. If not, write to the Free +Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111, USA. */ + +/* Binary format (all values are in MSB-first order): + * 1 byte: number of fields in each index entry + * For each record: + * 2 bytes: total length of record, including this 2 byte length + * For each field: + * 2 bytes: length of field, not including this 2 byte length or NUL + * terminator + * N bytes: data in field, NUL-terminated + */ + + +#include "ds.h" +#include "gnatsd.h" +#include "ds-file.h" + +struct index_desc +{ + DatabaseInfo database; + char *path; + FieldList fields; + char *separator; + int isBinary; + int numberOfFields; + int *fieldInIndex; + time_t mtime; + int indexRead; + PR *prChain; +}; + +typedef struct index_file_desc +{ + IndexDesc desc; + FILE *fileDes; +} *IndexFileDesc; + +struct indexEntry +{ + char *buf; + char **fields; + PR *nextPR; + PR *prevPR; +}; + +static PR *nextIndexEntryBinary (IndexFileDesc fp); + +static int +format_field (FieldIndex field, const char *value, const char *separator, + char **dest, size_t *length) +{ + char datebuf[30]; + size_t valLen; + size_t destLen; + size_t sepLen; + + if (value == NULL) + { + value = ""; + } + + if (fieldDefForIndex (field)->nospaces) + { + char *r = strchr (value, ' '); + if (r != NULL) + { + valLen = r - value; + } + } + + if (fieldDefForIndex (field)->datatype == Date) + { + time_t d = 0; + if (value != NULL && *value != '\0') + { + /* We used to fail here if the date was invalid, but that's + a mistake. */ + d = get_any_date (value); + } + sprintf (datebuf, "%d", (int) d); + value = datebuf; + } + + if (value == NULL) + { + value = ""; + } + destLen = *length; + valLen = strlen (value); + sepLen = strlen (separator); + + *dest = xrealloc (*dest, destLen + valLen + sepLen + 1); + memcpy (*dest + destLen, separator, sepLen); + memcpy (*dest + destLen + sepLen, value, valLen); + (*dest)[destLen + sepLen + valLen] = '\0'; + *length = destLen + sepLen + valLen; + return sepLen + valLen; +} + +char * +createIndexEntry (PR *pr, size_t *reclen) +{ + FieldList f; + char *res; + IndexDesc indexDesc = getIndexDesc (pr->database); + + if (indexIsBinary (pr->database)) + { + return createIndexEntryBinary (pr, reclen); + } + + /* XXX ??? !!! For now. We need a way to refer to "PRID". */ + asprintf (&res, "%s/%s", field_value (pr, CATEGORY (pr->database)), + field_value (pr, NUMBER (pr->database))); + *reclen = strlen (res); + + for (f = indexDesc->fields; f != NULL; f = f->next) + { + FieldIndex i = simpleFieldIndexValue (f->ent); + if (format_field (i, field_value (pr, i), indexDesc->separator, &res, + reclen) < 0) + { + free (res); + res = NULL; + break; + } + } + append_string (&res, "\n"); + (*reclen)++; + return res; +} + +static char * +find_sep (IndexDesc indexDesc, char *start) +{ + char *end; + int seplen = strlen (indexDesc->separator); + + do { + end = strchr (start, indexDesc->separator[0]); + if (end != NULL) + { + if (seplen == 1 || strncmp (start, indexDesc->separator, seplen) == 0) + { + return end; + } + end++; + } + } while (end != NULL); + + return end; +} + +static char * +readBinaryRecord (IndexFileDesc fp, size_t *length) +{ + char *res; + unsigned char entlen[2]; + + if (fread (entlen, 1, 2, fp->fileDes) != 2) + { + return NULL; + } + *length = entlen[0] * 256 + entlen[1] - 2; + res = xmalloc (*length); + if (fread (res, 1, *length, fp->fileDes) == *length) + { + return res; + } + else + { + free (res); + return NULL; + } +} + +static char * +extractBinaryField (char **record, size_t *lengthPtr) +{ + size_t length = *lengthPtr; + if (length > 0) + { + char *recLen = *record; + size_t fieldLen = (recLen[0] & 255) * 256 + (recLen[1] & 255); + if ((fieldLen + 3) <= length) + { + char *recptr = *record + 2; + *lengthPtr = length - (fieldLen + 3); + (*record) = recLen + fieldLen + 3; + return recptr; + } + } + return NULL; +} + +/* Return the next index entry from FP. */ +static PR * +nextIndexEntry (IndexFileDesc fp) +{ + char *start, *end; + char *buf; + PR *res; + int x; + int seplen; + FieldList f; + FilePRInfo pi; + Index *p; + + if (fp->desc->isBinary) + { + return nextIndexEntryBinary (fp); + } + + res = allocPR (fp->desc->database); + pi = (FilePRInfo) res->ds_private; + p = pi->index; + seplen = strlen (fp->desc->separator); + + p->prevPR = NULL; + p->nextPR = NULL; + p->fields = (char **) xmalloc (sizeof (char *) + * get_num_fields (fp->desc->database)); + memset (p->fields, 0, sizeof (char *) * get_num_fields (fp->desc->database)); + while ((buf = read_line (fp->fileDes, NULL)) != NULL) + { + if (buf[0] != '#') + { + break; + } + else + { + free (buf); + } + } + + if (buf == NULL) + { + goto no_entry; + } + + p->buf = buf; + x = strlen (buf) - 1; + if (buf[x] == '\n') + { + buf[x] = '\0'; + } + + start = buf; + end = strchr (start, '/'); + if (end == NULL) + { + goto no_entry; + } + *end = '\0'; + p->fields[fieldDefForIndex (CATEGORY (fp->desc->database))->number] = start; + + start = end + 1; + end = find_sep (fp->desc, start); + + if (end == NULL) + { + goto no_entry; + } + *end = '\0'; + p->fields[fieldDefForIndex (NUMBER (fp->desc->database))->number] = start; + + for (f = fp->desc->fields; f != NULL && end != NULL; f = f->next) + { + FieldIndex i = simpleFieldIndexValue (f->ent); + + if (i != InvalidFieldIndex) + { + start = end + seplen; + p->fields[fieldNumber (i)] = start; + } + if (f->next != NULL) + { + end = find_sep (fp->desc, start); + if (end != NULL) + { + *end = '\0'; + } + } + } + + if (f == NULL) + { + return res; + } + +no_entry: + + free_pr (res); + return NULL; +} + +static PR * +nextIndexEntryBinary (IndexFileDesc fp) +{ + char *record; + PR *res = NULL; + size_t length; + FilePRInfo pi; + + record = readBinaryRecord (fp, &length); + if (record != NULL) + { + FieldList f; + Index *indexEnt; + + res = allocPR (fp->desc->database); + pi = (FilePRInfo) res->ds_private; + indexEnt = pi->index; + indexEnt->prevPR = NULL; + indexEnt->nextPR = NULL; + indexEnt->fields + = (char **) xmalloc (sizeof (char *) + * get_num_fields (fp->desc->database)); + indexEnt->buf = record; + memset (indexEnt->fields, 0, sizeof (char *) + * get_num_fields (fp->desc->database)); + indexEnt->fields[fieldDefForIndex (NUMBER (fp->desc->database))->number] + = extractBinaryField (&record, &length); + indexEnt->fields[fieldDefForIndex (CATEGORY (fp->desc->database))->number] + = extractBinaryField (&record, &length); + for (f = fp->desc->fields; res != NULL && f != NULL; f = f->next) + { + FieldIndex i = simpleFieldIndexValue (f->ent); + + if (i != InvalidFieldIndex) + { + indexEnt->fields[fieldNumber (i)] + = extractBinaryField (&record, &length); + if (indexEnt->fields[fieldNumber (i)] == NULL) + { + free_pr (res); + res = NULL; + } + } + else + { + free_pr (res); + res = NULL; + } + } + } + return res; +} + +static void +appendBinaryFieldContents (char **dest, + const char *value, size_t *recLenPtr) +{ + size_t valLen; + size_t recLen = *recLenPtr; + + if (value == NULL) + { + value = ""; + valLen = 0; + } + else + { + valLen = strlen (value); + } + *dest = xrealloc (*dest, recLen + valLen + 3); + memcpy (*dest + recLen + 2, value, valLen + 1); + (*dest)[recLen + 0] = valLen / 256; + (*dest)[recLen + 1] = valLen % 256; + *recLenPtr += valLen + 3; +} + +char * +createIndexEntryBinary (PR *pr, size_t *recLen) +{ + const DatabaseInfo database = pr->database; + const IndexDesc indexDesc = getIndexDesc (database); + FieldList f; + char *res; + + *recLen = 2; + res = xmalloc (2); + appendBinaryFieldContents (&res, field_value (pr, NUMBER (pr->database)), + recLen); + appendBinaryFieldContents (&res, field_value (pr, CATEGORY (pr->database)), + recLen); + for (f = indexDesc->fields; f != NULL; f = f->next) + { + FieldIndex i = simpleFieldIndexValue (f->ent); + size_t fieldLenOffset = *recLen; + int valLen; + + /* Two bytes for the field length. */ + *recLen += 2; + valLen = format_field (i, field_value (pr, i), "", &res, recLen); + res[fieldLenOffset + 0] = valLen / 256; + res[fieldLenOffset + 1] = valLen & 255; + /* One for the NUL character; format_field() always appends one, + but doesn't include it in the length. */ + (*recLen)++; + } + res[0] = *recLen / 256; + res[1] = *recLen % 256; + return res; +} + +static char * +findPrCategoryBinary (IndexFileDesc fp, const char *number) +{ + unsigned char fieldcount; + char *record; + size_t recLen; + + if (fread (&fieldcount, 1, 1, fp->fileDes) != 1) + { + return NULL; + } + while ((record = readBinaryRecord (fp, &recLen)) != NULL) + { + char *recPos = record; + char *prNum = extractBinaryField (&recPos, &recLen); + if (prNum != NULL && strcmp (prNum, number) == 0) + { + char *category = extractBinaryField (&recPos, &recLen); + free (record); + return category; + } + free (record); + } + return NULL; +} + +/* Find problem report NUMBER in the index file, returning its category. */ +static char * +findPrCategory (IndexFileDesc fp, const char *number) +{ + char *buf; + char *res = NULL; + + while ((res == NULL) && (buf = read_line (fp->fileDes, NULL)) != NULL) + { + if (buf[0] != '#') + { + char *start = buf; + char *end = strchr (start, '/'); + if (end != NULL) + { + char *numStart = end + 1; + char *category = start; + + *end = '\0'; + end = find_sep (fp->desc, numStart); + if (end != NULL) + { + *end = '\0'; + + if (strcmp (numStart, number) == 0) + { + res = xstrdup (category); + } + } + } + } + free (buf); + } + + return res; +} + +static time_t +statIndex (char *name) +{ + struct stat buf; + int i; + + i = stat (name, &buf); + if (i < 0) + { + return (time_t)-1; + } + + return buf.st_mtime; +} + +static IndexFileDesc +openIndex (IndexDesc indexDesc, ErrorDesc *err) +{ + FILE *fp; + time_t t; + char *index_filename = gnats_adm_dir (indexDesc->database, indexDesc->path); + IndexFileDesc res + = (IndexFileDesc) xmalloc (sizeof (struct index_file_desc)); + + res->desc = indexDesc; + + fp = fopen (index_filename, "r"); + + if (fp != NULL) + { + t = statIndex (index_filename); + /* ??? XXX !!! Do we want to do something here? */ + if (t != (time_t)-1) + { + indexDesc->mtime = t; + } + res->fileDes = fp; + if (indexDesc->isBinary) + { + unsigned char fieldCount; + if ((fread (&fieldCount, 1, 1, res->fileDes) != 1) + || (fieldCount != indexDesc->numberOfFields)) + { + setError (err, CODE_INVALID_INDEX, + "Index file %s contains incorrect number of fields", + index_filename); + fclose (res->fileDes); + free (res); + res = NULL; + } + } + } + else + { + setError (err, CODE_FILE_ERROR, "Unable to open index file %s", + index_filename); + free (res); + res = NULL; + } + + free (index_filename); + return res; +} + +static void +closeIndex (IndexFileDesc desc) +{ + fclose (desc->fileDes); + free (desc); +} + +/* getIndex - reads in the entire set of PRs with index entries. */ +static PR * +getIndex (IndexDesc indexDesc, ErrorDesc *err) +{ + IndexFileDesc fp = openIndex (indexDesc, err); + PR *new_chain = NULL; + PR *end_chain = NULL; + + if (fp != NULL) + { + PR *i; + + while ((i = nextIndexEntry (fp))) + { + if (new_chain == NULL) + { + new_chain = i; + end_chain = i; + } + else + { + FilePRInfo i_pi = (FilePRInfo) i->ds_private; + FilePRInfo e_pi = (FilePRInfo) end_chain->ds_private; + i_pi->index->prevPR = end_chain; + e_pi->index->nextPR = i; + end_chain = i; + } + } + + closeIndex (fp); + } + indexDesc->indexRead = 1; + return new_chain; +} + +static PR * +getNewIndexIfChanged (DatabaseInfo database, ErrorDesc *err) +{ + int rereadIndex; + IndexDesc indexDesc = getIndexDesc (database); + + if (indexDesc->indexRead) + { + time_t t; + char *index_filename = gnats_adm_dir (database, indexDesc->path); + + t = statIndex (index_filename); + free (index_filename); + + if (t == (time_t)-1) + { + setError (err, CODE_FILE_ERROR, + "GNATS cannot stat the index: %s.", strerror (errno)); + return 0; + } + if (t > indexDesc->mtime) + { + rereadIndex = 1; + } + else + { + rereadIndex = 0; + } + } + else + { + rereadIndex = 1; + } + + if (rereadIndex) + { + return getIndex (indexDesc, err); + } + else + { + return NULL; + } +} + +void +freePRIndex (DatabaseInfo database, FilePRInfo pi) +{ + if (pi->index != NULL && pi->index->fields != NULL) + { + if (pi->index->buf != NULL) + { + free (pi->index->buf); + pi->index->buf = NULL; + } + else + { + int x; + + for (x = get_num_fields (database) - 1; x >= 0; x--) + { + if (pi->index->fields[x] != NULL) + { + free (pi->index->fields[x]); + pi->index->fields[x] = NULL; + } + } + } + free (pi->index->fields); + pi->index->fields = NULL; + } +} + +void +allocIndex (FilePRInfo pi) +{ + pi->index = (Index *) xmalloc (sizeof (Index)); + pi->index->buf = NULL; + pi->index->fields = NULL; + pi->index->prevPR = NULL; + pi->index->nextPR = NULL; +} + +void +buildIndexEntry (PR *pr) +{ + int x; + int num_fields = get_num_fields (pr->database); + Index *newIndex; + FilePRInfo pi; + + if (pr->ds_private == NULL) + { + pr_init (pr); + pi = (FilePRInfo) pr->ds_private; + } + else + { + pi = (FilePRInfo) pr->ds_private; + if (pi->index != NULL) + { + freePRIndex (pr->database, pi); + } + } + newIndex = pi->index; + + newIndex->fields = (char **) xmalloc (sizeof (char *) * num_fields); + memset (newIndex->fields, 0, sizeof (char *) * num_fields); + newIndex->buf = NULL; + for (x = 0; x < num_fields; x++) + { + FieldIndex field = getNthField (pr->database, x); + if (isIndexedFieldIndex (field)) + { + const char *fc = field_value (pr, field); + + if (fieldDefForIndex (field)->datatype == Date) + { + if (fc == NULL + || fc[0] == '\0' + || (strcmp (fc, "-1") == 0)) + { + newIndex->fields[x] = xstrdup ("0"); + } + else + { + time_t t = get_any_date (fc); + asprintf (&(newIndex->fields[x]), "%d", (int) t); + } + } + else + { + char *fcCopy; + + if (fc == NULL) + { + fcCopy = xstrdup (""); + } + else + { + fcCopy = xstrdup (fc); + } + + /* ??? XXX Should we do this here? It's already being + done in the "write out the index" code. */ + if (fieldDefForIndex (field)->nospaces) + { + /* Try to minimize the leakage. */ + char *p = strchr (fcCopy, ' '); + if (p != NULL) + { + *p = '\0'; + } + } + newIndex->fields[x] = fcCopy; + } + } + } +} + +int +isIndexedFieldIndex (FieldIndex index) +{ + IndexDesc indexDesc; + if (index == InvalidFieldIndex) + { + return 0; + } + else + { + indexDesc = getIndexDesc (fieldDefForIndex (index)->database); + return indexDesc->fieldInIndex[fieldDefForIndex (index)->number]; + } +} + +void +finishIndexDesc (DatabaseInfo database, IndexDesc new) +{ + int numFields = get_num_fields (database); + + new->fieldInIndex = (int *) xmalloc (sizeof (int) * numFields); + + { + int x; + + for (x = 0; x < numFields; x++) + { + new->fieldInIndex[x] = 0; + } + } + + { + FieldList f; + + for (f = new->fields; f != NULL; f = f->next) + { + FieldIndex i = simpleFieldIndexValue (f->ent); + if (i != InvalidFieldIndex) + { + new->fieldInIndex[fieldNumber (i)] = 1; + } + } + } + new->fieldInIndex[fieldNumber (NUMBER (database))] = 1; + new->fieldInIndex[fieldNumber (CATEGORY (database))] = 1; +} + +int +isIndexedField (ComplexFieldIndex ent) +{ + if (parseComplexFieldIndex (ent) != 0) + { + return 0; + } + return isIndexedFieldIndex (simpleFieldIndexValue (ent)); +} + +/* Write out the index for DATABASE's PR chain. Returns 0 on success, + a non-zero value otherwise (and ERR will be filled in). */ +int +writeIndex (const DatabaseInfo database, ErrorDesc *err) +{ + FILE *fp; + char *path, *workfile; + PR *pr; + IndexDesc indexDesc = getIndexDesc (database); + struct stat buf; + + workfile = gnats_adm_dir (database, "indXXXXXX"); + fp = fopen_temporary_file (workfile, "w", 0644); + if (fp == NULL) + { + setError (err, CODE_FILE_ERROR, + "writeIndex () can't open the temporary file %s", + workfile); + free (workfile); + return -1; + } + + if (indexIsBinary (database)) + { + unsigned char fieldCount = indexFieldCount (database); + fwrite (&fieldCount, 1, 1, fp); + } + + for (pr = getFirstPR (database, err); pr != NULL ; pr = getNextPR (pr)) + { + size_t length; + char *line = createIndexEntry (pr, &length); + + if (line != NULL) + { + fwrite (line, 1, length, fp); + free (line); + } + } + + fclose (fp); + + block_signals (); + + path = gnats_adm_dir (database, indexDesc->path); + + /* sanity check -- make sure nobody has changed the index since we + last read it */ + if (stat (path, &buf) < 0) + { + /* if the index ain't there, no problem. if it's there, but + unstattable, we're going to declare it a serious problem. + not sure if this is the right thing to do... */ + if (errno != ENOENT) + { + setError (err, CODE_FILE_ERROR, + "GNATS cannot stat the index: %s.", strerror (errno)); + log_msg (LOG_ERR, 1, "error in writeIndex:", + getErrorMessage (*err)); + return -1; + } + } + + if (buf.st_mtime > indexDesc->mtime) + { + setError (err, CODE_INVALID_INDEX, + "Index has been modified since last read"); + log_msg (LOG_ERR, 1, "error in writeIndex:", + getErrorMessage (*err)); + /* the index is corrupt: alert the administrator */ + punt (database, 0, "Attempting to write index file %s,\n\ +but index has been modified since last read. A PR has been modified,\n\ +but the change has not been recorded in the index. You must manually\n\ +regenerate the index.", path); + return -1; + } + + if ((rename (workfile, path)) < 0) + { + if (errno != EXDEV) + { + free (path); + setError (err, CODE_FILE_ERROR, + "could not rename temporary index %s into gnats-adm: %s", + workfile, strerror (errno)); + return -1; + } + + if (copy_file (workfile, path)) + { + setError (err, CODE_FILE_ERROR, + "could not copy temporary index %s into %s: %s", + workfile, path, strerror (errno)); + return -1; + } + + unlink (workfile); + } + + free (path); + free (workfile); + unblock_signals (); + return 0; +} + +/* Add the PR to the GNATS index file. */ +int +addToIndex (PR *pr, ErrorDesc *err) +{ + FILE *fp; + char *path; + IndexDesc indexDesc = getIndexDesc (pr->database); + + buildIndexEntry (pr); + + block_signals (); + + path = gnats_adm_dir (pr->database, indexDesc->path); + + if (indexIsBinary (pr->database) && ! fileExists (path)) + { + unsigned char fieldCount = indexFieldCount (pr->database); + fp = fopen (path, "w"); + if (fp != NULL) + { + fwrite (&fieldCount, 1, 1, fp); + } + } + else + { + fp = fopen (path, "a+"); + } + if (fp == NULL) + { + setError (err, CODE_FILE_ERROR, + "Can't append to the GNATS index file (%s).", path); + unblock_signals (); + return -1; + } + + { + size_t reclen; + + char *line = createIndexEntry (pr, &reclen); + fwrite (line, 1, reclen, fp); + free (line); + } + + fclose (fp); + free (path); + unblock_signals (); + return 0; +} + +char * +indexValue (PR *pr, FieldIndex field) +{ + FilePRInfo pi = (FilePRInfo) pr->ds_private; + if (pi->index != NULL && pi->index->fields != NULL) + { + return pi->index->fields[field->number]; + } + else + { + return NULL; + } +} + +char * +getCategoryFromIndex (const DatabaseInfo database, const char *prnum, + ErrorDesc *err) +{ + IndexDesc indexDesc = getIndexDesc (database); + IndexFileDesc fp = openIndex (indexDesc, err); + if (fp != NULL) + { + char *category; + if (fp->desc->isBinary) + { + category = findPrCategoryBinary (fp, prnum); + } + else + { + category = findPrCategory (fp, prnum); + } + closeIndex (fp); + if (category == NULL) + { + setError (err, CODE_NONEXISTENT_PR, + "PR %s is not listed in the index.", prnum); + } + return category; + } + else + { + return NULL; + } +} + +IndexDesc +getIndexDesc (const DatabaseInfo database) +{ + FileDBInfo dbi = (FileDBInfo) getDatastorePrivate(database); + if (dbi == NULL) + { + return NULL; + } + return dbi->indexDesc; +} + +void +setIndexDesc (DatabaseInfo database, IndexDesc new) +{ + if (databaseValid (database)) + { + FileDBInfo dbi = (FileDBInfo) getDatastorePrivate(database); + finishIndexDesc (database, new); + dbi->indexDesc = new; + } +} + +void +freeIndexDesc (IndexDesc p) +{ + free (p->path); + freeFieldList (p->fields); + free (p->separator); + if (p->fieldInIndex != NULL) + { + free (p->fieldInIndex); + } + free (p); +} + +IndexDesc +newIndexDesc (const DatabaseInfo database) +{ + IndexDesc res = xmalloc (sizeof (struct index_desc)); + + res->database = database; + res->prChain = NULL; + res->path = NULL; + res->fields = NULL; + res->separator = xstrdup ("|"); + res->isBinary = 0; + res->numberOfFields = 0; + res->fieldInIndex = NULL; + res->mtime = 0; + res->indexRead = 0; + + return res; +} + +void +addFieldToIndex (IndexDesc desc, FieldList ent) +{ + if (desc->fields == NULL) + { + desc->fields = ent; + } + else + { + FieldList f = desc->fields; + while (f->next != NULL) + { + f = f->next; + } + f->next = ent; + } + desc->numberOfFields++; +} + +void +setIndexDescPath (IndexDesc desc, const char *path) +{ + desc->path = xstrdup (path); +} + +void +setIndexDescSeparator (IndexDesc desc, const char *separator) +{ + free (desc->separator); + desc->separator = xstrdup (separator); +} + +void +setIndexDescBinary (IndexDesc desc, int flag) +{ + desc->isBinary = flag; +} + +static void +freePRChain (PR *start) +{ + while (start != NULL) + { + PR *next = getNextPR (start); + free_pr (start); + start = next; + } +} + +int +checkPRChain (const DatabaseInfo database, ErrorDesc *err) +{ + IndexDesc desc = getIndexDesc (database); + PR *new_chain = getNewIndexIfChanged (database, err); + + if (new_chain != NULL) + { + freePRChain (desc->prChain); + desc->prChain = new_chain; + return 1; + } + else + { + return 0; + } +} + +void +initIndex (DatabaseInfo database) +{ + clearPRChain (database); +} + +PR * +getFirstPR (const DatabaseInfo database, ErrorDesc *err) +{ + IndexDesc desc = getIndexDesc (database); + + /* first check the PRChain to make sure that the index isn't stale */ + checkPRChain (database, err); + + return desc->prChain; +} + +void +clearPRChain (const DatabaseInfo database) +{ + IndexDesc desc = getIndexDesc (database); + if (desc != NULL) + { + if (desc->prChain != NULL) + { + freePRChain (desc->prChain); + desc->prChain = NULL; + } + desc->indexRead = 0; + } +} + +/* Replace a PR in the index with a copy of a new PR. + By generating a copy of the new PR, we allow for the new PR + to be free'd without harming the index pr chain. */ +int +replaceCurrentPRInIndex (PR *curr_pr, PR *new_pr, ErrorDesc *err) +{ + PR *pr_replace, *pr_copy; + const DatabaseInfo database = curr_pr->database; + const char *new_pr_num = field_value (new_pr, NUMBER (database)); + const char *curr_pr_num = field_value (curr_pr, NUMBER (database)); + FilePRInfo curr_pi = (FilePRInfo) curr_pr->ds_private; + + if (strcmp (curr_pr_num, new_pr_num) != 0) + { + return 0; + } + + /* create a copy of the new pr to be put into the index pr chain */ + pr_copy = allocPR (database); + set_field (pr_copy, NUMBER (curr_pr->database), new_pr_num, err); + set_field (pr_copy, CATEGORY (database), + field_value (new_pr, CATEGORY (database)), err); + setPrevPR (pr_copy, curr_pi->index->prevPR); + setNextPR (pr_copy, curr_pi->index->nextPR); + pr_load (pr_copy, err); + + /* put the copy of new_pr into place in the index pr chain... */ + + if (curr_pi->index->prevPR == NULL) + { + /* the pr to be replaced is at the head of the pr chain */ + IndexDesc indexDesc = getIndexDesc (database); + free_pr (indexDesc->prChain); + indexDesc->prChain = pr_copy; + return 1; + } + + /* save a pointer to the pr to be replaced so that it can later be free'd */ + pr_replace = getNextPR (curr_pi->index->prevPR); + + setNextPR (curr_pi->index->prevPR, pr_copy); + + if (curr_pi->index->nextPR != NULL) + { + /* the pr to be replaced is not at the end of the pr chain */ + setPrevPR (curr_pi->index->nextPR, pr_copy); + } + + free_pr (pr_replace); + + return 1; +} + +/* Remove PR PRNUM from the index for the current database, and rewrite the + database file. The database must have been locked before calling this + function. */ +int +removePRFromIndex (const DatabaseInfo database, const char *prNum, + ErrorDesc *err) +{ + PR *list; + PR *prev = NULL; + IndexDesc indexDesc = getIndexDesc (database); + FilePRInfo list_pi; + + if (! is_gnats_locked (database)) + { + setError (err, CODE_GNATS_NOT_LOCKED, + "Must lock the database before removing a PR from the index"); + return -1; + } + + list = getFirstPR (database, err); + while (list != NULL + && strcmp (field_value (list, NUMBER (database)), prNum) != 0) + { + prev = list; + list = getNextPR (list); + } + + if (list == NULL) + { + setError (err, CODE_NONEXISTENT_PR, + "PR %s not in index", prNum); + return -2; + } + + list_pi = (FilePRInfo) list->ds_private; + if (prev != NULL) + { + FilePRInfo prev_pi = (FilePRInfo) prev->ds_private; + prev_pi->index->nextPR = list_pi->index->nextPR; + } + else + { + indexDesc->prChain = list_pi->index->nextPR; + } + + return writeIndex (database, err); +} + +PR * +getNextPR (PR *pr) +{ + FilePRInfo pi = (FilePRInfo) pr->ds_private; + return pi->index->nextPR; +} + +void +setNextPR (PR *pr, PR *next_pr) +{ + FilePRInfo pi = (FilePRInfo) pr->ds_private; + pi->index->nextPR = next_pr; +} + +PR * +getPrevPR (PR *pr) +{ + FilePRInfo pi = (FilePRInfo) pr->ds_private; + return pi->index->prevPR; +} + +void +setPrevPR (PR *pr, PR *prev_pr) +{ + FilePRInfo pi = (FilePRInfo) pr->ds_private; + pi->index->prevPR = prev_pr; +} + +int +indexIsBinary (const DatabaseInfo database) +{ + IndexDesc indexDesc = getIndexDesc (database); + if (indexDesc != NULL) + { + return indexDesc->isBinary; + } + else + { + return 0; + } +} + +int +indexFieldCount (const DatabaseInfo database) +{ + IndexDesc indexDesc = getIndexDesc (database); + if (indexDesc != NULL) + { + return indexDesc->numberOfFields; + } + else + { + return 0; + } +} Index: ds-file/index.h =================================================================== RCS file: ds-file/index.h diff -N ds-file/index.h --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ ds-file/index.h 19 Apr 2005 00:49:20 -0000 @@ -0,0 +1,75 @@ +#ifndef _INDEX_H_ +#define _INDEX_H_ + +typedef struct indexEntry Index; +typedef struct index_desc *IndexDesc; + +#include "gnats.h" +#include "database.h" +#include "field.h" +#include "pr.h" +#include "ds-file.h" + +extern void initIndex (DatabaseInfo database); + +extern IndexDesc newIndexDesc (const DatabaseInfo database); +extern void addFieldToIndex (IndexDesc desc, FieldList ent); +extern void setIndexDescPath (IndexDesc desc, const char *path); +extern void setIndexDescSeparator (IndexDesc desc, const char *separator); +extern void setIndexDescBinary (IndexDesc desc, int flag); + +extern void allocIndex (FilePRInfo info); + +extern void clearPRChain (const DatabaseInfo database); +extern int checkPRChain (const DatabaseInfo database, ErrorDesc *err); + +extern PR *getFirstPR (const DatabaseInfo database, ErrorDesc *err); + +extern PR *getPrevPR (PR *pr); +extern PR *getNextPR (PR *pr); +extern void setPrevPR (PR *pr, PR *prev_pr); +extern void setNextPR (PR *pr, PR *next_pr); + +extern void freePRIndex (DatabaseInfo database, FilePRInfo info); + +/* Construct an index entry for PR. */ +extern void buildIndexEntry (PR *pr); + +/* Return a non-zero value if FIELD is part of the index for its database. */ +extern int isIndexedFieldIndex (FieldIndex field); +extern int isIndexedField (ComplexFieldIndex field); + +/* Write out the database's PR index from the copy in memory. Returns 0 on + success, a non-zero value otherwise. */ +extern int writeIndex (const DatabaseInfo database, ErrorDesc *err); +/* Add the PR to the GNATS index file for its database. Returns + 0 on success, a non-zero value otherwise. */ +extern int addToIndex (PR *pr, ErrorDesc *err); + +/* Create a new string representing the index entry for PR in DEST. */ +extern char *createIndexEntry (PR *pr, size_t *entLen); +extern char *createIndexEntryBinary (PR *pr, size_t *entLen); + +extern int replaceCurrentPRInIndex (PR *curr_pr, PR *newPR, ErrorDesc *err); + +extern char *indexValue (PR *pr, FieldIndex field); + +extern char *getCategoryFromIndex (const DatabaseInfo database, + const char *prnum, ErrorDesc *err); + +extern int removePRFromIndex (const DatabaseInfo database, const char *prNum, + ErrorDesc *err); + +extern void finishIndexDesc (const DatabaseInfo database, IndexDesc new); + +extern void setIndexDesc (DatabaseInfo database, const IndexDesc desc); +extern IndexDesc getIndexDesc (const DatabaseInfo database); +extern void freeIndexDesc (IndexDesc desc); + +extern int indexIsBinary (const DatabaseInfo database); + +extern int indexFieldCount (const DatabaseInfo database); + +extern int verifyPRExists (const DatabaseInfo database, const char *prID); +#endif + Index: ds-file/pr.c =================================================================== RCS file: ds-file/pr.c diff -N ds-file/pr.c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ ds-file/pr.c 19 Apr 2005 00:49:20 -0000 @@ -0,0 +1,598 @@ +/* Flat-file datastore API - functions operating on a single PR. + Copyright (C) 2005 Free Software Foundation, Inc. + Contributed by Mel Hatzis . + +This file is part of GNU GNATS. + +GNU GNATS is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GNU GNATS is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along +with GNU GNATS; see the file COPYING. if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "ds.h" +#include "pcodes.h" +#include "ds-file.h" + +/* Get the PR at PATH. PR is the PR entry to be filled in. + If PRUNE is non-zero, don't read any multitext fields. */ +static int +get_pr (PR *pr, const char *path, int prune) +{ + FILE *fp = fopen (path, "r"); + + if (fp == (FILE *)NULL) + { + return 0; + } + + if (read_header (pr, fp) < 0) + { + return 0; + } + + read_pr (pr, fp, prune); + + fclose (fp); + + return 1; +} + +static PR * +get_pr_from_index (const DatabaseInfo database, const char *prnum, + ErrorDesc *err) +{ + PR *pr = getFirstPR (database, err); + + /* If they gave it to us with the category, remove it. */ + if (( strrchr (prnum, '/')) != NULL) + { + prnum = strrchr (prnum, '/') + 1; + } + + while (pr != NULL && strcmp (prnum, field_value (pr, NUMBER (database))) != 0) + { + pr = getNextPR (pr); + } + + if (pr == NULL) + { + setError (err, CODE_NONEXISTENT_PR, + "No PR %s listed in the index.", prnum); + return NULL; + } + + return pr; +} + +static bool +pr_file_readable (const char *path, ErrorDesc *err) +{ + FILE *fp; + + if (path == NULL) + { + return FALSE; + } + + if ((fp = fopen (path, "r")) == NULL) + { + setError (err, CODE_FILE_ERROR, "Can't open file `%s'", path); + return FALSE; + } + + fclose (fp); + return TRUE; +} + +static char * +get_pr_path (const DatabaseInfo database, PR *pr, const char *prnum, + ErrorDesc *err) +{ + char *path = NULL; + const char *category = NULL; + char *categoryFromIndex = NULL; + + if (pr != NULL) + { + category = field_value (pr, CATEGORY (database)); + } + else + { + categoryFromIndex = getCategoryFromIndex (database, prnum, err); + category = categoryFromIndex; + } + + if (category != NULL) + { + asprintf (&path, "%s/%s/%s", databaseDir (database), category, prnum); + if (categoryFromIndex != NULL) + { + free (categoryFromIndex); + } + } + + return path; +} + +/* Return the next available unique GNATS id. */ +static int +getBugNumber (const DatabaseInfo database, ErrorDesc *err) +{ + char *sbuf; + int bug_number; + FILE *bug_file; + + /* First try to find and lock the gnats lock file. We need this since + they want every bug to have a unique number. If lock doesn't exist, + make it, if possible. */ + sbuf = gnats_adm_dir (database, "current"); + + block_signals (); + + bug_file = fopen (sbuf, "r+"); + if (bug_file == (FILE *) NULL) + { + log_msg (LOG_INFO, 1, "file 'current' not found, creating", sbuf); + bug_file = fopen (sbuf, "w+"); + if (bug_file != (FILE *) NULL) + { + bug_number = 1; + } + else + { + setError (err, CODE_FILE_ERROR, + "Can't create the GNATS 'current' file (%s).", + sbuf); + return -1; + } + } + else + { + if (fscanf (bug_file, "%d", &bug_number) != 1) + { + setError (err, CODE_FILE_ERROR, + "Can't read from the global PR number counter (%s).", + sbuf); + return -1; + } + + bug_number++; + rewind (bug_file); + } + + fprintf (bug_file, "%d", bug_number); + + fclose (bug_file); + unblock_signals (); + free (sbuf); + + return bug_number; +} + +void +pr_init(PR *pr) +{ + FilePRInfo pi = (FilePRInfo) xmalloc (sizeof (struct file_pr_info)); + pr->ds_private = pi; + allocIndex (pi); +} + +void +pr_destroy(PR *pr) +{ + + FilePRInfo pi = (FilePRInfo) pr->ds_private; + if (pi != NULL) + { + if (pi->index != NULL) + { + freePRIndex (pr->database, pi); + free (pi->index); + } + free (pi); + pr->ds_private = NULL; + } +} + +/* Load a PR containing only indexed field data with it's entire data set. */ +int +pr_load (PR *pr, ErrorDesc *err) +{ + char *path = gen_pr_path (pr); + const char *num = field_value (pr, NUMBER (pr->database)); + int val; + + if (num == NULL || path == NULL) + { + setError (err, CODE_ERROR, "Invalid PR in pr_load()"); + if (path != NULL) + { + free (path); + } + return -1; + } + else + { + val = get_pr (pr, path, 0); + + free (path); + if (val == 0) + { + setError (err, CODE_FILE_ERROR, "Unable to read PR %s", num); + return -1; + } + else + { + return 0; + } + } +} + +/* Read a file into a new PR data structure. */ +PR * +pr_load_by_id (const DatabaseInfo database, const char *prnum, + int prune, ErrorDesc *err) +{ + PR *pr = NULL; + PR *index_pr = get_pr_from_index (database, prnum, err); + char *path = get_pr_path (database, index_pr, prnum, err); + + if (path != NULL) + { + if (pr_file_readable (path, err)) + { + pr = allocPR (database); + setPrevPR (pr, getPrevPR (index_pr)); + setNextPR (pr, getNextPR (index_pr)); + if (get_pr (pr, path, prune) == 0) + { + free_pr (pr); + pr = NULL; + } + } + free (path); + } + return pr; +} + +int +pr_load_fields (PR *pr, FieldList flds) +{ + int do_open_pr = 0; + + if (! PR_IS_FULL (pr)) + { + if (flds == NULL) + { + do_open_pr = 1; + } + else + { + while (flds != NULL) + { + if (! isIndexedField (flds->ent)) + { + do_open_pr = 1; + break; + } + flds = flds->next; + } + } + } + + if (do_open_pr) + { + ErrorDesc err; + + if (pr_load (pr, &err)) + { + return -1; + } + } + return 1; +} + +int +pr_exists (const DatabaseInfo database, const char *prnum, ErrorDesc *err) +{ + PR *pr = get_pr_from_index (database, prnum, err); + char *path = get_pr_path (database, pr, prnum, err); + int res = 0; + if (path != NULL) + { + res = fileExists (path); + free (path); + if (res == 0) + { + setError (err, CODE_FILE_ERROR, "Can't open file `%s'", path); + } + } + return res; +} + +/* Write a file out to FILENAME with PR in it. */ +int +pr_create (PR *pr, ErrorDesc *err) +{ + int errnum = 0; + int bug_number = 0; + char *path = NULL; + char *prPath = NULL; + char *bug_name = NULL; + char number[14]; /* No more than 13 digits in a PR #, ha ha. */ + time_t seconds = time ((time_t) 0); + char arrival_time[GNATS_TIME_LENGTH]; + const DatabaseInfo database = pr->database; + const char *category = field_value (pr, CATEGORY (database)); + int flag_autocreate = createCategoryDirs (database); + + /* Set arrival date and time of response. */ + gnats_strftime (arrival_time, GNATS_TIME_LENGTH, "%a %b %d %H:%M:%S %z %Y", + localtime (&seconds)); + set_field (pr, ARRIVAL_DATE (database), arrival_time, err); + + /* Put together the path to where the bug will be stored. If the dir + is not there, and the category is the default, auto-create that one, + if we want to. If not, make the bug pending, and store in there. */ + + asprintf (&path, "%s/%s", databaseDir (database), category); + + errnum = fileExists (path) ? 0 : -1; + + if (errnum != 0 && ! flag_autocreate) + { + /* XXX ??? !!! Should append a message to the email being sent out. */ + category = defaultCategory(database); + set_field (pr, CATEGORY (database), defaultCategory(database), err); + log_msg (LOG_INFO, 1, "directory does not exist, changing to default:", + path); + free (path); + asprintf (&path, "%s/%s", databaseDir (database), category); + errnum = fileExists (path) ? 0 : -1; + } + + /* Check ERR again, to see if default category was there. */ + if (errnum != 0) + { + mode_t mode = categoryDirPerms (database); + + if (strcmp (category, defaultCategory(database)) == 0) + { + log_msg (LOG_INFO, 1, defaultCategory(database), + " does not exist, creating..."); + if (mkdir (path, mode) != 0) + { + setError (err, CODE_FILE_ERROR, + "Can't create `%s' directory under %s.", + defaultCategory(database), databaseDir (database)); + free (path); + return -1; + } + } + else if (flag_autocreate) + { + if (mkdir (path, mode) != 0) + { + setError (err, CODE_FILE_ERROR, + "Cannot create group: %s", path); + free (path); + return -1; + } + else + { + log_msg (LOG_INFO, 1, "creating directory:", path); + } + } + } + + free (path); + + bug_number = getBugNumber (database, err); + if (bug_number > 0) + { + sprintf (number, "%d", bug_number); + set_field (pr, NUMBER (database), number, err); + + /* Write the file out. */ + asprintf (&bug_name, "%s/%d", category, bug_number); + asprintf (&prPath, "%s/%s", databaseDir (database), bug_name); + if (createPrFile (pr, prPath, 1, err) != 0) + { + bug_number = -1; + } + else + { + log_msg (LOG_INFO, 1, "PR written out:", prPath); + + /* Add a line into the index for use by query-pr. */ + /* This should be done regardless of whether the bug is default or + not. */ + if (addToIndex (pr, err) != 0) + { + bug_number = -1; + } + } + } + if (prPath != NULL) + { + free (prPath); + } + if (bug_name != NULL) + { + free (bug_name); + } + + return bug_number; +} + +int +pr_update (PR *curr_pr, PR *new_pr, ErrorDesc *err) +{ + char *new_path = NULL; + char *old_path = NULL; + char *bkup_path = NULL; + FILE *prfile; + const DatabaseInfo database = new_pr->database; + + /* check to see if the category changes, and if so, make sure + * the new category dir exists */ + if (strcmp (field_value (curr_pr, CATEGORY (database)), + field_value (new_pr, CATEGORY (database))) != 0) + { + char *dirPath; + + asprintf (&dirPath, "%s/%s", databaseDir (database), + field_value (new_pr, CATEGORY (database))); + if (! fileExists (dirPath)) + { + if (createCategoryDirs (database)) + { + mode_t mode = categoryDirPerms (database); + + if (mkdir (dirPath, mode) != 0) + { + setError (err, CODE_FILE_ERROR, + "Error creating directory for category %s: %s", + field_value (new_pr, CATEGORY (database)), + strerror (errno)); + free (dirPath); + return 0; + } + } + else + { + setError (err, CODE_FILE_ERROR, + "Directory for category %s does not exist", + field_value (new_pr, CATEGORY (database))); + free (dirPath); + return 0; + } + } + } + + /* backup the current PR file */ + old_path = gen_pr_path (curr_pr); + asprintf (&bkup_path, "%s.old", old_path); + if (rename (old_path, bkup_path) < 0) + { + if (errno != EXDEV) + { + setError (err, CODE_FILE_ERROR, "Could not rename %s: %s", + old_path, strerror (errno)); + free (bkup_path); + free (old_path); + return 0; + } + if (copy_file (old_path, bkup_path) != 0) + { + setError (err, CODE_FILE_ERROR, "Could not copy %s to %s: %s", + old_path, bkup_path, strerror (errno)); + free (bkup_path); + free (old_path); + return 0; + } + + /* Don't complain if this fails, since trying to write to it will give + us the diagnostic if it's really serious. */ + unlink (old_path); + } + + /* Now build the new PR file. */ + new_path = gen_pr_path (new_pr); + prfile = fopen (new_path, "w+"); + if (prfile == (FILE *) NULL) + { + setError (err, CODE_FILE_ERROR, "Cannot write the PR to %s: %s", + new_path, strerror (errno)); + free (bkup_path); + free (old_path); + free (new_path); + return 0; + } + + /* We had to wait until now because we wanted to wait and see + about changing the closed_date. XXX ??? !!! FIXME */ + + buildIndexEntry (new_pr); + + write_entire_header (prfile, new_pr, "\n"); + fprintf (prfile, "\n"); + write_entire_pr (prfile, new_pr, "\n"); + + if (fclose (prfile) == EOF) + { + setError (err, CODE_FILE_ERROR, "Error writing out PR %s: %s", + new_path, strerror (errno)); + free (bkup_path); + free (old_path); + free (new_path); + return 0; + } + + unlink (bkup_path); + + /* unlink the old file, if it is in another category dir. */ + if (strcmp (field_value (curr_pr, CATEGORY (database)), + field_value (new_pr, CATEGORY (database))) != 0) + { + unlink (old_path); + } + + free (bkup_path); + free (old_path); + free (new_path); + + /* write out the new index. */ + replaceCurrentPRInIndex (curr_pr, new_pr, err); + if (writeIndex (database, err) != 0) + { + return 0; + } + + return 1; +} + +int +pr_delete (const DatabaseInfo database, const char *prnum, ErrorDesc *err) +{ + PR *pr; + char *path = NULL; + + pr = get_pr_from_index (database, prnum, err); + path = get_pr_path (database, pr, prnum, err); + + if (path == NULL || !pr_file_readable (path, err)) + { + if (path != NULL) + { + free (path); + } + return -1; + } + + if (removePRFromIndex (database, prnum, err)) + { + free (path); + return -5; + } + + if (unlink (path)) + { + setError (err, CODE_FILE_ERROR, "Unable to unlink file %s\n", path); + free (path); + return -6; + } + + free (path); + return 1; +} + Index: ds-file/t-fconfig =================================================================== RCS file: ds-file/t-fconfig diff -N ds-file/t-fconfig --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ ds-file/t-fconfig 19 Apr 2005 00:49:20 -0000 @@ -0,0 +1,50 @@ + +/* The flat-file datastore configuration options... */ + +dsDescription : indexDescription + ; + +indexDescription: indexheader '{' indexlist '}' { + setIndexDesc (databaseBeingDefined, indexEntry); + indexEntry = NULL; + } + | indexheader '{' parseError '}' { + freeIndexDesc (indexEntry); + indexEntry = NULL; + } + | parseError + ; + +indexheader : INDEXTOK { + indexEntry = newIndexDesc (databaseBeingDefined); + } + ; + +indexlist : indexEnt + | indexlist indexEnt + ; + +indexEnt : PATHTOK QSTRING { + setIndexDescPath (indexEntry, qStrVal ($2)); + } + | FIELDSTOK '{' indexFieldList '}' + | FIELDSTOK '{' parseError '}' + | indexSep + ; + +indexFieldList : FieldListMember { + addFieldToIndex (indexEntry, $1); + } + | indexFieldList FieldListMember { + addFieldToIndex (indexEntry, $2); + } + ; + +indexSep : SEPARATORTOK QSTRING { + setIndexDescSeparator (indexEntry, takeQString ($2)); + } + | BINARYINDEXTOK booleanVal { + setIndexDescBinary (indexEntry, $2); + } + ; +